diff --git a/.gitignore b/.gitignore index 13bbe5b911..6677fea66a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,15 @@ -.idea/ - +.DS_Store +.idea +*.iml +rebel.xml +private.properties +target/ +dist/ +private/ +classes/ +build/ +node_modules/ +bower_components/ +derby.log +gcm.properties +npm-debug.log \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index b83ade6962..0000000000 --- a/.gitmodules +++ /dev/null @@ -1,28 +0,0 @@ -[submodule "docdoku-plm-server"] - path = docdoku-plm-server - url = https://github.com/docdoku/docdoku-plm-server.git - branch = master -[submodule "docdoku-plm-front"] - path = docdoku-plm-front - url = https://github.com/docdoku/docdoku-plm-front.git - branch = master -[submodule "docdoku-plm-sample-data"] - path = docdoku-plm-sample-data - url = https://github.com/docdoku/docdoku-plm-sample-data.git - branch = master -[submodule "docdoku-plm-doc"] - path = docdoku-plm-doc - url = https://github.com/docdoku/docdoku-plm-doc.git - branch = master -[submodule "docdoku-plm-docker"] - path = docdoku-plm-docker - url = https://github.com/docdoku/docdoku-plm-docker.git - branch = master -[submodule "docdoku-plm-conversion-service"] - path = docdoku-plm-conversion-service - url = https://github.com/docdoku/docdoku-plm-conversion-service.git - branch = master -[submodule "docdoku-plm-api"] - path = docdoku-plm-api - url = https://github.com/docdoku/docdoku-plm-api.git - branch = master diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000000..7c8f131725 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,44 @@ +Args4j - The MIT License - http://opensource.org/licenses/mit-license.php +Elasticsearch - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +Commons IO - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +Commons Codec - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +Lucene - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +Apache POI - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +JODConverter - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +mustache.js - The MIT License - http://opensource.org/licenses/mit-license.php +LESSCSS - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +Modernizr - The MIT License - http://opensource.org/licenses/mit-license.php +jQuery - The MIT License - http://opensource.org/licenses/mit-license.php +Three.js - The MIT License - http://opensource.org/licenses/mit-license.php +Underscore.js - The MIT License - http://opensource.org/licenses/mit-license.php +Buzz - The MIT License - http://opensource.org/licenses/mit-license.php +RequireJS - The MIT License - http://opensource.org/licenses/mit-license.php +Backbone.js - The MIT License - http://opensource.org/licenses/mit-license.php +bootstrap-switch - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +dataTables - BSD license 3-Clause http://opensource.org/licenses/BSD-3-Clause +OkHttp - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +mustache.java - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +Guava - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +artofsolving - LGPL Version 2.1, http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +java3d vecmath - Java Distribution License (JDL), https://java3d.java.net/jdl-java3d.pdf +ITextPDF - AGPL Version 3, https://www.gnu.org/licenses/agpl-3.0.txt +Dozer - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +Joda-Time - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +Bouncy Castle - The MIT License - http://opensource.org/licenses/mit-license.php +Glassfish - COMMON DEVELOPMENT AND DISTRIBUTION (CDDL) Version 1.0, https://glassfish.java.net/public/CDDL+GPL.html +Jackson FasterXML - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +Swagger - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +Brsanthu MigBase64 - BSD license 3-Clause http://opensource.org/licenses/BSD-3-Clause +Underscore - The MIT License - http://opensource.org/licenses/mit-license.php +Moment - The MIT License - http://opensource.org/licenses/mit-license.php +Lodash - The MIT License - http://opensource.org/licenses/mit-license.php +Fontawesome - SIL OFL 1.1 - http://scripts.sil.org/OFL +Bootbox - The MIT License - http://opensource.org/licenses/mit-license.php +Tweenjs - The MIT License - http://opensource.org/licenses/mit-license.php +Unorm - The MIT License - http://opensource.org/licenses/mit-license.php +BuzzJS - The MIT License - http://opensource.org/licenses/mit-license.php +AsyncJS - The MIT License - http://opensource.org/licenses/mit-license.php +SelectizeJS - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ +Datatables - The MIT License - http://opensource.org/licenses/mit-license.php +dateformat - The MIT License - http://opensource.org/licenses/mit-license.php +dat.gui - Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ \ No newline at end of file diff --git a/README.md b/README.md index 632ad28d54..89d7507e6a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Welcome to the DocDokuPLM project +

Welcome to the DocDokuPLM project

## What is DocDokuPLM? @@ -16,15 +16,15 @@ DocDokuPLM is a high end Open Source PLM solution. ## Project support -The Project is lead by the DocDoku team. +The Project is lead by the DocDoku team and is part of the [OW2 Consortium](http://ow2.org/). ## Setting Up You have a full description of the [Installation Guide](https://github.com/docdoku/docdoku-plm/wiki/Installation-Guide). -To know how to use the PLM, you can look at the [Quick Starter Guide](https://github.com/docdoku/docdoku-plm/wiki/Quick-Starter-Guide) or at the [User Guide](http://docdokuplm.com/docdoku-plm/user-guide/en/2.5/). +To know how to use the PLM, you can look at the [Quick Starter Guide](https://github.com/docdoku/docdoku-plm/wiki/Quick-Starter-Guide) or at the [User Guide](http://docdokuplm.com/docdoku-plm/user-guide/en/2.0/). -## Coding +## Coding You may fork or just clone the code on [GitHub](https://github.com/docdoku/docdoku-plm) and then compile and package it with Maven. @@ -34,4 +34,4 @@ You have more information in the [Development Guide](https://github.com/docdoku/ AGPL version 3 -## [Cloud Hosting](https://docdokuplm.net) +## [Cloud Hosting](http://docdokuplm.net) diff --git a/docdoku-api-java/README.md b/docdoku-api-java/README.md new file mode 100644 index 0000000000..f6184f8400 --- /dev/null +++ b/docdoku-api-java/README.md @@ -0,0 +1,23 @@ +# Docdoku API Java + +Generates a jar that could be used in other projects to consume DocdokuPLM web services + +## Documentation + +Create a client, then use http services, example : + + ApiClient client = new DocdokuPLMBasicClient("http://localhost:8080/api", login, password).getClient(); + AccountDTO myAccount = new AccountsApi(client).getAccount(); + +See all classes under `src/test/java` for api usage + +## Development guide + +Build + +* Run `mvn clean install` docdoku-api module +* Run `mvn clean install` this module + +Tests + +Tests needs a running server and are skipped from build, they validate the build from generated sources. Edit TestConfig.java if needed. diff --git a/docdoku-api-java/pom.xml b/docdoku-api-java/pom.xml new file mode 100644 index 0000000000..51db425588 --- /dev/null +++ b/docdoku-api-java/pom.xml @@ -0,0 +1,234 @@ + + + + docdoku-plm + com.docdoku + 2.5-SNAPSHOT + + jar + 4.0.0 + docdoku-api-java + + + + io.swagger + swagger-annotations + ${swagger-annotations-version} + + + commons-codec + commons-codec + 1.5 + + + + org.glassfish.jersey.core + jersey-client + ${jersey-version} + + + org.glassfish.jersey.media + jersey-media-multipart + ${jersey-version} + + + org.glassfish.jersey.media + jersey-media-json-jackson + 2.22.1 + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-version} + + + com.fasterxml.jackson.datatype + jackson-datatype-joda + 2.1.5 + + + joda-time + joda-time + ${jodatime-version} + + + + com.squareup.okhttp3 + okhttp + 3.2.0 + + + + + com.brsanthu + migbase64 + 2.2 + + + + + junit + junit + ${junit-version} + test + + + commons-io + commons-io + 2.4 + test + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.10 + + + generate-sources + add-source + + + ${project.basedir}/src/main/java + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + + + io.swagger + swagger-codegen-maven-plugin + 2.1.5 + + + generate-sources + + generate + + + ${project.parent.basedir}/docdoku-api/target/swagger/swagger.json + java + ${project.build.directory}/generated-sources/swagger + com.docdoku.api.services + com.docdoku.api.client + com.docdoku.api.models + jersey2 + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + prepare-package + + copy-dependencies + + + ${project.build.directory}/lib + false + false + true + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.16 + + true + + ${env.URL} + ${env.LOGIN} + ${env.PASSWORD} + ${env.WORKSPACE} + ${env.DEBUG} + + + + + + + + running-instance + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.16 + + false + + + + + + + + + 1.5.8 + 2.12 + 2.4.2 + 2.3 + 1.0.0 + 4.8.1 + + + \ No newline at end of file diff --git a/docdoku-api-java/src/main/java/com/docdoku/api/DocdokuPLMBasicClient.java b/docdoku-api-java/src/main/java/com/docdoku/api/DocdokuPLMBasicClient.java new file mode 100644 index 0000000000..85e49e5935 --- /dev/null +++ b/docdoku-api-java/src/main/java/com/docdoku/api/DocdokuPLMBasicClient.java @@ -0,0 +1,45 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + +import com.docdoku.api.client.ApiClient; +import okhttp3.Credentials; + +/** + * This class helps to create the swagger client. + * @author Morgan Guimard + */ +public class DocdokuPLMBasicClient extends DocdokuPLMClient{ + + public DocdokuPLMBasicClient(String host, String login, String password) { + this(host,login,password,false); + } + + public DocdokuPLMBasicClient(String host, String login, String password, boolean debug) { + super(host,debug); + client.addDefaultHeader("Authorization", Credentials.basic(login, password)); + } + + public ApiClient getClient(){ + return client; + } + +} diff --git a/docdoku-api-java/src/main/java/com/docdoku/api/DocdokuPLMClient.java b/docdoku-api-java/src/main/java/com/docdoku/api/DocdokuPLMClient.java new file mode 100644 index 0000000000..34d64d4014 --- /dev/null +++ b/docdoku-api-java/src/main/java/com/docdoku/api/DocdokuPLMClient.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + +import com.docdoku.api.client.ApiClient; + +import java.text.SimpleDateFormat; + +/** + * This class helps to create the swagger client. + * @author Morgan Guimard + */ +public class DocdokuPLMClient { + + protected ApiClient client; + protected String host; + protected boolean debug; + + public DocdokuPLMClient(String host, boolean debug) { + this.host = host; + this.debug = debug; + createClient(); + } + + public void connect(String login, String password){} + + protected void createClient() { + client = new ApiClient(); + client.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")); + client.setBasePath(host); + client.setDebugging(debug); + } + + public ApiClient getClient() { + return client; + } +} diff --git a/docdoku-api-java/src/main/java/com/docdoku/api/DocdokuPLMCookieClient.java b/docdoku-api-java/src/main/java/com/docdoku/api/DocdokuPLMCookieClient.java new file mode 100644 index 0000000000..6d063411a1 --- /dev/null +++ b/docdoku-api-java/src/main/java/com/docdoku/api/DocdokuPLMCookieClient.java @@ -0,0 +1,80 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + +import com.docdoku.api.client.ApiClient; +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.AccountDTO; +import com.docdoku.api.services.AccountsApi; +import okhttp3.Credentials; + +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class helps to create the swagger client. + * @author Morgan Guimard + */ +public class DocdokuPLMCookieClient extends DocdokuPLMClient{ + + private String cookie; + + private static final Logger LOGGER = Logger.getLogger(DocdokuPLMCookieClient.class.getName()); + + public DocdokuPLMCookieClient(String host, String login, String password) { + this(host,login,password,false); + } + + public DocdokuPLMCookieClient(String host, String login, String password, boolean debug) { + super(host,debug); + connect(login, password); + } + + + public void connect(String login, String password){ + + client.addDefaultHeader("Authorization", Credentials.basic(login, password)); + + try { + AccountDTO account = new AccountsApi(client).getAccount(); + LOGGER.log(Level.INFO,"Connected as " + account.getName()); + Map> responseHeaders = client.getResponseHeaders(); + System.out.println(responseHeaders); + List strings = responseHeaders.get("Set-Cookie"); + if(strings != null && !strings.isEmpty()){ + this.cookie = strings.get(0); + createClient(); + client.addDefaultHeader("Cookie", this.cookie); + } else { + LOGGER.log(Level.WARNING,"Cannot fetch cookie"); + } + + } catch (ApiException e) { + LOGGER.log(Level.SEVERE,"Cannot connect to docdoku plm server http response code = " + client.getStatusCode()); } + } + + public ApiClient getClient(){ + return client; + } + +} diff --git a/docdoku-api-java/src/main/java/com/docdoku/api/DocdokuPLMJWTClient.java b/docdoku-api-java/src/main/java/com/docdoku/api/DocdokuPLMJWTClient.java new file mode 100644 index 0000000000..46af1f0d4a --- /dev/null +++ b/docdoku-api-java/src/main/java/com/docdoku/api/DocdokuPLMJWTClient.java @@ -0,0 +1,79 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.AccountDTO; +import com.docdoku.api.models.LoginRequestDTO; +import com.docdoku.api.services.AuthApi; + +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class helps to create the swagger client. + * @author Morgan Guimard + */ +public class DocdokuPLMJWTClient extends DocdokuPLMClient{ + + private String token; + + private static final Logger LOGGER = Logger.getLogger(DocdokuPLMJWTClient.class.getName()); + + public DocdokuPLMJWTClient(String host, String login, String password) { + this(host,login,password,false); + } + + public DocdokuPLMJWTClient(String host, String login, String password, boolean debug) { + super(host,debug); + connect(login, password); + } + + @Override + public void connect(String login, String password){ + + LoginRequestDTO loginRequestDTO = new LoginRequestDTO(); + loginRequestDTO.setLogin(login); + loginRequestDTO.setPassword(password); + + try { + AccountDTO account = new AuthApi(client).login(loginRequestDTO); + LOGGER.log(Level.INFO,"Connected as " + account.getName()); + Map> responseHeaders = client.getResponseHeaders(); + System.out.println(responseHeaders); + List strings = responseHeaders.get("jwt"); + if(strings != null && !strings.isEmpty()){ + this.token = strings.get(0); + createClient(); + client.addDefaultHeader("Authorization", "Bearer " + this.token); + } else { + LOGGER.log(Level.WARNING,"Cannot fetch token"); + } + + } catch (ApiException e) { + LOGGER.log(Level.SEVERE,"Cannot connect to docdoku plm server http response code = " + client.getStatusCode()); + } + + } + +} diff --git a/docdoku-api-java/src/main/java/com/docdoku/api/models/utils/LastIterationHelper.java b/docdoku-api-java/src/main/java/com/docdoku/api/models/utils/LastIterationHelper.java new file mode 100644 index 0000000000..878511f7cd --- /dev/null +++ b/docdoku-api-java/src/main/java/com/docdoku/api/models/utils/LastIterationHelper.java @@ -0,0 +1,43 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api.models.utils; + +import com.docdoku.api.models.DocumentIterationDTO; +import com.docdoku.api.models.DocumentRevisionDTO; +import com.docdoku.api.models.PartIterationDTO; +import com.docdoku.api.models.PartRevisionDTO; +/** + * This class helps to get last iteration from documents or parts + * @Author Morgan Guimard + */ +public class LastIterationHelper { + + public static DocumentIterationDTO getLastIteration(DocumentRevisionDTO documentRevisionDTO){ + int iterations = documentRevisionDTO.getDocumentIterations().size(); + return documentRevisionDTO.getDocumentIterations().get(iterations - 1); + } + + public static PartIterationDTO getLastIteration(PartRevisionDTO partRevisionDTO){ + int iterations = partRevisionDTO.getPartIterations().size(); + return partRevisionDTO.getPartIterations().get(iterations - 1); + } + +} diff --git a/docdoku-api-java/src/main/java/com/docdoku/api/models/utils/UploadDownloadHelper.java b/docdoku-api-java/src/main/java/com/docdoku/api/models/utils/UploadDownloadHelper.java new file mode 100644 index 0000000000..60fdc3eb57 --- /dev/null +++ b/docdoku-api-java/src/main/java/com/docdoku/api/models/utils/UploadDownloadHelper.java @@ -0,0 +1,130 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api.models.utils; + +import com.docdoku.api.client.ApiClient; +import com.docdoku.api.client.ApiException; +import com.docdoku.api.client.Pair; +import com.docdoku.api.models.DocumentIterationDTO; +import com.docdoku.api.models.PartIterationDTO; + +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.MediaType; +import java.io.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +/** + * This class helps to upload and download files on documents and parts + * + * @implNote : This is a temporary class and will be removed as soon as fix generated upload and download + * methods with swagger-codegen + * + * @author Morgan Guimard + */ +public class UploadDownloadHelper { + + private static final int CHUNK_SIZE = 1024 * 8; + + public static void uploadAttachedFile(PartIterationDTO partIterationDTO, ApiClient client, File file) throws ApiException { + String path = "/files/{workspaceId}/parts/{number}/{version}/{iteration}/attachedfiles".replaceAll("\\{format\\}", "json") + .replaceAll("\\{" + "workspaceId" + "\\}", client.escapeString(partIterationDTO.getWorkspaceId())) + .replaceAll("\\{" + "number" + "\\}", client.escapeString(partIterationDTO.getNumber())) + .replaceAll("\\{" + "version" + "\\}", client.escapeString(partIterationDTO.getVersion())) + .replaceAll("\\{" + "iteration" + "\\}", String.valueOf(partIterationDTO.getIteration())); + + upload(path, file, client); + } + + public static void uploadAttachedFile(DocumentIterationDTO documentIterationDTO, ApiClient client, File file) throws ApiException { + + String path = "/files/{workspaceId}/documents/{documentId}/{version}/{iteration}".replaceAll("\\{format\\}", "json") + .replaceAll("\\{" + "workspaceId" + "\\}", client.escapeString(documentIterationDTO.getWorkspaceId())) + .replaceAll("\\{" + "documentId" + "\\}", client.escapeString(documentIterationDTO.getDocumentMasterId())) + .replaceAll("\\{" + "version" + "\\}", client.escapeString(documentIterationDTO.getVersion())) + .replaceAll("\\{" + "iteration" + "\\}", String.valueOf(documentIterationDTO.getIteration())); + + upload(path,file,client); + + } + + public static void uploadNativeCADFile(PartIterationDTO partIterationDTO, ApiClient client, File file) throws ApiException { + String path = "/files/{workspaceId}/parts/{number}/{version}/{iteration}/nativecad".replaceAll("\\{format\\}", "json") + .replaceAll("\\{" + "workspaceId" + "\\}", client.escapeString(partIterationDTO.getWorkspaceId())) + .replaceAll("\\{" + "number" + "\\}", client.escapeString(partIterationDTO.getNumber())) + .replaceAll("\\{" + "version" + "\\}", client.escapeString(partIterationDTO.getVersion())) + .replaceAll("\\{" + "iteration" + "\\}", String.valueOf(partIterationDTO.getIteration())); + + upload(path,file,client); + } + + private static void upload(String path, File file, ApiClient client) throws ApiException { + List queryParams = new ArrayList(); + Map headerParams = new HashMap(); + Map formParams = new HashMap(); + formParams.put("upload", file); + final String[] accepts = {}; + final String accept = client.selectHeaderAccept(accepts); + final String[] contentTypes = {MediaType.MULTIPART_FORM_DATA}; + final String contentType = client.selectHeaderContentType(contentTypes); + String[] authNames = new String[]{}; + client.invokeAPI(path, "POST", queryParams, file, headerParams, formParams, accept, contentType, authNames, null); + } + + public static File downloadFile(String fileName, ApiClient client) throws ApiException { + String path = "/files/" + fileName; + List queryParams = new ArrayList(); + Map headerParams = new HashMap(); + Map formParams = new HashMap(); + final String[] accepts = {"application/octet-stream"}; + final String accept = client.selectHeaderAccept(accepts); + final String[] contentTypes = {}; + final String contentType = client.selectHeaderContentType(contentTypes); + String[] authNames = new String[]{}; + GenericType returnType = new GenericType() { + }; + File file = client.invokeAPI(path, "GET", queryParams, null, headerParams, formParams, accept, contentType, authNames, returnType); + Map> responseHeaders = client.getResponseHeaders(); + List encoding = responseHeaders.get("Content-Encoding"); + + return encoding != null && "gzip".equals(encoding.get(0)) ? uncompress(file) : file; + } + + + private static File uncompress(File file) { + File out = new File(file.getAbsolutePath() + ".unzip"); + int length; + byte[] data = new byte[CHUNK_SIZE]; + try (GZIPInputStream is = new GZIPInputStream(new FileInputStream(file)); OutputStream os = new FileOutputStream(out)) { + while ((length = is.read(data)) != -1) { + os.write(data, 0, length); + } + os.flush(); + return out; + } catch (IOException e) { + return file; + } + } + +} diff --git a/docdoku-api-java/src/main/java/com/docdoku/api/models/utils/WorkflowHelper.java b/docdoku-api-java/src/main/java/com/docdoku/api/models/utils/WorkflowHelper.java new file mode 100644 index 0000000000..965ec1bf1f --- /dev/null +++ b/docdoku-api-java/src/main/java/com/docdoku/api/models/utils/WorkflowHelper.java @@ -0,0 +1,85 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api.models.utils; + +import com.docdoku.api.models.*; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * This class helps to manipulate workflow objects + * @Author Morgan Guimard + */ +public class WorkflowHelper { + + public static ActivityDTO getCurrentActivity(WorkflowDTO workflow){ + + List activities = workflow.getActivities(); + int currentStep = getCurrentStep(workflow); + if (currentStep < activities.size()) { + return activities.get(currentStep); + } else { + return null; + } + + } + + public static int getCurrentStep(WorkflowDTO workflow){ + List activities = workflow.getActivities(); + int i = 0; + for (ActivityDTO activity : activities) { + if (activity.getComplete()) { + i++; + } else { + break; + } + } + return i; + } + + + public static List getRunningTasks(WorkflowDTO workflowDTO) { + return getRunningTasks(getCurrentActivity(workflowDTO)); + } + + public static List getRunningTasks(ActivityDTO currentActivity) { + List tasks = new ArrayList<>(); + for(TaskDTO task : currentActivity.getTasks()){ + if(TaskDTO.StatusEnum.IN_PROGRESS.equals(task.getStatus())){ + tasks.add(task); + } + } + return tasks; + } + + public static Set getRolesInvolved(WorkflowModelDTO workflowModel) { + Set roles = new HashSet<>(); + for(ActivityModelDTO activityModelDTO:workflowModel.getActivityModels()){ + for(TaskModelDTO taskModelDTO : activityModelDTO.getTaskModels()){ + roles.add(taskModelDTO.getRole()); + } + } + return roles; + } +} diff --git a/docdoku-api-java/src/test/java/com/docdoku/api/AccountsApiTest.java b/docdoku-api-java/src/test/java/com/docdoku/api/AccountsApiTest.java new file mode 100644 index 0000000000..0cff478e86 --- /dev/null +++ b/docdoku-api-java/src/test/java/com/docdoku/api/AccountsApiTest.java @@ -0,0 +1,71 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.AccountDTO; +import com.docdoku.api.services.AccountsApi; +import junit.framework.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.UUID; + +@RunWith(JUnit4.class) +public class AccountsApiTest { + + @Test + public void createAccountTest() throws ApiException { + String login = "USER-"+ UUID.randomUUID().toString().substring(0,6); + AccountDTO accountDTO = new AccountDTO(); + accountDTO.setLogin(login); + accountDTO.setEmail("my@email.com"); + accountDTO.setNewPassword("password"); + accountDTO.setLanguage("en"); + accountDTO.setName("Mr " + login); + accountDTO.setTimeZone("CET"); + AccountDTO account = new AccountsApi(TestConfig.GUEST_CLIENT).createAccount(accountDTO); + Assert.assertEquals(account.getLogin(), login); + } + + @Test + public void getAccountTest() throws ApiException { + AccountDTO account = new AccountsApi(TestConfig.BASIC_CLIENT).getAccount(); + Assert.assertEquals(account.getLogin(), TestConfig.LOGIN); + } + + @Test + public void updateAccountTest() throws ApiException { + + String newName = UUID.randomUUID().toString().substring(0,6); + + AccountsApi accountsApi = new AccountsApi(TestConfig.BASIC_CLIENT); + AccountDTO account = accountsApi.getAccount(); + account.setName(newName); + + AccountDTO updatedAccount = accountsApi.updateAccount(account); + Assert.assertEquals(updatedAccount.getName(), newName); + Assert.assertEquals(updatedAccount,account); + + } + +} diff --git a/docdoku-api-java/src/test/java/com/docdoku/api/DocdokuPLMClientTest.java b/docdoku-api-java/src/test/java/com/docdoku/api/DocdokuPLMClientTest.java new file mode 100644 index 0000000000..81a45530f7 --- /dev/null +++ b/docdoku-api-java/src/test/java/com/docdoku/api/DocdokuPLMClientTest.java @@ -0,0 +1,80 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + +import com.docdoku.api.client.ApiClient; +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.AccountDTO; +import com.docdoku.api.models.WorkspaceDTO; +import com.docdoku.api.models.WorkspaceListDTO; +import com.docdoku.api.services.AccountsApi; +import com.docdoku.api.services.WorkspacesApi; +import junit.framework.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.UUID; + +/** + * This class tests DocdokuPLMClient class + * @Author Morgan Guimard + */ + +@RunWith(JUnit4.class) +public class DocdokuPLMClientTest { + @Test + public void basicTests() throws ApiException { + runTest(TestConfig.BASIC_CLIENT); + } + + @Test + public void cookieTests() throws ApiException { + runTest(TestConfig.COOKIE_CLIENT); + } + + @Test + @Ignore + public void jwtTests() throws ApiException { + runTest(TestConfig.JWT_CLIENT); + } + + + private void runTest(ApiClient client) throws ApiException { + WorkspacesApi workspacesApi = new WorkspacesApi(client); + AccountsApi accountsApi = new AccountsApi(client); + + AccountDTO account = accountsApi.getAccount(); + Assert.assertEquals(TestConfig.LOGIN, account.getLogin()); + + WorkspaceDTO workspace = new WorkspaceDTO(); + workspace.setId(UUID.randomUUID().toString()); + + workspacesApi.createWorkspace(workspace, TestConfig.LOGIN); + + WorkspaceListDTO workspacesForConnectedUser = workspacesApi.getWorkspacesForConnectedUser(); + Assert.assertNotNull(workspacesForConnectedUser); + Assert.assertTrue("Should contain created workspace", workspacesForConnectedUser.getAllWorkspaces().contains(workspace)); + workspacesApi.deleteWorkspace(workspace.getId()); + } + +} diff --git a/docdoku-api-java/src/test/java/com/docdoku/api/DocumentApiTest.java b/docdoku-api-java/src/test/java/com/docdoku/api/DocumentApiTest.java new file mode 100644 index 0000000000..4ac9afa467 --- /dev/null +++ b/docdoku-api-java/src/test/java/com/docdoku/api/DocumentApiTest.java @@ -0,0 +1,126 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.*; +import com.docdoku.api.models.utils.LastIterationHelper; +import com.docdoku.api.models.utils.UploadDownloadHelper; +import com.docdoku.api.services.DocumentApi; +import com.docdoku.api.services.FoldersApi; +import org.apache.commons.io.FileUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.UUID; + +@RunWith(JUnit4.class) +public class DocumentApiTest { + + private DocumentApi documentApi = new DocumentApi(TestConfig.BASIC_CLIENT); + private FoldersApi foldersApi = new FoldersApi(TestConfig.BASIC_CLIENT); + + @Test + public void documentApiUsageTests() throws ApiException { + + // Create a document + DocumentCreationDTO document = new DocumentCreationDTO(); + document.setReference(UUID.randomUUID().toString().substring(0,6)); + document.setTitle("GeneratedDoc"); + + DocumentRevisionDTO createdDocument = foldersApi.createDocumentMasterInFolder(TestConfig.WORKSPACE, document, TestConfig.WORKSPACE); + Assert.assertEquals(createdDocument.getDocumentMasterId(),document.getReference()); + + // Check in + DocumentRevisionDTO checkedInDocument = documentApi.checkInDocument(TestConfig.WORKSPACE, createdDocument.getDocumentMasterId(), createdDocument.getVersion(), ""); + Assert.assertEquals(checkedInDocument.getDocumentMasterId(),document.getReference()); + Assert.assertEquals(LastIterationHelper.getLastIteration(checkedInDocument).getIteration(), Integer.valueOf("1")); + + // Check out + DocumentRevisionDTO checkedOutDocument = documentApi.checkOutDocument(TestConfig.WORKSPACE, createdDocument.getDocumentMasterId(), createdDocument.getVersion(), ""); + Assert.assertEquals(checkedOutDocument.getDocumentMasterId(),document.getReference()); + Assert.assertEquals(LastIterationHelper.getLastIteration(checkedOutDocument).getIteration(),Integer.valueOf("2")); + + // Undo check out + DocumentRevisionDTO undoCheckOutDocument= documentApi.undoCheckOutDocument(TestConfig.WORKSPACE, createdDocument.getDocumentMasterId(), createdDocument.getVersion(), ""); + Assert.assertEquals(undoCheckOutDocument,checkedInDocument); + + // Check out + checkedOutDocument = documentApi.checkOutDocument(TestConfig.WORKSPACE, createdDocument.getDocumentMasterId(), createdDocument.getVersion(), ""); + + // Edit + DocumentIterationDTO lastIteration = LastIterationHelper.getLastIteration(checkedOutDocument); + Assert.assertNull(lastIteration.getModificationDate()); + lastIteration.setRevisionNote("Something modified"); + + DocumentIterationDTO updatedIteration = documentApi.updateDocumentIteration(TestConfig.WORKSPACE, checkedOutDocument.getDocumentMasterId(), checkedOutDocument.getVersion(), "2", lastIteration); + + Assert.assertNotNull(updatedIteration.getModificationDate()); + lastIteration.setModificationDate(updatedIteration.getModificationDate()); + Assert.assertEquals(lastIteration,updatedIteration); + + // Check in + checkedInDocument = documentApi.checkInDocument(TestConfig.WORKSPACE, createdDocument.getDocumentMasterId(), createdDocument.getVersion(), ""); + Assert.assertNull(checkedInDocument.getCheckOutUser()); + + // Release + DocumentRevisionDTO releasedDocument = documentApi.releaseDocumentRevision(TestConfig.WORKSPACE, checkedInDocument.getDocumentMasterId(), checkedInDocument.getVersion(), ""); + Assert.assertEquals(releasedDocument.getStatus(), DocumentRevisionDTO.StatusEnum.RELEASED); + + // Mark as obsolete + DocumentRevisionDTO obsoleteDocument = documentApi.markDocumentRevisionAsObsolete(TestConfig.WORKSPACE, releasedDocument.getDocumentMasterId(), releasedDocument.getVersion(), ""); + Assert.assertEquals(obsoleteDocument.getStatus(), DocumentRevisionDTO.StatusEnum.OBSOLETE); + + } + + @Test + public void uploadDownloadAttachedFilesToDocumentTest() throws ApiException, IOException { + + // Create a document + DocumentCreationDTO documentCreation = new DocumentCreationDTO(); + documentCreation.setReference(UUID.randomUUID().toString().substring(0, 6)); + documentCreation.setTitle("GeneratedDoc"); + + DocumentRevisionDTO document = foldersApi.createDocumentMasterInFolder(TestConfig.WORKSPACE, documentCreation, TestConfig.WORKSPACE); + + URL fileURL = DocumentApiTest.class.getClassLoader().getResource("com/docdoku/api/attached-file.md"); + File file = new File(fileURL.getPath()); + + DocumentIterationDTO lastIteration = LastIterationHelper.getLastIteration(document); + UploadDownloadHelper.uploadAttachedFile(lastIteration, TestConfig.BASIC_CLIENT, file); + + document = documentApi.getDocumentRevision(TestConfig.WORKSPACE, document.getDocumentMasterId(), document.getVersion(), null); + + lastIteration = LastIterationHelper.getLastIteration(document); + Assert.assertFalse(lastIteration.getAttachedFiles().isEmpty()); + + File downloadedFile = UploadDownloadHelper.downloadFile(lastIteration.getAttachedFiles().get(0), TestConfig.BASIC_CLIENT); + Assert.assertTrue(FileUtils.contentEquals(file, downloadedFile)); + + } + + +} diff --git a/docdoku-api-java/src/test/java/com/docdoku/api/FoldersApiTest.java b/docdoku-api-java/src/test/java/com/docdoku/api/FoldersApiTest.java new file mode 100644 index 0000000000..4dc76818d9 --- /dev/null +++ b/docdoku-api-java/src/test/java/com/docdoku/api/FoldersApiTest.java @@ -0,0 +1,56 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.FolderDTO; +import com.docdoku.api.services.FoldersApi; +import junit.framework.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; +import java.util.UUID; + +@RunWith(JUnit4.class) +public class FoldersApiTest { + + @Test + public void getRootFoldersTest() throws ApiException { + FoldersApi foldersApi = new FoldersApi(TestConfig.BASIC_CLIENT); + List rootFolders = foldersApi.getRootFolders(TestConfig.WORKSPACE, null); + Assert.assertNotNull(rootFolders); + } + + @Test + public void createRootFoldersTest() throws ApiException { + String folderName = "Folder-"+UUID.randomUUID().toString().substring(0,6); + FolderDTO folder = new FolderDTO(); + folder.setName(folderName); + FoldersApi foldersApi = new FoldersApi(TestConfig.BASIC_CLIENT); + foldersApi.createSubFolder(TestConfig.WORKSPACE,TestConfig.WORKSPACE, folder); + List rootFolders = foldersApi.getRootFolders(TestConfig.WORKSPACE, null); + Assert.assertEquals(rootFolders.stream() + .filter(folderDTO -> folderName.equals(folderDTO.getName())) + .count(),1); + } +} diff --git a/docdoku-api-java/src/test/java/com/docdoku/api/LanguagesApiTest.java b/docdoku-api-java/src/test/java/com/docdoku/api/LanguagesApiTest.java new file mode 100644 index 0000000000..5169b242b1 --- /dev/null +++ b/docdoku-api-java/src/test/java/com/docdoku/api/LanguagesApiTest.java @@ -0,0 +1,42 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.services.LanguagesApi; +import junit.framework.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; + +@RunWith(JUnit4.class) +public class LanguagesApiTest { + + @Test + public void getLanguagesTest() throws ApiException { + LanguagesApi languagesApi = new LanguagesApi(TestConfig.GUEST_CLIENT); + List languages = languagesApi.getLanguages(); + Assert.assertTrue(languages.contains("en")); + Assert.assertTrue(languages.contains("fr")); + } +} diff --git a/docdoku-api-java/src/test/java/com/docdoku/api/PartApiTest.java b/docdoku-api-java/src/test/java/com/docdoku/api/PartApiTest.java new file mode 100644 index 0000000000..ddbda7d719 --- /dev/null +++ b/docdoku-api-java/src/test/java/com/docdoku/api/PartApiTest.java @@ -0,0 +1,151 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.PartCreationDTO; +import com.docdoku.api.models.PartIterationDTO; +import com.docdoku.api.models.PartRevisionDTO; +import com.docdoku.api.models.utils.LastIterationHelper; +import com.docdoku.api.models.utils.UploadDownloadHelper; +import com.docdoku.api.services.PartApi; +import com.docdoku.api.services.PartbinaryApi; +import com.docdoku.api.services.PartsApi; +import org.apache.commons.io.FileUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.UUID; + +@RunWith(JUnit4.class) +public class PartApiTest { + + private PartApi partApi = new PartApi(TestConfig.BASIC_CLIENT); + private PartbinaryApi partbinaryApi = new PartbinaryApi(TestConfig.BASIC_CLIENT); + private PartsApi partsApi = new PartsApi(TestConfig.BASIC_CLIENT); + + @Test + public void partApiUsageTests() throws ApiException { + + // Create a part + PartCreationDTO part = new PartCreationDTO(); + part.setNumber(UUID.randomUUID().toString().substring(0, 6)); + part.setName("GeneratedPart"); + + PartRevisionDTO createdPart = partsApi.createNewPart(TestConfig.WORKSPACE, part); + Assert.assertEquals(createdPart.getNumber(), part.getNumber()); + + // Check in + PartRevisionDTO checkedInPart = partApi.checkIn(TestConfig.WORKSPACE, createdPart.getNumber(), createdPart.getVersion(), ""); + Assert.assertEquals(checkedInPart.getNumber(),part.getNumber()); + Assert.assertEquals(LastIterationHelper.getLastIteration(checkedInPart).getIteration(), Integer.valueOf("1")); + + // Check out + PartRevisionDTO checkedOutPart = partApi.checkOut(TestConfig.WORKSPACE, createdPart.getNumber(), createdPart.getVersion(), ""); + Assert.assertEquals(checkedOutPart.getNumber(),part.getNumber()); + Assert.assertEquals(LastIterationHelper.getLastIteration(checkedOutPart).getIteration(),Integer.valueOf("2")); + + // Undo check out + PartRevisionDTO undoCheckOutPart= partApi.undoCheckOut(TestConfig.WORKSPACE, createdPart.getNumber(), createdPart.getVersion(), ""); + Assert.assertEquals(undoCheckOutPart,checkedInPart); + + // Check out + checkedOutPart = partApi.checkOut(TestConfig.WORKSPACE, createdPart.getNumber(), createdPart.getVersion(), ""); + + // Edit + PartIterationDTO lastIteration = LastIterationHelper.getLastIteration(checkedOutPart); + Assert.assertNull(lastIteration.getModificationDate()); + lastIteration.setIterationNote("Something modified"); + + PartRevisionDTO updatedPartRevision = partApi.updatePartIteration(TestConfig.WORKSPACE, checkedOutPart.getNumber(), checkedOutPart.getVersion(), 2, lastIteration); + + PartIterationDTO updatedIteration = LastIterationHelper.getLastIteration(updatedPartRevision); + Assert.assertNotNull(updatedIteration.getModificationDate()); + lastIteration.setModificationDate(updatedIteration.getModificationDate()); + Assert.assertEquals(lastIteration,updatedIteration); + + // Check in + checkedInPart = partApi.checkIn(TestConfig.WORKSPACE, createdPart.getNumber(), createdPart.getVersion(), ""); + Assert.assertNull(checkedInPart.getCheckOutUser()); + + // Release + PartRevisionDTO releasedPart = partApi.releasePartRevision(TestConfig.WORKSPACE, checkedInPart.getNumber(), checkedInPart.getVersion(), ""); + Assert.assertEquals(releasedPart.getStatus(), PartRevisionDTO.StatusEnum.RELEASED); + + // Mark as obsolete + PartRevisionDTO obsoletePart = partApi.markPartRevisionAsObsolete(TestConfig.WORKSPACE, releasedPart.getNumber(), releasedPart.getVersion(), ""); + Assert.assertEquals(obsoletePart.getStatus(), PartRevisionDTO.StatusEnum.OBSOLETE); + + } + + + @Test + public void uploadDownloadAttachedFilesToPartTest() throws ApiException, IOException { + + // Create a part + PartCreationDTO partCreation = new PartCreationDTO(); + partCreation.setNumber(UUID.randomUUID().toString().substring(0, 6)); + partCreation.setName("GeneratedPart"); + + PartRevisionDTO part = partsApi.createNewPart(TestConfig.WORKSPACE, partCreation); + URL fileURL = PartApiTest.class.getClassLoader().getResource("com/docdoku/api/attached-file.md"); + File file = new File(fileURL.getPath()); + + PartIterationDTO lastIteration = LastIterationHelper.getLastIteration(part); + UploadDownloadHelper.uploadAttachedFile(lastIteration,TestConfig.BASIC_CLIENT,file); + part = partsApi.getPartRevision(TestConfig.WORKSPACE, part.getNumber(), part.getVersion()); + lastIteration = LastIterationHelper.getLastIteration(part); + Assert.assertFalse(lastIteration.getAttachedFiles().isEmpty()); + + File downloadedFile = UploadDownloadHelper.downloadFile(lastIteration.getAttachedFiles().get(0), TestConfig.BASIC_CLIENT); + Assert.assertTrue(FileUtils.contentEquals(file, downloadedFile)); + + } + + + @Test + public void uploadDownloadNativeCADFileToPartTest() throws ApiException, IOException { + + // Create a part + PartCreationDTO partCreation = new PartCreationDTO(); + partCreation.setNumber(UUID.randomUUID().toString().substring(0, 6)); + partCreation.setName("GeneratedPart"); + + PartRevisionDTO part = partsApi.createNewPart(TestConfig.WORKSPACE, partCreation); + URL fileURL = PartApiTest.class.getClassLoader().getResource("com/docdoku/api/native-cad.json"); + File file = new File(fileURL.getPath()); + + PartIterationDTO lastIteration = LastIterationHelper.getLastIteration(part); + UploadDownloadHelper.uploadNativeCADFile(lastIteration,TestConfig.BASIC_CLIENT,file); + part = partsApi.getPartRevision(TestConfig.WORKSPACE, part.getNumber(), part.getVersion()); + lastIteration = LastIterationHelper.getLastIteration(part); + Assert.assertFalse(lastIteration.getNativeCADFile().isEmpty()); + + File downloadedFile = UploadDownloadHelper.downloadFile(lastIteration.getNativeCADFile(), TestConfig.BASIC_CLIENT); + Assert.assertTrue(FileUtils.contentEquals(file, downloadedFile)); + + } +} diff --git a/docdoku-api-java/src/test/java/com/docdoku/api/TaskApiTest.java b/docdoku-api-java/src/test/java/com/docdoku/api/TaskApiTest.java new file mode 100644 index 0000000000..e4b5654a6e --- /dev/null +++ b/docdoku-api-java/src/test/java/com/docdoku/api/TaskApiTest.java @@ -0,0 +1,144 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.*; +import com.docdoku.api.models.utils.WorkflowHelper; +import com.docdoku.api.services.*; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@RunWith(JUnit4.class) +public class TaskApiTest { + + private WorkspacesApi workspacesApi = new WorkspacesApi(TestConfig.BASIC_CLIENT); + private RolesApi rolesApi = new RolesApi(TestConfig.BASIC_CLIENT); + private UsersApi usersApi = new UsersApi(TestConfig.BASIC_CLIENT); + private WorkflowModelsApi workflowModelsApi = new WorkflowModelsApi(TestConfig.BASIC_CLIENT); + private WorkspaceWorkflowsApi workspaceWorkflowsApi = new WorkspaceWorkflowsApi(TestConfig.BASIC_CLIENT); + private TasksApi tasksApi = new TasksApi(TestConfig.BASIC_CLIENT); + + @Test + public void tests() throws ApiException { + + String workflowModelReference = "W_MODEL-" + UUID.randomUUID().toString().substring(0, 6); + String roleName = "ROLE-" + UUID.randomUUID().toString().substring(0, 6); + String groupId = "GROUP-" + UUID.randomUUID().toString().substring(0, 6); + + // Get current user to create a role with default assignee + UserDTO user = usersApi.whoami(TestConfig.WORKSPACE); + + + UserGroupDTO userGroup = new UserGroupDTO(); + userGroup.setWorkspaceId(TestConfig.WORKSPACE); + userGroup.setId(groupId); + + workspacesApi.createGroup(TestConfig.WORKSPACE,userGroup); + workspacesApi.addUser(TestConfig.WORKSPACE,user, userGroup.getId()); + + RoleDTO role = new RoleDTO(); + role.setWorkspaceId(TestConfig.WORKSPACE); + role.setName(roleName); + // Assign default assigned users + role.getDefaultAssignedUsers().add(user); + + + RoleDTO createdRole = rolesApi.createRole(TestConfig.WORKSPACE, role); + + // Create a task model + TaskModelDTO taskModelDTO = new TaskModelDTO(); + taskModelDTO.setTitle("TASK_1"); + taskModelDTO.setInstructions("Do something please"); + taskModelDTO.setNum(0); + taskModelDTO.setRole(createdRole); + + List tasks = new ArrayList<>(); + tasks.add(taskModelDTO); + + // Create an activity model + ActivityModelDTO activityModelDTO = new ActivityModelDTO(); + activityModelDTO.setLifeCycleState("ACTIVITY_1"); + activityModelDTO.setStep(0); + activityModelDTO.setType(ActivityModelDTO.TypeEnum.SEQUENTIAL); + activityModelDTO.setTaskModels(tasks); + + List activityModels = new ArrayList<>(); + activityModels.add(activityModelDTO); + + // Create a workflow model + WorkflowModelDTO workflowModelDTO = new WorkflowModelDTO(); + workflowModelDTO.setReference(workflowModelReference); + workflowModelDTO.setFinalLifeCycleState("FINAL_STATE"); + workflowModelDTO.setActivityModels(activityModels); + + + WorkflowModelDTO workflowModel = workflowModelsApi.createWorkflowModel(TestConfig.WORKSPACE, workflowModelDTO); + Assert.assertEquals(workflowModel.getId(), workflowModelReference); + + // Use this model to create a workflow + + // We need to assigned users and or groups + List roleMapping = new ArrayList<>(); + RoleMappingDTO roleMappingDTO = new RoleMappingDTO(); + roleMappingDTO.setRoleName(role.getName()); + roleMappingDTO.getGroupIds().add(groupId); + roleMapping.add(roleMappingDTO); + + // Create a workflow container + WorkspaceWorkflowCreationDTO workspaceWorkflowCreationDTO = new WorkspaceWorkflowCreationDTO(); + workspaceWorkflowCreationDTO.setId(UUID.randomUUID().toString().substring(0, 6)); + workspaceWorkflowCreationDTO.setRoleMapping(roleMapping); + workspaceWorkflowCreationDTO.setWorkflowModelId(workflowModel.getId()); + + WorkspaceWorkflowDTO workspaceWorkflow = workspaceWorkflowsApi.createWorkspaceWorkflow(TestConfig.WORKSPACE, workspaceWorkflowCreationDTO); + List assignedTasks = tasksApi.getAssignedTasksForGivenUser(TestConfig.WORKSPACE, TestConfig.LOGIN); + Assert.assertEquals(1, + assignedTasks.stream() + .filter(taskDTO -> taskDTO.getWorkflowId() != null && + taskDTO.getWorkflowId().equals(workspaceWorkflow.getWorkflow().getId())) + .count()); + + ActivityDTO currentActivity = WorkflowHelper.getCurrentActivity(workspaceWorkflow.getWorkflow()); + List runningTasks = WorkflowHelper.getRunningTasks(currentActivity); + TaskDTO firstRunningTasks = runningTasks.get(0); + Assert.assertNotNull(firstRunningTasks); + + String taskId = workspaceWorkflow.getWorkflow().getId() + "-" + currentActivity.getStep() + "-" + firstRunningTasks.getNum(); + + + TaskProcessDTO taskProcessDTO = new TaskProcessDTO(); + taskProcessDTO.setAction(TaskProcessDTO.ActionEnum.APPROVE); + taskProcessDTO.setComment("Test are passing !"); + tasksApi.processTask(TestConfig.WORKSPACE, taskId, taskProcessDTO); + + + } + + +} diff --git a/docdoku-api-java/src/test/java/com/docdoku/api/TestConfig.java b/docdoku-api-java/src/test/java/com/docdoku/api/TestConfig.java new file mode 100644 index 0000000000..79e488fc67 --- /dev/null +++ b/docdoku-api-java/src/test/java/com/docdoku/api/TestConfig.java @@ -0,0 +1,48 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + +import com.docdoku.api.client.ApiClient; + +public class TestConfig { + + public static String URL; + public static String LOGIN; + public static String PASSWORD; + public static String WORKSPACE; + public static boolean DEBUG; + public static ApiClient GUEST_CLIENT; + public static ApiClient BASIC_CLIENT; + public static ApiClient JWT_CLIENT; + public static ApiClient COOKIE_CLIENT; + + static{ + URL = System.getProperty("url") != null ? System.getProperty("url") : "http://localhost:8080/api"; + LOGIN = System.getProperty("login") != null ? System.getProperty("login") : "test"; + PASSWORD = System.getProperty("password") != null ? System.getProperty("password") : "test"; + WORKSPACE = System.getProperty("workspace") != null ? System.getProperty("workspace") : "test"; + DEBUG = System.getProperty("debug") != null ? Boolean.parseBoolean(System.getProperty("debug")) : false; + GUEST_CLIENT = new DocdokuPLMClient(URL,DEBUG).getClient(); + BASIC_CLIENT = new DocdokuPLMBasicClient(URL,LOGIN, PASSWORD, DEBUG).getClient(); + JWT_CLIENT = new DocdokuPLMBasicClient(URL, LOGIN, PASSWORD, DEBUG).getClient(); + COOKIE_CLIENT = new DocdokuPLMCookieClient(URL, LOGIN, PASSWORD, DEBUG).getClient(); + } +} diff --git a/docdoku-api-java/src/test/java/com/docdoku/api/TimezonesApiTest.java b/docdoku-api-java/src/test/java/com/docdoku/api/TimezonesApiTest.java new file mode 100644 index 0000000000..26a4040379 --- /dev/null +++ b/docdoku-api-java/src/test/java/com/docdoku/api/TimezonesApiTest.java @@ -0,0 +1,42 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.services.TimezoneApi; +import junit.framework.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; + +@RunWith(JUnit4.class) +public class TimezonesApiTest { + + @Test + public void getTimezonesTest() throws ApiException { + TimezoneApi timezoneApi = new TimezoneApi(TestConfig.GUEST_CLIENT); + List timeZones = timezoneApi.getTimeZones(); + Assert.assertTrue(timeZones.contains("CET")); + } +} diff --git a/docdoku-api-java/src/test/java/com/docdoku/api/UploadDownloadConsistencyTest.java b/docdoku-api-java/src/test/java/com/docdoku/api/UploadDownloadConsistencyTest.java new file mode 100644 index 0000000000..512baaca14 --- /dev/null +++ b/docdoku-api-java/src/test/java/com/docdoku/api/UploadDownloadConsistencyTest.java @@ -0,0 +1,93 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.DocumentCreationDTO; +import com.docdoku.api.models.DocumentIterationDTO; +import com.docdoku.api.models.DocumentRevisionDTO; +import com.docdoku.api.models.utils.LastIterationHelper; +import com.docdoku.api.models.utils.UploadDownloadHelper; +import com.docdoku.api.services.DocumentApi; +import com.docdoku.api.services.FoldersApi; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +@RunWith(JUnit4.class) +public class UploadDownloadConsistencyTest { + + private DocumentApi documentApi = new DocumentApi(TestConfig.BASIC_CLIENT); + private FoldersApi foldersApi = new FoldersApi(TestConfig.BASIC_CLIENT); + + + @Test + public void uploadZipTest() throws ApiException, IOException { + + // Create a document + DocumentCreationDTO documentCreation = new DocumentCreationDTO(); + documentCreation.setReference(UUID.randomUUID().toString().substring(0, 6)); + documentCreation.setTitle("GeneratedDoc"); + + DocumentRevisionDTO document = foldersApi.createDocumentMasterInFolder(TestConfig.WORKSPACE, documentCreation, TestConfig.WORKSPACE); + + URL fileURL = UploadDownloadConsistencyTest.class.getClassLoader().getResource("com/docdoku/api/attached-file.zip"); + File file = new File(fileURL.getPath()); + + List originalEntries = getEntries(file); + Assert.assertEquals(1,originalEntries.size()); + + DocumentIterationDTO lastIteration = LastIterationHelper.getLastIteration(document); + UploadDownloadHelper.uploadAttachedFile(lastIteration, TestConfig.BASIC_CLIENT, file); + document = documentApi.getDocumentRevision(TestConfig.WORKSPACE, document.getDocumentMasterId(), document.getVersion(), null); + lastIteration = LastIterationHelper.getLastIteration(document); + File downloadedFile = UploadDownloadHelper.downloadFile(lastIteration.getAttachedFiles().get(0), TestConfig.BASIC_CLIENT); + + List downloadedEntries = getEntries(downloadedFile); + Assert.assertTrue(new HashSet<>(originalEntries).equals(new HashSet<>(downloadedEntries))); + + + } + + private List getEntries(File file){ + List fileNames = new ArrayList<>(); + try (ZipFile zipFile = new ZipFile(file)) { + Enumeration zipEntries = zipFile.entries(); + while (zipEntries.hasMoreElements()) { + String fileName = ((ZipEntry) zipEntries.nextElement()).getName(); + fileNames.add(fileName); + } + } catch (IOException e) { + e.printStackTrace(); + } + return fileNames; + } + + +} diff --git a/docdoku-api-java/src/test/java/com/docdoku/api/UserApiTest.java b/docdoku-api-java/src/test/java/com/docdoku/api/UserApiTest.java new file mode 100644 index 0000000000..dc36d08900 --- /dev/null +++ b/docdoku-api-java/src/test/java/com/docdoku/api/UserApiTest.java @@ -0,0 +1,60 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.UserDTO; +import com.docdoku.api.services.UsersApi; +import junit.framework.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; + +@RunWith(JUnit4.class) +public class UserApiTest { + + private UsersApi usersApi = new UsersApi(TestConfig.BASIC_CLIENT); + + @Test + public void whoamiTest() throws ApiException { + UserDTO currentUser = usersApi.whoami(TestConfig.WORKSPACE); + Assert.assertEquals(currentUser.getLogin(),TestConfig.LOGIN); + Assert.assertEquals(currentUser.getWorkspaceId(),TestConfig.WORKSPACE); + } + + @Test + public void getAdminInWorkspaceTest() throws ApiException { + UserDTO admin = usersApi.getAdminInWorkspace(TestConfig.WORKSPACE); + Assert.assertEquals(admin.getWorkspaceId(),TestConfig.WORKSPACE); + } + + @Test + public void getUsersInWorkspaceTest() throws ApiException { + List users = usersApi.getUsersInWorkspace(TestConfig.WORKSPACE); + Assert.assertEquals(users.stream() + .filter(user -> TestConfig.LOGIN.equals(user.getLogin())) + .count(),1); + } + +} diff --git a/docdoku-api-java/src/test/java/com/docdoku/api/WorkflowApiTest.java b/docdoku-api-java/src/test/java/com/docdoku/api/WorkflowApiTest.java new file mode 100644 index 0000000000..ad9c1a4719 --- /dev/null +++ b/docdoku-api-java/src/test/java/com/docdoku/api/WorkflowApiTest.java @@ -0,0 +1,269 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.*; +import com.docdoku.api.models.utils.WorkflowHelper; +import com.docdoku.api.services.*; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +@RunWith(JUnit4.class) +public class WorkflowApiTest { + + private WorkspacesApi workspacesApi = new WorkspacesApi(TestConfig.BASIC_CLIENT); + private RolesApi rolesApi = new RolesApi(TestConfig.BASIC_CLIENT); + private UsersApi usersApi = new UsersApi(TestConfig.BASIC_CLIENT); + private WorkflowModelsApi workflowModelsApi = new WorkflowModelsApi(TestConfig.BASIC_CLIENT); + private WorkspaceWorkflowsApi workspaceWorkflowsApi = new WorkspaceWorkflowsApi(TestConfig.BASIC_CLIENT); + private WorkflowsApi workflowsApi = new WorkflowsApi(TestConfig.BASIC_CLIENT); + private TasksApi tasksApi = new TasksApi(TestConfig.BASIC_CLIENT); + private PartsApi partsApi = new PartsApi(TestConfig.BASIC_CLIENT); + private DocumentApi documentApi = new DocumentApi(TestConfig.BASIC_CLIENT); + private FoldersApi foldersApi = new FoldersApi(TestConfig.BASIC_CLIENT); + + + @Test + public void testWorkflowOnWorkspacesWorkflow() throws ApiException { + + // Create a workflow model + String workflowModelReference = "W_MODEL-" + UUID.randomUUID().toString().substring(0, 6); + WorkflowModelDTO workflowModel = createModel(workflowModelReference); + Assert.assertEquals(workflowModel.getId(), workflowModelReference); + + List roleMapping = resolveDefaultRoles(workflowModel); + + // Create a workflow container + WorkspaceWorkflowCreationDTO workspaceWorkflowCreationDTO = new WorkspaceWorkflowCreationDTO(); + workspaceWorkflowCreationDTO.setId(UUID.randomUUID().toString().substring(0, 6)); + workspaceWorkflowCreationDTO.setRoleMapping(roleMapping); + workspaceWorkflowCreationDTO.setWorkflowModelId(workflowModel.getId()); + + WorkspaceWorkflowDTO workspaceWorkflow = workspaceWorkflowsApi.createWorkspaceWorkflow(TestConfig.WORKSPACE, workspaceWorkflowCreationDTO); + Assert.assertEquals(workspaceWorkflow.getId(),workspaceWorkflowCreationDTO.getId()); + Assert.assertEquals(workspaceWorkflow.getWorkspaceId(),TestConfig.WORKSPACE); + runAsserts(workspaceWorkflow.getWorkflow(), workflowModel); + processTask(workspaceWorkflow.getWorkflow(), workflowModel); + + workspaceWorkflowsApi.deleteWorkspaceWorkflow(TestConfig.WORKSPACE, workspaceWorkflow.getId()); + } + + @Test + public void testWorkflowOnPart() throws ApiException { + + // Create a workflow model + String workflowModelReference = "W_MODEL-" + UUID.randomUUID().toString().substring(0, 6); + WorkflowModelDTO workflowModel = createModel(workflowModelReference); + Assert.assertEquals(workflowModel.getId(), workflowModelReference); + + List roleMapping = resolveDefaultRoles(workflowModel); + + // Create a part + + // Create a part + PartCreationDTO part = new PartCreationDTO(); + part.setNumber(UUID.randomUUID().toString().substring(0, 6)); + part.setWorkflowModelId(workflowModelReference); + part.setRoleMapping(roleMapping); + PartRevisionDTO newPart = partsApi.createNewPart(TestConfig.WORKSPACE, part); + partsApi.checkIn(TestConfig.WORKSPACE, newPart.getNumber(),newPart.getVersion(), ""); + + WorkflowDTO workflow = workflowsApi.getWorkflowInstance(TestConfig.WORKSPACE, newPart.getWorkflow().getId()); + Assert.assertEquals(workflow,newPart.getWorkflow()); + runAsserts(newPart.getWorkflow(), workflowModel); + + processTask(newPart.getWorkflow(), workflowModel); + + + } + + @Test + public void testWorkflowOnDocument() throws ApiException { + + // Create a workflow model + String workflowModelReference = "W_MODEL-" + UUID.randomUUID().toString().substring(0, 6); + WorkflowModelDTO workflowModel = createModel(workflowModelReference); + Assert.assertEquals(workflowModel.getId(), workflowModelReference); + + List roleMapping = resolveDefaultRoles(workflowModel); + + // Create a document + DocumentCreationDTO document = new DocumentCreationDTO(); + document.setReference(UUID.randomUUID().toString().substring(0,6)); + document.setWorkflowModelId(workflowModelReference); + document.setRoleMapping(roleMapping); + + DocumentRevisionDTO createdDocument = foldersApi.createDocumentMasterInFolder(TestConfig.WORKSPACE, document, TestConfig.WORKSPACE); + // TODO : upload file to doc, then download, then checkin + documentApi.checkInDocument(TestConfig.WORKSPACE, createdDocument.getDocumentMasterId(), createdDocument.getVersion(), ""); + + WorkflowDTO workflow = workflowsApi.getWorkflowInstance(TestConfig.WORKSPACE, createdDocument.getWorkflow().getId()); + Assert.assertEquals(workflow,createdDocument.getWorkflow()); + + runAsserts(createdDocument.getWorkflow(), workflowModel); + + try { + processTask(createdDocument.getWorkflow(), workflowModel); + } catch (ApiException e){ + Assert.assertEquals("Before marking a task as done or rejecting it on a document you must have downloaded and opened it at least one time",e.getMessage()); + } + + } + + private void runAsserts(WorkflowDTO workflowDTO, WorkflowModelDTO workflowModel) { + Assert.assertNotNull(workflowDTO); + Assert.assertEquals(workflowDTO.getFinalLifeCycleState(),workflowModel.getFinalLifeCycleState()); + Assert.assertNotNull(workflowDTO.getActivities()); + Assert.assertEquals(workflowDTO.getActivities().size(), workflowModel.getActivityModels().size()); + Assert.assertEquals(workflowDTO.getActivities().get(0).getLifeCycleState(), workflowModel.getActivityModels().get(0).getLifeCycleState()); + Assert.assertEquals(workflowDTO.getActivities().get(0).getTasks().size(), workflowModel.getActivityModels().get(0).getTaskModels().size()); + Assert.assertEquals(workflowDTO.getActivities().get(0).getTasks().get(0).getTitle(), workflowModel.getActivityModels().get(0).getTaskModels().get(0).getTitle()); + Assert.assertEquals(workflowDTO.getActivities().get(0).getTasks().get(1).getTitle(), workflowModel.getActivityModels().get(0).getTaskModels().get(1).getTitle()); + } + + private void processTask(WorkflowDTO createdWorkflow, WorkflowModelDTO createdFrom) throws ApiException { + + UserDTO me = usersApi.whoami(TestConfig.WORKSPACE); + + // Retrieve workflow by it's id + WorkflowDTO workflow = workflowsApi.getWorkflowInstance(TestConfig.WORKSPACE, createdWorkflow.getId()); + Assert.assertEquals(workflow,createdWorkflow); + + // Get first running task, assert our user is in assigned users + ActivityDTO currentActivity = WorkflowHelper.getCurrentActivity(workflow); + List runningTasks = WorkflowHelper.getRunningTasks(workflow); + TaskDTO firstTask = runningTasks.get(0); + + Assert.assertTrue(firstTask.getAssignedUsers().contains(me)); + Assert.assertEquals(firstTask.getStatus(), TaskDTO.StatusEnum.IN_PROGRESS); + + // Approve the first task + TaskProcessDTO taskProcessDTO = new TaskProcessDTO(); + taskProcessDTO.setComment("Okay"); + taskProcessDTO.setSignature("data:image/gif;base64,R0lGODlhAQABAAAAACw="); + taskProcessDTO.setAction(TaskProcessDTO.ActionEnum.APPROVE); + + String taskId = workflow.getId() + "-" + currentActivity.getStep() + "-" + firstTask.getNum(); + + tasksApi.processTask(TestConfig.WORKSPACE, taskId, taskProcessDTO); + + TaskDTO task = tasksApi.getTask(TestConfig.WORKSPACE, taskId); + Assert.assertEquals(TaskDTO.StatusEnum.APPROVED, task.getStatus()); + + workflow = workflowsApi.getWorkflowInstance(TestConfig.WORKSPACE, workflow.getId()); + + currentActivity = WorkflowHelper.getCurrentActivity(workflow); + TaskDTO nextTask = WorkflowHelper.getRunningTasks(currentActivity).get(0); + Assert.assertEquals(createdFrom.getActivityModels().get(0).getTaskModels().get(1).getTitle(),nextTask.getTitle()); + + } + + private List resolveDefaultRoles(WorkflowModelDTO workflowModel) { + Set rolesInvolved = WorkflowHelper.getRolesInvolved(workflowModel); + List roleMapping = new ArrayList<>(); + + // we need to resolve the roles (use defaults assignments) + for(RoleDTO role:rolesInvolved){ + + RoleMappingDTO roleMappingDTO = new RoleMappingDTO(); + roleMappingDTO.setRoleName(role.getName()); + + for(UserGroupDTO group :role.getDefaultAssignedGroups()){ + roleMappingDTO.getGroupIds().add(group.getId()); + } + for(UserDTO user:role.getDefaultAssignedUsers()){ + roleMappingDTO.getUserLogins().add(user.getLogin()); + } + roleMapping.add(roleMappingDTO); + } + + return roleMapping; + } + + public WorkflowModelDTO createModel(String workflowModelReference) throws ApiException { + + String roleName = "ROLE-" + UUID.randomUUID().toString().substring(0, 6); + + // Get current user to create a role with default assignee + UserDTO user = usersApi.whoami(TestConfig.WORKSPACE); + List userGroups = workspacesApi.getUserGroups(TestConfig.WORKSPACE); + + RoleDTO role = new RoleDTO(); + role.setWorkspaceId(TestConfig.WORKSPACE); + role.setName(roleName); + + // Assign default assigned users + role.getDefaultAssignedUsers().add(user); + + // Assign default assigned groups + if(!userGroups.isEmpty()) { + role.getDefaultAssignedGroups().add(userGroups.get(0)); + } + + RoleDTO createdRole = rolesApi.createRole(TestConfig.WORKSPACE, role); + + // Create a task model + TaskModelDTO taskModelDTO_1 = new TaskModelDTO(); + taskModelDTO_1.setTitle("TASK_1"); + taskModelDTO_1.setInstructions("Do something please"); + taskModelDTO_1.setNum(0); + taskModelDTO_1.setRole(createdRole); + + TaskModelDTO taskModelDTO_2 = new TaskModelDTO(); + taskModelDTO_2.setTitle("TASK_2"); + taskModelDTO_2.setInstructions("Do something else"); + taskModelDTO_2.setNum(1); + taskModelDTO_2.setRole(createdRole); + + List tasks = new ArrayList<>(); + tasks.add(taskModelDTO_1); + tasks.add(taskModelDTO_2); + + // Create an activity model + ActivityModelDTO activityModelDTO = new ActivityModelDTO(); + activityModelDTO.setLifeCycleState("ACTIVITY_1"); + activityModelDTO.setStep(0); + activityModelDTO.setType(ActivityModelDTO.TypeEnum.SEQUENTIAL); + activityModelDTO.setTaskModels(tasks); + + List activityModels = new ArrayList<>(); + activityModels.add(activityModelDTO); + + // Create a workflow model + WorkflowModelDTO workflowModelDTO = new WorkflowModelDTO(); + workflowModelDTO.setReference(workflowModelReference); + workflowModelDTO.setFinalLifeCycleState("FINAL_STATE"); + workflowModelDTO.setActivityModels(activityModels); + + WorkflowModelDTO workflowModel = workflowModelsApi.createWorkflowModel(TestConfig.WORKSPACE, workflowModelDTO); + return workflowModel; + } + +} diff --git a/docdoku-api-java/src/test/java/com/docdoku/api/WorkspacesApiTest.java b/docdoku-api-java/src/test/java/com/docdoku/api/WorkspacesApiTest.java new file mode 100644 index 0000000000..5ffda26121 --- /dev/null +++ b/docdoku-api-java/src/test/java/com/docdoku/api/WorkspacesApiTest.java @@ -0,0 +1,127 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api; + + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.*; +import com.docdoku.api.services.AccountsApi; +import com.docdoku.api.services.WorkspacesApi; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; +import java.util.UUID; + +@RunWith(JUnit4.class) +public class WorkspacesApiTest { + + @Test + public void createWorkspaceTest() throws ApiException { + WorkspaceDTO workspace = new WorkspaceDTO(); + String workspaceId = UUID.randomUUID().toString().substring(0,6); + workspace.setId(workspaceId); + workspace.setDescription("Generated by tests"); + workspace.setFolderLocked(false); + WorkspacesApi workspacesApi = new WorkspacesApi(TestConfig.BASIC_CLIENT); + WorkspaceDTO createdWorkspace = workspacesApi.createWorkspace(workspace, TestConfig.LOGIN); + Assert.assertEquals(workspace,createdWorkspace); + workspacesApi.deleteWorkspace(workspaceId); + } + + @Test + public void getWorkspaceList() throws ApiException { + WorkspaceDTO workspace = new WorkspaceDTO(); + String workspaceId = UUID.randomUUID().toString().substring(0,6); + workspace.setId(workspaceId); + workspace.setDescription("Generated by tests"); + workspace.setFolderLocked(false); + WorkspacesApi workspacesApi = new WorkspacesApi(TestConfig.BASIC_CLIENT); + WorkspaceDTO createdWorkspace = workspacesApi.createWorkspace(workspace, TestConfig.LOGIN); + WorkspaceListDTO workspacesForConnectedUser = workspacesApi.getWorkspacesForConnectedUser(); + Assert.assertTrue(workspacesForConnectedUser.getAllWorkspaces().contains(createdWorkspace)); + workspacesApi.deleteWorkspace(workspaceId); + } + + @Test + public void updateWorkspace() throws ApiException { + WorkspaceDTO workspace = new WorkspaceDTO(); + String workspaceId = UUID.randomUUID().toString().substring(0,6); + workspace.setId(workspaceId); + workspace.setDescription("Generated by tests"); + workspace.setFolderLocked(false); + WorkspacesApi workspacesApi = new WorkspacesApi(TestConfig.BASIC_CLIENT); + WorkspaceDTO createdWorkspace = workspacesApi.createWorkspace(workspace, TestConfig.LOGIN); + String newDescription = "Updated by tests"; + createdWorkspace.setDescription(newDescription); + WorkspaceDTO updatedWorkspace = workspacesApi.updateWorkspace(workspaceId, createdWorkspace); + Assert.assertEquals(updatedWorkspace.getDescription(),newDescription); + Assert.assertEquals(updatedWorkspace,createdWorkspace); + workspacesApi.deleteWorkspace(workspaceId); + } + + @Test + public void addUserInWorkspace() throws ApiException { + AccountDTO newAccount = createAccount(); + UserDTO userToAdd = new UserDTO(); + userToAdd.setLogin(newAccount.getLogin()); + WorkspacesApi workspacesApi = new WorkspacesApi(TestConfig.BASIC_CLIENT); + workspacesApi.addUser(TestConfig.WORKSPACE, userToAdd, null); + List usersInWorkspace = workspacesApi.getUsersInWorkspace(TestConfig.WORKSPACE); + Assert.assertEquals(usersInWorkspace.stream().filter(userDTO -> userDTO.getLogin().equals(userToAdd.getLogin())).count(), 1); + } + + @Test + public void addUserInGroup() throws ApiException { + AccountDTO newAccount = createAccount(); + UserDTO userToAdd = new UserDTO(); + userToAdd.setLogin(newAccount.getLogin()); + UserGroupDTO group = createGroup(); + WorkspacesApi workspacesApi = new WorkspacesApi(TestConfig.BASIC_CLIENT); + workspacesApi.addUser(TestConfig.WORKSPACE, userToAdd, group.getId()); + List usersInGroup = workspacesApi.getUsersInGroup(TestConfig.WORKSPACE, group.getId()); + Assert.assertEquals(usersInGroup.stream().filter(userDTO -> userDTO.getLogin().equals(userToAdd.getLogin())).count(),1); + } + + private UserGroupDTO createGroup() throws ApiException { + String groupId = UUID.randomUUID().toString().substring(0,6); + UserGroupDTO group = new UserGroupDTO(); + group.setWorkspaceId(TestConfig.WORKSPACE); + group.setId(groupId); + return new WorkspacesApi(TestConfig.BASIC_CLIENT).createGroup(TestConfig.WORKSPACE, group); + } + + + private AccountDTO createAccount() throws ApiException { + String login = "USER-"+ UUID.randomUUID().toString().substring(0,6); + AccountDTO accountDTO = new AccountDTO(); + accountDTO.setLogin(login); + accountDTO.setEmail("my@email.com"); + accountDTO.setNewPassword("password"); + accountDTO.setLanguage("en"); + accountDTO.setName("Mr " + login); + accountDTO.setTimeZone("CET"); + return new AccountsApi(TestConfig.GUEST_CLIENT).createAccount(accountDTO); + } + +} diff --git a/docdoku-api-java/src/test/resources/com/docdoku/api/attached-file.md b/docdoku-api-java/src/test/resources/com/docdoku/api/attached-file.md new file mode 100644 index 0000000000..d00fb07231 --- /dev/null +++ b/docdoku-api-java/src/test/resources/com/docdoku/api/attached-file.md @@ -0,0 +1 @@ +Hey, some data inside ! \ No newline at end of file diff --git a/docdoku-api-java/src/test/resources/com/docdoku/api/attached-file.zip b/docdoku-api-java/src/test/resources/com/docdoku/api/attached-file.zip new file mode 100644 index 0000000000..b50d2114a6 Binary files /dev/null and b/docdoku-api-java/src/test/resources/com/docdoku/api/attached-file.zip differ diff --git a/docdoku-api-java/src/test/resources/com/docdoku/api/native-cad.json b/docdoku-api-java/src/test/resources/com/docdoku/api/native-cad.json new file mode 100644 index 0000000000..08a133b487 --- /dev/null +++ b/docdoku-api-java/src/test/resources/com/docdoku/api/native-cad.json @@ -0,0 +1 @@ +{"some":"3D data"} \ No newline at end of file diff --git a/docdoku-api-js/.gitignore b/docdoku-api-js/.gitignore new file mode 100644 index 0000000000..d88b002282 --- /dev/null +++ b/docdoku-api-js/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +lib/spec.json \ No newline at end of file diff --git a/docdoku-api-js/Gruntfile.js b/docdoku-api-js/Gruntfile.js new file mode 100644 index 0000000000..f91e75739a --- /dev/null +++ b/docdoku-api-js/Gruntfile.js @@ -0,0 +1,41 @@ +'use strict'; + +module.exports = function (grunt) { + + require('load-grunt-tasks')(grunt); + + grunt.initConfig({ + copy:{ + spec:{ + files : { + 'lib/spec.json':'../docdoku-api/target/swagger/swagger.json' + } + }, + browser:{ + files : { + 'dist/index.html':'example/browser.html' + } + } + }, + clean: { + options: { + force: true + }, + files:['lib/spec.json', 'dist'] + }, + + browserify: { + options:{ + standalone:true + }, + dist: { + files: { + 'dist/docdoku-api.js': ['lib/*.js'] + } + } + } + + }); + + grunt.registerTask('build',['clean', 'copy:spec','browserify:dist', 'copy:browser']); +}; diff --git a/docdoku-api-js/README.md b/docdoku-api-js/README.md new file mode 100644 index 0000000000..3f81158f1d --- /dev/null +++ b/docdoku-api-js/README.md @@ -0,0 +1,61 @@ +# Docdoku API JS + +Generate a node module to integrate in your nodejs and/or browser application + +## Documentation + +This module is designed to work in a nodejs or a browser application + +### Browser + +Add the script to your page, then it's available from the window object + + + + + +### Node Js + +Install package, then in your app + + var DocdokuPlmClient = require('../lib/docdoku-api'); + + var client = new DocdokuPlmClient({url:'http://localhost:8080/api'}); + + client.getApi().then(function(api){ + api.languages.getLanguages().then(function(response){ + console.log(response.obj); + }); + }); + + +## Development guide + +Package folders description + +* `lib` : main module sources +* `test` : test sources. Uses mocha (command : `mocha run test`) + +Build + +* Run `mvn clean install` in docdoku-api module +* Run `npm run build` in this module directory + +Tests + +* Run `npm install` before. Change with your credentials (needs a running server). + + mocha test/*.test.js --url=http://localhost:8080/api --login=foo --password=bar --workspace=foo + +* Browser testing : run `npm run build`, then launch a static server in dist folder `http-server -p 8086` to see results in browser diff --git a/docdoku-api-js/example/browser.html b/docdoku-api-js/example/browser.html new file mode 100644 index 0000000000..3ae6ed3a7c --- /dev/null +++ b/docdoku-api-js/example/browser.html @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/docdoku-api-js/example/node-module.js b/docdoku-api-js/example/node-module.js new file mode 100644 index 0000000000..6eaab1e93b --- /dev/null +++ b/docdoku-api-js/example/node-module.js @@ -0,0 +1,12 @@ +// Use this lib as a node module + +var DocdokuPlmClient = require('../lib/docdoku-api'); + +var client = new DocdokuPlmClient({url:'http://localhost:8080/api'}); + +client.getApi().then(function(api){ + api.languages.getLanguages().then(function(response){ + console.log(response.obj); + }); +}); + diff --git a/docdoku-api-js/lib/docdoku-api.js b/docdoku-api-js/lib/docdoku-api.js new file mode 100644 index 0000000000..0439538ff3 --- /dev/null +++ b/docdoku-api-js/lib/docdoku-api.js @@ -0,0 +1,37 @@ +var SwaggerClient = require('swagger-client'); +var auth = require('swagger-client/lib/auth'); +var spec = require('./spec'); + +var DocdokuPLMClient = module.exports = function(options){ + this.setOptions(options); +}; + +DocdokuPLMClient.prototype.setOptions = function(options){ + + this.options = options || {}; + this.authorizations = this.options.authorizations || {}; + + if(this.options.basicAuth){ + this.authorizations.basicAuth = new SwaggerClient.PasswordAuthorization(this.options.login,this.options.password); + } + + if(this.options.cookie){ + this.authorizations.cookieAuth = new SwaggerClient.ApiKeyAuthorization('Cookie', this.options.cookie, 'header'); + } + + if(this.options.jwt){ + this.authorizations.jwtAuth = new SwaggerClient.ApiKeyAuthorization('Authorization', 'Bearer ' + this.options.jwt, 'header'); + } + + return this; +}; + +DocdokuPLMClient.prototype.getApi = function(){ + this.client = new SwaggerClient({ + url: this.options.url, + spec:spec, + usePromise:true, + authorizations:this.authorizations + }); + return this.client; +}; diff --git a/docdoku-api-js/lib/exports.js b/docdoku-api-js/lib/exports.js new file mode 100644 index 0000000000..6253d2cc74 --- /dev/null +++ b/docdoku-api-js/lib/exports.js @@ -0,0 +1,3 @@ +if(typeof window !== 'undefined'){ + window.DocdokuPlmClient = require('./docdoku-api'); +} diff --git a/docdoku-api-js/package.json b/docdoku-api-js/package.json new file mode 100644 index 0000000000..59f6d928b5 --- /dev/null +++ b/docdoku-api-js/package.json @@ -0,0 +1,23 @@ +{ + "name": "docdoku-api-js", + "version": "1.0.0", + "description": "", + "author": "", + "directories": { + "example": "example" + }, + "scripts": { + "clean": "npm install && grunt clean", + "build": "npm install && grunt build" + }, + "devDependencies": { + "chai": "^3.5.0", + "grunt": "~0.4.1", + "grunt-browserify": "^5.0.0", + "grunt-contrib-clean": "~0.5.0", + "grunt-contrib-copy": "~0.5.0", + "load-grunt-tasks": "~0.3.0", + "swagger-client": "^2.1.15", + "yargs": "^4.7.1" + } +} diff --git a/docdoku-api-js/pom.xml b/docdoku-api-js/pom.xml new file mode 100644 index 0000000000..6ccbc064ed --- /dev/null +++ b/docdoku-api-js/pom.xml @@ -0,0 +1,62 @@ + + + + docdoku-plm + com.docdoku + 2.5-SNAPSHOT + + + pom + 4.0.0 + docdoku-api-js + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.3.2 + + + clean-dist + clean + + exec + + + npm + + run + clean + + + + + build-dist + generate-resources + + exec + + + npm + + run + build + + + + + + + + \ No newline at end of file diff --git a/docdoku-api-js/test/accounts-api.test.js b/docdoku-api-js/test/accounts-api.test.js new file mode 100644 index 0000000000..510355d2f7 --- /dev/null +++ b/docdoku-api-js/test/accounts-api.test.js @@ -0,0 +1,52 @@ +var chai = require('chai'); +var expect = chai.expect; +var DocdokuPlmClient = require('../lib/docdoku-api'); +var config = require('yargs').argv; + +describe('Accounts api tests', function() { + + it('Should create an account', function(done){ + + var client = new DocdokuPlmClient({url:config.url}); + + client.getApi().then(function(api){ + + expect(api.apis.accounts).to.be.defined; + expect(api.apis.accounts.createAccount).to.be.a.function; + + api.apis.accounts.createAccount({ + body:{ + login:'USER-'+Date.now(), + email:'foo@foo.bar', + timeZone:'CET', + language:'en', + name:'Generated', + newPassword:'password' + } + }).then(function(response){ + var accountCreated = response.obj; + expect(accountCreated).to.be.an.object; + done(); + }); + + }); + }); + + it('Should get current user account', function(done){ + + var client = new DocdokuPlmClient({ + url:config.url, + login:config.login, + password:config.password, + basicAuth:true + }); + + client.getApi().then(function(api){ + api.apis.accounts.getAccount().then(function(response){ + expect(response.obj.login).to.equal(config.login); + done(); + }); + }); + }); + +}); diff --git a/docdoku-api-js/test/auth.test.js b/docdoku-api-js/test/auth.test.js new file mode 100644 index 0000000000..65c6556fed --- /dev/null +++ b/docdoku-api-js/test/auth.test.js @@ -0,0 +1,61 @@ +var chai = require('chai'); +var expect = chai.expect; +var DocdokuPlmClient = require('../lib/docdoku-api'); +var config = require('yargs').argv; + + +describe('Api auth tests', function() { + + var jwt, cookie; + + it('Should login and get session id and jwt', function(done) { + + var client = new DocdokuPlmClient({url:config.url}); + client.getApi().then(function(api){ + api.auth.login({body:{login:config.login,password:config.password}}).then(function(response){ + var headers = response.headers; + jwt = headers.jwt; + cookie = headers['set-cookie'][0]; + expect(jwt).to.be.defined; + expect(cookie).to.be.defined; + done(); + }); + }); + }); +/* + + it('Should be able tu use basic auth', function(done) { + + var client = new DocdokuPlmClient({ + url:config.url, + login:config.login, + password:config.password, + basicAuth:true + }); + + client.getApi().then(function(api){ + api.accounts.getAccount().then(function(response){ + expect(response.status,200); + done(); + }); + }); + });*/ + + it('Should be able tu use cookie', function(done) { + + var client = new DocdokuPlmClient({ + url:config.url, + cookie:cookie + }); + + client.getApi().then(function(api){ + api.accounts.getAccount().then(function(response){ + expect(response.status,200); + done(); + }); + }); + }); + + + +}); diff --git a/docdoku-api-js/test/client.test.js b/docdoku-api-js/test/client.test.js new file mode 100644 index 0000000000..4c09148594 --- /dev/null +++ b/docdoku-api-js/test/client.test.js @@ -0,0 +1,29 @@ +var chai = require('chai'); +var expect = chai.expect; +var DocdokuPlmClient = require('../lib/docdoku-api'); +var config = require('yargs').argv; + +describe('Api client creation', function() { + it('Should affect options to client when using constructor', function() { + var client = new DocdokuPlmClient({url:config.url}); + expect(client.options.url).to.be.defined; + expect(client.options.url).to.equal(config.url); + }); + + it('Should modify options with setter', function() { + var client = new DocdokuPlmClient(); + client.setOptions({url:config.url}); + expect(client.options.url).to.be.defined; + expect(client.options.url).to.equal(config.url); + }); + + it('Should load the spec and give an api object', function(done) { + var client = new DocdokuPlmClient({url:config.url}); + client.getApi().then(function(api){ + expect(api).to.be.defined; + expect(api).to.be.an.object; + done(); + }); + }); + +}); diff --git a/docdoku-api-js/test/document-api.test.js b/docdoku-api-js/test/document-api.test.js new file mode 100644 index 0000000000..b92214f7de --- /dev/null +++ b/docdoku-api-js/test/document-api.test.js @@ -0,0 +1,100 @@ +var chai = require('chai'); +var expect = chai.expect; +var DocdokuPlmClient = require('../lib/docdoku-api'); +var config = require('yargs').argv; + +describe('Document api tests', function() { + + it('Should create a document', function(done){ + + var client = new DocdokuPlmClient({ + url:config.url, + login:config.login, + password:config.password, + basicAuth:true + }); + + client.getApi().then(function(api){ + + expect(api.apis.folders).to.be.defined; + expect(api.apis.folders.createAccount).to.be.a.function; + var documentReference = 'DOC-'+Date.now(); + api.apis.folders.createDocumentMasterInFolder({ + workspaceId:config.workspace, + body:{ + reference:documentReference, + title:'GeneratedDoc' + }, + folderId:config.workspace + }).then(function(response){ + var documentCreated = response.obj; + expect(documentCreated).to.be.an.object; + expect(documentCreated.documentMasterId).to.equal(documentReference); + done(); + }); + + }); + }); + + it('Should list documents', function(done){ + + var client = new DocdokuPlmClient({ + url:config.url, + login:config.login, + password:config.password, + basicAuth:true + }); + + client.getApi().then(function(api){ + + api.apis.documents.getDocumentsInWorkspace({ + workspaceId:config.workspace, + start:0, + configSpecType:null + }).then(function(response){ + var documents = response.obj; + expect(documents).to.be.an.array; + expect(documents.length).not.to.be.empty; + done(); + }); + + }); + }); + + it('Should list checkin a document', function(done){ + + var client = new DocdokuPlmClient({ + url:config.url, + login:config.login, + password:config.password, + basicAuth:true + }); + + client.getApi().then(function(api){ + + var documentReference = 'DOCCHECKIN-'+Date.now(); + api.apis.folders.createDocumentMasterInFolder({ + workspaceId:config.workspace, + body:{ + reference:documentReference, + title:'GeneratedDoc' + }, + folderId:config.workspace + }).then(function(response){ + + var documentCreated = response.obj; + + api.apis.document.checkInDocument({ + workspaceId:config.workspace, + documentId:documentCreated.documentMasterId, + documentVersion:documentCreated.version + }).then(function(response){ + expect(response.status).to.equal(200); + done(); + }); + + }); + + }); + }); +}); diff --git a/docdoku-api-js/test/language-api.test.js b/docdoku-api-js/test/language-api.test.js new file mode 100644 index 0000000000..c52b8df9e1 --- /dev/null +++ b/docdoku-api-js/test/language-api.test.js @@ -0,0 +1,26 @@ +var chai = require('chai'); +var expect = chai.expect; +var DocdokuPlmClient = require('../lib/docdoku-api'); +var config = require('yargs').argv; + +describe('Languages API tests', function() { + + it('Should get language list', function(done) { + + var client = new DocdokuPlmClient({url:config.url}); + + client.getApi().then(function(api){ + + expect(api.apis.languages).to.be.defined; + expect(api.apis.languages.getLanguages).to.be.a.function; + + api.apis.languages.getLanguages().then(function(response){ + expect(response.obj).to.be.an.array; + done(); + }); + }); + + }); + +}); + diff --git a/docdoku-api-js/test/timezones-api.test.js b/docdoku-api-js/test/timezones-api.test.js new file mode 100644 index 0000000000..b8492d4ec9 --- /dev/null +++ b/docdoku-api-js/test/timezones-api.test.js @@ -0,0 +1,28 @@ +var chai = require('chai'); +var expect = chai.expect; +var DocdokuPlmClient = require('../lib/docdoku-api'); +var config = require('yargs').argv; + +describe('Timezones API tests', function() { + + it('Should get timezone list', function(done) { + + var client = new DocdokuPlmClient({url:config.url}); + client.getApi().then(function(api){ + + expect(api.apis.timezone).to.be.defined; + expect(api.apis.timezone.getTimeZones).to.be.a.function; + + api.apis.timezone.getTimeZones().then(function(response){ + var timezone = response.obj; + expect(timezone).to.be.an.array; + done(); + }); + + }); + }); + + + +}); + diff --git a/docdoku-api/README.md b/docdoku-api/README.md new file mode 100644 index 0000000000..84474bb3b9 --- /dev/null +++ b/docdoku-api/README.md @@ -0,0 +1,7 @@ +# Docdoku API + +Generates a json file to describe web services. + +## Documentation + +TODO \ No newline at end of file diff --git a/docdoku-api/pom.xml b/docdoku-api/pom.xml new file mode 100644 index 0000000000..ec7e1faf95 --- /dev/null +++ b/docdoku-api/pom.xml @@ -0,0 +1,174 @@ + + + + + docdoku-plm + com.docdoku + 2.5-SNAPSHOT + + + 4.0.0 + docdoku-api + jar + + + + + + javax + javaee-api + 7.0 + provided + + + + com.docdoku + docdoku-common + 2.5-SNAPSHOT + provided + + + + com.github.kongchen + swagger-maven-plugin + 3.1.3 + + + io.swagger + swagger-jersey-jaxrs + + + + + + com.google.guava + guava + 16.0 + + + net.sf.dozer + dozer + 5.4.0 + + + java3d + vecmath + 1.3.1 + + + org.apache.poi + poi + 3.9 + + + org.apache.poi + poi-ooxml + 3.9 + + + org.bitbucket.b_c + jose4j + 0.5.1 + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.10 + + + generate-sources + add-source + + + ${project.parent.basedir}/docdoku-server/docdoku-server-rest/src/main/java + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + + + com.github.kongchen + swagger-maven-plugin + 3.1.3 + + + compile + + generate + + + + + + + /api + com.docdoku.server.rest + http,https + + DocdokuPLM API + v2.5 + + + + basicAuth + basic + + + com.docdoku.api.swagger.JaxrsReader + ${project.build.directory}/swagger + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + prepare-package + + copy-dependencies + + + ${project.build.directory}/lib + false + false + true + + + + + + + + \ No newline at end of file diff --git a/docdoku-api/src/main/java/com/docdoku/api/swagger/JaxrsReader.java b/docdoku-api/src/main/java/com/docdoku/api/swagger/JaxrsReader.java new file mode 100644 index 0000000000..af529954e0 --- /dev/null +++ b/docdoku-api/src/main/java/com/docdoku/api/swagger/JaxrsReader.java @@ -0,0 +1,426 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.api.swagger; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.kongchen.swagger.docgen.LogAdapter; +import com.github.kongchen.swagger.docgen.reader.AbstractReader; +import com.github.kongchen.swagger.docgen.reader.ClassSwaggerReader; +import io.swagger.annotations.*; +import io.swagger.converter.ModelConverters; +import io.swagger.jaxrs.ext.SwaggerExtension; +import io.swagger.jaxrs.ext.SwaggerExtensions; +import io.swagger.models.*; +import io.swagger.models.Tag; +import io.swagger.models.parameters.Parameter; +import io.swagger.models.properties.ArrayProperty; +import io.swagger.models.properties.MapProperty; +import io.swagger.models.properties.Property; +import io.swagger.models.properties.RefProperty; +import io.swagger.util.Json; +import org.springframework.core.annotation.AnnotationUtils; + +import javax.ws.rs.Consumes; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.Produces; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The JaxrsReader class is used to generate the spec from docdoku-server-rest classes and annotations. + * + * The original reader has been taken from github and modified to fit our configuration. + * See https://github.com/kongchen/swagger-maven-plugin for more details + * + * @author Morgan Guimard + */ +public class JaxrsReader extends AbstractReader implements ClassSwaggerReader { + + private static final Logger LOGGER = Logger.getLogger(JaxrsReader.class.getName()); + static ObjectMapper m = Json.mapper(); + + public JaxrsReader(Swagger swagger, LogAdapter LOG) { + super(swagger, LOG); + } + + public Swagger read(Set> classes) { + for (Class cls : classes) + read(cls); + return swagger; + } + + public Swagger getSwagger() { + return this.swagger; + } + + public Swagger read(Class cls) { + return read(cls, "", null, false, new String[0], new String[0], new HashMap(), new ArrayList()); + } + + protected Swagger read(Class cls, String parentPath, String parentMethod, boolean readHidden, String[] parentConsumes, String[] parentProduces, Map parentTags, List parentParameters) { + if (swagger == null) { + swagger = new Swagger(); + } + + Api api = AnnotationUtils.findAnnotation(cls, Api.class); + javax.ws.rs.Path apiPath = AnnotationUtils.findAnnotation(cls, javax.ws.rs.Path.class); + + if (!canReadApi(readHidden, api)) { + return swagger; + } + + Map tags = updateTagsForApi(parentTags, api); + List securities = getSecurityRequirements(api); + + Method methods[] = cls.getMethods(); + + for (Method method : methods) { + ApiOperation apiOperation = AnnotationUtils.findAnnotation(method, ApiOperation.class); + if (apiOperation == null || apiOperation.hidden()) { + continue; + } + javax.ws.rs.Path methodPath = AnnotationUtils.findAnnotation(method, javax.ws.rs.Path.class); + + String operationPath = getPath(apiPath, methodPath, parentPath); + operationPath = cleanRegexPath(operationPath); + + if (operationPath != null && apiOperation != null) { + Map regexMap = new HashMap(); + operationPath = parseOperationPath(operationPath, regexMap); + + String httpMethod = extractOperationMethod(apiOperation, method, SwaggerExtensions.chain()); + + Operation operation = parseMethod(method); + + updateOperationParameters(parentParameters, regexMap, operation); + + updateOperationProtocols(apiOperation, operation); + + String[] apiConsumes = new String[0]; + String[] apiProduces = new String[0]; + + Annotation annotation = AnnotationUtils.getAnnotation(cls, Consumes.class); + if (annotation != null) + apiConsumes = ((Consumes) annotation).value(); + annotation = AnnotationUtils.getAnnotation(cls, Produces.class); + if (annotation != null) + apiProduces = ((Produces) annotation).value(); + + apiConsumes = updateOperationConsumes(parentConsumes, apiConsumes, operation); + apiProduces = updateOperationProduces(parentProduces, apiProduces, operation); + + handleSubResource(apiConsumes, httpMethod, apiProduces, tags, method, operationPath, operation); + + httpMethod = httpMethod == null ? parentMethod : httpMethod; + updateTagsForOperation(operation, apiOperation); + updateOperation(apiConsumes, apiProduces, tags, securities, operation); + updatePath(operationPath, httpMethod, operation); + } + } + + return swagger; + } + + + private void handleSubResource(String[] apiConsumes, String httpMethod, String[] apiProduces, Map tags, Method method, String operationPath, Operation operation) { + if (isSubResource(method)) { + Type t = method.getGenericReturnType(); + Class responseClass = method.getReturnType(); + Swagger subSwagger = read(responseClass, operationPath, httpMethod, true, apiConsumes, apiProduces, tags, operation.getParameters()); + } + } + + protected boolean isSubResource(Method method) { + Type t = method.getGenericReturnType(); + Class responseClass = method.getReturnType(); + if (responseClass != null && AnnotationUtils.findAnnotation(responseClass, Api.class) != null) { + return true; + } + return false; + } + + String getPath(javax.ws.rs.Path classLevelPath, javax.ws.rs.Path methodLevelPath, String parentPath) { + // Fix for empty path in sub resources. Should be fixed in a cleaner way ... + if (classLevelPath == null && methodLevelPath == null) { + return parentPath; + } + StringBuilder b = new StringBuilder(); + if (parentPath != null && !"".equals(parentPath) && !"/".equals(parentPath)) { + if (!parentPath.startsWith("/")) + parentPath = "/" + parentPath; + if (parentPath.endsWith("/")) + parentPath = parentPath.substring(0, parentPath.length() - 1); + + b.append(parentPath); + } + if (classLevelPath != null) { + b.append(classLevelPath.value()); + } + if (methodLevelPath != null && !"/".equals(methodLevelPath.value())) { + String methodPath = methodLevelPath.value(); + if (!methodPath.startsWith("/") && !b.toString().endsWith("/")) { + b.append("/"); + } + if (methodPath.endsWith("/")) { + methodPath = methodPath.substring(0, methodPath.length() - 1); + } + b.append(methodPath); + } + String output = b.toString(); + if (!output.startsWith("/")) + output = "/" + output; + if (output.endsWith("/") && output.length() > 1) + return output.substring(0, output.length() - 1); + else + return output; + } + + + public Operation parseMethod(Method method) { + Operation operation = new Operation(); + + ApiOperation apiOperation = (ApiOperation) AnnotationUtils.findAnnotation(method, ApiOperation.class); + + + String operationId = method.getName(); + String responseContainer = null; + + Class responseClass = null; + Map defaultResponseHeaders = null; + Set> customExtensions = null; + + if (apiOperation != null) { + if (apiOperation.hidden()) + return null; + if (!"".equals(apiOperation.nickname())) + operationId = apiOperation.nickname(); + + defaultResponseHeaders = parseResponseHeaders(apiOperation.responseHeaders()); + + operation.summary(apiOperation.value()).description(apiOperation.notes()); + + customExtensions = parseCustomExtensions(apiOperation.extensions()); + if (customExtensions != null) { + for (Map extension : customExtensions) { + if (extension != null) { + for (Map.Entry map : extension.entrySet()) { + operation.setVendorExtension(map.getKey().startsWith("x-") ? map.getKey() : "x-" + map.getKey(), map.getValue()); + } + } + } + } + + if (apiOperation.response() != null && !Void.class.equals(apiOperation.response())) + responseClass = apiOperation.response(); + if (!"".equals(apiOperation.responseContainer())) + responseContainer = apiOperation.responseContainer(); + // Not using "authorizations" annotation in docdoku-server-rest + // Unused code + remove deprecated call + /*if (apiOperation.authorizations() != null) { + List securities = new ArrayList(); + for (Authorization auth : apiOperation.authorizations()) { + if (auth.value() != null && !"".equals(auth.value())) { + SecurityRequirement security = new SecurityRequirement(); + security.setName(auth.value()); + AuthorizationScope[] scopes = auth.scopes(); + for (AuthorizationScope scope : scopes) { + if (scope.scope() != null && !"".equals(scope.scope())) { + security.addScope(scope.scope()); + } + } + securities.add(security); + } + } + if (securities.size() > 0) { + for (SecurityRequirement sec : securities) + operation.security(sec); + } + }*/ + } + + if (responseClass == null) { + // pick out response from method declaration + LOGGER.log(Level.FINE, "picking up response class from method " + method); + Type t = method.getGenericReturnType(); + responseClass = method.getReturnType(); + if (!responseClass.equals(java.lang.Void.class) && !"void".equals(responseClass.toString()) + && AnnotationUtils.findAnnotation(responseClass, Api.class) == null) { + LOGGER.log(Level.FINE, "reading model " + responseClass); + Map models = ModelConverters.getInstance().readAll(t); + } + } + if (responseClass != null + && !responseClass.equals(java.lang.Void.class) + && !responseClass.equals(javax.ws.rs.core.Response.class) + && AnnotationUtils.findAnnotation(responseClass, Api.class) == null) { + if (isPrimitive(responseClass)) { + Property responseProperty = null; + Property property = ModelConverters.getInstance().readAsProperty(responseClass); + if (property != null) { + if ("list".equalsIgnoreCase(responseContainer)) + responseProperty = new ArrayProperty(property); + else if ("map".equalsIgnoreCase(responseContainer)) + responseProperty = new MapProperty(property); + else + responseProperty = property; + operation.response(apiOperation.code(), new Response() + .description("successful operation") + .schema(responseProperty) + .headers(defaultResponseHeaders)); + } + } else if (!responseClass.equals(java.lang.Void.class) && !"void".equals(responseClass.toString())) { + Map models = ModelConverters.getInstance().read(responseClass); + if (models.size() == 0) { + Property p = ModelConverters.getInstance().readAsProperty(responseClass); + operation.response(apiOperation.code(), new Response() + .description("successful operation") + .schema(p) + .headers(defaultResponseHeaders)); + } + for (String key : models.keySet()) { + Property responseProperty; + + if ("list".equalsIgnoreCase(responseContainer)) + responseProperty = new ArrayProperty(new RefProperty().asDefault(key)); + else if ("map".equalsIgnoreCase(responseContainer)) + responseProperty = new MapProperty(new RefProperty().asDefault(key)); + else + responseProperty = new RefProperty().asDefault(key); + operation.response(apiOperation.code(), new Response() + .description("successful operation") + .schema(responseProperty) + .headers(defaultResponseHeaders)); + swagger.model(key, models.get(key)); + } + models = ModelConverters.getInstance().readAll(responseClass); + for (String key : models.keySet()) { + swagger.model(key, models.get(key)); + } + } + } + + operation.operationId(operationId); + + Annotation annotation; + annotation = AnnotationUtils.findAnnotation(method, Consumes.class); + if (annotation != null) { + String[] apiConsumes = ((Consumes) annotation).value(); + for (String mediaType : apiConsumes) + operation.consumes(mediaType); + } + + annotation = AnnotationUtils.findAnnotation(method, Produces.class); + if (annotation != null) { + String[] apiProduces = ((Produces) annotation).value(); + for (String mediaType : apiProduces) + operation.produces(mediaType); + } + + ApiResponses responseAnnotation = AnnotationUtils.findAnnotation(method, ApiResponses.class); + if (responseAnnotation != null) { + updateApiResponse(operation, responseAnnotation); + } + + annotation = AnnotationUtils.findAnnotation(method, Deprecated.class); + if (annotation != null) + operation.deprecated(true); + + // FIXME `hidden` is never used + boolean hidden = false; + if (apiOperation != null) + hidden = apiOperation.hidden(); + + // process parameters + Class[] parameterTypes = method.getParameterTypes(); + Type[] genericParameterTypes = method.getGenericParameterTypes(); + Annotation[][] paramAnnotations = method.getParameterAnnotations(); + + // paramTypes = method.getParameterTypes + // genericParamTypes = method.getGenericParameterTypes + for (int i = 0; i < parameterTypes.length; i++) { + Type type = genericParameterTypes[i]; + List annotations = Arrays.asList(paramAnnotations[i]); + List parameters = getParameters(type, annotations); + + for (Parameter parameter : parameters) { + operation.parameter(parameter); + } + } + if (operation.getResponses() == null) { + operation.defaultResponse(new Response().description("successful operation")); + } + + // Process @ApiImplicitParams + this.readImplicitParameters(method, operation); + + return operation; + } + + + public String extractOperationMethod(ApiOperation apiOperation, Method method, Iterator chain) { + if (apiOperation.httpMethod() != null && !"".equals(apiOperation.httpMethod())) + return apiOperation.httpMethod().toLowerCase(); + else if (AnnotationUtils.findAnnotation(method, javax.ws.rs.GET.class) != null) + return "get"; + else if (AnnotationUtils.findAnnotation(method, javax.ws.rs.PUT.class) != null) + return "put"; + else if (AnnotationUtils.findAnnotation(method, javax.ws.rs.POST.class) != null) + return "post"; + else if (AnnotationUtils.findAnnotation(method, javax.ws.rs.DELETE.class) != null) + return "delete"; + else if (AnnotationUtils.findAnnotation(method, javax.ws.rs.OPTIONS.class) != null) + return "options"; + else if (AnnotationUtils.findAnnotation(method, javax.ws.rs.HEAD.class) != null) + return "head"; + else if (AnnotationUtils.findAnnotation(method, io.swagger.jaxrs.PATCH.class) != null) + return "patch"; + else { + // check for custom HTTP Method annotations + Annotation[] declaredAnnotations = method.getDeclaredAnnotations(); + for (Annotation declaredAnnotation : declaredAnnotations) { + Annotation[] innerAnnotations = declaredAnnotation.annotationType().getAnnotations(); + for (Annotation innerAnnotation : innerAnnotations) { + if (innerAnnotation instanceof HttpMethod) { + HttpMethod httpMethod = (HttpMethod) innerAnnotation; + return httpMethod.value().toLowerCase(); + } + } + } + + if (chain.hasNext()) { + return chain.next().extractOperationMethod(apiOperation, method, chain); + } + } + + return null; + } + + private String cleanRegexPath(String dirty) { + return dirty + .replace(": [^/].*", "") + .replace(":[0-9]+", "") + .replace(":[A-Z]+", ""); + } +} \ No newline at end of file diff --git a/docdoku-cli/dplm b/docdoku-cli/dplm new file mode 100755 index 0000000000..4b0802553e --- /dev/null +++ b/docdoku-cli/dplm @@ -0,0 +1,24 @@ +#! /usr/bin/env sh + +PRG="$0" +while [ -h "$PRG" ] ; do + PRG=`readlink "$PRG"` +done +dir=`dirname $PRG` + +ARGS="" +while test $# -gt 0 +do + case "$1" in + *\ * ) + ARGS=$ARGS" \"$1\"" + ;; + *) + ARGS=$ARGS" $1" + ;; + esac + shift +done +java -Xmx1024M -classpath $dir/target/docdoku-cli-jar-with-dependencies.jar com.docdoku.cli.MainCommand $ARGS + + diff --git a/docdoku-cli/dplm-shell b/docdoku-cli/dplm-shell new file mode 100755 index 0000000000..245acae19b --- /dev/null +++ b/docdoku-cli/dplm-shell @@ -0,0 +1,11 @@ +#! /usr/bin/env sh + +PRG="$0" +while [ -h "$PRG" ] ; do + PRG=`readlink "$PRG"` +done +dir=`dirname $PRG` + +cd $dir +echo "To setup your scripting environment just enter: load('init.js')" +jjs --language=es6 -scripting -J-Xmx1024M -classpath $dir/target/docdoku-cli-jar-with-dependencies.jar $@ diff --git a/docdoku-cli/init.js b/docdoku-cli/init.js new file mode 100644 index 0000000000..dfa34e51ea --- /dev/null +++ b/docdoku-cli/init.js @@ -0,0 +1,30 @@ +function readPassword(msg){ + var c = java.lang.System.console(); + return new java.lang.String(c.readPassword(msg)); +} + +var api = new JavaImporter( + com.docdoku.api.models, + com.docdoku.api.models.utils, + com.docdoku.api.client, + com.docdoku.api.client.auth, + com.docdoku.api.services +); + +print(); +var url = readLine('What is the server url? [https://docdokuplm.net:443/api] '); +if(url.isEmpty()){ + url = 'https://docdokuplm.net:443/api'; +} +var username = readLine('What is your username? '); +var password = readPassword('What is your password? '); +var client = com.docdoku.cli.services.DocdokuClientFactory.createClient(url,username,password); +var ws = {}; +ws.accounts = new api.AccountsApi(client); +ws.workspaces = new api.WorkspacesApi(client); +print(); +print('global objects initialized: api and ws'); +print("server url set to '${url}' using username '${username}'"); +print('sample: var folder = new api.FolderDTO()'); +print('sample: ws.workspaces.getWorkspacesForConnectedUser()'); +print(); diff --git a/docdoku-cli/pom.xml b/docdoku-cli/pom.xml new file mode 100644 index 0000000000..52fd146c31 --- /dev/null +++ b/docdoku-cli/pom.xml @@ -0,0 +1,111 @@ + + + 4.0.0 + + com.docdoku + docdoku-plm + 2.5-SNAPSHOT + + docdoku-cli + jar + docdoku-cli Command Line Interface + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.16 + + true + + ${env.HOST} + ${env.PORT} + ${env.LOGIN} + ${env.PASSWORD} + ${env.WORKSPACE} + + + + + ${project.artifactId} + + + + + running-instance + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.16 + + false + + + + + + + + + + + com.docdoku + docdoku-api-java + ${project.version} + jar + + + org.glassfish + javax.json + 1.0.4 + + + commons-io + commons-io + 2.4 + + + args4j + args4j + 2.0.23 + + + junit + junit + 4.11 + test + + + \ No newline at end of file diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/MainCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/MainCommand.java new file mode 100644 index 0000000000..bcee08252c --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/MainCommand.java @@ -0,0 +1,191 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.cli; + +import com.docdoku.cli.commands.common.AccountInfosCommand; +import com.docdoku.cli.commands.common.FileStatusCommand; +import com.docdoku.cli.commands.common.HelpCommand; +import com.docdoku.cli.commands.common.WorkspacesCommand; +import com.docdoku.cli.commands.documents.*; +import com.docdoku.cli.commands.parts.*; +import com.docdoku.cli.helpers.CommandLine; +import org.kohsuke.args4j.CmdLineParser; + +import java.util.Arrays; + +public class MainCommand { + + + private static final String PART = "part"; + private static final String DOCUMENT = "document"; + + /** + * Main function wrapper + */ + public static void main(String[] args) { + try { + switch (args[0]) { + case "status": + case "stat": + case "st": + if (PART.equals(args[1])) { + execCommand(new PartStatusCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else if (DOCUMENT.equals(args[1])) { + execCommand(new DocumentStatusCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else { + execCommand(new FileStatusCommand(), Arrays.copyOfRange(args, 1, args.length)); + } + break; + case "get": + if (PART.equals(args[1])) { + execCommand(new PartGetCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else if (DOCUMENT.equals(args[1])) { + execCommand(new DocumentGetCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else { + execCommand(new HelpCommand(), Arrays.copyOfRange(args, 1, args.length)); + } + break; + case "put": + if (PART.equals(args[1])) { + execCommand(new PartPutCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else if (DOCUMENT.equals(args[1])) { + execCommand(new DocumentPutCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else { + execCommand(new HelpCommand(), Arrays.copyOfRange(args, 1, args.length)); + } + break; + case "checkout": + case "co": + if (PART.equals(args[1])) { + execCommand(new PartCheckOutCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else if (DOCUMENT.equals(args[1])) { + execCommand(new DocumentCheckOutCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else { + execCommand(new HelpCommand(), Arrays.copyOfRange(args, 1, args.length)); + } + break; + case "undocheckout": + case "uco": + if (PART.equals(args[1])) { + execCommand(new PartUndoCheckOutCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else if (DOCUMENT.equals(args[1])) { + execCommand(new DocumentUndoCheckOutCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else { + execCommand(new HelpCommand(), Arrays.copyOfRange(args, 1, args.length)); + } + break; + case "checkin": + case "ci": + if (PART.equals(args[1])) { + execCommand(new PartCheckInCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else if (DOCUMENT.equals(args[1])) { + execCommand(new DocumentCheckInCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else { + execCommand(new HelpCommand(), Arrays.copyOfRange(args, 1, args.length)); + } + break; + + case "search": + case "s": + if (PART.equals(args[1])) { + execCommand(new PartSearchCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else if (DOCUMENT.equals(args[1])) { + execCommand(new DocumentSearchCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else { + execCommand(new HelpCommand(), Arrays.copyOfRange(args, 1, args.length)); + } + break; + + case "create": + case "cr": + if (PART.equals(args[1])) { + execCommand(new PartCreationCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else if (DOCUMENT.equals(args[1])) { + execCommand(new DocumentCreationCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else { + execCommand(new HelpCommand(), Arrays.copyOfRange(args, 1, args.length)); + } + break; + case "list": + case "l": + if (PART.equals(args[1])) { + execCommand(new PartListCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else if (DOCUMENT.equals(args[1])) { + execCommand(new DocumentListCommand(), Arrays.copyOfRange(args, 2, args.length)); + } else { + execCommand(new HelpCommand(), Arrays.copyOfRange(args, 1, args.length)); + } + break; + + case "folders": + case "f": + execCommand(new FolderListCommand(), Arrays.copyOfRange(args, 1, args.length)); + break; + + case "baselinelist": + case "bl": + execCommand(new BaselineListCommand(), Arrays.copyOfRange(args, 1, args.length)); + break; + + case "conversion": + case "cv": + execCommand(new ConversionCommand(), Arrays.copyOfRange(args, 1, args.length)); + break; + + case "workspaces": + case "wl": + execCommand(new WorkspacesCommand(), Arrays.copyOfRange(args, 1, args.length)); + break; + + case "account": + case "a": + execCommand(new AccountInfosCommand(), Arrays.copyOfRange(args, 1, args.length)); + break; + + case "help": + case "?": + case "h": + execCommand(new HelpCommand(), Arrays.copyOfRange(args, 1, args.length)); + break; + default: + execCommand(new HelpCommand(), args); + break; + } + } catch (Exception e) { + execCommand(new HelpCommand(), args); + } + } + + private MainCommand() { + super(); + } + + private static void execCommand(CommandLine cl, String[] args) { + CmdLineParser parser = new CmdLineParser(cl); + try { + parser.parseArgument(args); + cl.exec(); + } catch (Exception e) { + cl.getOutput().printException(e); + } + } + +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/AbstractCommandLine.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/AbstractCommandLine.java new file mode 100644 index 0000000000..cb917b0c00 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/AbstractCommandLine.java @@ -0,0 +1,61 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.cli.commands; +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.CliOutput; +import com.docdoku.cli.helpers.CommandLine; +import org.kohsuke.args4j.Option; + +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public abstract class AbstractCommandLine implements CommandLine { + + @Option(name="-u", aliases = "--user", metaVar = "", usage="user for login") + protected String user; + + @Option(name="-F", aliases = "--format", metaVar = "", usage="output format, possible value: json") + protected CliOutput.formats format = CliOutput.formats.HUMAN; + + + //A default value is set in case an exception is raised + //inside the CmdLineParser.parseArgument(args) method. + protected CliOutput output = CliOutput.getOutput(format, Locale.getDefault()); + + @Override + public void exec() throws Exception { + Locale userLocale = new AccountsManager().getUserLocale(user); + output = CliOutput.getOutput(format,userLocale); + + execImpl(); + + } + + @Override + public CliOutput getOutput(){ + return output; + } + + public abstract void execImpl() throws Exception; +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/BaseCommandLine.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/BaseCommandLine.java new file mode 100644 index 0000000000..e413e72da1 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/BaseCommandLine.java @@ -0,0 +1,95 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.cli.commands; + +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.CliOutput; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.cli.services.DocdokuClientFactory; +import com.docdoku.api.client.ApiClient; +import org.kohsuke.args4j.Option; + +import java.io.Console; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public abstract class BaseCommandLine extends AbstractCommandLine { + + @Option(name="-P", aliases = "--port", metaVar = "", usage="port number to use for connection; default is 80") + protected int port=80; + + @Option(name="-h", aliases = "--host", metaVar = "", usage="host of the DocDokuPLM server to connect; default is docdokuplm.net") + protected String host="docdokuplm.net"; + + @Option(name="-p", aliases = "--password", metaVar = "", usage="password to log in") + protected String password; + + @Option(name="-S", aliases = "--ssl", usage="use a ssl (tls) connection") + protected boolean ssl; + + protected ApiClient client; + + private void promptForUser(Locale locale){ + Console c = System.console(); + if(c == null){ + return; + } + + user = c.readLine(LangHelper.getLocalizedMessage("PromptUser",locale) + " '" + host + "': "); + } + + private void promptForPassword(Locale locale){ + Console c = System.console(); + if(c == null){ + return; + } + password = new String(c.readPassword(LangHelper.getLocalizedMessage("PromptPassword",locale) + " '" + user + "@" + host +"': ")); + } + + @Override + public void exec() throws Exception { + + Locale userLocale = new AccountsManager().getUserLocale(user); + output = CliOutput.getOutput(format,userLocale); + if(user==null && format.equals(CliOutput.formats.HUMAN)){ + promptForUser(userLocale); + } + if(password==null && format.equals(CliOutput.formats.HUMAN)){ + promptForPassword(userLocale); + } + + String apiBasePath = getServerURL().toString() + "/api"; + + client = DocdokuClientFactory.createClient(apiBasePath,user,password); + + execImpl(); + + } + + public URL getServerURL() throws MalformedURLException { + return new URL( ssl ? "https":"http",host,port,""); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/common/AccountInfosCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/common/AccountInfosCommand.java new file mode 100644 index 0000000000..2fea8688f0 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/common/AccountInfosCommand.java @@ -0,0 +1,46 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.cli.commands.common; + +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.api.models.AccountDTO; +import com.docdoku.api.services.AccountsApi; + +import java.io.IOException; + +/** + * + * @author Morgan Guimard + */ +public class AccountInfosCommand extends BaseCommandLine { + + @Override + public void execImpl() throws Exception { + AccountDTO accountDTO = new AccountsApi(client).getAccount(); + output.printAccount(accountDTO); + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("AccountInfosCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/common/FileStatusCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/common/FileStatusCommand.java new file mode 100644 index 0000000000..0b06058937 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/common/FileStatusCommand.java @@ -0,0 +1,88 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.cli.commands.common; + +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.cli.helpers.MetaDirectoryManager; +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.DocumentRevisionDTO; +import com.docdoku.api.models.PartRevisionDTO; +import com.docdoku.api.services.DocumentApi; +import com.docdoku.api.services.PartApi; +import org.kohsuke.args4j.Argument; + +import java.io.File; +import java.io.IOException; + +/** + * + * @author Morgan Guimard + */ +public class FileStatusCommand extends BaseCommandLine { + + @Argument(metaVar = "", required=true, index=0, usage = "specify the file of a document or a part to get a status") + private File file; + + @Override + public void execImpl() throws Exception { + + MetaDirectoryManager meta = new MetaDirectoryManager(file.getParentFile()); + String filePath = file.getAbsolutePath(); + + String workspace = meta.getWorkspace(filePath); + long lastModified = meta.getLastModifiedDate(filePath); + String strRevision = meta.getRevision(filePath); + + if(meta.isDocumentRelated(filePath)){ + String ref = meta.getDocumentId(filePath); + try { + DocumentApi documentApi = new DocumentApi(client); + DocumentRevisionDTO documentRevision = documentApi.getDocumentRevision(workspace,ref,strRevision,null); + output.printDocumentRevision(documentRevision,lastModified); + } catch (ApiException e) { + meta.deleteEntryInfo(file.getAbsolutePath()); + output.printException(e); + } + } + else if(meta.isPartRelated(filePath)){ + String ref = meta.getPartNumber(filePath); + try { + PartApi partApi = new PartApi(client); + PartRevisionDTO partRevision = partApi.getPartRevision(workspace, ref, strRevision); + output.printPartRevision(partRevision, lastModified); + } catch (ApiException e) { + meta.deleteEntryInfo(file.getAbsolutePath()); + output.printException(e); + } + }else{ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("FileNotIndexedException",user)); + } + + } + + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("FileStatusCommand",user); + } + +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/common/HelpCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/common/HelpCommand.java new file mode 100644 index 0000000000..e44986498f --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/common/HelpCommand.java @@ -0,0 +1,92 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.cli.commands.common; + +import com.docdoku.cli.commands.AbstractCommandLine; +import com.docdoku.cli.commands.parts.*; +import com.docdoku.cli.helpers.CommandLine; +import com.docdoku.cli.helpers.LangHelper; +import org.kohsuke.args4j.Argument; + +import java.io.IOException; + +/** + * + * @author Florent Garin + */ +public class HelpCommand extends AbstractCommandLine { + + @Argument(metaVar = "", index=0, usage = "the command to display the help information") + private String command=""; + + @Override + public void execImpl() throws Exception { + + CommandLine cl; + switch(command){ + case "status": case "stat": case "st": + cl=new PartStatusCommand(); + break; + + case "get": + cl=new PartGetCommand(); + break; + + case "put": + cl=new PartPutCommand(); + break; + + case "checkout": case "co": + cl=new PartCheckOutCommand(); + break; + + case "undocheckout": case "uco": + cl=new PartUndoCheckOutCommand(); + break; + + case "checkin": case "ci": + cl=new PartCheckInCommand(); + break; + + case "create": case "cr": + cl=new PartCreationCommand(); + break; + + case "help": case "?" : case "h": + cl=new HelpCommand(); + break; + + default: + cl=null; + } + if(cl == null){ + output.printUsage(); + return; + } + output.printCommandUsage(cl); + + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("HelpCommandDescription", user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/common/WorkspacesCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/common/WorkspacesCommand.java new file mode 100644 index 0000000000..70568eee67 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/common/WorkspacesCommand.java @@ -0,0 +1,54 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.cli.commands.common; + +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.api.models.AccountDTO; +import com.docdoku.api.models.WorkspaceListDTO; +import com.docdoku.api.services.AccountsApi; +import com.docdoku.api.services.WorkspacesApi; + +import java.io.IOException; + +/** + * + * @author Morgan Guimard + */ +public class WorkspacesCommand extends BaseCommandLine { + + @Override + public void execImpl() throws Exception { + + AccountDTO accountDTO = new AccountsApi(client).getAccount(); + AccountsManager accountsManager = new AccountsManager(); + accountsManager.saveAccount(accountDTO); + + WorkspaceListDTO workspaces = new WorkspacesApi(client).getWorkspacesForConnectedUser(); + output.printWorkspaces(workspaces.getAllWorkspaces()); + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("WorkspaceCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentCheckInCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentCheckInCommand.java new file mode 100644 index 0000000000..681371eb6b --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentCheckInCommand.java @@ -0,0 +1,127 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.cli.commands.documents; + +import com.docdoku.api.models.DocumentIterationDTO; +import com.docdoku.api.models.DocumentIterationKey; +import com.docdoku.api.models.DocumentRevisionDTO; +import com.docdoku.api.services.DocumentApi; +import com.docdoku.api.models.utils.LastIterationHelper; +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.FileHelper; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.cli.helpers.MetaDirectoryManager; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.io.File; +import java.io.IOException; + +/** + * + * @author Morgan Guimard + */ +public class DocumentCheckInCommand extends BaseCommandLine { + + @Option(metaVar = "", name="-r", aliases = "--revision", usage="specify revision of the document to check in ('A', 'B'...); if not specified the document identity (id and revision) corresponding to the file will be selected") + private String revision; + + @Option(metaVar = "", name = "-o", aliases = "--id", usage = "the id of the document to check in; if not specified choose the document corresponding to the file") + private String id; + + @Argument(metaVar = "[ | ]", index=0, usage = "specify the file of the document to check in or the path where files are stored (default is working directory)") + private File path = new File(System.getProperty("user.dir")); + + @Option(name="-n", aliases = "--no-upload", usage="do not upload the file of the document if any") + private boolean noUpload; + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "", usage="workspace on which operations occur") + protected String workspace; + + @Option(metaVar = "", name = "-m", aliases = "--message", usage = "a message specifying the iteration modifications") + private String message; + + @Override + public void execImpl() throws Exception { + + if(id ==null || revision==null){ + loadMetadata(); + } + + DocumentApi documentApi = new DocumentApi(client); + DocumentRevisionDTO dr = documentApi.getDocumentRevision(workspace,id,revision,null); + + DocumentIterationDTO di = LastIterationHelper.getLastIteration(dr); + DocumentIterationKey docIPK = new DocumentIterationKey(); + docIPK.setWorkspaceId(workspace); + docIPK.setDocumentMasterId(id); + docIPK.setDocumentRevisionVersion(revision); + docIPK.setIteration(di.getIteration()); + + if(!noUpload && !di.getAttachedFiles().isEmpty()){ + + for(String bin :di.getAttachedFiles()){ + // TODO may break since refactor fullname / filename + String fileName = bin; + File localFile = new File(path,fileName); + if(localFile.exists()){ + FileHelper fh = new FileHelper(user,password,output,new AccountsManager().getUserLocale(user)); + + fh.uploadDocumentFile(getServerURL(), localFile, docIPK); + localFile.setWritable(false); + } + } + + } + + if(message != null && !message.isEmpty()){ + di.setRevisionNote(message); + documentApi.updateDocumentIteration(workspace, id, revision, String.valueOf(di.getIteration()), di); + } + + output.printInfo(LangHelper.getLocalizedMessage("CheckingInDocument",user) + " : " + id + + "-" + dr.getVersion() + "-" + di.getIteration() + " (" + workspace + ")"); + + documentApi.checkInDocument(workspace,id, revision,""); + + } + + private void loadMetadata() throws IOException { + if(path.isDirectory()){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("DocumentIdOrRevisionNotSpecified1",user)); + } + MetaDirectoryManager meta = new MetaDirectoryManager(path.getParentFile()); + String filePath = path.getAbsolutePath(); + id = meta.getDocumentId(filePath); + String strRevision = meta.getRevision(filePath); + if(id ==null || strRevision==null){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("DocumentIdOrRevisionNotSpecified2",user)); + } + revision = strRevision; + path=path.getParentFile(); + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("DocumentCheckInCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentCheckOutCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentCheckOutCommand.java new file mode 100644 index 0000000000..bab9dbc04a --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentCheckOutCommand.java @@ -0,0 +1,133 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.cli.commands.documents; + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.DocumentIterationDTO; +import com.docdoku.api.models.DocumentRevisionDTO; +import com.docdoku.api.models.DocumentRevisionKey; +import com.docdoku.api.models.UserDTO; +import com.docdoku.api.services.DocumentApi; +import com.docdoku.api.models.utils.LastIterationHelper; +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.FileHelper; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.cli.helpers.MetaDirectoryManager; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import javax.security.auth.login.LoginException; +import java.io.File; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class DocumentCheckOutCommand extends BaseCommandLine { + + @Option(metaVar = "", name="-r", aliases = "--revision", usage="specify revision of the document to check out ('A', 'B'...); if not specified the document identity (id and revision) corresponding to the file will be selected") + private String revision; + + @Option(metaVar = "", name = "-o", aliases = "--id", usage = "the id of the document to check out; if not specified choose the document corresponding to the file") + private String id; + + @Argument(metaVar = "[ | ]", index=0, usage = "specify the file of the document to check out or the path where files are stored (default is working directory)") + private File path = new File(System.getProperty("user.dir")); + + @Option(name="-n", aliases = "--no-download", usage="do not download the files of the document if any") + private boolean noDownload; + + @Option(name="-f", aliases = "--force", usage="overwrite existing files even if they have been modified locally") + private boolean force; + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "", usage="workspace on which operations occur") + protected String workspace; + + @Override + public void execImpl() throws Exception { + if(id==null || revision==null){ + loadMetadata(); + } + + String strRevision = revision==null?null:revision; + checkoutDocument(id, strRevision); + } + + private void loadMetadata() throws IOException { + if(path.isDirectory()){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("DocumentIdOrRevisionNotSpecified1",user)); + } + MetaDirectoryManager meta = new MetaDirectoryManager(path.getParentFile()); + String filePath = path.getAbsolutePath(); + id = meta.getDocumentId(filePath); + String strRevision = meta.getRevision(filePath); + if(id==null || strRevision==null){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("DocumentIdOrRevisionNotSpecified2",user)); + } + revision = strRevision; + path=path.getParentFile(); + } + + private void checkoutDocument(String id, String pRevision) throws IOException, ApiException, LoginException, NoSuchAlgorithmException { + + Locale locale = new AccountsManager().getUserLocale(user); + + DocumentRevisionKey documentRevisionKey = new DocumentRevisionKey(); + documentRevisionKey.setWorkspaceId(workspace); + documentRevisionKey.setDocumentMasterId(id); + documentRevisionKey.setVersion(pRevision); + + DocumentApi documentApi = new DocumentApi(client); + DocumentRevisionDTO dr = documentApi.getDocumentRevision(workspace,id,pRevision,null); + DocumentIterationDTO di = LastIterationHelper.getLastIteration(dr); + + output.printInfo( + LangHelper.getLocalizedMessage("CheckingOutDocument",locale) + + " : " + + id + "-" + dr.getVersion() + "-" + di.getIteration() + " (" + workspace + ")"); + + UserDTO checkOutUser = dr.getCheckOutUser(); + + if(checkOutUser == null) { + try{ + dr = documentApi.checkOutDocument(workspace,id,pRevision,""); + di = LastIterationHelper.getLastIteration(dr); + }catch (ApiException e){ + output.printException(e); + } + } + + if(!noDownload && !di.getAttachedFiles().isEmpty()){ + FileHelper fh = new FileHelper(user,password,output,locale); + fh.downloadDocumentFiles(getServerURL(), path, workspace, id, dr, di, force); + } + + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("DocumentCheckOutCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentCreationCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentCreationCommand.java new file mode 100644 index 0000000000..a1f6e5bef2 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentCreationCommand.java @@ -0,0 +1,88 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.cli.commands.documents; + +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.FileHelper; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.api.models.DocumentCreationDTO; +import com.docdoku.api.models.DocumentIterationKey; +import com.docdoku.api.models.DocumentRevisionKey; +import com.docdoku.api.services.FoldersApi; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.io.File; +import java.io.IOException; + +/** + * + * @author Morgan Guimard + */ +public class DocumentCreationCommand extends BaseCommandLine { + + @Option(metaVar = "", name = "-o", aliases = "--id", required = true, usage = "the id of the document to save") + private String id; + + @Option(metaVar = "", name = "-N", aliases = "--title", usage = "the title of the document to save") + private String title; + + @Option(metaVar = "<description>", name = "-d", aliases = "--description", usage = "the description of the document to save") + private String description; + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Argument(metaVar = "<file>", required = true, index=0, usage = "specify the file of the document to import") + private File file; + + @Override + public void execImpl() throws Exception { + + FoldersApi foldersApi = new FoldersApi(client); + DocumentCreationDTO documentCreationDTO = new DocumentCreationDTO(); + documentCreationDTO.setTitle(title); + documentCreationDTO.setWorkspaceId(workspace); + documentCreationDTO.setDescription(description); + documentCreationDTO.setReference(id); + + foldersApi.createDocumentMasterInFolder(workspace, documentCreationDTO, workspace); + DocumentRevisionKey docRPK = new DocumentRevisionKey(); + docRPK.setWorkspaceId(workspace); + docRPK.setDocumentMasterId(id); + docRPK.setVersion("A"); + + DocumentIterationKey docIPK = new DocumentIterationKey(); + docIPK.setWorkspaceId(workspace); + docIPK.setDocumentMasterId(id); + docIPK.setDocumentRevisionVersion("A"); + docIPK.setIteration(1); + + FileHelper fh = new FileHelper(user,password,output,new AccountsManager().getUserLocale(user)); + fh.uploadDocumentFile(getServerURL(), file, docIPK); + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("DocumentCreationCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentGetCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentGetCommand.java new file mode 100644 index 0000000000..c3a7b03128 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentGetCommand.java @@ -0,0 +1,121 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.documents; + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.DocumentIterationDTO; +import com.docdoku.api.models.DocumentRevisionDTO; +import com.docdoku.api.services.DocumentApi; +import com.docdoku.api.models.utils.LastIterationHelper; +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.FileHelper; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.cli.helpers.MetaDirectoryManager; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import javax.security.auth.login.LoginException; +import java.io.File; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + +/** + * + * @author Morgan Guimard + */ +public class DocumentGetCommand extends BaseCommandLine { + + @Option(metaVar = "<revision>", name="-r", aliases = "--revision", usage="specify revision of the document to retrieve ('A', 'B'...); default is the latest") + private String revision; + + @Option(name="-i", aliases = "--iteration", metaVar = "<iteration>", usage="specify iteration of the document to retrieve ('1','2', '24'...); default is the latest") + private int iteration; + + @Option(metaVar = "<id>", name = "-o", aliases = "--id", usage = "the id of the document to fetch; if not specified choose the document corresponding to the file") + private String id; + + @Argument(metaVar = "[<file> | <dir>]", index=0, usage = "specify the file of the document to fetch or the path where files are stored (default is working directory)") + private File path = new File(System.getProperty("user.dir")); + + @Option(name="-f", aliases = "--force", usage="overwrite existing files even if they have been modified locally") + private boolean force; + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Override + public void execImpl() throws Exception { + + if(id==null){ + loadMetadata(); + } + + String strRevision = revision==null?null:revision; + getDocument(id,strRevision,iteration); + + } + + private void loadMetadata() throws IOException { + if(path.isDirectory()){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("DocumentIdOrRevisionNotSpecified1",user)); + } + MetaDirectoryManager meta = new MetaDirectoryManager(path.getParentFile()); + String filePath = path.getAbsolutePath(); + id = meta.getDocumentId(filePath); + String strRevision = meta.getRevision(filePath); + if(id==null || strRevision==null){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("DocumentIdOrRevisionNotSpecified2",user)); + } + revision = strRevision; + iteration=0; + path=path.getParentFile(); + } + + private void getDocument(String pId, String pRevision, int pIteration) throws IOException, ApiException, LoginException, NoSuchAlgorithmException { + + DocumentApi documentApi = new DocumentApi(client); + DocumentRevisionDTO dr = documentApi.getDocumentRevision(workspace,pId,pRevision,null); + + DocumentIterationDTO di; + + if(pIteration == 0){ + di = LastIterationHelper.getLastIteration(dr); + }else if(pIteration > dr.getDocumentIterations().size()){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("IterationNotExisting",user)); + }else{ + di = dr.getDocumentIterations().get(pIteration-1); + } + + if(di.getAttachedFiles().isEmpty()){ + output.printInfo(LangHelper.getLocalizedMessage("NoFilesForDocument",user) + " : " + id + " " + dr.getVersion() + "." + di.getIteration() + " (" + workspace + ")"); + }else{ + FileHelper fh = new FileHelper(user,password,output,new AccountsManager().getUserLocale(user)); + fh.downloadDocumentFiles(getServerURL(), path, workspace, id, dr, di, force); + } + + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("DocumentGetCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentListCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentListCommand.java new file mode 100644 index 0000000000..58d575e274 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentListCommand.java @@ -0,0 +1,68 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.documents; + +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.api.models.DocumentRevisionDTO; +import com.docdoku.api.services.DocumentsApi; +import com.docdoku.api.services.FoldersApi; +import org.kohsuke.args4j.Option; + +import java.io.IOException; +import java.util.List; + +/** + * + * @author Morgan Guimard + */ +public class DocumentListCommand extends BaseCommandLine { + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Option(name="-f", aliases = "--folder", usage="remote folder to list, default is workspace root folder") + private String folder = null; + + @Option(name="-c", aliases = "--checkedOut", usage="list only checked out files") + private boolean checkedOutDocsOnly = false; + + @Override + public void execImpl() throws Exception { + + if(checkedOutDocsOnly){ + DocumentsApi documentsApi = new DocumentsApi(client); + List<DocumentRevisionDTO> documentRevisions = documentsApi.getCheckedOutDocuments(workspace); + output.printDocumentRevisions(documentRevisions); + }else{ + FoldersApi foldersApi = new FoldersApi(client); + String decodedPath = folder == null ? workspace : workspace+"/"+folder; + List<DocumentRevisionDTO> documentRevisions = foldersApi.getDocumentsWithGivenFolderIdAndWorkspaceId(workspace,decodedPath,null); + output.printDocumentRevisions(documentRevisions); + } + + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("DocumentListCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentPutCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentPutCommand.java new file mode 100644 index 0000000000..d7a25d3482 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentPutCommand.java @@ -0,0 +1,97 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.documents; + +import com.docdoku.api.models.DocumentIterationDTO; +import com.docdoku.api.models.DocumentIterationKey; +import com.docdoku.api.models.DocumentRevisionDTO; +import com.docdoku.api.models.DocumentRevisionKey; +import com.docdoku.api.services.DocumentApi; +import com.docdoku.api.models.utils.LastIterationHelper; +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.FileHelper; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.cli.helpers.MetaDirectoryManager; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.io.File; +import java.io.IOException; + +/** + * + * @author Morgan Guimard + */ +public class DocumentPutCommand extends BaseCommandLine { + + @Option(metaVar = "<revision>", name="-r", aliases = "--revision", usage="specify revision of the document to save ('A', 'B'...); if not specified the document identity (id and revision) corresponding to the file will be selected") + private String revision; + + @Option(metaVar = "<id>", name = "-o", aliases = "--id", usage = "the id of the document to save; if not specified choose the document corresponding to the file if it has already been imported") + private String id; + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Argument(metaVar = "<file>", required = true, index=0, usage = "specify the file of the document to import") + private File file; + + @Override + public void execImpl() throws Exception { + if(id==null || revision==null){ + loadMetadata(); + } + + DocumentRevisionKey docRPK = new DocumentRevisionKey(); + docRPK.setWorkspaceId(workspace); + docRPK.setDocumentMasterId(id); + docRPK.setVersion(revision); + + DocumentApi documentApi = new DocumentApi(client); + DocumentRevisionDTO dr = documentApi.getDocumentRevision(workspace,id,revision,null); + DocumentIterationDTO di = LastIterationHelper.getLastIteration(dr); + + DocumentIterationKey docIPK = new DocumentIterationKey(); + docIPK.setWorkspaceId(workspace); + docIPK.setDocumentMasterId(id); + docIPK.setDocumentRevisionVersion(revision); + docIPK.setIteration(di.getIteration()); + + FileHelper fh = new FileHelper(user,password,output, new AccountsManager().getUserLocale(user)); + fh.uploadDocumentFile(getServerURL(), file, docIPK); + } + + private void loadMetadata() throws IOException { + MetaDirectoryManager meta = new MetaDirectoryManager(file.getParentFile()); + String filePath = file.getAbsolutePath(); + id = meta.getDocumentId(filePath); + String strRevision = meta.getRevision(filePath); + if(id==null || strRevision==null){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("DocumentIdOrRevisionNotSpecified2",user)); + } + revision = strRevision; + } + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("DocumentPutCommandDescription", user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentSearchCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentSearchCommand.java new file mode 100644 index 0000000000..c75b7ffacf --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentSearchCommand.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.documents; + +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.api.models.DocumentRevisionDTO; +import com.docdoku.api.services.DocumentsApi; +import org.kohsuke.args4j.Option; + +import java.io.IOException; +import java.util.List; + +/** + * + * @author Morgan Guimard + */ +public class DocumentSearchCommand extends BaseCommandLine { + + @Option(name = "-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage = "workspace on which operations occur") + protected String workspace; + + @Option(name = "-s", aliases = "--search", required = true, metaVar = "<search>", usage = "search string") + protected String searchValue; + + @Override + public void execImpl() throws Exception { + DocumentsApi documentsApi = new DocumentsApi(client); + List<DocumentRevisionDTO> documentRevisions = documentsApi.searchDocumentRevision(workspace,searchValue); + output.printDocumentRevisions(documentRevisions); + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("DocumentSearchCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentStatusCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentStatusCommand.java new file mode 100644 index 0000000000..7c7ffc8e6e --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentStatusCommand.java @@ -0,0 +1,96 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.documents; + +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.cli.helpers.MetaDirectoryManager; +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.DocumentRevisionDTO; +import com.docdoku.api.services.DocumentApi; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.io.File; +import java.io.IOException; + +/** + * + * @author Morgan Guimard + */ +public class DocumentStatusCommand extends BaseCommandLine { + + @Option(metaVar = "<revision>", name="-r", aliases = "--revision", usage="specify revision of the document to get a status ('A', 'B'...); if not specified the document identity (id and revision) corresponding to the file will be selected") + private String revision; + + @Option(metaVar = "<id>", name = "-o", aliases = "--id", usage = "the id of the document to get a status; if not specified choose the document corresponding to the file") + private String id; + + @Argument(metaVar = "[<file>]", index=0, usage = "specify the file of the document to get a status") + private File file; + + @Option(name="-w", aliases = "--workspace", required = false, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + private long lastModified; + + @Override + public void execImpl() throws Exception { + try { + + if(id==null || revision==null || workspace==null){ + loadMetadata(); + } + + DocumentApi documentApi = new DocumentApi(client); + + DocumentRevisionDTO documentRevisionDTO = documentApi.getDocumentRevision(workspace,id,revision,null); + output.printDocumentRevision(documentRevisionDTO,lastModified); + + } catch (ApiException e) { + MetaDirectoryManager meta = new MetaDirectoryManager(file.getParentFile()); + meta.deleteEntryInfo(file.getAbsolutePath()); + output.printException(e); + } + } + + private void loadMetadata() throws IOException { + if(file==null){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("DocumentIdOrRevisionNotSpecified1",user)); + } + MetaDirectoryManager meta = new MetaDirectoryManager(file.getParentFile()); + String filePath = file.getAbsolutePath(); + id = meta.getDocumentId(filePath); + workspace = meta.getWorkspace(filePath); + lastModified = meta.getLastModifiedDate(filePath); + String strRevision = meta.getRevision(filePath); + if(id==null || strRevision==null || workspace == null){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("DocumentIdOrRevisionNotSpecified2",user)); + } + revision = strRevision; + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("DocumentStatusCommandDescription",user); + } + +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentUndoCheckOutCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentUndoCheckOutCommand.java new file mode 100644 index 0000000000..648324ae39 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/DocumentUndoCheckOutCommand.java @@ -0,0 +1,99 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.documents; + +import com.docdoku.api.models.DocumentIterationDTO; +import com.docdoku.api.models.DocumentRevisionDTO; +import com.docdoku.api.services.DocumentApi; +import com.docdoku.api.models.utils.LastIterationHelper; +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.FileHelper; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.cli.helpers.MetaDirectoryManager; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.io.File; +import java.io.IOException; + +/** + * + * @author Morgan Guimard + */ +public class DocumentUndoCheckOutCommand extends BaseCommandLine { + + @Option(metaVar = "<revision>", name="-r", aliases = "--revision", usage="specify revision of the document to undo check out ('A', 'B'...); if not specified the document identity (id and revision) corresponding to the file will be selected") + private String revision; + + @Option(metaVar = "<id>", name = "-o", aliases = "--id", usage = "the id of the document to undo check out; if not specified choose the document corresponding to the file") + private String id; + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Argument(metaVar = "[<file> | <dir>]", index=0, usage = "specify the file of the document to undo check out or the path where files are stored (default is working directory)") + private File path = new File(System.getProperty("user.dir")); + + @Option(name="-d", aliases = "--download", usage="download the previous files of the document if any to revert the local copy") + private boolean download; + + @Option(name="-f", aliases = "--force", usage="overwrite existing files even if they have been modified locally") + private boolean force; + + @Override + public void execImpl() throws Exception { + if(id==null || revision==null){ + loadMetadata(); + } + + DocumentApi documentApi = new DocumentApi(client); + DocumentRevisionDTO dr = documentApi.undoCheckOutDocument(workspace, id, revision,""); + DocumentIterationDTO di = LastIterationHelper.getLastIteration(dr); + + output.printInfo(LangHelper.getLocalizedMessage("UndoCheckoutDocument",user) + " : " + id + "-" + dr.getVersion() + "-" + di.getIteration()+1 + " (" + workspace + ")"); + + if(download && !di.getAttachedFiles().isEmpty()){ + FileHelper fh = new FileHelper(user,password,output,new AccountsManager().getUserLocale(user)); + fh.downloadDocumentFiles(getServerURL(), path, workspace, id, dr, di, force); + } + } + + private void loadMetadata() throws IOException { + if(path.isDirectory()){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("DocumentIdOrRevisionNotSpecified1",user)); + } + MetaDirectoryManager meta = new MetaDirectoryManager(path.getParentFile()); + String filePath = path.getAbsolutePath(); + id = meta.getDocumentId(filePath); + String strRevision = meta.getRevision(filePath); + if(id==null || strRevision==null){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("DocumentIdOrRevisionNotSpecified2",user)); + } + revision = strRevision; + path=path.getParentFile(); + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("UndoCheckOutCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/FolderListCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/FolderListCommand.java new file mode 100644 index 0000000000..a6968f054a --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/documents/FolderListCommand.java @@ -0,0 +1,56 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.documents; + +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.api.models.FolderDTO; +import com.docdoku.api.services.FoldersApi; +import org.kohsuke.args4j.Option; + +import java.io.IOException; +import java.util.List; + +/** + * + * @author Morgan Guimard + */ +public class FolderListCommand extends BaseCommandLine { + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Option(name="-f", aliases = "--folder", usage="remote folder to list sub folders, default is workspace root folder") + private String folder = null; + + @Override + public void execImpl() throws Exception { + FoldersApi foldersApi = new FoldersApi(client); + String decodedPath = folder == null ? workspace : workspace+":"+folder; + List<FolderDTO> folders = foldersApi.getSubFolders(workspace,decodedPath,null); + output.printFolders(folders); + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("FolderListCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/BaselineListCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/BaselineListCommand.java new file mode 100644 index 0000000000..05f780cecf --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/BaselineListCommand.java @@ -0,0 +1,58 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.parts; + +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.api.models.ProductBaselineDTO; +import com.docdoku.api.services.PartApi; +import org.kohsuke.args4j.Option; + +import java.io.IOException; +import java.util.List; + +/** + * + * @author Morgan Guimard + */ +public class BaselineListCommand extends BaseCommandLine { + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Option(metaVar = "<partnumber>", required = true, name = "-o", aliases = "--part", usage = "the part number of the part to verify the existence of baselines") + private String number; + + @Option(metaVar = "<revision>", required = true, name="-r", aliases = "--revision", usage="specify revision of the part to analyze ('A', 'B'...)") + private String revision; + + @Override + public void execImpl() throws Exception { + PartApi partApi = new PartApi(client); + List<ProductBaselineDTO> productBaselines = partApi.getBaselinesWherePartRevisionHasIterations(workspace, number, revision); + output.printBaselines(productBaselines); + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("BaselineListCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/ConversionCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/ConversionCommand.java new file mode 100644 index 0000000000..15d5c3bdba --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/ConversionCommand.java @@ -0,0 +1,60 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.parts; + +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.api.models.ConversionDTO; +import com.docdoku.api.services.PartApi; +import org.kohsuke.args4j.Option; + +import java.io.IOException; + +/** + * + * @author Morgan Guimard + */ +public class ConversionCommand extends BaseCommandLine { + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Option(metaVar = "<partnumber>", required = true, name = "-o", aliases = "--part", usage = "the part number of the part to verify the existence of conversion") + private String number; + + @Option(metaVar = "<revision>", required = true, name="-r", aliases = "--revision", usage="specify revision of the part to analyze ('A', 'B'...)") + private String revision; + + @Option(name="-i", required = true, aliases = "--iteration", metaVar = "<iteration>", usage="specify iteration of the part to retrieve ('1','2', '24'...); default is the latest") + private int iteration; + + @Override + public void execImpl() throws Exception { + PartApi partApi = new PartApi(client); + ConversionDTO conversion = partApi.getConversionStatus(workspace, number, revision, iteration); + output.printConversion(conversion); + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("ConversionCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartCheckInCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartCheckInCommand.java new file mode 100644 index 0000000000..aa6ec226ac --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartCheckInCommand.java @@ -0,0 +1,134 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.parts; + +import com.docdoku.api.models.PartIterationDTO; +import com.docdoku.api.models.PartIterationKey; +import com.docdoku.api.models.PartRevisionDTO; +import com.docdoku.api.models.PartRevisionKey; +import com.docdoku.api.models.utils.LastIterationHelper; +import com.docdoku.api.services.PartApi; +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.FileHelper; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.cli.helpers.MetaDirectoryManager; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.io.File; +import java.io.IOException; + +/** + * + * @author Florent Garin + */ +public class PartCheckInCommand extends BaseCommandLine { + + @Option(metaVar = "<revision>", name="-r", aliases = "--revision", usage="specify revision of the part to check in ('A', 'B'...); if not specified the part identity (number and revision) corresponding to the cad file will be selected") + private String revision; + + @Option(metaVar = "<partnumber>", name = "-o", aliases = "--part", usage = "the part number of the part to check in; if not specified choose the part corresponding to the cad file") + private String partNumber; + + @Argument(metaVar = "[<cadfile> | <dir>]", index=0, usage = "specify the cad file of the part to check in or the path where cad files are stored (default is working directory)") + private File path = new File(System.getProperty("user.dir")); + + @Option(name="-n", aliases = "--no-upload", usage="do not upload the cad file of the part if any") + private boolean noUpload; + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Option(metaVar = "<message>", name = "-m", aliases = "--message", usage = "a message specifying the iteration modifications") + private String message; + + @Override + public void execImpl() throws Exception { + + if(partNumber==null || revision==null){ + loadMetadata(); + } + + PartApi partApi = new PartApi(client); + + PartRevisionDTO pr = partApi.getPartRevision(workspace,partNumber,revision); + PartIterationDTO pi = LastIterationHelper.getLastIteration(pr); + + PartRevisionKey partRPK = new PartRevisionKey(); + partRPK.setWorkspaceId(workspace); + partRPK.setPartMasterNumber(partNumber); + partRPK.setVersion(revision); + + PartIterationKey partIPK = new PartIterationKey(); + partIPK.setWorkspaceId(workspace); + partIPK.setPartMasterNumber(partNumber); + partIPK.setPartRevisionVersion(revision); + partIPK.setIteration(pi.getIteration()); + + if(!noUpload){ + String bin = pi.getNativeCADFile(); + if(bin!=null){ + // TODO check file path may break + String fileName = bin; + File localFile = new File(path,fileName); + if(localFile.exists()){ + + FileHelper fh = new FileHelper(user,password,output,new AccountsManager().getUserLocale(user)); + fh.uploadNativeCADFile(getServerURL(), localFile, partIPK); + localFile.setWritable(false); + } + } + } + + if(message != null && !message.isEmpty()){ + pi.setIterationNote(message); + partApi.updatePartIteration(workspace,partNumber,revision, pi.getIteration(), pi); + } + + output.printInfo(LangHelper.getLocalizedMessage("CheckingInPart",user) + " : " + partNumber + " " + pr.getVersion() + "." + pi.getIteration() + " (" + workspace + ")"); + + partApi.checkIn(workspace,partNumber,revision,""); + + } + + private void loadMetadata() throws IOException { + if(path.isDirectory()){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("PartNumberOrRevisionNotSpecified1",user)); + } + MetaDirectoryManager meta = new MetaDirectoryManager(path.getParentFile()); + String filePath = path.getAbsolutePath(); + partNumber = meta.getPartNumber(filePath); + String strRevision = meta.getRevision(filePath); + if(partNumber==null || strRevision==null){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("PartNumberOrRevisionNotSpecified2",user)); + } + revision = strRevision; + //once partNumber and revision have been inferred, set path to folder where files are stored + //in order to implement perform the rest of the treatment + path=path.getParentFile(); + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("PartCheckInCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartCheckOutCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartCheckOutCommand.java new file mode 100644 index 0000000000..2ff258ff4d --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartCheckOutCommand.java @@ -0,0 +1,151 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.parts; + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.PartIterationDTO; +import com.docdoku.api.models.PartRevisionDTO; +import com.docdoku.api.models.PartUsageLinkDTO; +import com.docdoku.api.models.utils.LastIterationHelper; +import com.docdoku.api.services.PartApi; +import com.docdoku.api.services.PartsApi; +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.FileHelper; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.cli.helpers.MetaDirectoryManager; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import javax.security.auth.login.LoginException; +import java.io.File; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class PartCheckOutCommand extends BaseCommandLine { + + @Option(metaVar = "<revision>", name="-r", aliases = "--revision", usage="specify revision of the part to check out ('A', 'B'...); if not specified the part identity (number and revision) corresponding to the cad file will be selected") + private String revision; + + @Option(metaVar = "<partnumber>", name = "-o", aliases = "--part", usage = "the part number of the part to check out; if not specified choose the part corresponding to the cad file") + private String partNumber; + + @Argument(metaVar = "[<cadfile> | <dir>]", index=0, usage = "specify the cad file of the part to check out or the path where cad files are stored (default is working directory)") + private File path = new File(System.getProperty("user.dir")); + + @Option(name="-n", aliases = "--no-download", usage="do not download the native cad file of the part if any") + private boolean noDownload; + + @Option(name="-f", aliases = "--force", usage="overwrite existing files even if they have been modified locally") + private boolean force; + + @Option(name="-R", aliases = "--recursive", usage="execute the command through the product structure hierarchy") + private boolean recursive; + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Option(name="-b", aliases = "--baseline", metaVar = "<baseline>", usage="baseline to filter") + protected int baselineId; + + @Override + public void execImpl() throws Exception { + if(partNumber==null || revision==null){ + loadMetadata(); + } + + String strRevision = revision==null?null:revision; + checkoutPart(partNumber,strRevision); + } + + private void loadMetadata() throws IOException { + if(path.isDirectory()){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("PartNumberOrRevisionNotSpecified1",user)); + } + MetaDirectoryManager meta = new MetaDirectoryManager(path.getParentFile()); + String filePath = path.getAbsolutePath(); + partNumber = meta.getPartNumber(filePath); + String strRevision = meta.getRevision(filePath); + if(partNumber==null || strRevision==null){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("PartNumberOrRevisionNotSpecified2",user)); + } + revision = strRevision; + //once partNumber and revision have been inferred, set path to folder where files are stored + //in order to implement perform the rest of the treatment + path=path.getParentFile(); + } + + private void checkoutPart(String pPartNumber, String pRevision) throws IOException, ApiException, LoginException, NoSuchAlgorithmException { + + PartsApi partsApi = new PartsApi(client); + PartApi partApi = new PartApi(client); + + Locale locale = new AccountsManager().getUserLocale(user); + + //PartMaster pm = productS.getPartMaster(new PartMasterKey(workspace, pPartNumber)); + PartRevisionDTO pr; + PartIterationDTO pi; + + output.printInfo(LangHelper.getLocalizedMessage("CheckingOutPart",locale)+ " : "+ pPartNumber); + + if(baselineId != 0){ + pi = partsApi.filterPartMasterInBaseline(workspace, pPartNumber,String.valueOf(baselineId)); + pr = partsApi.getPartRevision(workspace, pPartNumber, pi.getVersion()); + }else { + pr = partsApi.getPartRevision(workspace, pPartNumber, pRevision); + pi = LastIterationHelper.getLastIteration(pr); + } + + if(pr.getCheckOutUser() == null) { + try{ + pr = partApi.checkOut(workspace, pPartNumber, pr.getVersion(),""); + pi = LastIterationHelper.getLastIteration(pr); + }catch (Exception e){ + output.printException(e); + } + } + + String bin = pi.getNativeCADFile(); + + if(bin!=null && !noDownload){ + FileHelper fh = new FileHelper(user,password,output,locale); + fh.downloadNativeCADFile(getServerURL(), path, workspace, pPartNumber, pr, pi, force); + } + + if(recursive){ + + for(PartUsageLinkDTO link:pi.getComponents()){ + checkoutPart(link.getComponent().getNumber(), null); + } + } + + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("PartCheckOutCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartCreationCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartCreationCommand.java new file mode 100644 index 0000000000..463077e272 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartCreationCommand.java @@ -0,0 +1,92 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.parts; + +import com.docdoku.api.models.*; +import com.docdoku.api.services.PartsApi; +import com.docdoku.api.models.utils.LastIterationHelper; +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.FileHelper; +import com.docdoku.cli.helpers.LangHelper; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.io.File; +import java.io.IOException; + +/** + * + * @author Morgan Guimard + */ +public class PartCreationCommand extends BaseCommandLine { + + @Option(metaVar = "<partnumber>", name = "-o", aliases = "--part", required = true, usage = "the part number of the part to save") + private String partNumber; + + @Option(metaVar = "<partname>", name = "-N", aliases = "--partname", usage = "the part name of the part to save") + private String partName; + + @Option(metaVar = "<description>", name = "-d", aliases = "--description", usage = "the description of the part to save") + private String description; + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Option(name="-s", aliases = "--standard", metaVar = "<format>", usage="save as standard part") + protected boolean standardPart = false; + + @Argument(metaVar = "<cadfile>", required = true, index=0, usage = "specify the cad file of the part to import") + private File cadFile; + + @Override + public void execImpl() throws Exception { + + PartsApi partsApi = new PartsApi(client); + PartCreationDTO partCreationDTO = new PartCreationDTO(); + partCreationDTO.setDescription(description); + partCreationDTO.setWorkspaceId(workspace); + partCreationDTO.setName(partName); + partCreationDTO.setNumber(partNumber); + partCreationDTO.setStandardPart(standardPart); + PartRevisionDTO pr = partsApi.createNewPart(workspace, partCreationDTO); + + PartIterationDTO lastIteration = LastIterationHelper.getLastIteration(pr); + PartRevisionKey partRPK = new PartRevisionKey(); + partRPK.setWorkspaceId(workspace); + partRPK.setPartMasterNumber(partNumber); + partRPK.setVersion(pr.getVersion()); + + PartIterationKey partIPK = new PartIterationKey(); + partIPK.setWorkspaceId(workspace); + partIPK.setPartMasterNumber(partNumber); + partIPK.setPartRevisionVersion(pr.getVersion()); + partIPK.setIteration(lastIteration.getIteration()); + + FileHelper fh = new FileHelper(user,password,output,new AccountsManager().getUserLocale(user)); + fh.uploadNativeCADFile(getServerURL(), cadFile, partIPK); + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("PartCreationCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartGetCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartGetCommand.java new file mode 100644 index 0000000000..58f04ddf37 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartGetCommand.java @@ -0,0 +1,145 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.parts; + +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.PartIterationDTO; +import com.docdoku.api.models.PartRevisionDTO; +import com.docdoku.api.models.PartUsageLinkDTO; +import com.docdoku.api.services.PartApi; +import com.docdoku.api.services.PartsApi; +import com.docdoku.api.models.utils.LastIterationHelper; +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.FileHelper; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.cli.helpers.MetaDirectoryManager; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import javax.security.auth.login.LoginException; +import java.io.File; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + +/** + * + * @author Florent Garin + */ +public class PartGetCommand extends BaseCommandLine { + + + @Option(metaVar = "<revision>", name="-r", aliases = "--revision", usage="specify revision of the part to retrieve ('A', 'B'...); default is the latest") + private String revision; + + @Option(name="-i", aliases = "--iteration", metaVar = "<iteration>", usage="specify iteration of the part to retrieve ('1','2', '24'...); default is the latest") + private int iteration; + + @Option(metaVar = "<partnumber>", name = "-o", aliases = "--part", usage = "the part number of the part to fetch; if not specified choose the part corresponding to the cad file") + private String partNumber; + + @Argument(metaVar = "[<cadfile> | <dir>]", index=0, usage = "specify the cad file of the part to fetch or the path where cad files are stored (default is working directory)") + private File path = new File(System.getProperty("user.dir")); + + @Option(name="-f", aliases = "--force", usage="overwrite existing files even if they have been modified locally") + private boolean force; + + @Option(name="-R", aliases = "--recursive", usage="execute the command through the product structure hierarchy") + private boolean recursive; + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Option(name="-b", aliases = "--baseline", metaVar = "<baseline>", usage="baseline to filter") + protected int baselineId; + + @Override + public void execImpl() throws Exception { + + if(partNumber==null){ + loadMetadata(); + } + + String strRevision = revision==null?null:revision; + + + getPart(partNumber, strRevision, iteration); + } + + private void loadMetadata() throws IOException { + if(path.isDirectory()){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("PartNumberOrRevisionNotSpecified1",user)); + } + MetaDirectoryManager meta = new MetaDirectoryManager(path.getParentFile()); + String filePath = path.getAbsolutePath(); + partNumber = meta.getPartNumber(filePath); + String strRevision = meta.getRevision(filePath); + if(partNumber==null || strRevision==null){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("PartNumberOrRevisionNotSpecified2",user)); + } + revision = strRevision; + //The part is inferred from the cad file, hence fetch the fresh (latest) iteration + iteration=0; + //once partNumber and revision have been inferred, set path to folder where files are stored + //in order to implement perform the rest of the treatment + path=path.getParentFile(); + } + + private void getPart(String pPartNumber, String pRevision, int pIteration) throws IOException, ApiException, LoginException, NoSuchAlgorithmException { + + // TODO may break => unused parameter pIteration ?? should review algorithm and command specs + + PartsApi partsApi = new PartsApi(client); + PartApi partApi = new PartApi(client); + + PartRevisionDTO pr; + PartIterationDTO pi; + + if(baselineId != 0){ + pi = partsApi.filterPartMasterInBaseline(workspace, pPartNumber, String.valueOf(baselineId)); + pr = partApi.getPartRevision(workspace,pPartNumber,pi.getVersion()); + }else { + pr = partsApi.getPartRevision(workspace, pPartNumber, pRevision); + pi = LastIterationHelper.getLastIteration(pr); + } + + String bin = pi.getNativeCADFile(); + + if(bin!=null){ + FileHelper fh = new FileHelper(user,password,output,new AccountsManager().getUserLocale(user)); + fh.downloadNativeCADFile(getServerURL(), path, workspace, pPartNumber, pr, pi, force); + }else{ + output.printInfo(LangHelper.getLocalizedMessage("NoFileForPart",user) + " : " + pPartNumber + " " + pr.getVersion() + "." + pi.getIteration() + " (" + workspace + ")"); + } + + if(recursive){ + for(PartUsageLinkDTO link:pi.getComponents()){ + getPart(link.getComponent().getNumber(),null,0); + } + } + + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("PartGetCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartListCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartListCommand.java new file mode 100644 index 0000000000..b824141754 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartListCommand.java @@ -0,0 +1,67 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.parts; + +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.api.models.CountDTO; +import com.docdoku.api.models.PartRevisionDTO; +import com.docdoku.api.services.PartsApi; +import org.kohsuke.args4j.Option; + +import java.io.IOException; +import java.util.List; + +/** + * + * @author Morgan Guimard + */ +public class PartListCommand extends BaseCommandLine { + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Option(name="-c", aliases = "--count", usage="return the number of part revisions within the workspace") + private boolean count; + + @Option(name="-s", aliases = "--start", usage="start offset") + private int start; + + @Option(name="-m", aliases = "--max-results", usage="max results") + private int max; + + @Override + public void execImpl() throws Exception { + PartsApi partsApi = new PartsApi(client); + if(count){ + CountDTO countDTO = partsApi.getTotalNumberOfParts(workspace); + output.printPartRevisionsCount(countDTO.getCount()); + }else{ + List<PartRevisionDTO> partRevisions = partsApi.getPartRevisions(workspace, start, max); + output.printPartRevisions(partRevisions); + } + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("PartListCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartPutCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartPutCommand.java new file mode 100644 index 0000000000..3d2d2c0ae1 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartPutCommand.java @@ -0,0 +1,97 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.parts; + +import com.docdoku.api.models.PartIterationDTO; +import com.docdoku.api.models.PartIterationKey; +import com.docdoku.api.models.PartRevisionDTO; +import com.docdoku.api.models.PartRevisionKey; +import com.docdoku.api.services.PartApi; +import com.docdoku.api.models.utils.LastIterationHelper; +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.FileHelper; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.cli.helpers.MetaDirectoryManager; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.io.File; +import java.io.IOException; + +/** + * + * @author Florent Garin + */ +public class PartPutCommand extends BaseCommandLine { + + @Option(metaVar = "<revision>", name="-r", aliases = "--revision", usage="specify revision of the part to save ('A', 'B'...); if not specified the part identity (number and revision) corresponding to the cad file will be selected") + private String revision; + + @Option(metaVar = "<partnumber>", name = "-o", aliases = "--part", usage = "the part number of the part to save; if not specified choose the part corresponding to the cad file if it has already been imported") + private String partNumber; + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Argument(metaVar = "<cadfile>", required = true, index=0, usage = "specify the cad file of the part to import") + private File cadFile; + + @Override + public void execImpl() throws Exception { + if(partNumber==null || revision==null){ + loadMetadata(); + } + + PartRevisionKey partRPK = new PartRevisionKey(); + partRPK.setWorkspaceId(workspace); + partRPK.setPartMasterNumber(partNumber); + partRPK.setVersion(revision); + + PartApi partApi = new PartApi(client); + PartRevisionDTO pr = partApi.getPartRevision(workspace, partNumber, revision); + PartIterationDTO pi = LastIterationHelper.getLastIteration(pr); + + PartIterationKey partIPK = new PartIterationKey(); + partIPK.setWorkspaceId(workspace); + partIPK.setPartMasterNumber(partNumber); + partIPK.setPartRevisionVersion(revision); + partIPK.setIteration(pi.getIteration()); + + FileHelper fh = new FileHelper(user,password,output, new AccountsManager().getUserLocale(user)); + fh.uploadNativeCADFile(getServerURL(), cadFile, partIPK); + } + + private void loadMetadata() throws IOException { + MetaDirectoryManager meta = new MetaDirectoryManager(cadFile.getParentFile()); + String filePath = cadFile.getAbsolutePath(); + partNumber = meta.getPartNumber(filePath); + String strRevision = meta.getRevision(filePath); + if(partNumber==null || strRevision==null){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("PartNumberOrRevisionNotSpecified2",user)); + } + revision = strRevision; + } + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("PartPutCommandDescription", user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartSearchCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartSearchCommand.java new file mode 100644 index 0000000000..4cd875e71c --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartSearchCommand.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.parts; + +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.api.models.PartRevisionDTO; +import com.docdoku.api.services.PartsApi; +import org.kohsuke.args4j.Option; + +import java.io.IOException; +import java.util.List; + +/** + * + * @author Morgan Guimard + */ +public class PartSearchCommand extends BaseCommandLine { + + @Option(name = "-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage = "workspace on which operations occur") + protected String workspace; + + @Option(name = "-s", aliases = "--search", required = true, metaVar = "<search>", usage = "search string") + protected String searchValue; + + @Override + public void execImpl() throws Exception { + PartsApi partsApi = new PartsApi(client); + List<PartRevisionDTO> partRevisions = partsApi.searchPartRevisions(workspace, searchValue); + output.printPartRevisions(partRevisions); + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("PartSearchCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartStatusCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartStatusCommand.java new file mode 100644 index 0000000000..3e5b41ff00 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartStatusCommand.java @@ -0,0 +1,102 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.parts; + +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.cli.helpers.MetaDirectoryManager; +import com.docdoku.api.client.ApiException; +import com.docdoku.api.models.PartRevisionDTO; +import com.docdoku.api.services.PartApi; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.io.File; +import java.io.IOException; + +/** + * + * @author Florent Garin + */ +public class PartStatusCommand extends BaseCommandLine { + + @Option(metaVar = "<revision>", name="-r", aliases = "--revision", usage="specify revision of the part to get a status ('A', 'B'...); if not specified the part identity (number and revision) corresponding to the cad file will be selected") + private String revision; + + @Option(metaVar = "<partnumber>", name = "-o", aliases = "--part", usage = "the part number of the part to get a status; if not specified choose the part corresponding to the cad file") + private String partNumber; + + @Argument(metaVar = "[<cadfile>]", index=0, usage = "specify the cad file of the part to get a status") + private File cadFile; + + @Option(name="-w", aliases = "--workspace", required = false, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + private long lastModified; + + @Override + public void execImpl() throws Exception { + try { + + if(partNumber==null || revision==null || workspace==null){ + loadMetadata(); + } + + if(revision == null){ + // TODO get part master service ??? + + // PartMaster pm = productS.getPartMaster(new PartMasterKey(workspace, partNumber)); + // output.printPartMaster(pm, lastModified); + }else{ + PartApi partApi = new PartApi(client); + PartRevisionDTO partRevision = partApi.getPartRevision(workspace, partNumber, revision); + output.printPartRevision(partRevision, lastModified); + } + + } catch (ApiException e) { + MetaDirectoryManager meta = new MetaDirectoryManager(cadFile.getParentFile()); + meta.deleteEntryInfo(cadFile.getAbsolutePath()); + output.printException(e); + } + } + + private void loadMetadata() throws IOException { + if(cadFile==null){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("PartNumberOrRevisionNotSpecified1",user)); + } + MetaDirectoryManager meta = new MetaDirectoryManager(cadFile.getParentFile()); + String filePath = cadFile.getAbsolutePath(); + partNumber = meta.getPartNumber(filePath); + workspace = meta.getWorkspace(filePath); + lastModified = meta.getLastModifiedDate(filePath); + String strRevision = meta.getRevision(filePath); + if(partNumber==null || strRevision==null || workspace == null){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("PartNumberOrRevisionNotSpecified2",user)); + } + revision = strRevision; + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("PartStatusCommandDescription",user); + } + +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartUndoCheckOutCommand.java b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartUndoCheckOutCommand.java new file mode 100644 index 0000000000..5a617466ea --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/commands/parts/PartUndoCheckOutCommand.java @@ -0,0 +1,101 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.parts; + +import com.docdoku.api.models.PartIterationDTO; +import com.docdoku.api.models.PartRevisionDTO; +import com.docdoku.api.services.PartApi; +import com.docdoku.api.models.utils.LastIterationHelper; +import com.docdoku.cli.commands.BaseCommandLine; +import com.docdoku.cli.helpers.AccountsManager; +import com.docdoku.cli.helpers.FileHelper; +import com.docdoku.cli.helpers.LangHelper; +import com.docdoku.cli.helpers.MetaDirectoryManager; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.io.File; +import java.io.IOException; + +/** + * + * @author Florent Garin + */ +public class PartUndoCheckOutCommand extends BaseCommandLine { + + @Option(metaVar = "<revision>", name="-r", aliases = "--revision", usage="specify revision of the part to undo check out ('A', 'B'...); if not specified the part identity (number and revision) corresponding to the cad file will be selected") + private String revision; + + @Option(metaVar = "<partnumber>", name = "-o", aliases = "--part", usage = "the part number of the part to undo check out; if not specified choose the part corresponding to the cad file") + private String partNumber; + + @Option(name="-w", aliases = "--workspace", required = true, metaVar = "<workspace>", usage="workspace on which operations occur") + protected String workspace; + + @Argument(metaVar = "[<cadfile> | <dir>]", index=0, usage = "specify the cad file of the part to undo check out or the path where cad files are stored (default is working directory)") + private File path = new File(System.getProperty("user.dir")); + + @Option(name="-d", aliases = "--download", usage="download the previous cad file of the part if any to revert the local copy") + private boolean download; + + @Option(name="-f", aliases = "--force", usage="overwrite existing files even if they have been modified locally") + private boolean force; + + @Override + public void execImpl() throws Exception { + if(partNumber==null || revision==null){ + loadMetadata(); + } + + PartApi partApi = new PartApi(client); + PartRevisionDTO pr = partApi.undoCheckOut(workspace, partNumber, revision, ""); + PartIterationDTO pi = LastIterationHelper.getLastIteration(pr); + output.printInfo(LangHelper.getLocalizedMessage("UndoCheckoutPart",user) + " : " + partNumber + " " + pr.getVersion() + "." + pi.getIteration()+1 + " (" + workspace + ")"); + + String bin = pi.getNativeCADFile(); + if(bin!=null && download){ + FileHelper fh = new FileHelper(user,password,output,new AccountsManager().getUserLocale(user)); + fh.downloadNativeCADFile(getServerURL(), path, workspace, partNumber, pr, pi, force); + } + } + + private void loadMetadata() throws IOException { + if(path.isDirectory()){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("PartNumberOrRevisionNotSpecified1",user)); + } + MetaDirectoryManager meta = new MetaDirectoryManager(path.getParentFile()); + String filePath = path.getAbsolutePath(); + partNumber = meta.getPartNumber(filePath); + String strRevision = meta.getRevision(filePath); + if(partNumber==null || strRevision==null){ + throw new IllegalArgumentException(LangHelper.getLocalizedMessage("PartNumberOrRevisionNotSpecified2",user)); + } + revision = strRevision; + //once partNumber and revision have been inferred, set path to folder where files are stored + //in order to implement perform the rest of the treatment + path=path.getParentFile(); + } + + @Override + public String getDescription() throws IOException { + return LangHelper.getLocalizedMessage("UndoCheckOutCommandDescription",user); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/helpers/AccountsManager.java b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/AccountsManager.java new file mode 100644 index 0000000000..f482396f3e --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/AccountsManager.java @@ -0,0 +1,83 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.helpers; + +import com.docdoku.api.models.AccountDTO; + +import java.io.*; +import java.util.Locale; +import java.util.Properties; + +public class AccountsManager { + + private static final String ACCOUNTS_FILE = ".dplm_account_properties"; + private static final String LANGUAGE_PROP = "language"; + + private File accountsFile; + private Properties accountsProps; + + public AccountsManager() throws IOException { + + accountsFile = new File(System.getProperty("user.home"),ACCOUNTS_FILE); + accountsProps = new Properties(); + + if(!accountsFile.exists()){ + accountsFile.createNewFile(); + } + + try{ + accountsProps.loadFromXML(new BufferedInputStream(new FileInputStream(accountsFile))); + }catch(IOException ex){ + accountsFile.delete(); + } + + } + + private void saveIndex() throws IOException { + OutputStream out = new BufferedOutputStream(new FileOutputStream(accountsFile)); + accountsProps.storeToXML(out, null); + } + + public String getUserLanguage(String userLogin){ + return accountsProps.getProperty(userLogin + "." + LANGUAGE_PROP); + } + + public void setUserLanguage(String userLogin, String language) throws IOException { + accountsProps.setProperty(userLogin + "." + LANGUAGE_PROP, language); + saveIndex(); + } + + public Locale getUserLocale(String userLogin){ + if(userLogin == null || userLogin.isEmpty()){ + return Locale.getDefault(); + }else{ + String userLanguage = getUserLanguage(userLogin); + if(userLanguage == null) + return Locale.getDefault(); + else + return new Locale(getUserLanguage(userLogin)); + } + } + + public void saveAccount(AccountDTO accountDTO) throws IOException { + setUserLanguage(accountDTO.getLogin(),accountDTO.getLanguage()); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/helpers/CliOutput.java b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/CliOutput.java new file mode 100644 index 0000000000..adc2cd810e --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/CliOutput.java @@ -0,0 +1,75 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.helpers; + +import com.docdoku.api.models.*; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Locale; + +import static com.docdoku.cli.helpers.CliOutput.formats.HUMAN; + +/** + * @author Morgan Guimard + * @version 2.0, 14/11/14 + * @since V2.0 + */ +public abstract class CliOutput { + + public enum formats { + HUMAN, + JSON + } + public static CliOutput getOutput(formats pFormat, Locale pLocale) { + formats format = pFormat; + if(format == null){ + format = HUMAN; + } + switch(format){ + case HUMAN: return new HumanOutput(pLocale); + case JSON: return new JSONOutput(); + default: return new HumanOutput(pLocale); + } + } + + public abstract void printException(Exception e); + public abstract void printCommandUsage(CommandLine cl) throws IOException; + public abstract void printUsage(); + public abstract void printInfo(String s); + + public abstract void printWorkspaces(List<WorkspaceDTO> workspaceDTOs); + public abstract void printPartRevisionsCount(int partRevisionsCount); + public abstract void printPartRevisions(List<PartRevisionDTO> partRevisions); + public abstract void printBaselines(List<ProductBaselineDTO> productBaselines); + public abstract void printPartRevision(PartRevisionDTO pr, long lastModified); + public abstract void printPartMaster(PartMaster pm, long lastModified); + public abstract void printConversion(ConversionDTO conversion); + public abstract void printAccount(AccountDTO accountDTO); + public abstract void printDocumentRevision(DocumentRevisionDTO dr, long lastModified); + public abstract void printDocumentRevisions(List<DocumentRevisionDTO> documentRevisions); + public abstract void printFolders(List<FolderDTO> folders); + + public abstract FilterInputStream getMonitor(long maximum, InputStream in); + +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/helpers/CommandLine.java b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/CommandLine.java new file mode 100644 index 0000000000..0444f2580a --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/CommandLine.java @@ -0,0 +1,33 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.helpers; + +import java.io.IOException; + +/** + * + * @author Florent Garin + */ +public interface CommandLine { + CliOutput getOutput(); + void exec() throws Exception; + String getDescription() throws IOException; +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/helpers/ConsoleProgressMonitorInputStream.java b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/ConsoleProgressMonitorInputStream.java new file mode 100644 index 0000000000..36a7403485 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/ConsoleProgressMonitorInputStream.java @@ -0,0 +1,73 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.helpers; + +import org.apache.commons.io.FileUtils; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; + +public class ConsoleProgressMonitorInputStream extends FilterInputStream { + + private long maximum; + private long totalRead; + private PrintStream outputStream = System.out; + private int rotationChar; + + private static final char[] ROTATION = {'|','|','|','|','/','/','/','/','-','-','-','-','\\','\\','\\','\\'}; + + public ConsoleProgressMonitorInputStream(long maximum, InputStream in){ + super(in); + this.maximum=maximum; + } + + @Override + public int read(byte b[]) throws IOException { + int length = super.read(b, 0, b.length); + totalRead += length; + + int percentage = (int)((totalRead * 100.0f) / maximum); + + String percentageToPrint; + if(percentage==100) { + percentageToPrint = "" + percentage; + } else { + percentageToPrint = (percentage < 10) ? " " + percentage : " " + percentage; + } + + if(length ==-1) { + outputStream.println("\r" + "100%"); + }else { + if(maximum!=-1) { + outputStream.print("\r" + percentageToPrint + "% Total " + FileUtils.byteCountToDisplaySize(totalRead) + " " + ROTATION[rotationChar % ROTATION.length] + " "); + }else{ + outputStream.print("\r" + " Total " + FileUtils.byteCountToDisplaySize(totalRead) + " " + ROTATION[rotationChar % ROTATION.length] + " "); + } + } + + rotationChar++; + return length; + } + + +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/helpers/FileHelper.java b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/FileHelper.java new file mode 100644 index 0000000000..72acec079c --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/FileHelper.java @@ -0,0 +1,374 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.helpers; + +import com.docdoku.api.models.*; +import com.docdoku.api.models.utils.LastIterationHelper; + +import javax.security.auth.login.LoginException; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.List; +import java.util.Locale; +import java.util.zip.GZIPInputStream; + +public class FileHelper { + + private static final int CHUNK_SIZE = 1024*8; + private static final int BUFFER_CAPACITY = 1024*32; + + private String login; + private String password; + private CliOutput output; + private Locale locale; + + public FileHelper(String login, String password, CliOutput output, Locale locale) { + this.login = login; + this.password = password; + this.output = output; + this.locale = locale; + } + + private String downloadFile(File pLocalFile, String pURL) throws IOException, LoginException, NoSuchAlgorithmException { + FilterInputStream in = null; + OutputStream out = null; + HttpURLConnection conn = null; + try { + //Hack for NTLM proxy + //perform a head method to negotiate the NTLM proxy authentication + // Always replace trailing slashes with %20 + // avoid 505 bad http version error cause servers interpret everything after first space as the http version + URL url = new URL(pURL.replace(" ", "%20")); + + output.printInfo( + LangHelper.getLocalizedMessage("DownloadingFile",locale) + + " : " + + pLocalFile.getName() + " " + + LangHelper.getLocalizedMessage("From",locale) + " " + + url.getHost()); + + performHeadHTTPMethod(url); + + out = new BufferedOutputStream(new FileOutputStream(pLocalFile), BUFFER_CAPACITY); + + conn = (HttpURLConnection) url.openConnection(); + conn.setUseCaches(false); + conn.setAllowUserInteraction(true); + conn.setRequestProperty("Connection", "Keep-Alive"); + conn.setRequestProperty("Accept-Encoding", "gzip"); + conn.setRequestMethod("GET"); + byte[] encoded = Base64.getEncoder().encode((login + ":" + password).getBytes("ISO-8859-1")); + conn.setRequestProperty("Authorization", "Basic " + new String(encoded, "US-ASCII")); + conn.connect(); + manageHTTPCode(conn); + + + MessageDigest md = MessageDigest.getInstance("MD5"); + InputStream connInputStream; + if ("gzip".equals(conn.getContentEncoding())) { + connInputStream =new GZIPInputStream(conn.getInputStream()); + } + else { + connInputStream =conn.getInputStream(); + } + + in = output.getMonitor(conn.getContentLength(),new DigestInputStream(new BufferedInputStream(connInputStream, BUFFER_CAPACITY), md)); + + byte[] data = new byte[CHUNK_SIZE]; + int length; + + while ((length = in.read(data)) != -1) { + out.write(data, 0, length); + } + out.flush(); + + byte[] digest = md.digest(); + return Base64.getEncoder().encodeToString(digest); + } finally { + if(out!=null) { + out.close(); + } + if(in!=null) { + in.close(); + } + if(conn!=null) { + conn.disconnect(); + } + } + } + + private String uploadFile(File pLocalFile, String pURL) throws IOException, LoginException, NoSuchAlgorithmException { + InputStream in = null; + OutputStream out = null; + HttpURLConnection conn = null; + try { + //Hack for NTLM proxy + //perform a head method to negociate the NTLM proxy authentication + URL url = new URL(pURL); + + output.printInfo( + LangHelper.getLocalizedMessage("UploadingFile", locale) + + " : " + + pLocalFile.getName() + " " + + LangHelper.getLocalizedMessage("To", locale) + " " + + url.getHost()); + performHeadHTTPMethod(url); + + + conn = (HttpURLConnection) url.openConnection(); + conn.setDoOutput(true); + conn.setUseCaches(false); + conn.setAllowUserInteraction(true); + conn.setRequestProperty("Connection", "Keep-Alive"); + byte[] encoded = Base64.getEncoder().encode((login + ":" + password).getBytes("ISO-8859-1")); + conn.setRequestProperty("Authorization", "Basic " + new String(encoded, "US-ASCII")); + + String lineEnd = "\r\n"; + String twoHyphens = "--"; + String boundary = "--------------------" + Long.toString(System.currentTimeMillis(), 16); + byte[] header = (twoHyphens + boundary + lineEnd + "Content-Disposition: form-data; name=\"upload\";" + " filename=\"" + pLocalFile.getName() + "\"" + lineEnd + lineEnd).getBytes("ISO-8859-1"); + byte[] footer = (lineEnd + twoHyphens + boundary + twoHyphens + lineEnd).getBytes("ISO-8859-1"); + + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); + long len = header.length + pLocalFile.length() + footer.length; + conn.setFixedLengthStreamingMode((int) len); + out = new BufferedOutputStream(conn.getOutputStream(), BUFFER_CAPACITY); + out.write(header); + + byte[] data = new byte[CHUNK_SIZE]; + int length; + MessageDigest md = MessageDigest.getInstance("MD5"); + in = output.getMonitor(pLocalFile.length(), new DigestInputStream(new BufferedInputStream(new FileInputStream(pLocalFile), BUFFER_CAPACITY),md)); + while ((length = in.read(data)) != -1) { + out.write(data, 0, length); + } + + out.write(footer); + out.flush(); + + manageHTTPCode(conn); + + byte[] digest = md.digest(); + return Base64.getEncoder().encodeToString(digest); + } finally { + if(out!=null) { + out.close(); + } + if(in!=null) { + in.close(); + } + if(conn!=null) { + conn.disconnect(); + } + } + } + + private void manageHTTPCode(HttpURLConnection conn) throws IOException, LoginException { + int code = conn.getResponseCode(); + switch (code){ + case 401: case 403: + throw new LoginException(LangHelper.getLocalizedMessage("LoginError",locale)); + case 500: + throw new IOException(conn.getHeaderField("Reason-Phrase")); + default: + break; + } + } + + private void performHeadHTTPMethod(URL url) throws IOException { + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setUseCaches(false); + conn.setAllowUserInteraction(true); + conn.setRequestProperty("Connection", "Keep-Alive"); + byte[] encoded = Base64.getEncoder().encode((login + ":" + password).getBytes("ISO-8859-1")); + conn.setRequestProperty("Authorization", "Basic " + new String(encoded, "US-ASCII")); + conn.setRequestMethod("HEAD"); + conn.connect(); + } + + private static String getPartURL(URL serverURL, PartIterationKey pPartIPK, String pRemoteFileName) throws UnsupportedEncodingException, MalformedURLException { + return serverURL + + "/api/files/" + + URLEncoder.encode(pPartIPK.getWorkspaceId(), "UTF-8") + "/" + + "parts/" + + URLEncoder.encode(pPartIPK.getPartMasterNumber(), "UTF-8") + "/" + + pPartIPK.getPartRevisionVersion() + "/" + + pPartIPK.getIteration() + "/nativecad/" + + pRemoteFileName; + } + + private static String getDocumentURL(URL serverURL, DocumentIterationKey pDocIPK, String pRemoteFileName) throws UnsupportedEncodingException, MalformedURLException { + return serverURL + + "/api/files/" + + URLEncoder.encode(pDocIPK.getWorkspaceId(), "UTF-8") + "/" + + "documents/" + + URLEncoder.encode(pDocIPK.getDocumentMasterId(), "UTF-8") + "/" + + pDocIPK.getDocumentRevisionVersion()+ "/" + + pDocIPK.getIteration() + "/" + + pRemoteFileName; + } + + public static String getPartURLUpload(URL serverURL, PartIterationKey pPart) throws UnsupportedEncodingException, MalformedURLException { + return serverURL + + "/api/files/" + + URLEncoder.encode(pPart.getWorkspaceId(), "UTF-8") + "/" + + "parts/" + + URLEncoder.encode(pPart.getPartMasterNumber(), "UTF-8") + "/" + + pPart.getPartRevisionVersion() + "/" + + pPart.getIteration() + "/nativecad/"; + } + + private static String getDocumentURLUpload(URL serverURL, DocumentIterationKey docIPK) throws UnsupportedEncodingException { + return serverURL + + "/api/files/" + + URLEncoder.encode(docIPK.getWorkspaceId(), "UTF-8") + "/" + + "documents/" + + URLEncoder.encode(docIPK.getDocumentMasterId(), "UTF-8") + "/" + + docIPK.getDocumentRevisionVersion() + "/" + + docIPK.getIteration(); + } + + public static boolean confirmOverwrite(String fileName){ + Console c = System.console(); + String response = c.readLine("The file '" + fileName + "' has been modified locally, do you want to overwrite it [y/N]?"); + return "y".equalsIgnoreCase(response); + } + + public void uploadNativeCADFile(URL serverURL, File cadFile, PartIterationKey partIPK) throws IOException, LoginException, NoSuchAlgorithmException { + String digest = uploadFile(cadFile, FileHelper.getPartURLUpload(serverURL, partIPK)); + + File path = cadFile.getParentFile(); + MetaDirectoryManager meta = new MetaDirectoryManager(path); + + saveMetadata(meta, partIPK, digest, cadFile); + + } + + public void downloadNativeCADFile(URL serverURL, File path, String workspace, String partNumber, PartRevisionDTO pr, PartIterationDTO pi, boolean force) throws IOException, LoginException, NoSuchAlgorithmException { + String bin = pi.getNativeCADFile(); + String fileName = FileHelper.getFileName(bin); + UserDTO checkOutUser = pr.getCheckOutUser(); + PartIterationDTO lastIteration = LastIterationHelper.getLastIteration(pr); + PartIterationKey partIPK = new PartIterationKey(); + partIPK.setWorkspaceId(workspace); + partIPK.setPartMasterNumber(partNumber); + partIPK.setPartRevisionVersion(pr.getVersion()); + partIPK.setIteration(pi.getIteration()); + + boolean writable = (checkOutUser != null) && (checkOutUser.getLogin().equals(login)) && (lastIteration.getIteration()==pi.getIteration()); + File localFile = new File(path,fileName); + MetaDirectoryManager meta = new MetaDirectoryManager(path); + + if(localFile.exists() && !force && localFile.lastModified()!=meta.getLastModifiedDate(localFile.getAbsolutePath())){ + boolean confirm = FileHelper.confirmOverwrite(localFile.getAbsolutePath()); + if(!confirm) { + return; + } + } + localFile.delete(); + String digest = downloadFile(localFile, FileHelper.getPartURL(serverURL, partIPK, fileName)); + localFile.setWritable(writable); + + saveMetadata(meta, partIPK, digest, localFile); + } + + private void saveMetadata(MetaDirectoryManager meta, PartIterationKey partIPK, String digest, File localFile) throws IOException { + String filePath=localFile.getAbsolutePath(); + meta.setDigest(filePath,digest); + meta.setPartNumber(filePath,partIPK.getPartMasterNumber()); + meta.setWorkspace(filePath,partIPK.getWorkspaceId()); + meta.setRevision(filePath,partIPK.getPartRevisionVersion()); + meta.setIteration(filePath,partIPK.getIteration()); + meta.setLastModifiedDate(filePath, localFile.lastModified()); + } + + private void saveMetadata(MetaDirectoryManager meta, DocumentIterationKey docIPK, String digest, File localFile) throws IOException { + String filePath=localFile.getAbsolutePath(); + meta.setDigest(filePath,digest); + meta.setDocumentId(filePath,docIPK.getDocumentMasterId()); + meta.setWorkspace(filePath,docIPK.getWorkspaceId()); + meta.setRevision(filePath,docIPK.getDocumentRevisionVersion()); + meta.setIteration(filePath,docIPK.getIteration()); + meta.setLastModifiedDate(filePath, localFile.lastModified()); + } + + public void downloadDocumentFiles(URL serverURL, File path, String workspace, String id, DocumentRevisionDTO dr, DocumentIterationDTO di, boolean force) throws IOException, LoginException, NoSuchAlgorithmException { + List<String> bins = di.getAttachedFiles(); + + for(String bin:bins){ + // TODO may break + String fileName = FileHelper.getFileName(bin); + UserDTO checkOutUser = dr.getCheckOutUser(); + DocumentIterationKey docIPK = new DocumentIterationKey(); + docIPK.setWorkspaceId(workspace); + docIPK.setDocumentMasterId(id); + docIPK.setDocumentRevisionVersion(dr.getVersion()); + docIPK.setIteration(di.getIteration()); + + DocumentIterationDTO lastIteration = LastIterationHelper.getLastIteration(dr); + + boolean writable = (checkOutUser != null) && (checkOutUser.getLogin().equals(login)) && (lastIteration.getIteration()==di.getIteration()); + File localFile = new File(path,fileName); + MetaDirectoryManager meta = new MetaDirectoryManager(path); + + if(localFile.exists() && !force && localFile.lastModified()!=meta.getLastModifiedDate(localFile.getAbsolutePath())){ + boolean confirm = FileHelper.confirmOverwrite(localFile.getAbsolutePath()); + if(!confirm) { + return; + } + } + + localFile.delete(); + String digest = downloadFile(localFile, FileHelper.getDocumentURL(serverURL, docIPK, fileName)); + localFile.setWritable(writable); + + saveMetadata(meta, docIPK, digest, localFile); + + } + } + + + public void uploadDocumentFile(URL serverURL, File file, DocumentIterationKey docIPK) throws IOException, LoginException, NoSuchAlgorithmException { + String digest = uploadFile(file, FileHelper.getDocumentURLUpload(serverURL, docIPK)); + + File path = file.getParentFile(); + MetaDirectoryManager meta = new MetaDirectoryManager(path); + + saveMetadata(meta, docIPK, digest, file); + } + + public static String getFileName(String path) { + if (path == null || path.isEmpty()) { + return null; + } + int lastSlash = path.lastIndexOf("/"); + return path.substring(lastSlash+1, path.length()); + } + +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/helpers/HumanOutput.java b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/HumanOutput.java new file mode 100644 index 0000000000..36c2421b7e --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/HumanOutput.java @@ -0,0 +1,276 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.helpers; + +import com.docdoku.api.models.*; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.text.DateFormat; +import java.util.List; +import java.util.Locale; + +public class HumanOutput extends CliOutput{ + + private Locale locale; + private final static PrintStream ERROR_STREAM = System.err; + private final static PrintStream OUTPUT_STREAM = System.out; + public HumanOutput(Locale pLocale) { + locale = pLocale; + } + + @Override + public void printException(Exception e) { + ERROR_STREAM.println(e.getMessage()); + if(e instanceof CmdLineException){ + printUsage(); + } + } + + @Override + public void printCommandUsage(CommandLine cl) throws IOException { + CmdLineParser parser = new CmdLineParser(cl); + OUTPUT_STREAM.println(cl.getDescription()); + OUTPUT_STREAM.println(); + parser.printUsage(OUTPUT_STREAM); + OUTPUT_STREAM.println(); + } + + @Override + public void printUsage() { + ERROR_STREAM.println(LangHelper.getLocalizedMessage("Usage",locale)); + ERROR_STREAM.println(); + printAvailableCommands(); + ERROR_STREAM.println(); + ERROR_STREAM.println(LangHelper.getLocalizedMessage("AdditionalInfos",locale)); + } + + private void printAvailableCommands(){ + ERROR_STREAM.println(LangHelper.getLocalizedMessage("AvailableCommands",locale) +":"); + ERROR_STREAM.println(" checkin (ci)"); + ERROR_STREAM.println(" checkout (co)"); + ERROR_STREAM.println(" create (cr)"); + ERROR_STREAM.println(" get"); + ERROR_STREAM.println(" help (?, h)"); + ERROR_STREAM.println(" put"); + ERROR_STREAM.println(" status (stat, st)"); + ERROR_STREAM.println(" undocheckout (uco)"); + ERROR_STREAM.println(); + ERROR_STREAM.println(LangHelper.getLocalizedMessage("InstructionCommands",locale)); + } + + @Override + public void printInfo(String s) { + OUTPUT_STREAM.println(s); + } + + @Override + public void printWorkspaces(List<WorkspaceDTO> workspaceDTOs) { + for(WorkspaceDTO workspaceDTO :workspaceDTOs){ + OUTPUT_STREAM.println(workspaceDTO.getId()); + } + } + + @Override + public void printPartRevisionsCount(int partRevisionsCount) { + OUTPUT_STREAM.println(LangHelper.getLocalizedMessage("Count",locale) + " : " + partRevisionsCount); + } + + @Override + public void printPartRevisions(List<PartRevisionDTO> partRevisions) { + for(PartRevisionDTO pr: partRevisions) { + printRevisionStatus(1, pr); + } + } + + @Override + public void printBaselines(List<ProductBaselineDTO> productBaselines) { + if(productBaselines.isEmpty()){ + OUTPUT_STREAM.println(LangHelper.getLocalizedMessage("NoBaseline",locale)); + return; + } + for(ProductBaselineDTO productBaseline : productBaselines) { + OUTPUT_STREAM.println("#" + productBaseline.getId() + " : " + productBaseline.getName()); + } + } + + @Override + public void printPartRevision(PartRevisionDTO pr, long lastModified) { + printRevisionStatus(1,pr); + } + + @Override + public void printPartMaster(PartMaster pm, long lastModified) { + // TODO : rewrite with DTO + /* + int revColSize = pm.getLastRevision().getVersion().length(); + for(PartRevision pr:pm.getPartRevisions()){ + printRevisionStatus(revColSize,pr); + }*/ + } + + @Override + public void printConversion(ConversionDTO conversion) { + if(conversion.getSucceed()){ + OUTPUT_STREAM.println(LangHelper.getLocalizedMessage("ConversionSucceed",locale)); + }else if(conversion.getPending()){ + OUTPUT_STREAM.println(LangHelper.getLocalizedMessage("ConversionInProgress",locale)); + } else{ + OUTPUT_STREAM.println(LangHelper.getLocalizedMessage("ConversionFailed",locale)); + } + OUTPUT_STREAM.println(LangHelper.getLocalizedMessage("ConversionStarted",locale) + " : " + conversion.getStartDate()); + OUTPUT_STREAM.println(LangHelper.getLocalizedMessage("ConversionEnded",locale) + " : " + conversion.getEndDate()); + } + + @Override + public void printAccount(AccountDTO accountDTO) { + OUTPUT_STREAM.println(accountDTO.getLogin()); + OUTPUT_STREAM.println(accountDTO.getEmail()); + OUTPUT_STREAM.println(accountDTO.getLanguage()); + OUTPUT_STREAM.println(accountDTO.getTimeZone()); + } + + @Override + public void printDocumentRevision(DocumentRevisionDTO documentRevisionDTO, long lastModified) { + printRevisionStatus(1,documentRevisionDTO); + } + + @Override + public void printDocumentRevisions(List<DocumentRevisionDTO> documentRevisions) { + for(DocumentRevisionDTO dr: documentRevisions) { + printDocumentRevision(dr, 1); + } + } + + @Override + public void printFolders(List<FolderDTO> folders) { + for (FolderDTO folder : folders) { + OUTPUT_STREAM.println(folder.getName()); + } + } + + @Override + public FilterInputStream getMonitor(long maximum, InputStream in) { + return new ConsoleProgressMonitorInputStream(maximum,in); + } + + private String fillWithEmptySpace(String txt, int totalChar){ + StringBuilder b = new StringBuilder(txt); + for(int i = 0; i < totalChar-txt.length();i++) { + b.insert(0, ' '); + } + + return b.toString(); + } + + private void printRevisionStatus(int revColSize, PartRevisionDTO pr){ + + String revision = fillWithEmptySpace(pr.getVersion(),revColSize); + DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT, Locale.US); + + OUTPUT_STREAM.println("# "+pr.getNumber()); + + UserDTO checkOutUser = pr.getCheckOutUser(); + String checkout = ""; + if(checkOutUser != null){ + checkout = " " + + LangHelper.getLocalizedMessage("CheckedOutBy",locale) + + " " + + pr.getCheckOutUser() + + " " + + LangHelper.getLocalizedMessage("On",locale) + + " " + + df.format(pr.getCheckOutDate()); + } + OUTPUT_STREAM.println(LangHelper.getLocalizedMessage("Revision",locale) + " " + revision + checkout); + int lastIteration = pr.getPartIterations().size() - 1; + int iteColSize = (lastIteration +"").length(); + int dateColSize=0; + int authorColSize=0; + + for(PartIterationDTO pi:pr.getPartIterations()){ + dateColSize = Math.max(dateColSize, df.format(pi.getCreationDate()).length()); + authorColSize = Math.max(authorColSize, pi.getAuthor().toString().length()); + } + for(PartIterationDTO pi:pr.getPartIterations()){ + printIterationStatus(pi, iteColSize, dateColSize +1, authorColSize+1); + } + OUTPUT_STREAM.println(""); + } + + private void printIterationStatus(PartIterationDTO pi, int iteColSize, int dateColSize, int authorColSize){ + String iteration = fillWithEmptySpace(pi.getIteration()+"", iteColSize+1); + DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT, Locale.US); + String date = fillWithEmptySpace(df.format(pi.getCreationDate()), dateColSize); + String author = fillWithEmptySpace(pi.getAuthor()+"", authorColSize); + String note = pi.getIterationNote()==null?"":pi.getIterationNote(); + OUTPUT_STREAM.println(iteration + " |" + date + " |" + author + " | " + note); + } + + + private void printRevisionStatus(int revColSize, DocumentRevisionDTO dr){ + + String revision = fillWithEmptySpace(dr.getVersion(),revColSize); + DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT, Locale.US); + + OUTPUT_STREAM.println("# " + dr.getDocumentMasterId()); + + UserDTO checkOutUser = dr.getCheckOutUser(); + String checkout = ""; + if(checkOutUser != null){ + checkout = " " + + LangHelper.getLocalizedMessage("CheckedOutBy",locale) + + " " + + dr.getCheckOutUser() + + " " + + LangHelper.getLocalizedMessage("On",locale) + + " " + + df.format(dr.getCheckOutDate()); + } + OUTPUT_STREAM.println(LangHelper.getLocalizedMessage("Revision",locale) + " " + revision + checkout); + int lastIteration = dr.getDocumentIterations().size() - 1; + int iteColSize = (lastIteration +"").length(); + int dateColSize=0; + int authorColSize=0; + + for(DocumentIterationDTO di:dr.getDocumentIterations()){ + dateColSize = Math.max(dateColSize, df.format(di.getCreationDate()).length()); + authorColSize = Math.max(authorColSize, di.getAuthor().toString().length()); + } + for(DocumentIterationDTO di:dr.getDocumentIterations()){ + printIterationStatus(di, iteColSize, dateColSize +1, authorColSize+1); + } + OUTPUT_STREAM.println(""); + } + + private void printIterationStatus(DocumentIterationDTO di, int iteColSize, int dateColSize, int authorColSize){ + String iteration = fillWithEmptySpace(di.getIteration()+"", iteColSize+1); + DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT, Locale.US); + String date = fillWithEmptySpace(df.format(di.getCreationDate()), dateColSize); + String author = fillWithEmptySpace(di.getAuthor()+"", authorColSize); + String note = di.getRevisionNote()==null?"" : di.getRevisionNote(); + OUTPUT_STREAM.println(iteration + " |" + date + " |" + author + " | " + note); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/helpers/JSONOutput.java b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/JSONOutput.java new file mode 100644 index 0000000000..b89be54b43 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/JSONOutput.java @@ -0,0 +1,292 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.helpers; + +import com.docdoku.api.models.*; +import com.docdoku.api.models.utils.LastIterationHelper; +import org.kohsuke.args4j.CmdLineParser; + +import javax.json.*; +import java.io.*; +import java.util.Date; +import java.util.List; +import java.util.logging.Logger; + +public class JSONOutput extends CliOutput { + + private static final Logger LOGGER = Logger.getLogger(JSONOutput.class.getName()); + private final static PrintStream ERROR_STREAM = System.err; + private final static PrintStream OUTPUT_STREAM = System.out; + + public JSONOutput() { + } + + @Override + public void printException(Exception e) { + JsonObject jsonObj = Json.createObjectBuilder() + .add("error", e.getMessage()) + .build(); + ERROR_STREAM.println(jsonObj.toString()); + } + + @Override + public void printCommandUsage(CommandLine cl) throws IOException { + CmdLineParser parser = new CmdLineParser(cl); + ByteArrayOutputStream o = new ByteArrayOutputStream(); + parser.printUsage(o); + JsonObject jsonObj = Json.createObjectBuilder() + .add("description", cl.getDescription()) + .add("usage", o.toString()) + .build(); + OUTPUT_STREAM.println(jsonObj.toString()); + } + + @Override + public void printUsage() { + ERROR_STREAM.println("{\"usage\":\"Not available for json output\"}"); + } + + @Override + public void printInfo(String s) { + JsonObject jsonObj = Json.createObjectBuilder() + .add("info", s) + .build(); + OUTPUT_STREAM.println(jsonObj.toString()); + } + + @Override + public void printWorkspaces(List<WorkspaceDTO> workspaceDTOs) { + JsonArrayBuilder jsonArray = Json.createArrayBuilder(); + for(WorkspaceDTO workspaceDTO:workspaceDTOs){ + jsonArray.add(workspaceDTO.getId()); + } + OUTPUT_STREAM.println(jsonArray.build().toString()); + } + + @Override + public void printPartRevisionsCount(int partMastersCount) { + JsonObject jsonObj = Json.createObjectBuilder() + .add("count", partMastersCount) + .build(); + OUTPUT_STREAM.println(jsonObj.toString()); + } + + @Override + public void printPartRevisions(List<PartRevisionDTO> partRevisions) { + JsonArrayBuilder jsonArrayBuilder = Json.createArrayBuilder(); + for (PartRevisionDTO partRevision : partRevisions) { + jsonArrayBuilder.add(getPartRevision(partRevision, 0L)); + } + OUTPUT_STREAM.println(jsonArrayBuilder.build().toString()); + } + + @Override + public void printBaselines(List<ProductBaselineDTO> productBaselines) { + JsonArrayBuilder jsonArray = Json.createArrayBuilder(); + for (ProductBaselineDTO productBaseline : productBaselines) { + JsonObject jsonBaseline = Json.createObjectBuilder() + .add("id", productBaseline.hashCode()) + .add("name", productBaseline.getName()) + .add("configurationItem", productBaseline.getConfigurationItemId()) + .build(); + jsonArray.add(jsonBaseline); + } + OUTPUT_STREAM.println(jsonArray.build().toString()); + } + + @Override + public void printPartRevision(PartRevisionDTO pr, long lastModified) { + OUTPUT_STREAM.println(getPartRevision(pr, lastModified)); + } + + @Override + public void printPartMaster(PartMaster pm, long lastModified) { + // TODO : rewrite with DTO + /* + JsonArrayBuilder jsonRevisions = Json.createArrayBuilder(); + for (PartRevision pr : pm.getPartRevisions()) + jsonRevisions.add(getPartRevision(pr, lastModified)); + + JsonObject jsonMaster = Json.createObjectBuilder() + .add("revisions", jsonRevisions.build()).build(); + + //Add jsonMaster ? + OUTPUT_STREAM.println(getPartRevision(pm.getLastRevision(), lastModified)); + */ + } + + @Override + public void printConversion(ConversionDTO conversion) { + JsonObject jsonObj = Json.createObjectBuilder() + .add("pending", conversion.getPending()) + .add("succeed", conversion.getSucceed()) + .add("startDate", conversion.getStartDate().toString()) + .add("endDate", conversion.getEndDate().toString()) + .build(); + OUTPUT_STREAM.println(jsonObj.toString()); + } + + @Override + public void printAccount(AccountDTO accountDTO) { + JsonObject jsonObj = Json.createObjectBuilder() + .add("login", accountDTO.getLogin()) + .add("language", accountDTO.getLanguage()) + .add("email", accountDTO.getEmail()) + .add("timezone", accountDTO.getTimeZone()) + .build(); + + OUTPUT_STREAM.println(jsonObj.toString()); + } + + @Override + public void printDocumentRevision(DocumentRevisionDTO documentRevisionDTO, long lastModified) { + OUTPUT_STREAM.println(getDocumentRevision(documentRevisionDTO, lastModified)); + } + + @Override + public void printDocumentRevisions(List<DocumentRevisionDTO> documentRevisions) { + JsonArrayBuilder jsonArray = Json.createArrayBuilder(); + for (DocumentRevisionDTO documentRevision : documentRevisions) { + jsonArray.add(getDocumentRevision(documentRevision, 0L)); + } + OUTPUT_STREAM.println(jsonArray.build().toString()); + } + + @Override + public void printFolders(List<FolderDTO> folders) { + JsonArrayBuilder jsonArray = Json.createArrayBuilder(); + for (FolderDTO folder : folders) { + jsonArray.add(folder.getName()); + } + OUTPUT_STREAM.println(jsonArray.build().toString()); + } + + @Override + public FilterInputStream getMonitor(long maximum, InputStream in) { + return new JSONProgressMonitorInputStream(maximum, in); + } + + private JsonObject getPartRevision(PartRevisionDTO pr, long lastModified) { + + JsonObjectBuilder jsonStatusBuilder = Json.createObjectBuilder(); + + if (pr != null) { + UserDTO user = pr.getCheckOutUser(); + String login = user != null ? user.getLogin() : ""; + Date checkoutDate = pr.getCheckOutDate(); + Long timeStamp = checkoutDate != null ? checkoutDate.getTime() : null; + jsonStatusBuilder.add("isReleased", pr.getStatus().equals(PartRevisionDTO.StatusEnum.RELEASED)); + jsonStatusBuilder.add("isCheckedOut", user != null); + jsonStatusBuilder.add("partNumber", pr.getNumber()); + jsonStatusBuilder.add("checkoutUser", login); + if(timeStamp != null) { + jsonStatusBuilder.add("checkoutDate", timeStamp); + }else{ + jsonStatusBuilder.add("checkoutDate", JsonValue.NULL); + } + jsonStatusBuilder.add("workspace", pr.getWorkspaceId()); + jsonStatusBuilder.add("version", pr.getVersion()); + + if(pr.getDescription() != null) { + jsonStatusBuilder.add("description", pr.getDescription()); + }else{ + jsonStatusBuilder.add("description", JsonValue.NULL); + } + + jsonStatusBuilder.add("lastModified", lastModified); + + PartIterationDTO lastIteration = LastIterationHelper.getLastIteration(pr); + + if (lastIteration != null && lastIteration.getNativeCADFile() != null) { + String nativeCADFileName = lastIteration.getNativeCADFile(); + jsonStatusBuilder.add("cadFileName", nativeCADFileName); + } + + List<PartIterationDTO> partIterations = pr.getPartIterations(); + + if (partIterations != null) { + JsonArrayBuilder jsonIterationsBuilder = Json.createArrayBuilder(); + for (PartIterationDTO partIteration : partIterations) { + jsonIterationsBuilder.add(partIteration.getIteration()); + } + jsonStatusBuilder.add("iterations", jsonIterationsBuilder.build()); + } + + } + + return jsonStatusBuilder.build(); + } + + private JsonObject getDocumentRevision(DocumentRevisionDTO documentRevisionDTO, long lastModified) { + + JsonObjectBuilder jsonStatusBuilder = Json.createObjectBuilder(); + + if (documentRevisionDTO != null) { + UserDTO userDTO = documentRevisionDTO.getCheckOutUser(); + String login = userDTO != null ? userDTO.getLogin() : ""; + Date checkoutDate = documentRevisionDTO.getCheckOutDate(); + Long timeStamp = checkoutDate != null ? checkoutDate.getTime() : null; + jsonStatusBuilder.add("isCheckedOut", checkoutDate != null ); + jsonStatusBuilder.add("id", documentRevisionDTO.getDocumentMasterId()); + jsonStatusBuilder.add("checkoutUser", login); + if(timeStamp != null) { + jsonStatusBuilder.add("checkoutDate", timeStamp); + }else{ + jsonStatusBuilder.add("checkoutDate", JsonValue.NULL); + } + jsonStatusBuilder.add("workspace", documentRevisionDTO.getWorkspaceId()); + jsonStatusBuilder.add("version", documentRevisionDTO.getVersion()); + + if(documentRevisionDTO.getDescription() != null) { + jsonStatusBuilder.add("description", documentRevisionDTO.getDescription()); + }else{ + jsonStatusBuilder.add("description", JsonValue.NULL); + } + + jsonStatusBuilder.add("lastModified", lastModified); + + DocumentIterationDTO lastIterationDTO = getDocumentRevisionDTOLastIteration(documentRevisionDTO); + + if (lastIterationDTO != null && lastIterationDTO.getAttachedFiles() != null) { + JsonArrayBuilder jsonFilesBuilder = Json.createArrayBuilder(); + for(String fileName:lastIterationDTO.getAttachedFiles()) + jsonFilesBuilder.add(fileName); + + jsonStatusBuilder.add("files", jsonFilesBuilder.build()); + } + + List<DocumentIterationDTO> documentIterations = documentRevisionDTO.getDocumentIterations(); + if (documentIterations != null) { + JsonArrayBuilder jsonIterationsBuilder = Json.createArrayBuilder(); + for (DocumentIterationDTO documentIteration : documentIterations) { + jsonIterationsBuilder.add(documentIteration.getIteration()); + } + jsonStatusBuilder.add("iterations", jsonIterationsBuilder.build()); + } + } + return jsonStatusBuilder.build(); + } + + private DocumentIterationDTO getDocumentRevisionDTOLastIteration(DocumentRevisionDTO documentRevisionDTO) { + int iterations = documentRevisionDTO.getDocumentIterations().size(); + return documentRevisionDTO.getDocumentIterations().get(iterations-1); + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/helpers/JSONProgressMonitorInputStream.java b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/JSONProgressMonitorInputStream.java new file mode 100644 index 0000000000..1118503edb --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/JSONProgressMonitorInputStream.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.helpers; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; + +public class JSONProgressMonitorInputStream extends FilterInputStream { + + private long maximum; + private long totalRead; + private int oldPercentage=-1; + private PrintStream outputStream = System.out; + + public JSONProgressMonitorInputStream(long maximum, InputStream in){ + super(in); + this.maximum=maximum; + } + + @Override + public int read(byte[] b) throws IOException { + int length = super.read(b, 0, b.length); + totalRead += length; + int percentage = (int)((totalRead * 100.0f) / maximum); + + if(percentage > oldPercentage) { + outputStream.println("{\"progress\":" + percentage + "}"); + } + + oldPercentage = percentage ; + return length; + } + + +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/helpers/LangHelper.java b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/LangHelper.java new file mode 100644 index 0000000000..e4e7f9582e --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/LangHelper.java @@ -0,0 +1,42 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.helpers; + +import java.io.IOException; +import java.util.Locale; +import java.util.ResourceBundle; + +public class LangHelper { + + private static final String BUNDLE_NAME = "com.docdoku.cli.i18n.LocalStrings"; + + private LangHelper() {} + + public static String getLocalizedMessage(String key, String userLogin) throws IOException { + return getLocalizedMessage(key, new AccountsManager().getUserLocale(userLogin)); + } + + public static String getLocalizedMessage(String key, Locale locale){ + ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_NAME, locale); + return bundle.getString(key); + } + +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/helpers/MetaDirectoryManager.java b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/MetaDirectoryManager.java new file mode 100644 index 0000000000..b05110fc05 --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/helpers/MetaDirectoryManager.java @@ -0,0 +1,147 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.helpers; + +import java.io.*; +import java.util.Properties; + +public class MetaDirectoryManager { + + private File metaDirectory; + private Properties indexProps; + + + private static final String META_DIRECTORY_NAME = ".dplm"; + private static final String INDEX_FILE_NAME = "index.xml"; + + private static final String PART_NUMBER_PROP = "partNumber"; + private static final String REVISION_PROP = "revision"; + private static final String ITERATION_PROP = "iteration"; + private static final String WORKSPACE_PROP = "workspace"; + private static final String ID_PROP = "id"; + private static final String LAST_MODIFIED_DATE_PROP = "lastModifiedDate"; + private static final String DIGEST_PROP = "digest"; + + public MetaDirectoryManager(File workingDirectory) throws IOException { + this.metaDirectory=new File(workingDirectory,META_DIRECTORY_NAME); + if(!metaDirectory.exists()) { + metaDirectory.mkdir(); + } + + File indexFile = new File(metaDirectory,INDEX_FILE_NAME); + indexProps = new Properties(); + if(indexFile.exists()){ + try{ + indexProps.loadFromXML(new BufferedInputStream(new FileInputStream(indexFile))); + }catch(IOException ex){ + indexFile.delete(); + } + } + } + + private void saveIndex() throws IOException { + File indexFile = new File(metaDirectory,INDEX_FILE_NAME); + if(!indexFile.exists()) { + indexFile.createNewFile(); + } + + OutputStream out = new BufferedOutputStream(new FileOutputStream(indexFile)); + indexProps.storeToXML(out, null); + } + + public void setPartNumber(String filePath, String partNumber) throws IOException { + indexProps.setProperty(filePath + "." + PART_NUMBER_PROP, partNumber); + saveIndex(); + } + + public void setDocumentId(String filePath, String id) throws IOException { + indexProps.setProperty(filePath + "." + ID_PROP, id); + saveIndex(); + } + + public void setRevision(String filePath, String revision) throws IOException { + indexProps.setProperty(filePath + "." + REVISION_PROP, revision); + saveIndex(); + } + + public void setIteration(String filePath, int iteration) throws IOException { + indexProps.setProperty(filePath + "." + ITERATION_PROP, iteration+""); + saveIndex(); + } + + public void setLastModifiedDate(String filePath, long lastModifiedDate) throws IOException { + indexProps.setProperty(filePath + "." + LAST_MODIFIED_DATE_PROP, lastModifiedDate+""); + saveIndex(); + } + + public void setWorkspace(String filePath, String workspaceId) throws IOException { + indexProps.setProperty(filePath + "." + WORKSPACE_PROP, workspaceId+""); + saveIndex(); + } + + public void setDigest(String filePath, String digest) throws IOException { + indexProps.setProperty(filePath + "." + DIGEST_PROP, digest); + saveIndex(); + } + + public long getLastModifiedDate(String filePath){ + return Long.parseLong(indexProps.getProperty(filePath + "." + LAST_MODIFIED_DATE_PROP,"0")); + } + + public String getPartNumber(String filePath){ + return indexProps.getProperty(filePath + "." + PART_NUMBER_PROP); + } + + public String getWorkspace(String filePath){ + return indexProps.getProperty(filePath + "." + WORKSPACE_PROP); + } + + public String getRevision(String filePath){ + return indexProps.getProperty(filePath + "." + REVISION_PROP); + } + + public int getIteration(String filePath){ + return Integer.parseInt(indexProps.getProperty(filePath + "." + ITERATION_PROP,"0")); + } + + public void deleteEntryInfo(String filePath) throws IOException { + indexProps.remove(filePath + "." + ID_PROP); + indexProps.remove(filePath + "." + PART_NUMBER_PROP); + indexProps.remove(filePath + "." + REVISION_PROP); + indexProps.remove(filePath + "." + ITERATION_PROP); + indexProps.remove(filePath + "." + LAST_MODIFIED_DATE_PROP); + indexProps.remove(filePath + "." + DIGEST_PROP); + indexProps.remove(filePath + "." + WORKSPACE_PROP); + saveIndex(); + } + + public String getDocumentId(String filePath) { + return indexProps.getProperty(filePath + "." + ID_PROP); + } + + public boolean isDocumentRelated(String filePath) { + return getDocumentId(filePath) != null; + } + + public boolean isPartRelated(String filePath) { + return getPartNumber(filePath) != null; + } +} diff --git a/docdoku-cli/src/main/java/com/docdoku/cli/services/DocdokuClientFactory.java b/docdoku-cli/src/main/java/com/docdoku/cli/services/DocdokuClientFactory.java new file mode 100644 index 0000000000..5689a6c9bb --- /dev/null +++ b/docdoku-cli/src/main/java/com/docdoku/cli/services/DocdokuClientFactory.java @@ -0,0 +1,34 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.cli.services; + +import com.docdoku.api.DocdokuPLMBasicClient; +import com.docdoku.api.client.ApiClient; + +public class DocdokuClientFactory { + + public static ApiClient createClient(String path, String user, String password){ + return new DocdokuPLMBasicClient(path, user, password).getClient(); + } +} + + diff --git a/docdoku-cli/src/main/resources/com/docdoku/cli/i18n/LocalStrings.properties b/docdoku-cli/src/main/resources/com/docdoku/cli/i18n/LocalStrings.properties new file mode 100644 index 0000000000..50961b4aa1 --- /dev/null +++ b/docdoku-cli/src/main/resources/com/docdoku/cli/i18n/LocalStrings.properties @@ -0,0 +1,61 @@ +WorkspaceCommandDescription=List all workspaces the user belongs to +HelpCommandDescription=Describe the usage of this program or its subcommands +BaselineListCommandDescription=Retrieve all baselines where part revision has some iterations +PartCheckInCommandDescription=Perform a check in operation in order to validate the working copy of the part and hence make it visible to the whole team +DocumentCheckInCommandDescription=Perform a check in operation in order to validate the working copy of the document and hence make it visible to the whole team +PartCheckOutCommandDescription=Perform a check out operation and thus reserve the part for modification +DocumentCheckOutCommandDescription=Perform a check out operation and thus reserve the document for modification +UndoCheckOutCommandDescription=Cancel the check out operation made previously. All current modifications will be lost +ConversionCommandDescription=Retrieve conversion status for given part iteration +PartGetCommandDescription=Retrieve the cad file of the given part as well as its sub-components if the command is performed recursively +DocumentGetCommandDescription=Retrieve the files of the given document +PartCreationCommandDescription=Create a part and save the current local copy of the cad file to the server. The part will remain checked out +DocumentCreationCommandDescription=Create a document and save the current local copy of the file to the server. The document will remain checked out +PartListCommandDescription=Retrieve all part revisions in the given workspace +PartPutCommandDescription=Save the current local copy of the cad file to the server. The part will remain checked out +DocumentPutCommandDescription=Save the current local copy of the file to the server. The document will remain checked out +PartSearchCommandDescription=Retrieve all part revisions matching the provided search value (expressed as a part number or a name) +DocumentSearchCommandDescription=Retrieve all document revisions matching the provided search value (expressed as an id or a name) +PartStatusCommandDescription=Print the status of the selected part +DocumentStatusCommandDescription=Print the status of the selected document +AccountInfosCommandDescription=Print the account information for given login and password +DocumentListCommandDescription=Print the documents contained in the given folder or checked out by the user +FolderListCommandDescription=Print the sub folders of a given folder +FileStatusCommand=Print the status of a document or a part for given file +Usage=usage: dplm <command> [<args>]\nDocDokuPLM command-line client, version 1.0.\nType 'dplm help <command>' for help on a specific command +AdditionalInfos=For additional information, see http://www.docdokuplm.com +AvailableCommands=Available commands +InstructionCommands=Each command is followed by either 'document' or 'part'\nex: dplm get document -P 443 -u joe -p password -w Demo_workspace -o Wen_Test_01 -r A -S +Count=Count +NoBaseline=No baseline +ConversionSucceed=Conversion succeed +ConversionInProgress=Conversion in progress +ConversionFailed=Conversion failed +ConversionStarted=Conversion started on +ConversionEnded=Conversion ended on +CheckedOutBy=Checked out by +Revision=Revision +On=on +PartNumberOrRevisionNotSpecified1=<workspace> or <partnumber> or <revision> are not specified and the supplied path is not a file +PartNumberOrRevisionNotSpecified2=<workspace> or <partnumber> or <revision> are not specified and cannot be inferred from file +DocumentIdOrRevisionNotSpecified1=<workspace> or <id> or <revision> are not specified and the supplied path is not a file +DocumentIdOrRevisionNotSpecified2=<workspace> or <id> or <revision> are not specified and cannot be inferred from file +FileNotIndexedException=The entity cannot be inferred from file +CheckingInPart=Checking in part +CheckingOutPart=Checking out part +CheckingInDocument=Checking in document +CheckingOutDocument=Checking out document +NoFileForPart=No file for part +NoFilesForDocument=No file for document +PartIterationNotFoundForConfiguration=Cannot find a part iteration in configuration +ConfigSpecNotMatchingRevision=Configuration specification and revision doesn't match +IterationNotExisting=Iteration doesn't exist +UndoCheckoutPart=Undo checking out part +UndoCheckoutDocument=Undo checking out document +DownloadingFile=Downloading file +UploadingFile=Uploading file +From=from +To=to +LoginError=Error trying to login +PromptUser=Please enter your login for +PromptPassword=Please enter your password for \ No newline at end of file diff --git a/CONTRIBUTING.md b/docdoku-cli/src/main/resources/com/docdoku/cli/i18n/LocalStrings_en.properties similarity index 100% rename from CONTRIBUTING.md rename to docdoku-cli/src/main/resources/com/docdoku/cli/i18n/LocalStrings_en.properties diff --git a/docdoku-cli/src/main/resources/com/docdoku/cli/i18n/LocalStrings_fr.properties b/docdoku-cli/src/main/resources/com/docdoku/cli/i18n/LocalStrings_fr.properties new file mode 100644 index 0000000000..3565f7f254 --- /dev/null +++ b/docdoku-cli/src/main/resources/com/docdoku/cli/i18n/LocalStrings_fr.properties @@ -0,0 +1,61 @@ +WorkspaceCommandDescription=Liste tous les espaces de travail dans lequel l'utilisateur est actif +HelpCommandDescription=D\u00e9crit l'utilisation de ce programme et de ses sous-commandes +BaselineListCommandDescription=R\u00e9cup\u00e8re toutes les baselines où l'article a des it\u00e9rations +PartCheckInCommandDescription=Effectue la lib\u00e9ration de l'objet afin de valider la copie de travail de l'article et donc de le rendre visible \u00e0 toute l'\u00e9quipe +DocumentCheckInCommandDescription=Effectue la lib\u00e9ration de l'objet afin de valider la copie de travail du document et donc de le rendre visible \u00e0 toute l'\u00e9quipe +PartCheckOutCommandDescription=Effectue la r\u00e9servation de l'objet, et ainsi vous permet d'y apporter des modifications +DocumentCheckOutCommandDescription=Effectue la r\u00e9servation de l'objet, et ainsi vous permet d'y apporter des modifications +UndoCheckOutCommandDescription=Annule la r\u00e9servation de l'objet. Toutes les modifications courantes seront perdues +ConversionCommandDescription=R\u00e9cup\u00e8re le statut de la conversion pour l'article donn\u00e9 +PartGetCommandDescription=R\u00e9cup\u00e8re le fichier de CAO de l'article donn\u00e9e ainsi que ses sous-composants si la commande est effectu\u00e9e de mani\u00e8re r\u00e9cursive +DocumentGetCommandDescription=R\u00e9cup\u00e8re le fichier du document donn\u00e9e +PartCreationCommandDescription=Cr\u00e9e un article sur le serveur +DocumentCreationCommandDescription=Cr\u00e9e un document sur le serveur +PartListCommandDescription=R\u00e9cup\u00e8re le statut de la conversion pour l'article donn\u00e9 +PartPutCommandDescription=Enregistre la copie locale actuelle du fichier de CAO sur le serveur. L'article restera r\u00e9serv\u00e9 +DocumentPutCommandDescription=Enregistre la copie locale actuelle du fichier sur le serveur. Le document restera r\u00e9serv\u00e9 +PartSearchCommandDescription=R\u00e9cup\u00e8re toutes les r\u00e9visions des articles correspondant \u00e0 la valeur de recherche fournie (exprim\u00e9e en r\u00e9f\u00e9rence ou nom d'article) +DocumentSearchCommandDescription=R\u00e9cup\u00e8re toutes les r\u00e9visions des documents correspondant \u00e0 la valeur de recherche fournie (exprim\u00e9e en r\u00e9f\u00e9rence ou nom de document) +PartStatusCommandDescription=Affiche l'\u00e9tat de l'article s\u00e9lectionn\u00e9 +DocumentStatusCommandDescription=Affiche le statut du document +AccountInfosCommandDescription=Affiche les informations du compte pour l'identifiant et le mot de passe donn\u00e9s +DocumentListCommandDescription=Affiche une liste de documents, par dossier ou r\u00e9serv\u00e9s +FolderListCommandDescription=Affiche les sous-dossiers d'un dossier donn\u00e9 +FileStatusCommand=Affiche le statut de l'article ou du document pour le fichier donn\u00e9 +Usage=Utilisation: dplm <commande> [<arguments>]\nDocDokuPLM command-line client, version 1.0.\nTaper 'dplm help <commande>' pour de l'aide sp\u00e9cifique \u00e0 la commande donn\u00e9e +AdditionalInfos=Pour plus d'informations, visitez http://www.docdokuplm.com +AvailableCommands=Commandes disponibles +InstructionCommands=Chaque commande est suivie par 'document' ou 'part'\nex: dplm get document -P 443 -u joe -p password -w Demo_workspace -o Wen_Test_01 -r A -S +Count=D\u00e9compte +NoBaseline=Aucune baseline +ConversionSucceed=Conversion r\u00e9ussie +ConversionInProgress=Conversion en cours +ConversionFailed=Conversion \u00e9chou\u00e9e +ConversionStarted=Conversion d\u00e9marr\u00e9e le +ConversionEnded=Conversion termin\u00e9e le +CheckedOutBy=R\u00e9serv\u00e9 par +Revision=R\u00e9vision +On=le +PartNumberOrRevisionNotSpecified1=<workspace> ou <partnumber> ou <revision> ne sont pas sp\u00e9cifi\u00e9s et le chemin fourni n'est pas un fichier valide +PartNumberOrRevisionNotSpecified2=<workspace> ou <partnumber> ou <revision> ne sont pas sp\u00e9cifi\u00e9s et ne peuvent \u00eatre inf\u00e9r\u00e9s depuis le nom du fichier +DocumentIdOrRevisionNotSpecified1=<workspace> ou <id> ou <revision> ne sont pas sp\u00e9cifi\u00e9s et le chemin fourni n'est pas un fichier valide +DocumentIdOrRevisionNotSpecified2=<workspace> ou <id> ou <revision> ne sont pas sp\u00e9cifi\u00e9s et ne peuvent \u00eatre inf\u00e9r\u00e9s depuis le nom du fichier +FileNotIndexedException=L'entit\u00e9 ne peut \u00eatre inf\u00e9r\u00e9e depuis le fichier donn\u00e9 +CheckingInPart=Lib\u00e9ration de l'article +CheckingOutPart=R\u00e9servation de l'article +CheckingInDocument=Lib\u00e9ration du document +CheckingOutDocument=R\u00e9servation de du document +NoFileForPart=Pas de fichier pour l'article +NoFilesForDocument=Pas de fichiers pour le document +PartIterationNotFoundForConfiguration=Impossible de trouver un article correspondant \u00e0 cette configuration +ConfigSpecNotMatchingRevision=La configuration et r\u00e9vision de l'article demand\u00e9s ne peuvent \u00eatre r\u00e9solus +IterationNotExisting=L'it\u00e9ration n'existe pas +UndoCheckoutPart=Annulation de la r\u00e9servation de l'article +UndoCheckoutDocument=Annulation de la r\u00e9servation du document +DownloadingFile=T\u00e9l\u00e9chargement du fichier +UploadingFile=T\u00e9l\u00e9versement du fichier +From=depuis +To=vers +LoginError=Erreur lors de l'authentification +PromptUser=Entrez votre identifiant pour +PromptPassword=Entrez votre mot de passe pour \ No newline at end of file diff --git a/docdoku-cli/src/test/java/com/docdoku/cli/commands/common/AllCommandTest.java b/docdoku-cli/src/test/java/com/docdoku/cli/commands/common/AllCommandTest.java new file mode 100644 index 0000000000..c445d6770a --- /dev/null +++ b/docdoku-cli/src/test/java/com/docdoku/cli/commands/common/AllCommandTest.java @@ -0,0 +1,773 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.common; + +import com.docdoku.cli.MainCommand; +import com.docdoku.cli.helpers.FileHelper; +import com.docdoku.cli.helpers.LangHelper; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import javax.json.*; +import java.io.*; +import java.net.URL; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Locale; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Stream; + +@RunWith(JUnit4.class) +public class AllCommandTest { + + String[] AUTH_ARGS = {"-u", TestConfig.LOGIN , "-p", TestConfig.PASSWORD, "-h" , TestConfig.HOST, "-P", TestConfig.PORT, "-F", "json"}; + + private final static Logger LOGGER = Logger.getLogger(AllCommandTest.class.getName()); + private final static URL documentFile = AllCommandTest.class.getClassLoader().getResource("com/docdoku/cli/commands/common/upload-document.txt"); + private final static URL partFile = AllCommandTest.class.getClassLoader().getResource("com/docdoku/cli/commands/common/upload-part.txt"); + private final static URL putDocumentFile = AllCommandTest.class.getClassLoader().getResource("com/docdoku/cli/commands/common/put-document.txt"); + private final static URL putPartFile = AllCommandTest.class.getClassLoader().getResource("com/docdoku/cli/commands/common/put-part.txt"); + private final static String NO_ERRORS_EXPECTED = "Should be no errors"; + private final static String OUTPUT_EXPECTED = "Should have an output"; + + private final static ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final static ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + + static { + System.setOut(new PrintStream(outContent)); + System.setErr(new PrintStream(errContent)); + } + + @After + public void resetStreams(){ + outContent.reset(); + errContent.reset(); + } + + @Test + public void workspaceListTest() throws Exception { + + String[] command = {"wl"}; + String[] args = {}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(NO_ERRORS_EXPECTED, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programOutput)); + JsonArray workspaces = reader.readArray(); + reader.close(); + + Assert.assertNotNull(workspaces); + Assert.assertTrue(workspaces.size()>0); + + } + + @Test + public void accountInfoTest() throws Exception { + + String[] command = {"a"}; + String[] args = {}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(NO_ERRORS_EXPECTED, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programOutput)); + JsonObject account = reader.readObject(); + reader.close(); + + Assert.assertNotNull(account); + Assert.assertEquals(account.getString("login"), TestConfig.LOGIN ); + + } + + @Test + public void documentCreationTest() throws Exception { + + String filePath = documentFile.getPath(); + String documentId = "DOC-"+UUID.randomUUID().toString().substring(0,6); + String documentTitle = "DocTitle"; + String documentDescription = "DocDescription"; + + String[] command = {"cr", "document"}; + String[] args = {"-w" , TestConfig.WORKSPACE , "-o", documentId, "-N", documentTitle, "-d", documentDescription, filePath}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(NO_ERRORS_EXPECTED, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + String[] splitOutput = programOutput.split("\n"); + int linesCount = splitOutput.length; + Assert.assertTrue("Should have at least two lines", linesCount >= 2); + + String firstLine = splitOutput[0]; + JsonReader firstLineReader = Json.createReader(new StringReader(firstLine)); + JsonObject infoLine = firstLineReader.readObject(); + firstLineReader.close(); + + String uploadingFile = LangHelper.getLocalizedMessage("UploadingFile", new Locale("en")); + Assert.assertTrue(infoLine.getString("info").startsWith(uploadingFile)); + + String lastLine = splitOutput[linesCount-1]; + JsonReader lastLineReader = Json.createReader(new StringReader(lastLine)); + JsonObject progressLine = lastLineReader.readObject(); + lastLineReader.close(); + Assert.assertEquals(progressLine.getInt("progress"), 100); + + } + + @Test + public void partCreationTest(){ + + String filePath = partFile.getPath(); + String partNumber = "PART-"+UUID.randomUUID().toString().substring(0,6); + String partTitle = "PartTitle"; + String partDescription = "PartDescription"; + + String[] command = {"cr", "part"}; + String[] args = {"-w" , TestConfig.WORKSPACE , "-o", partNumber, "-N", partTitle, "-d", partDescription, filePath}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(NO_ERRORS_EXPECTED, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + String[] splitOutput = programOutput.split("\n"); + int linesCount = splitOutput.length; + Assert.assertTrue("Should have at least two lines", linesCount >= 2); + + String firstLine = splitOutput[0]; + JsonReader firstLineReader = Json.createReader(new StringReader(firstLine)); + JsonObject infoLine = firstLineReader.readObject(); + firstLineReader.close(); + + String uploadingFile = LangHelper.getLocalizedMessage("UploadingFile", new Locale("en")); + Assert.assertTrue(infoLine.getString("info").startsWith(uploadingFile)); + + String lastLine = splitOutput[linesCount-1]; + JsonReader lastLineReader = Json.createReader(new StringReader(lastLine)); + JsonObject progressLine = lastLineReader.readObject(); + lastLineReader.close(); + Assert.assertEquals(progressLine.getInt("progress"), 100); + } + + @Test + public void documentListTest(){ + + String[] command = {"l", "document"}; + String[] args = {"-w" , TestConfig.WORKSPACE}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(NO_ERRORS_EXPECTED, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programOutput)); + JsonArray documents = reader.readArray(); + reader.close(); + + Assert.assertNotNull(documents); + int documentsCount = documents.size(); + + // Test first object only + if(documentsCount > 0){ + LOGGER.log(Level.INFO, "Testing first document in documents array"); + JsonValue jsonValue = documents.get(0); + JsonValue.ValueType valueType = jsonValue.getValueType(); + Assert.assertEquals(valueType, JsonValue.ValueType.OBJECT); + JsonObject document = (JsonObject) jsonValue; + String id = document.getString("id"); + Assert.assertNotNull(id); + Assert.assertFalse(id.isEmpty()); + } + else { + LOGGER.log(Level.WARNING, "No document to test in documents array"); + } + + } + + @Test + public void partListTest(){ + String[] command = {"l", "part"}; + String[] args = {"-w", TestConfig.WORKSPACE}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(NO_ERRORS_EXPECTED, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programOutput)); + JsonArray parts = reader.readArray(); + reader.close(); + + Assert.assertNotNull(parts); + } + + @Test + public void partListCountTest(){ + + String[] command = {"l", "part"}; + String[] args = {"-w", TestConfig.WORKSPACE, "-c"}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(NO_ERRORS_EXPECTED, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programOutput)); + JsonObject partsCount = reader.readObject(); + reader.close(); + Assert.assertNotNull(partsCount); + Assert.assertTrue(partsCount.getInt("count") >= 0 ); + } + + @Test + public void partListTestWithLimit(){ + + createPart(); + + String[] command = {"l", "part"}; + String[] args = {"-w" , TestConfig.WORKSPACE, "-s", "0", "-m", "1" }; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(NO_ERRORS_EXPECTED, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programOutput)); + JsonArray parts = reader.readArray(); + reader.close(); + + Assert.assertNotNull(parts); + Assert.assertTrue(parts.size() == 1); + } + + @Test + public void checkInCheckOutPartTest(){ + + String partNumber = createPart(); + + String[] command = {"ci", "part"}; + String[] args = {"-w" , TestConfig.WORKSPACE, "-n", "-o", partNumber, "-r" , "A", "-m", "Checked in from junit tests"}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programOutput)); + JsonObject checkinInfos = reader.readObject(); + reader.close(); + + String checkingInFile = LangHelper.getLocalizedMessage("CheckingInPart", new Locale("en")); + Assert.assertTrue(checkinInfos.getString("info").startsWith(checkingInFile)); + + outContent.reset(); + errContent.reset(); + + String[] checkoutCommand = {"co", "part"}; + String[] checkoutArgs = {"-w" , TestConfig.WORKSPACE, "-n", "-o", partNumber, "-r" , "A"}; + MainCommand.main(getArgs(checkoutCommand, checkoutArgs)); + + programOutput = outContent.toString(); + programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader checkoutReader = Json.createReader(new StringReader(programOutput)); + JsonObject checkoutInfos = checkoutReader.readObject(); + reader.close(); + + String checkingOutFile = LangHelper.getLocalizedMessage("CheckingOutPart", new Locale("en")); + Assert.assertTrue(checkoutInfos.getString("info").startsWith(checkingOutFile)); + + outContent.reset(); + errContent.reset(); + + String[] undoCheckoutCommand = {"uco", "part"}; + String[] undoCheckoutArgs = {"-w" , TestConfig.WORKSPACE, "-o", partNumber, "-r" , "A"}; + MainCommand.main(getArgs(undoCheckoutCommand, undoCheckoutArgs)); + + programOutput = outContent.toString(); + programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader undoCheckoutReader = Json.createReader(new StringReader(programOutput)); + JsonObject undoCheckoutInfos = undoCheckoutReader.readObject(); + reader.close(); + + String undoCheckingOutFile = LangHelper.getLocalizedMessage("UndoCheckoutPart", new Locale("en")); + Assert.assertTrue(undoCheckoutInfos.getString("info").startsWith(undoCheckingOutFile)); + + } + + + @Test + public void checkInCheckOutDocumentTest(){ + + String documentId = createDocument(); + + String[] command = {"ci", "document"}; + String[] args = {"-w" , TestConfig.WORKSPACE, "-n", "-o", documentId, "-r" , "A", "-m", "Checked in from junit tests"}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programOutput)); + JsonObject checkinInfos = reader.readObject(); + reader.close(); + + String checkingInFile = LangHelper.getLocalizedMessage("CheckingInDocument", new Locale("en")); + Assert.assertTrue(checkinInfos.getString("info").startsWith(checkingInFile)); + + outContent.reset(); + errContent.reset(); + + String[] checkoutCommand = {"co", "document"}; + String[] checkoutArgs = {"-w" , TestConfig.WORKSPACE, "-n", "-o", documentId, "-r" , "A"}; + MainCommand.main(getArgs(checkoutCommand, checkoutArgs)); + + programOutput = outContent.toString(); + programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader checkoutReader = Json.createReader(new StringReader(programOutput)); + JsonObject checkoutInfos = checkoutReader.readObject(); + reader.close(); + + String checkingOutFile = LangHelper.getLocalizedMessage("CheckingOutDocument", new Locale("en")); + Assert.assertTrue(checkoutInfos.getString("info").startsWith(checkingOutFile)); + + outContent.reset(); + errContent.reset(); + + String[] undoCheckoutCommand = {"uco", "document"}; + String[] undoCheckoutArgs = {"-w" , TestConfig.WORKSPACE, "-o", documentId, "-r" , "A"}; + MainCommand.main(getArgs(undoCheckoutCommand, undoCheckoutArgs)); + + programOutput = outContent.toString(); + programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader undoCheckoutReader = Json.createReader(new StringReader(programOutput)); + JsonObject undoCheckoutInfos = undoCheckoutReader.readObject(); + reader.close(); + + String undoCheckingOutFile = LangHelper.getLocalizedMessage("UndoCheckoutDocument", new Locale("en")); + Assert.assertTrue(undoCheckoutInfos.getString("info").startsWith(undoCheckingOutFile)); + + } + + @Test + public void documentGetTest() throws IOException { + + String documentId = createDocument(); + + File tmpDir = Files.createTempDirectory("docdoku-cli-").toFile(); + + String[] command = {"get", "document"}; + String[] args = {"-w" , TestConfig.WORKSPACE, "-o", documentId, "-r" , "A", "-i", "1", tmpDir.getPath()}; + MainCommand.main(getArgs(command, args)); + + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + File downloadedFile = new File(tmpDir.getAbsolutePath() + File.separator + FileHelper.getFileName(documentFile.getPath())); + Assert.assertTrue(downloadedFile.exists()); + Assert.assertTrue("File content should be the same",FileUtils.contentEquals(downloadedFile, new File(documentFile.getPath()))); + + tmpDir.deleteOnExit(); + + } + + @Test + public void partGetTest() throws IOException { + + String partNumber = createPart(); + + File tmpDir = Files.createTempDirectory("docdoku-cli-").toFile(); + + String[] command = {"get", "part"}; + String[] args = {"-w" , TestConfig.WORKSPACE, "-o", partNumber, "-r" , "A", "-i", "1", tmpDir.getPath()}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + File downloadedFile = new File(tmpDir.getAbsolutePath() + File.separator + FileHelper.getFileName(partFile.getPath())); + Assert.assertTrue(downloadedFile.exists()); + Assert.assertTrue("File content should be the same",FileUtils.contentEquals(downloadedFile, new File(partFile.getPath()))); + + tmpDir.deleteOnExit(); + + } + + + @Test + public void documentPutTest() throws IOException { + + String documentId = createDocument(); + + File tmpDir = Files.createTempDirectory("docdoku-cli-").toFile(); + + String[] command = {"put", "document"}; + String[] args = {"-w" , TestConfig.WORKSPACE, "-o", documentId, "-r" , "A", putDocumentFile.getPath()}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + String[] splitOutput = programOutput.split("\n"); + int linesCount = splitOutput.length; + + Assert.assertTrue(linesCount >= 2); + + String firstLine = splitOutput[0]; + JsonReader firstLineReader = Json.createReader(new StringReader(firstLine)); + JsonObject infoLine = firstLineReader.readObject(); + firstLineReader.close(); + + String uploadingFile = LangHelper.getLocalizedMessage("UploadingFile", new Locale("en")); + Assert.assertTrue(infoLine.getString("info").startsWith(uploadingFile)); + + String lastLine = splitOutput[linesCount-1]; + JsonReader lastLineReader = Json.createReader(new StringReader(lastLine)); + JsonObject progressLine = lastLineReader.readObject(); + lastLineReader.close(); + Assert.assertEquals(progressLine.getInt("progress"), 100); + + tmpDir.deleteOnExit(); + + } + + @Test + public void partPutTest() throws IOException { + + String partNumber = createPart(); + + File tmpDir = Files.createTempDirectory("docdoku-cli-").toFile(); + + String[] command = {"put", "part"}; + String[] args = {"-w" , TestConfig.WORKSPACE, "-o", partNumber, "-r" , "A", putPartFile.getPath()}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + String[] splitOutput = programOutput.split("\n"); + int linesCount = splitOutput.length; + + Assert.assertTrue(linesCount >= 2); + + String firstLine = splitOutput[0]; + JsonReader firstLineReader = Json.createReader(new StringReader(firstLine)); + JsonObject infoLine = firstLineReader.readObject(); + firstLineReader.close(); + + String uploadingFile = LangHelper.getLocalizedMessage("UploadingFile", new Locale("en")); + Assert.assertTrue(infoLine.getString("info").startsWith(uploadingFile)); + + String lastLine = splitOutput[linesCount-1]; + JsonReader lastLineReader = Json.createReader(new StringReader(lastLine)); + JsonObject progressLine = lastLineReader.readObject(); + lastLineReader.close(); + Assert.assertEquals(progressLine.getInt("progress"), 100); + + tmpDir.deleteOnExit(); + + } + + @Test + public void documentStatusTest(){ + + String documentId = createDocument(); + + String[] command = {"st", "document"}; + String[] args = {"-w" , TestConfig.WORKSPACE, "-o", documentId, "-r" , "A"}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programOutput)); + JsonObject document = reader.readObject(); + reader.close(); + + Assert.assertEquals(document.getString("id"),documentId); + Assert.assertTrue(document.getBoolean("isCheckedOut")); + Assert.assertEquals(document.getString("workspace"), TestConfig.WORKSPACE); + Assert.assertEquals(document.getString("version"), "A"); + Assert.assertNotNull(document.getJsonArray("files")); + Assert.assertTrue(document.getJsonArray("files").size() == 1); + + } + + + @Test + public void partStatusTest(){ + + String partNumber = createPart(); + + String[] command = {"st", "part"}; + String[] args = {"-w" , TestConfig.WORKSPACE, "-o", partNumber, "-r" , "A"}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programOutput)); + JsonObject part = reader.readObject(); + reader.close(); + + Assert.assertEquals(part.getString("partNumber"),partNumber); + Assert.assertTrue(part.getBoolean("isCheckedOut")); + Assert.assertTrue(part.getString("cadFileName").endsWith(FileHelper.getFileName(partFile.getPath()))); + Assert.assertEquals(part.getString("workspace"), TestConfig.WORKSPACE); + Assert.assertEquals(part.getString("version"), "A"); + + } + + @Test + public void folderListTest(){ + + String[] command = {"f"}; + String[] args = {"-w" , TestConfig.WORKSPACE}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programOutput)); + JsonArray folders = reader.readArray(); + reader.close(); + + Assert.assertNotNull(folders); + + if(!folders.isEmpty()){ + testSubFolder("", folders); + } + + } + + @Test + public void conversionTest() throws InterruptedException { + + String partNumber = createPart(); + + String[] command = {"cv"}; + String[] args = {"-w" , TestConfig.WORKSPACE, "-o", partNumber, "-r", "A", "-i", "1"}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programOutput)); + JsonObject conversion = reader.readObject(); + reader.close(); + + Assert.assertNotNull(conversion); + Assert.assertNotNull(conversion.getBoolean("pending")); + Assert.assertNotNull(conversion.getBoolean("succeed")); + Assert.assertNotNull(conversion.getString("startDate")); + Assert.assertNotNull(conversion.getString("endDate")); + + } + + + @Test + public void baselineListTest(){ + + String partNumber = createPart(); + + String[] command = {"bl"}; + String[] args = {"-w" , TestConfig.WORKSPACE, "-o", partNumber, "-r", "A"}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programOutput)); + JsonArray baselines = reader.readArray(); + reader.close(); + + Assert.assertTrue(baselines.isEmpty()); + + } + + + @Test + public void helpTest(){ + + String[] command = {"h","-F","json"}; + MainCommand.main(command); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertFalse(programError.isEmpty()); + Assert.assertTrue(programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programError)); + JsonObject usage = reader.readObject(); + reader.close(); + + Assert.assertNotNull(usage); + Assert.assertEquals(usage.getString("usage"), "Not available for json output"); + + } + + + + private void testSubFolder(String parent, JsonArray folders){ + + outContent.reset(); + errContent.reset(); + + String subFolder = folders.getString(0); + + String[] command = {"f"}; + String[] args = {"-w" , TestConfig.WORKSPACE, "-f", parent+subFolder}; + MainCommand.main(getArgs(command, args)); + + String programOutput = outContent.toString(); + String programError = errContent.toString(); + + Assert.assertTrue(programError, programError.isEmpty()); + Assert.assertFalse(OUTPUT_EXPECTED, programOutput.isEmpty()); + + JsonReader reader = Json.createReader(new StringReader(programOutput)); + JsonArray subFolders = reader.readArray(); + reader.close(); + + if(!subFolders.isEmpty()){ + testSubFolder(parent+subFolder+":", subFolders); + } + + Assert.assertNotNull(subFolders); + + } + + private String createPart(){ + + String filePath = partFile.getPath(); + String partNumber = "Part-"+UUID.randomUUID().toString().substring(0,6); + + String[] command = {"cr", "part"}; + String[] args = {"-w" , TestConfig.WORKSPACE, "-o", partNumber, filePath}; + MainCommand.main(getArgs(command, args)); + + outContent.reset(); + errContent.reset(); + + return partNumber; + } + + + private String createDocument(){ + + String filePath = documentFile.getPath(); + String documentId = "Doc-"+UUID.randomUUID().toString().substring(0,6); + + String[] command = {"cr", "document"}; + String[] args = {"-w" , TestConfig.WORKSPACE, "-o", documentId, filePath}; + MainCommand.main(getArgs(command, args)); + + outContent.reset(); + errContent.reset(); + + return documentId; + } + + private String[] getArgs(String[] command, String[] args) { + return Stream.concat( + Stream.concat(Arrays.stream(command), Arrays.stream(AUTH_ARGS)), + Arrays.stream(args) + ).toArray(String[]::new); + } + +} diff --git a/docdoku-cli/src/test/java/com/docdoku/cli/commands/common/TestConfig.java b/docdoku-cli/src/test/java/com/docdoku/cli/commands/common/TestConfig.java new file mode 100644 index 0000000000..fbe8adead0 --- /dev/null +++ b/docdoku-cli/src/test/java/com/docdoku/cli/commands/common/TestConfig.java @@ -0,0 +1,38 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.cli.commands.common; + +public class TestConfig { + + public static String HOST; + public static String LOGIN; + public static String PASSWORD; + public static String WORKSPACE; + public static String PORT; + + static{ + HOST = System.getProperty("host") != null ? System.getProperty("host") : "localhost"; + PORT = System.getProperty("port") != null ? System.getProperty("port") : "8080"; + LOGIN = System.getProperty("login") != null ? System.getProperty("login") : "test"; + PASSWORD = System.getProperty("password") != null ? System.getProperty("password") : "test"; + WORKSPACE = System.getProperty("workspace") != null ? System.getProperty("workspace") : "test"; + } +} diff --git a/docdoku-cli/src/test/resources/com/docdoku/cli/commands/common/put-document.txt b/docdoku-cli/src/test/resources/com/docdoku/cli/commands/common/put-document.txt new file mode 100644 index 0000000000..e424466761 --- /dev/null +++ b/docdoku-cli/src/test/resources/com/docdoku/cli/commands/common/put-document.txt @@ -0,0 +1,3 @@ +Something to upload in DocdokuPLM ! + +AWESOME ! diff --git a/docdoku-cli/src/test/resources/com/docdoku/cli/commands/common/put-part.txt b/docdoku-cli/src/test/resources/com/docdoku/cli/commands/common/put-part.txt new file mode 100644 index 0000000000..2018c2462c --- /dev/null +++ b/docdoku-cli/src/test/resources/com/docdoku/cli/commands/common/put-part.txt @@ -0,0 +1,3 @@ +Something to upload in DocdokuPLM ! + +AMAZING ! diff --git a/docdoku-cli/src/test/resources/com/docdoku/cli/commands/common/upload-document.txt b/docdoku-cli/src/test/resources/com/docdoku/cli/commands/common/upload-document.txt new file mode 100644 index 0000000000..e2cf702343 --- /dev/null +++ b/docdoku-cli/src/test/resources/com/docdoku/cli/commands/common/upload-document.txt @@ -0,0 +1,3 @@ +Something to upload in DocdokuPLM ! + +GO GO POWER RANGERS ! diff --git a/docdoku-cli/src/test/resources/com/docdoku/cli/commands/common/upload-part.txt b/docdoku-cli/src/test/resources/com/docdoku/cli/commands/common/upload-part.txt new file mode 100644 index 0000000000..e2cf702343 --- /dev/null +++ b/docdoku-cli/src/test/resources/com/docdoku/cli/commands/common/upload-part.txt @@ -0,0 +1,3 @@ +Something to upload in DocdokuPLM ! + +GO GO POWER RANGERS ! diff --git a/docdoku-common/pom.xml b/docdoku-common/pom.xml new file mode 100644 index 0000000000..2bad005cc6 --- /dev/null +++ b/docdoku-common/pom.xml @@ -0,0 +1,47 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.docdoku</groupId> + <artifactId>docdoku-plm</artifactId> + <version>2.5-SNAPSHOT</version> + </parent> + <artifactId>docdoku-common</artifactId> + <packaging>jar</packaging> + <name>docdoku-common Common Packages</name> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.1</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + <encoding>${project.build.sourceEncoding}</encoding> + </configuration> + </plugin> + </plugins> + <finalName>${project.artifactId}</finalName> + </build> + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.11</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>javax</groupId> + <artifactId>javaee-api</artifactId> + <version>7.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + <version>1.9.5</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/docdoku-common/src/main/java/com/docdoku/core/change/ChangeIssue.java b/docdoku-common/src/main/java/com/docdoku/core/change/ChangeIssue.java new file mode 100644 index 0000000000..d79240ec84 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/change/ChangeIssue.java @@ -0,0 +1,113 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.change; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; + +import javax.persistence.*; +import java.util.Date; + +/** + * Represents an identified issue. + * The issue may result in one or more {@link ChangeRequest}. + * + * @author Florent Garin + * @version 2.0, 06/01/14 + * @since V2.0 + */ +@Table(name="CHANGEISSUE") +@Entity +@AssociationOverrides({ + @AssociationOverride( + name="tags", + joinTable = @JoinTable(name="CHANGEISSUE_TAG", + inverseJoinColumns={ + @JoinColumn(name="TAG_LABEL", referencedColumnName="LABEL"), + @JoinColumn(name="TAG_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }, + joinColumns={ + @JoinColumn(name="CHANGEISSUE_ID", referencedColumnName="ID") + } + ) + ), + @AssociationOverride( + name="affectedDocuments", + joinTable = @JoinTable(name="CHANGEISSUE_AFFECTED_DOCUMENT", + inverseJoinColumns={ + @JoinColumn(name="DOCUMENTMASTER_ID", referencedColumnName="DOCUMENTMASTER_ID"), + @JoinColumn(name="DOCUMENTREVISION_VERSION", referencedColumnName="DOCUMENTREVISION_VERSION"), + @JoinColumn(name="DOCUMENTMASTER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }, + joinColumns={ + @JoinColumn(name="CHANGEISSUE_ID", referencedColumnName="ID") + } + ) + ), + @AssociationOverride( + name="affectedParts", + joinTable = @JoinTable(name="CHANGEISSUE_AFFECTED_PART", + inverseJoinColumns={ + @JoinColumn(name="PARTMASTER_PARTNUMBER", referencedColumnName="PARTMASTER_PARTNUMBER"), + @JoinColumn(name="PARTREVISION_VERSION", referencedColumnName="PARTREVISION_VERSION"), + @JoinColumn(name="PARTMASTER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }, + joinColumns={ + @JoinColumn(name="CHANGEISSUE_ID", referencedColumnName="ID") + } + ) + ) +}) +@NamedQueries({ + @NamedQuery(name="ChangeIssue.findChangeIssuesByWorkspace",query="SELECT DISTINCT c FROM ChangeIssue c WHERE c.workspace.id = :workspaceId"), + @NamedQuery(name="ChangeIssue.findByReference", query="SELECT c FROM ChangeIssue c WHERE c.name LIKE :name AND c.workspace.id = :workspaceId") +}) +public class ChangeIssue extends ChangeItem { + + /** + * Identifies the person or organization at the origin of the change, may be null + * if it is the user who created the object. + */ + private String initiator; + + public ChangeIssue() { + } + + public ChangeIssue(Workspace pWorkspace, String pName, User pAuthor) { + super(pWorkspace, pName, pAuthor); + } + + public ChangeIssue(String name, Workspace workspace, User author, User assignee, Date creationDate, String description, Priority priority, Category category, String initiator) { + super(name, workspace, author, assignee, creationDate, description, priority, category); + this.initiator = initiator; + } + + public void setInitiator(String initiator) { // TODO Find utility of this attribute + this.initiator = initiator; + } + + public String getInitiator() { + return initiator; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/change/ChangeItem.java b/docdoku-common/src/main/java/com/docdoku/core/change/ChangeItem.java new file mode 100644 index 0000000000..eb20f1196b --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/change/ChangeItem.java @@ -0,0 +1,236 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.change; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.security.ACL; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * Abstract parent class from which change objects are derived. + * + * @author Florent Garin + * @version 2.0, 10/01/14 + * @since V2.0 + */ +@MappedSuperclass +public abstract class ChangeItem implements Serializable { + + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + protected int id; + + protected String name; + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + protected Workspace workspace; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + protected User author; + + @ManyToOne(fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="ASSIGNEE_LOGIN", referencedColumnName="LOGIN"), + @JoinColumn(name="ASSIGNEE_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + protected User assignee; + + @OneToOne(orphanRemoval = true, cascade=CascadeType.ALL, fetch=FetchType.EAGER) + protected ACL acl; + + @Temporal(TemporalType.TIMESTAMP) + protected java.util.Date creationDate; + + @Lob + protected String description; + + protected Priority priority; + + public enum Priority { + LOW, MEDIUM, HIGH, EMERGENCY + } + + /** + * An adaptive change maintains functionality for a different platform or + * environment. + * A corrective change corrects a defect. + * A perfective change adds functionality. + * A preventive change improves maintainability. + */ + protected Category category; + + public enum Category { + ADAPTIVE, CORRECTIVE, PERFECTIVE, PREVENTIVE, OTHER + } + + @ManyToMany + protected Set<PartIteration> affectedParts = new HashSet<>(); + + @ManyToMany + protected Set<DocumentIteration> affectedDocuments = new HashSet<>(); + + @ManyToMany(fetch=FetchType.EAGER) + protected Set<Tag> tags=new HashSet<>(); + + public ChangeItem(Workspace pWorkspace, String pName, User pAuthor) { + workspace=pWorkspace; + name=pName; + author=pAuthor; + } + + protected ChangeItem(String name, Workspace workspace, User author, User assignee, Date creationDate, String description, Priority priority, Category category) { + this.name = name; + this.workspace = workspace; + this.author = author; + this.assignee = assignee; + this.creationDate = creationDate; + this.description = description; + this.priority = priority; + this.category = category; + } + + public ChangeItem() { + } + + public int getId() { + return id; + } + + public Category getCategory() { + return category; + } + public void setCategory(Category category) { + this.category = category; + } + + public Priority getPriority() { + return priority; + } + public void setPriority(Priority priority) { + this.priority = priority; + } + + public Date getCreationDate() { + return creationDate; + } + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public ACL getACL() { + return acl; + } + public void setACL(ACL acl) { + this.acl = acl; + } + + public Set<Tag> getTags() { + return tags; + } + public void setTags(Set<Tag> pTags) { + tags.retainAll(pTags); + pTags.removeAll(tags); + tags.addAll(pTags); + } + + public boolean addTag(Tag pTag){ + return tags.add(pTag); + } + public boolean removeTag(Tag pTag){ + return tags.remove(pTag); + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public User getAuthor() { + return author; + } + public void setAuthor(User author) { + this.author = author; + } + + public String getAuthorName() { + return author.getName(); + } + + public Workspace getWorkspace() { + return workspace; + } + public void setWorkspace(Workspace workspace) { + this.workspace = workspace; + } + + public User getAssignee() { + return assignee; + } + public void setAssignee(User assignee) { + this.assignee = assignee; + } + + public String getAssigneeName() { + return assignee == null ? null : assignee.getName(); + } + + public Set<DocumentIteration> getAffectedDocuments() { + return affectedDocuments; + } + public void setAffectedDocuments(Set<DocumentIteration> affectedDocuments) { + this.affectedDocuments = affectedDocuments; + } + + public Set<PartIteration> getAffectedParts() { + return affectedParts; + } + public void setAffectedParts(Set<PartIteration> affectedParts) { + this.affectedParts = affectedParts; + } + + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + + public String getWorkspaceId() { + return workspace == null ? "" : workspace.getId(); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/change/ChangeOrder.java b/docdoku-common/src/main/java/com/docdoku/core/change/ChangeOrder.java new file mode 100644 index 0000000000..ba7727dde8 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/change/ChangeOrder.java @@ -0,0 +1,135 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.change; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; + +import javax.persistence.*; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * A directive to implement an approved {@link ChangeRequest}. + * + * @author Florent Garin + * @version 2.0, 09/01/14 + * @since V2.0 + */ +@Table(name="CHANGEORDER") +@Entity +@AssociationOverrides({ + @AssociationOverride( + name="tags", + joinTable = @JoinTable(name="CHANGEORDER_TAG", + inverseJoinColumns={ + @JoinColumn(name="TAG_LABEL", referencedColumnName="LABEL"), + @JoinColumn(name="TAG_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }, + joinColumns={ + @JoinColumn(name="CHANGEORDER_ID", referencedColumnName="ID") + } + ) + ), + @AssociationOverride( + name="affectedDocuments", + joinTable = @JoinTable(name="CHANGEORDER_AFFECTED_DOCUMENT", + inverseJoinColumns={ + @JoinColumn(name="DOCUMENTMASTER_ID", referencedColumnName="DOCUMENTMASTER_ID"), + @JoinColumn(name="DOCUMENTREVISION_VERSION", referencedColumnName="DOCUMENTREVISION_VERSION"), + @JoinColumn(name="DOCUMENTMASTER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }, + joinColumns={ + @JoinColumn(name="CHANGEORDER_ID", referencedColumnName="ID") + } + ) + ), + @AssociationOverride( + name="affectedParts", + joinTable = @JoinTable(name="CHANGEORDER_AFFECTED_PART", + inverseJoinColumns={ + @JoinColumn(name="PARTMASTER_PARTNUMBER", referencedColumnName="PARTMASTER_PARTNUMBER"), + @JoinColumn(name="PARTREVISION_VERSION", referencedColumnName="PARTREVISION_VERSION"), + @JoinColumn(name="PARTMASTER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }, + joinColumns={ + @JoinColumn(name="CHANGEORDER_ID", referencedColumnName="ID") + } + ) + ) +}) +@NamedQueries({ + @NamedQuery(name="ChangeOrder.findChangeOrdersByWorkspace",query="SELECT DISTINCT c FROM ChangeOrder c WHERE c.workspace.id = :workspaceId"), + @NamedQuery(name="ChangeOrder.countOrderByMilestonesAndWorkspace",query="SELECT COUNT(o) FROM ChangeOrder o WHERE o.workspace.id = :workspaceId AND o.milestone.id = :milestoneId"), + @NamedQuery(name="ChangeOrder.getOrderByMilestonesAndWorkspace",query="SELECT DISTINCT o FROM ChangeOrder o WHERE o.workspace.id = :workspaceId AND o.milestone.id = :milestoneId"), + @NamedQuery(name="ChangeOrder.findByReference", query="SELECT c FROM ChangeOrder c WHERE c.name LIKE :name AND c.workspace.id = :workspaceId"), + @NamedQuery(name="ChangeOrder.findByChangeRequest", query="SELECT c FROM ChangeOrder c WHERE c.workspace.id = :workspaceId AND :changeRequest member of c.addressedChangeRequests") +}) +public class ChangeOrder extends ChangeItem { + + + @ManyToMany(fetch=FetchType.LAZY) + @JoinTable(name="CHANGEORDER_CHANGEREQUEST", + inverseJoinColumns={ + @JoinColumn(name="CHANGEREQUEST_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="CHANGEORDER_ID", referencedColumnName="ID") + }) + private Set<ChangeRequest> addressedChangeRequests=new HashSet<>(); + + @ManyToOne(fetch = FetchType.EAGER) + private Milestone milestone; + + public ChangeOrder() { + } + + public ChangeOrder(Workspace pWorkspace, String pName, User pAuthor) { + super(pWorkspace, pName, pAuthor); + } + + public ChangeOrder(String name, Workspace workspace, User author, User assignee, Date creationDate, String description, Priority priority, Category category, Milestone milestone) { + super(name, workspace, author, assignee, creationDate, description, priority, category); + this.milestone = milestone; + } + + public Milestone getMilestone() { + return milestone; + } + public void setMilestone(Milestone milestone) { + this.milestone = milestone; + } + + public Set<ChangeRequest> getAddressedChangeRequests() { + return addressedChangeRequests; + } + public void setAddressedChangeRequests(Set<ChangeRequest> addressedChangeRequests) { + this.addressedChangeRequests = addressedChangeRequests; + } + + public int getMilestoneId(){ + return (milestone!=null) ? milestone.getId() : -1; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/change/ChangeRequest.java b/docdoku-common/src/main/java/com/docdoku/core/change/ChangeRequest.java new file mode 100644 index 0000000000..6fa1e6fb04 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/change/ChangeRequest.java @@ -0,0 +1,135 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.change; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; + +import javax.persistence.*; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * This class represents a request for a change, + * which addresses one or more {@link ChangeIssue}. + * @author Florent Garin + * @version 2.0, 09/01/14 + * @since V2.0 + */ +@Table(name="CHANGEREQUEST") +@Entity +@AssociationOverrides({ + @AssociationOverride( + name="tags", + joinTable = @JoinTable(name="CHANGEREQUEST_TAG", + inverseJoinColumns={ + @JoinColumn(name="TAG_LABEL", referencedColumnName="LABEL"), + @JoinColumn(name="TAG_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }, + joinColumns={ + @JoinColumn(name="CHANGEREQUEST_ID", referencedColumnName="ID") + } + ) + ), + @AssociationOverride( + name="affectedDocuments", + joinTable = @JoinTable(name="CHANGEREQ_AFFECTED_DOCUMENT", + inverseJoinColumns={ + @JoinColumn(name="DOCUMENTMASTER_ID", referencedColumnName="DOCUMENTMASTER_ID"), + @JoinColumn(name="DOCUMENTREVISION_VERSION", referencedColumnName="DOCUMENTREVISION_VERSION"), + @JoinColumn(name="DOCUMENTMASTER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }, + joinColumns={ + @JoinColumn(name="CHANGEREQUEST_ID", referencedColumnName="ID") + } + ) + ), + @AssociationOverride( + name="affectedParts", + joinTable = @JoinTable(name="CHANGEREQ_AFFECTED_PART", + inverseJoinColumns={ + @JoinColumn(name="PARTMASTER_PARTNUMBER", referencedColumnName="PARTMASTER_PARTNUMBER"), + @JoinColumn(name="PARTREVISION_VERSION", referencedColumnName="PARTREVISION_VERSION"), + @JoinColumn(name="PARTMASTER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }, + joinColumns={ + @JoinColumn(name="CHANGEREQUEST_ID", referencedColumnName="ID") + } + ) + ) +}) +@NamedQueries({ + @NamedQuery(name="ChangeRequest.findChangeRequestsByWorkspace",query="SELECT DISTINCT c FROM ChangeRequest c WHERE c.workspace.id = :workspaceId"), + @NamedQuery(name="ChangeRequest.countRequestByMilestonesAndWorkspace",query="SELECT COUNT(r) FROM ChangeRequest r WHERE r.workspace.id = :workspaceId AND r.milestone.id = :milestoneId"), + @NamedQuery(name="ChangeRequest.getRequestByMilestonesAndWorkspace",query="SELECT DISTINCT r FROM ChangeRequest r WHERE r.workspace.id = :workspaceId AND r.milestone.id = :milestoneId"), + @NamedQuery(name="ChangeRequest.findByReference", query="SELECT c FROM ChangeRequest c WHERE c.name LIKE :name AND c.workspace.id = :workspaceId"), + @NamedQuery(name="ChangeRequest.findByChangeIssue", query="SELECT c FROM ChangeRequest c WHERE c.workspace.id = :workspaceId AND :changeIssue member of c.addressedChangeIssues") +}) +public class ChangeRequest extends ChangeItem { + + + @ManyToMany(fetch=FetchType.LAZY) + @JoinTable(name="CHANGEREQUEST_CHANGEISSUE", + inverseJoinColumns={ + @JoinColumn(name="CHANGEISSUE_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="CHANGEREQUEST_ID", referencedColumnName="ID") + }) + private Set<ChangeIssue> addressedChangeIssues=new HashSet<>(); + + @ManyToOne(fetch = FetchType.EAGER) + private Milestone milestone; + + public ChangeRequest() { + } + + public ChangeRequest(Workspace pWorkspace, String pName, User pAuthor) { + super(pWorkspace, pName, pAuthor); + } + + public ChangeRequest(String name, Workspace workspace, User author, User assignee, Date creationDate, String description, Priority priority, Category category, Milestone milestone) { + super(name, workspace, author, assignee, creationDate, description, priority, category); + this.milestone = milestone; + } + + public Milestone getMilestone() { + return milestone; + } + public void setMilestone(Milestone milestone) { + this.milestone = milestone; + } + + public Set<ChangeIssue> getAddressedChangeIssues() { + return addressedChangeIssues; + } + public void setAddressedChangeIssues(Set<ChangeIssue> addressedChangeIssues) { + this.addressedChangeIssues = addressedChangeIssues; + } + + public int getMilestoneId(){ + return (milestone!=null) ? milestone.getId() : -1; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/change/Milestone.java b/docdoku-common/src/main/java/com/docdoku/core/change/Milestone.java new file mode 100644 index 0000000000..27dba1809d --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/change/Milestone.java @@ -0,0 +1,148 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.change; + +import com.docdoku.core.common.Workspace; +import com.docdoku.core.security.ACL; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; + +/** + * A milestone acts like a container for change items. + * This is useful for associating changes with specific features or project phases. + * + * @author Florent Garin + * @version 2.0, 05/02/14 + * @since V2.0 + */ +@Table(name = "MILESTONE") +@javax.persistence.Entity +@NamedQueries({ + @NamedQuery(name = "Milestone.findMilestonesByWorkspace", query = "SELECT DISTINCT m FROM Milestone m WHERE m.workspace.id = :workspaceId"), + @NamedQuery(name = "Milestone.findMilestonesByTitleAndWorkspace", query = "SELECT DISTINCT m FROM Milestone m WHERE m.workspace.id = :workspaceId AND m.title = :title") +}) +public class Milestone implements Serializable { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private int id; + + private String title = ""; + + @Temporal(TemporalType.TIMESTAMP) + private Date dueDate; + + @Lob + private String description; + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + private Workspace workspace; + + @OneToOne(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER) + private ACL acl; + + public Milestone() { + } + + public Milestone(Workspace pWorkspace, String pTitle) { + setWorkspace(pWorkspace); + title = pTitle; + } + + public Milestone(String title, Date dueDate, String description, Workspace workspace) { + this.title = title; + this.dueDate = dueDate; + this.description = description; + this.workspace = workspace; + } + + public int getId() { + return id; + } + + public Date getDueDate() { + return dueDate; + } + + public void setDueDate(Date dueDate) { + this.dueDate = dueDate; + } + + public Workspace getWorkspace() { + return workspace; + } + + public String getWorkspaceId() { + return workspace.getId(); + } + + public void setWorkspace(Workspace pWorkspace) { + workspace = pWorkspace; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public ACL getACL() { + return acl; + } + + public void setACL(ACL acl) { + this.acl = acl; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof Milestone)) + return false; + Milestone milestone = (Milestone) pObj; + + return milestone.id == id; + } + + @Override + public String toString() { + return title; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/change/ModificationNotification.java b/docdoku-common/src/main/java/com/docdoku/core/change/ModificationNotification.java new file mode 100644 index 0000000000..3d241722c4 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/change/ModificationNotification.java @@ -0,0 +1,142 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.change; + +import com.docdoku.core.common.User; +import com.docdoku.core.product.PartIteration; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; + +/** + * Class which instances are attached to assemblies in order to track + * modification that happened on sub-component. + * Its purpose is to notify users that those assemblies may be impacted by these modifications. + * + * @author Florent Garin + * @version 2.0, 12/03/15 + * @since V2.0 + */ +@Entity +@NamedQueries ({ + @NamedQuery(name="ModificationNotification.findByImpactedPartIteration", query = "SELECT n FROM ModificationNotification n WHERE n.impactedPart.iteration = :iteration AND n.impactedPart.partRevision.version = :version AND n.impactedPart.partRevision.partMaster.number = :partNumber AND n.impactedPart.partRevision.partMaster.workspace.id = :workspaceId ORDER BY n.acknowledged, n.modifiedPart.partRevision.partMaster.number, n.modifiedPart.iteration DESC"), + @NamedQuery(name="ModificationNotification.removeAllOnPartRevision", query = "DELETE FROM ModificationNotification n WHERE n.impactedPart.partRevision.version = :version AND n.impactedPart.partRevision.partMaster.number = :partNumber AND n.impactedPart.partRevision.partMaster.workspace.id = :workspaceId"), + @NamedQuery(name="ModificationNotification.removeAllOnPartIteration", query = "DELETE FROM ModificationNotification n WHERE n.impactedPart.partRevision.version = :version AND n.impactedPart.partRevision.partMaster.number = :partNumber AND n.impactedPart.partRevision.partMaster.workspace.id = :workspaceId AND n.impactedPart.iteration = :iteration") +}) +@Table(name="MODIFICATIONNOTIFICATION") +public class ModificationNotification implements Serializable { + + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + protected int id; + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="MODIFIED_ITERATION", referencedColumnName="ITERATION"), + @JoinColumn(name="MODIFIED_PARTMASTER_PARTNUMBER", referencedColumnName="PARTMASTER_PARTNUMBER"), + @JoinColumn(name="MODIFIED_PARTREVISION_VERSION", referencedColumnName="PARTREVISION_VERSION"), + @JoinColumn(name="MODIFIED_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private PartIteration modifiedPart; + + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="IMPACTED_ITERATION", referencedColumnName="ITERATION"), + @JoinColumn(name="IMPACTED_PARTMASTER_PARTNUMBER", referencedColumnName="PARTMASTER_PARTNUMBER"), + @JoinColumn(name="IMPACTED_PARTREVISION_VERSION", referencedColumnName="PARTREVISION_VERSION"), + @JoinColumn(name="IMPACTED_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private PartIteration impactedPart; + + private boolean acknowledged; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "ACKAUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "ACKAUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User acknowledgementAuthor; + + @Temporal(TemporalType.TIMESTAMP) + private Date acknowledgementDate; + + @Lob + private String acknowledgementComment; + + public ModificationNotification() { + } + + public int getId() { + return id; + } + + public PartIteration getImpactedPart() { + return impactedPart; + } + + public void setImpactedPart(PartIteration impactedPart) { + this.impactedPart = impactedPart; + } + + public PartIteration getModifiedPart() { + return modifiedPart; + } + + public void setModifiedPart(PartIteration modifiedPart) { + this.modifiedPart = modifiedPart; + } + + public boolean isAcknowledged() { + return acknowledged; + } + + public void setAcknowledged(boolean acknowledged) { + this.acknowledged = acknowledged; + } + + public User getAcknowledgementAuthor() { + return acknowledgementAuthor; + } + + public void setAcknowledgementAuthor(User acknowledgementAuthor) { + this.acknowledgementAuthor = acknowledgementAuthor; + } + + public Date getAcknowledgementDate() { + return acknowledgementDate; + } + + public void setAcknowledgementDate(Date acknowledgementDate) { + this.acknowledgementDate = acknowledgementDate; + } + + public String getAcknowledgementComment() { + return acknowledgementComment; + } + + public void setAcknowledgementComment(String acknowledgementComment) { + this.acknowledgementComment = acknowledgementComment; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/common/Account.java b/docdoku-common/src/main/java/com/docdoku/core/common/Account.java new file mode 100644 index 0000000000..60b9e718df --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/common/Account.java @@ -0,0 +1,147 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.common; + +import javax.persistence.Table; +import java.io.Serializable; +import java.util.Date; + +/** + * The Account class holds personal user data applicable inside the whole application. + * However {@link User} objects encapsulate personal information + * only in the context of a particular workspace. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="ACCOUNT") +@javax.persistence.Entity +public class Account implements Serializable, Cloneable { + + + @javax.persistence.Id + private String login=""; + + private String name; + private String email; + private String language; + private String timeZone = "Europe/London"; + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date creationDate; + + public Account(){ + } + + public Account(String pLogin){ + login = pLogin; + } + + public Account(String pLogin, String pName, String pEmail, String pLanguage, Date pCreationDate,String pTimeZone) { + login = pLogin; + name = pName; + email = pEmail; + language = pLanguage; + creationDate = pCreationDate; + timeZone = pTimeZone; + } + + public String getLogin() { + return login; + } + public void setLogin(String pLogin) { + login=pLogin; + } + + public String getName() { + return name; + } + public void setName(String pName) { + name = pName; + } + + public void setEmail(String pEmail) { + email = pEmail; + } + public String getEmail() { + return email; + } + + public void setLanguage(String pLanguage) { + language = pLanguage; + } + public String getLanguage() { + return language; + } + + public String getTimeZone() { + return timeZone; + } + + public void setTimeZone(String timeZone) { + this.timeZone = timeZone; + } + + public void setCreationDate(Date pCreationDate) { + creationDate = pCreationDate; + } + public Date getCreationDate() { + return creationDate; + } + + @Override + public String toString() { + return login; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof Account)){ + return false; + } + Account account = (Account) pObj; + return account.login.equals(login); + } + + @Override + public int hashCode() { + return login.hashCode(); + } + + /** + * perform a deep clone operation + */ + @Override + public Account clone() { + Account clone; + try { + clone = (Account) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + clone.creationDate = (Date) creationDate.clone(); + return clone; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/common/BinaryResource.java b/docdoku-common/src/main/java/com/docdoku/core/common/BinaryResource.java new file mode 100644 index 0000000000..3017ecd890 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/common/BinaryResource.java @@ -0,0 +1,217 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.common; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; + +/** + * BinaryResource is the representation + * of a file contained in either a document, part, template or any other file holder object. + * + * The fullName has the following pattern: + * {workspace_id}/{holder_type}/{holder_id}/{holder_version}/{file_type}/{file_name} + * + * For example we could have: + * project_X/documents/OUR ANSWER/A/1/AEAG_W08.doc + * + * {holder_version} and {file_type} are optional + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="BINARYRESOURCE") +@Inheritance() +@NamedQueries ({ + @NamedQuery(name="BinaryResource.diskUsageInPath", query = "SELECT sum(br.contentLength) FROM BinaryResource br WHERE br.fullName like :path") +}) +@Entity +public class BinaryResource implements Serializable, Comparable<BinaryResource>{ + + @Id + protected String fullName=""; + + protected long contentLength; + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date lastModified; + + public BinaryResource() { + } + + public BinaryResource(String pFullName, long pContentLength, Date pLastModified) { + fullName = pFullName; + contentLength = pContentLength; + lastModified = pLastModified; + } + + public String getWorkspaceId(){ + return BinaryResource.parseWorkspaceId(fullName); + } + + public static String parseWorkspaceId(String pFullName){ + int index = pFullName.indexOf('/'); + return pFullName.substring(0, index); + } + + public String getHolderType(){ + return BinaryResource.parseHolderType(fullName); + } + + public static String parseHolderType(String pFullName){ + String[] parts = pFullName.split("/"); + return parts[1]; + } + + public String getHolderId(){ + return BinaryResource.parseHolderId(fullName); + } + + public static String parseHolderId(String pFullName){ + String[] parts = pFullName.split("/"); + return parts[2]; + } + + public String getHolderRevision(){ + return BinaryResource.parseHolderRevision(fullName); + } + + public static String parseHolderRevision(String pFullName){ + String[] parts = pFullName.split("/"); + if(parts.length==6 || parts.length==7) + return parts[3]; + else + return null; + } + + public Integer getHolderIteration(){ + return BinaryResource.parseHolderIteration(fullName); + } + + public static Integer parseHolderIteration(String pFullName){ + String[] parts = pFullName.split("/"); + if(parts.length==6 || parts.length==7) + return new Integer(parts[4]); + else + return null; + } + + public String getFileType(){ + return BinaryResource.parseFileType(fullName); + } + + public static String parseFileType(String pFullName){ + String[] parts = pFullName.split("/"); + if(parts.length==7) + return parts[5]; + else if(parts.length==5) + return parts[3]; + else + return null; + } + + public String getNewFullName(String newName){ + int index = fullName.lastIndexOf('/'); + return fullName.substring(0,index) + '/' + newName; + } + + public void setFullName(String pFullName) { + fullName = pFullName; + } + + public void setContentLength(long pContentLength) { + contentLength = pContentLength; + } + + + public BinaryResource getPrevious(){ + String holderRevision = getHolderRevision(); + if(holderRevision==null) { + return null; + } + + String name = getName(); + String fileType = getFileType(); + int iteration=getHolderIteration(); + + String[] parts = fullName.split("/"); + + iteration--; + if(iteration>0){ + String previousFullName=parts[0] + "/" + parts[1] + "/" + parts[2] + "/" + holderRevision + "/" + iteration + (fileType==null?"":"/" + fileType) + "/" + name; + return new BinaryResource(previousFullName, contentLength, lastModified); + }else { + return null; + } + } + + public String getFullName() { + return fullName; + } + + public String getName(){ + int index= fullName.lastIndexOf('/'); + return fullName.substring(index+1); + } + + public long getContentLength() { + return contentLength; + } + + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof BinaryResource)) { + return false; + } + BinaryResource bin = (BinaryResource) pObj; + return bin.fullName.equals(fullName); + } + + @Override + public int hashCode() { + return fullName.hashCode(); + } + + @Override + public int compareTo(BinaryResource pBinaryResource) { + return fullName.compareTo(pBinaryResource.fullName); + } + + @Override + public String toString() { + return getName(); + } + + public Date getLastModified() { + return lastModified; + } + + public void setLastModified(Date lastModified) { + this.lastModified = lastModified; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/common/FileHolder.java b/docdoku-common/src/main/java/com/docdoku/core/common/FileHolder.java new file mode 100644 index 0000000000..a23221cd52 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/common/FileHolder.java @@ -0,0 +1,36 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.common; + +import java.util.Set; + +/** + * Interface implemented by objects that hold binary files. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +public interface FileHolder { + + Set<BinaryResource> getAttachedFiles(); + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/common/Organization.java b/docdoku-common/src/main/java/com/docdoku/core/common/Organization.java new file mode 100644 index 0000000000..8b9cef34ad --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/common/Organization.java @@ -0,0 +1,128 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.common; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * The Organization class represents an entity which groups users + * or more precisely {@link Account} + * as its validity spreads across workspaces. + * + * @author Florent Garin + * @version 2.0, 30/05/14 + * @since V2.0 + */ +@Table(name = "ORGANIZATION") +@javax.persistence.Entity +@NamedQueries ({ + @NamedQuery(name="Organization.ofAccount", query = "SELECT orga FROM Organization orga WHERE :account MEMBER OF orga.members") +}) +public class Organization implements Serializable { + + @Column(length = 100) + @javax.persistence.Id + private String name = ""; + + @javax.persistence.OneToOne(optional = false, fetch = FetchType.EAGER) + private Account owner; + + @Lob + private String description; + + @JoinTable(name="ORGANIZATION_ACCOUNT", + inverseJoinColumns={ + @JoinColumn(name="ACCOUNT_LOGIN", referencedColumnName="LOGIN") + }, + joinColumns={ + @JoinColumn(name="ORGANIZATION_NAME", referencedColumnName="NAME") + }) + @OrderColumn(name="ACCOUNT_ORDER") + @OneToMany(fetch = FetchType.EAGER) + private List<Account> members = new ArrayList<>(); + + public Organization() { + + } + public Organization(String pName, Account pOwner, String pDescription) { + name = pName; + owner = pOwner; + description = pDescription; + } + + public List<Account> getMembers() { + return members; + } + public boolean addMember(Account pAccount){ + return members.add(pAccount); + } + public boolean removeMember(Account pAccount){ + return members.remove(pAccount); + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + + public Account getOwner() { + return owner; + } + public void setOwner(Account owner) { + this.owner = owner; + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o){ + return true; + } + if (o == null || getClass() != o.getClass()){ + return false; + } + + Organization that = (Organization) o; + + return name.equals(that.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/common/User.java b/docdoku-common/src/main/java/com/docdoku/core/common/User.java new file mode 100644 index 0000000000..865376397a --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/common/User.java @@ -0,0 +1,146 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.common; + +import javax.persistence.*; +import java.io.Serializable; + +/** + * This class represents a user in the context of a specific workspace. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="USERDATA") +@javax.persistence.IdClass(com.docdoku.core.common.UserKey.class) +@javax.persistence.Entity +public class User implements Serializable, Cloneable { + + @Column(name = "WORKSPACE_ID", nullable = false, insertable = false, updatable = false) + private String workspaceId = ""; + + @Column(name = "LOGIN", nullable = false, insertable = false, updatable = false) + private String login = ""; + + @Id + @JoinColumn(name = "LOGIN") + @ManyToOne(optional = false, fetch = FetchType.EAGER) + private Account account; + + @Id + @JoinColumn(name = "WORKSPACE_ID") + @ManyToOne(optional = false, fetch = FetchType.EAGER) + private Workspace workspace; + + public User() { + } + + public User(Workspace pWorkspace, Account pAccount) { + setWorkspace(pWorkspace); + setAccount(pAccount); + } + + + public String getLogin() { + return login; + } + + public String getName() { + return account==null?null:account.getName(); + } + + public String getEmail() { + return account==null?null:account.getEmail(); + } + + public String getLanguage() { + return account==null?null:account.getLanguage(); + } + + public boolean isAdministrator() { + return login.equals(workspace.getAdmin().getLogin()); + } + + public UserKey getKey() { + return new UserKey(workspaceId, login); + } + + public void setAccount(Account pAccount) { + account = pAccount; + login = pAccount.getLogin(); + } + + public Account getAccount() { + return account; + } + + + public void setWorkspace(Workspace pWorkspace) { + workspace = pWorkspace; + workspaceId = workspace.getId(); + } + + public Workspace getWorkspace() { + return workspace; + } + + + public String getWorkspaceId() { + return workspaceId; + } + + @Override + public String toString() { + return login; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof User)) { + return false; + } + User user = (User) pObj; + return user.login.equals(login) && user.workspaceId.equals(workspaceId); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + login.hashCode(); + return hash; + } + + @Override + public User clone() { + User clone = null; + try { + clone = (User) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + return clone; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/common/UserGroup.java b/docdoku-common/src/main/java/com/docdoku/core/common/UserGroup.java new file mode 100644 index 0000000000..ff815550bd --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/common/UserGroup.java @@ -0,0 +1,111 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.common; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +/** + * Class which gathers users in a workspace context. + * + * @author Florent Garin + * @version 1.1, 8/07/09 + * @since V1.1 + */ +@Table(name="USERGROUP") +@javax.persistence.IdClass(com.docdoku.core.common.UserGroupKey.class) +@javax.persistence.Entity +@NamedQueries({ + @NamedQuery(name="UserGroup.findUserGroups", query="SELECT u FROM UserGroup u WHERE :user member of u.users AND u.workspaceId = :workspaceId") +}) +public class UserGroup implements Serializable { + + @Column(length=100) + @javax.persistence.Id + private String id=""; + + @javax.persistence.Column(name = "WORKSPACE_ID", length=100, nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String workspaceId=""; + + @javax.persistence.ManyToOne(optional=false, fetch=FetchType.EAGER) + private Workspace workspace; + + @ManyToMany(fetch=FetchType.EAGER) + @JoinTable(name="USERGROUP_USER", + inverseJoinColumns={ + @JoinColumn(name="USER_LOGIN", referencedColumnName="LOGIN"), + @JoinColumn(name="USER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }, + joinColumns={ + @JoinColumn(name="USERGROUP_ID", referencedColumnName="ID"), + @JoinColumn(name="USERGROUP_ID_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private Set<User> users=new HashSet<>(); + + public UserGroup(){ + + } + + public UserGroup(Workspace pWorkspace, String pId) { + setWorkspace(pWorkspace); + id=pId; + } + + public void setWorkspace(Workspace pWorkspace){ + workspace=pWorkspace; + workspaceId=workspace.getId(); + } + + public Set<User> getUsers() { + return users; + } + + public boolean addUser(User pUser){ + return users.add(pUser); + } + + public boolean removeUser(User pUser){ + return users.remove(pUser); + } + + public boolean isMember(User user){ + return users.contains(user); + } + public String getId() { + return id; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setUsers(Set<User> users) { + this.users = users; + } + + @Override + public String toString() { + return getId(); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/common/UserGroupKey.java b/docdoku-common/src/main/java/com/docdoku/core/common/UserGroupKey.java new file mode 100644 index 0000000000..74ee79ec93 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/common/UserGroupKey.java @@ -0,0 +1,83 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.common; + +import java.io.Serializable; + +/** + * Identity class of {@link UserGroup} objects. + * + * @author Florent Garin + */ +public class UserGroupKey implements Serializable { + + private String workspaceId; + private String id; + + public UserGroupKey() { + } + + public UserGroupKey(String pWorkspaceId, String pId) { + workspaceId=pWorkspaceId; + id=pId; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + id.hashCode(); + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof UserGroupKey)) { + return false; + } + UserGroupKey key = (UserGroupKey) pObj; + return key.id.equals(id) && key.workspaceId.equals(workspaceId); + } + + @Override + public String toString() { + return workspaceId + "-" + id; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String pWorkspaceId) { + workspaceId = pWorkspaceId; + } + + public String getId() { + return id; + } + + public void setId(String pId) { + id = pId; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/common/UserKey.java b/docdoku-common/src/main/java/com/docdoku/core/common/UserKey.java new file mode 100644 index 0000000000..fd9ca248de --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/common/UserKey.java @@ -0,0 +1,84 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.common; + +import java.io.Serializable; + +/** + * Identity class of {@link User} objects. + * + * @author Florent Garin + */ +public class UserKey implements Serializable { + + private String workspace; + private String account; + + public UserKey() { + } + + public UserKey(String pWorkspaceId, String pLogin) { + workspace =pWorkspaceId; + account =pLogin; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspace.hashCode(); + hash = 31 * hash + account.hashCode(); + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof UserKey)) { + return false; + } + UserKey key = (UserKey) pObj; + return key.account.equals(account) && key.workspace.equals(workspace); + } + + @Override + public String toString() { + return workspace + "-" + account; + } + + public String getWorkspace() { + return workspace; + } + + public void setWorkspace(String pWorkspaceId) { + workspace = pWorkspaceId; + } + + public String getAccount() { + return account; + } + + public void setAccount(String pLogin) { + account = pLogin; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/common/Version.java b/docdoku-common/src/main/java/com/docdoku/core/common/Version.java new file mode 100644 index 0000000000..f1093af71b --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/common/Version.java @@ -0,0 +1,124 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.common; + +import java.io.Serializable; +import java.util.LinkedList; +import java.util.List; + +/** + * + * @author Florent Garin + */ +public class Version implements Serializable, Comparable<Version>, Cloneable { + + private List<VersionUnit> mVersionUnits; + + public enum VersionUnit { + A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z + } + + public Version() { + mVersionUnits = new LinkedList<>(); + mVersionUnits.add(VersionUnit.A); + } + + public Version(String pStringVersion) { + mVersionUnits = new LinkedList<>(); + + for (int i = 0; i < pStringVersion.length(); i++) { + try { + mVersionUnits.add(VersionUnit.valueOf(pStringVersion.charAt(i) + "")); + } catch (IllegalArgumentException pIAEx) { + throw new VersionFormatException(pStringVersion, i); + } + } + + } + + public void increase() { + + for (int i = mVersionUnits.size() - 1; i > -1; i--) { + VersionUnit unit = mVersionUnits.get(i); + if (unit == VersionUnit.Z) { + if (i == 0) { + mVersionUnits.add(0, VersionUnit.A); + } + } else { + int ordinal = unit.ordinal(); + mVersionUnits.set(i, VersionUnit.values()[++ordinal]); + break; + } + } + + } + + @Override + public String toString() { + StringBuilder stringVersion = new StringBuilder(); + for (VersionUnit unit : mVersionUnits) { + stringVersion.append(unit.toString()); + } + + return stringVersion.toString(); + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof Version)) { + return false; + } + Version version = (Version) pObj; + //because of bug #6277781 Serialization of Enums over IIOP is broken. + //we compare the string representation. + //without this bug regular implementation would be: + //mVersionUnits.equals(version.mVersionUnits); + return toString().equals(version.toString()); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + public int compareTo(Version pVersion) { + return toString().compareTo(pVersion.toString()); + } + + /** + * perform a deep clone operation + */ + @Override + public Version clone() { + Version clone; + try { + clone = (Version) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + //perform a deep copy + clone.mVersionUnits = new LinkedList<>(mVersionUnits); + return clone; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/common/VersionFormatException.java b/docdoku-common/src/main/java/com/docdoku/core/common/VersionFormatException.java new file mode 100644 index 0000000000..49aeec2151 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/common/VersionFormatException.java @@ -0,0 +1,47 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.common; + +/** + * + * @author Florent Garin + */ +public class VersionFormatException extends IllegalArgumentException { + + private int mErrorOffset; + private String mInputString; + + + public VersionFormatException(String pInputString, int pErrorOffset) { + super("Error trying to parse the string \"" + pInputString + "\" at the character position " + pErrorOffset); + mErrorOffset = pErrorOffset; + mInputString = pInputString; + } + + public int getErrorOffset() { + return mErrorOffset; + } + + public String getInputString() { + return mInputString; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/common/Workspace.java b/docdoku-common/src/main/java/com/docdoku/core/common/Workspace.java new file mode 100644 index 0000000000..9fa594ceaa --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/common/Workspace.java @@ -0,0 +1,128 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.common; + + +import javax.persistence.*; +import java.io.Serializable; + +/** + * The context in which documents, workflow models, parts, products, templates and all + * the other objects reside. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="WORKSPACE") +@javax.persistence.Entity +@NamedQueries({ + @NamedQuery(name="Workspace.findWorkspacesWhereUserIsActive", query="SELECT w FROM Workspace w WHERE EXISTS (SELECT u.workspace FROM WorkspaceUserMembership u WHERE u.workspace = w AND u.member.login = :userLogin) OR EXISTS (SELECT g FROM WorkspaceUserGroupMembership g WHERE g.workspace = w AND EXISTS (SELECT gr FROM UserGroup gr, User us WHERE us.workspace = gr.workspace AND g.workspace = gr.workspace AND us.login = :userLogin AND us member of gr.users))"), + @NamedQuery(name="Workspace.findAllWorkspaces", query="SELECT w FROM Workspace w") +}) +public class Workspace implements Serializable, Cloneable { + + @Column(length=100) + @javax.persistence.Id + private String id=""; + + @javax.persistence.ManyToOne(optional=false, fetch=FetchType.EAGER) + private Account admin; + + @Lob + private String description; + + private boolean folderLocked; + + + public Workspace(String pId, Account pAdmin, String pDescription, boolean pFolderLocked) { + id = pId; + admin = pAdmin; + description = pDescription; + folderLocked=pFolderLocked; + } + public Workspace(String pId) { + id = pId; + } + public Workspace() { + } + + public Account getAdmin() { + return admin; + } + public void setAdmin(Account pAdmin) { + admin = pAdmin; + } + + public String getId() { + return id; + } + public void setId(String pId){ + id=pId; + } + + public String getDescription() { + return description; + } + public void setDescription(String pDescription) { + description = pDescription; + } + + public boolean isFolderLocked() { + return folderLocked; + } + public void setFolderLocked(boolean folderLocked) { + this.folderLocked = folderLocked; + } + + + @Override + public String toString() { + return id; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof Workspace)){ + return false; + } + Workspace workspace = (Workspace) pObj; + return workspace.id.equals(id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + + @Override + public Workspace clone() { + try { + return (Workspace) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedDocument.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedDocument.java new file mode 100644 index 0000000000..7b655bed89 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedDocument.java @@ -0,0 +1,126 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.configuration; + + +import com.docdoku.core.document.DocumentIteration; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; + +/** + * Class link that gathers a document collection and a given document iteration. + * + * @author Taylor LABEJOF + * @version 2.0, 25/08/14 + * @since V2.0 + */ + +@Table(name = "BASELINEDDOCUMENT") +@Entity +public class BaselinedDocument implements Serializable { + @EmbeddedId + private BaselinedDocumentKey baselinedDocumentKey; + + //@MapsId("documentCollectionId") + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumn(name = "DOCUMENTCOLLECTION_ID", referencedColumnName = "ID") + private DocumentCollection documentCollection; + + @ManyToOne(optional = false, fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "TARGET_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "TARGET_DOCUMENTMASTER_ID", referencedColumnName = "DOCUMENTMASTER_ID"), + @JoinColumn(name = "TARGET_DOCREVISION_VERSION", referencedColumnName = "DOCUMENTREVISION_VERSION"), + @JoinColumn(name = "TARGET_ITERATION", referencedColumnName = "ITERATION") + }) + private DocumentIteration targetDocument; + + @Column(name = "TARGET_ITERATION", nullable = false, insertable = false, updatable = false) + private int targetDocumentIteration; + + public BaselinedDocument() { + } + + public BaselinedDocument(DocumentCollection documentCollection, DocumentIteration targetDocument) { + this.documentCollection = documentCollection; + this.targetDocument = targetDocument; + this.baselinedDocumentKey = new BaselinedDocumentKey(documentCollection.getId(), targetDocument.getWorkspaceId(), targetDocument.getDocumentMasterId(), targetDocument.getVersion()); + this.targetDocumentIteration = targetDocument.getIteration(); + } + + public BaselinedDocumentKey getKey() { + return baselinedDocumentKey; + } + + @XmlTransient + public DocumentCollection getDocumentCollection() { + return documentCollection; + } + + public void setDocumentCollection(DocumentCollection documentCollection) { + this.documentCollection = documentCollection; + } + + public DocumentIteration getTargetDocument() { + return targetDocument; + } + + public String getTargetDocumentMasterId() { + return targetDocument.getDocumentMasterId(); + } + + public String getTargetDocumentVersion() { + return targetDocument.getVersion(); + } + + public int getTargetDocumentIteration() { + return targetDocumentIteration; + } + + public void setTargetDocumentIteration(int targetDocumentIteration) { + this.targetDocumentIteration = targetDocumentIteration; + } + + @Override + public boolean equals(Object o) { + if (this == o){ + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BaselinedDocument that = (BaselinedDocument) o; + + if (baselinedDocumentKey != null ? !baselinedDocumentKey.equals(that.baselinedDocumentKey) : that.baselinedDocumentKey != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return baselinedDocumentKey != null ? baselinedDocumentKey.hashCode() : 0; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedDocumentKey.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedDocumentKey.java new file mode 100644 index 0000000000..c7ef6d9ee5 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedDocumentKey.java @@ -0,0 +1,123 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.configuration; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.io.Serializable; + +/** + * Identity class of {@link BaselinedDocument} objects defined as an embeddable object in order + * to be used inside the baselined documents map in the {@link DocumentCollection} class. + * + * @author Taylor LABEJOF + * @version 2.0, 25/08/14 + * @since V2.0 + */ +@Embeddable +public class BaselinedDocumentKey implements Serializable { + @Column(name = "DOCUMENTCOLLECTION_ID", nullable = false, insertable = false, updatable = false) + private int documentCollectionId; + @Column(name = "TARGET_WORKSPACE_ID", length = 100, nullable = false, insertable = false, updatable = false) + private String targetDocumentWorkspaceId = ""; + @Column(name = "TARGET_DOCUMENTMASTER_ID", length = 100, nullable = false, insertable = false, updatable = false) + private String targetDocumentId = ""; + @Column(name = "TARGET_DOCREVISION_VERSION", length = 10, nullable = false, insertable = false, updatable = false) + private String targetDocumentVersion = ""; + + public BaselinedDocumentKey() { + } + + public BaselinedDocumentKey(int documentCollectionId, String workspaceId, String documentMasterId, String targetDocumentVersion) { + this.documentCollectionId = documentCollectionId; + this.targetDocumentWorkspaceId = workspaceId; + this.targetDocumentId = documentMasterId; + this.targetDocumentVersion = targetDocumentVersion; + } + + public int getDocumentCollectionId() { + return documentCollectionId; + } + + public void setDocumentCollectionId(int documentCollectionId) { + this.documentCollectionId = documentCollectionId; + } + + public String getTargetDocumentWorkspaceId() { + return targetDocumentWorkspaceId; + } + + public void setTargetDocumentWorkspaceId(String targetDocumentWorkspaceId) { + this.targetDocumentWorkspaceId = targetDocumentWorkspaceId; + } + + public String getTargetDocumentId() { + return targetDocumentId; + } + + public void setTargetDocumentId(String targetDocumentId) { + this.targetDocumentId = targetDocumentId; + } + + public String getTargetDocumentVersion() { + return targetDocumentVersion; + } + + public void setTargetDocumentVersion(String targetDocumentVersion) { + this.targetDocumentVersion = targetDocumentVersion; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()){ + return false; + } + + BaselinedDocumentKey that = (BaselinedDocumentKey) o; + + if (documentCollectionId != that.documentCollectionId){ + return false; + } + if (targetDocumentId != null ? !targetDocumentId.equals(that.targetDocumentId) : that.targetDocumentId != null) { + return false; + } + if (targetDocumentVersion != null ? !targetDocumentVersion.equals(that.targetDocumentVersion) : that.targetDocumentVersion != null) { + return false; + } + if (targetDocumentWorkspaceId != null ? !targetDocumentWorkspaceId.equals(that.targetDocumentWorkspaceId) : that.targetDocumentWorkspaceId != null) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = documentCollectionId; + result = 31 * result + (targetDocumentWorkspaceId != null ? targetDocumentWorkspaceId.hashCode() : 0); + result = 31 * result + (targetDocumentId != null ? targetDocumentId.hashCode() : 0); + result = 31 * result + (targetDocumentVersion != null ? targetDocumentVersion.hashCode() : 0); + return result; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedFolder.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedFolder.java new file mode 100644 index 0000000000..ecc4d707fa --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedFolder.java @@ -0,0 +1,191 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.configuration; + + +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.document.Folder; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Class link that gathers a document collection and a given folder. + * + * @author Taylor LABEJOF + * @version 2.0, 25/08/14 + * @since V2.0 + */ + +@Table(name="BASELINEDFOLDER") +@Entity +public class BaselinedFolder implements Serializable, Comparable<BaselinedFolder> { + @EmbeddedId + private BaselinedFolderKey baselinedFolderKey; + + @ManyToOne(optional=false, fetch=FetchType.EAGER) + @JoinColumn(name="FOLDERCOLLECTION_ID", referencedColumnName="ID") + private FolderCollection folderCollection; + + @Column(name="COMPLETEPATH") + private String completePath=""; + + @ManyToOne + @JoinColumns({ + @JoinColumn(name="PARENT_FOLDERCOLLECTION_ID", referencedColumnName="FOLDERCOLLECTION_ID", insertable = true, updatable = true), + @JoinColumn(name="PARENT_COMPLETEPATH", referencedColumnName="COMPLETEPATH", insertable = true, updatable = true), + }) + private BaselinedFolder parentFolder; + + @ManyToMany + @JoinTable(name = "BASELINEDFOLDER_DOCITERATION") + private List<DocumentIteration> documentIterations = new ArrayList<>(); + + + public BaselinedFolder(){ + } + public BaselinedFolder(FolderCollection folderCollection, String completePath) { + this.baselinedFolderKey = new BaselinedFolderKey(folderCollection.getId(),completePath); + this.folderCollection = folderCollection; + this.completePath = completePath; + if(!isRoot() && !isHome()){ + int index = completePath.lastIndexOf('/'); + parentFolder = new BaselinedFolder(folderCollection, completePath.substring(0, index)); + } + } + public BaselinedFolder(FolderCollection folderCollection, Folder folder) { + this(folderCollection,folder.getCompletePath()); + } + + public BaselinedFolderKey getBaselinedFolderKey() { + return baselinedFolderKey; + } + + @XmlTransient + public FolderCollection getFolderCollection() { + return folderCollection; + } + public int getFolderCollectionId() { + return baselinedFolderKey.getFolderCollection(); + } + public void setFolderCollection(FolderCollection folderCollection) { + this.folderCollection = folderCollection; + } + + public String getCompletePath() { + return completePath; + } + public void setCompletePath(String completePath) { + this.completePath = completePath; + } + + public BaselinedFolder getParentFolder() { + return parentFolder; + } + public void setParentFolder(BaselinedFolder parentFolder) { + this.parentFolder = parentFolder; + } + + public List<DocumentIteration> getDocumentIterations() { + return documentIterations; + } + public DocumentIteration getDocumentIteration(DocumentRevisionKey documentRevisionKey) { + for(DocumentIteration docI : documentIterations){ + if(docI.getKey().getDocumentRevision().equals(documentRevisionKey)){ + return docI; + } + } + return null; + } + public boolean hasDocumentRevision(DocumentRevisionKey documentRevisionKey){ + for(DocumentIteration docI : documentIterations){ + if(docI.getKey().getDocumentRevision().equals(documentRevisionKey)){ + return true; + } + } + return false; + } + public void addDocumentIteration(DocumentIteration documentIteration){ + documentIterations.add(documentIteration); + } + + public boolean isRoot() { + return !completePath.contains("/"); + } + public boolean isHome() { + try { + int index = completePath.lastIndexOf('/'); + return completePath.charAt(index+1) == '~'; + } catch (IndexOutOfBoundsException pIOOBEx) { + Logger.getLogger(BaselinedFolder.class.getName()).log(Level.FINEST,null, pIOOBEx); + return false; + } + } + + public String getShortName() { + if(isRoot()) { + return completePath; + } + + int index = completePath.lastIndexOf('/'); + return completePath.substring(index + 1); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BaselinedFolder that = (BaselinedFolder) o; + + return completePath.equals(that.completePath) && + folderCollection.equals(that.folderCollection); + + } + + @Override + public int hashCode() { + int result = folderCollection.hashCode(); + result = 31 * result + completePath.hashCode(); + return result; + } + + /** + * Compare to balinedFolder of the same Collection + * @param baselinedFolder Baselined to compare + * @return <0 if A lt B; 0 if A = B; >0 if A gt B + */ + @Override + public int compareTo(BaselinedFolder baselinedFolder) { + int compCollection = getFolderCollectionId()-baselinedFolder.getFolderCollectionId(); + return compCollection==0 ? completePath.compareTo(baselinedFolder.completePath) : compCollection; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedFolderKey.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedFolderKey.java new file mode 100644 index 0000000000..79aadba7ca --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedFolderKey.java @@ -0,0 +1,87 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.configuration; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.io.Serializable; + +/** + * Identity class of {@link BaselinedFolder} objects defined as an embeddable object in order + * to be used inside the baselined documents map in the {@link com.docdoku.core.configuration.DocumentCollection} class. + * + * @author Taylor LABEJOF + * @version 2.0, 25/08/14 + * @since V2.0 + */ +@Embeddable +public class BaselinedFolderKey implements Serializable{ + @Column(name = "FOLDERCOLLECTION_ID", nullable = false, insertable = false, updatable = false) + private int folderCollection; + @Column(name = "COMPLETEPATH", nullable = false, insertable = false, updatable = false) + private String completePath; + + + public BaselinedFolderKey(){ + } + public BaselinedFolderKey(int folderCollection, String completePath) { + this.folderCollection = folderCollection; + this.completePath = completePath; + } + + public int getFolderCollection() { + return folderCollection; + } + public void setFolderCollection(int folderCollection) { + this.folderCollection = folderCollection; + } + + public String getCompletePath() { + return completePath; + } + public void setCompletePath(String completePath) { + this.completePath = completePath; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BaselinedFolderKey that = (BaselinedFolderKey) o; + + return folderCollection == that.folderCollection + && !(completePath != null ? !completePath.equals(that.completePath) : that.completePath != null); + + } + + @Override + public int hashCode() { + int result = folderCollection; + result = 31 * result + (completePath != null ? completePath.hashCode() : 0); + return result; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedPart.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedPart.java new file mode 100644 index 0000000000..c6d18d35b2 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedPart.java @@ -0,0 +1,149 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.configuration; + + +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartIterationKey; +import com.docdoku.core.product.PartRevision; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Class link that gathers a part collection and a given part iteration. + * + * @author Florent Garin + * @version 2.0, 15/05/13 + * @since V2.0 + */ + +@Table(name = "BASELINEDPART") +@Entity +@NamedQueries({ + @NamedQuery(name = "BaselinedPart.existBaselinedPart", query = "SELECT count(b) FROM BaselinedPart b WHERE b.baselinedPartKey.targetPartNumber = :partNumber AND b.baselinedPartKey.targetPartWorkspaceId = :workspaceId"), + @NamedQuery(name = "BaselinedPart.findByReference", query = "SELECT b FROM BaselinedPart b WHERE b.partCollection.id = :partCollection AND b.baselinedPartKey.targetPartNumber LIKE :id") +}) +public class BaselinedPart implements Serializable { + + @EmbeddedId + private BaselinedPartKey baselinedPartKey; + + //@MapsId("partCollectionId") + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumn(name = "PARTCOLLECTION_ID", referencedColumnName = "ID") + private PartCollection partCollection; + + + @ManyToOne(optional = false, fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "TARGET_ITERATION", referencedColumnName = "ITERATION"), + @JoinColumn(name = "TARGET_PARTMASTER_PARTNUMBER", referencedColumnName = "PARTMASTER_PARTNUMBER"), + @JoinColumn(name = "TARGET_PARTREVISION_VERSION", referencedColumnName = "PARTREVISION_VERSION"), + @JoinColumn(name = "TARGET_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private PartIteration targetPart; + + + @Column(name = "TARGET_ITERATION", nullable = false, insertable = false, updatable = false) + private int targetPartIteration; + + @Column(name = "TARGET_PARTREVISION_VERSION", length = 10, nullable = false, insertable = false, updatable = false) + private String targetPartVersion = ""; + + public BaselinedPart() { + } + + public BaselinedPart(PartCollection partCollection, PartIteration targetPart) { + this.partCollection = partCollection; + this.targetPart = targetPart; + this.baselinedPartKey = new BaselinedPartKey(partCollection.getId(), targetPart.getWorkspaceId(), targetPart.getPartNumber()); + this.targetPartIteration = targetPart.getIteration(); + this.targetPartVersion = targetPart.getVersion(); + } + + + @XmlTransient + public PartCollection getPartCollection() { + return partCollection; + } + + public BaselinedPartKey getBaselinedPartKey() { + return baselinedPartKey; + } + + public PartIteration getTargetPart() { + return targetPart; + } + + public String getTargetPartVersion() { + return targetPartVersion; + } + + public void setTargetPartVersion(String targetPartVersion) { + this.targetPartVersion = targetPartVersion; + } + + public String getTargetPartNumber() { + return targetPart.getPartNumber(); + } + + public int getTargetPartIteration() { + return targetPartIteration; + } + + public void setTargetPartIteration(int targetPartIteration) { + this.targetPartIteration = targetPartIteration; + } + + public List<PartIterationKey> getReleasedIterations() { + List<PartIterationKey> partIterationKeyList = new ArrayList<>(); + for (PartRevision partRevision : targetPart.getPartRevision().getPartMaster().getPartRevisions()) { + if (partRevision.isReleased()) { + PartIterationKey partIterationKey = new PartIterationKey(partRevision.getWorkspaceId(), partRevision.getPartNumber(), partRevision.getVersion(), partRevision.getLastIteration().getIteration()); + partIterationKeyList.add(partIterationKey); + } + } + return partIterationKeyList; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BaselinedPart that = (BaselinedPart) o; + + return !(baselinedPartKey != null ? !baselinedPartKey.equals(that.baselinedPartKey) : that.baselinedPartKey != null); + } + + @Override + public int hashCode() { + return baselinedPartKey != null ? baselinedPartKey.hashCode() : 0; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedPartKey.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedPartKey.java new file mode 100644 index 0000000000..51ad15d594 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/BaselinedPartKey.java @@ -0,0 +1,102 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.configuration; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.io.Serializable; + +/** + * Identity class of {@link BaselinedPart} objects defined as an embeddable object in order + * to be used inside the baselined parts map in the {@link PartCollection} class. + * + * @author Florent Garin + */ +@Embeddable +public class BaselinedPartKey implements Serializable{ + @Column(name = "PARTCOLLECTION_ID", nullable = false, insertable = false, updatable = false) + private int partCollectionId; + + @Column(name = "TARGET_WORKSPACE_ID", length=100, nullable = false, insertable = false, updatable = false) + private String targetPartWorkspaceId=""; + + @Column(name = "TARGET_PARTMASTER_PARTNUMBER", length=100, nullable = false, insertable = false, updatable = false) + private String targetPartNumber=""; + + public BaselinedPartKey(){ + } + + public BaselinedPartKey(int partCollectionId, String targetPartWorkspaceId, String targetPartNumber) { + this.partCollectionId = partCollectionId; + this.targetPartWorkspaceId = targetPartWorkspaceId; + this.targetPartNumber = targetPartNumber; + } + + public int getPartCollectionId() { + return partCollectionId; + } + + public void setPartCollectionId(int partCollectionId) { + this.partCollectionId = partCollectionId; + } + + public String getTargetPartWorkspaceId() { + return targetPartWorkspaceId; + } + + public void setTargetPartWorkspaceId(String targetPartWorkspaceId) { + this.targetPartWorkspaceId = targetPartWorkspaceId; + } + + public String getTargetPartNumber() { + return targetPartNumber; + } + + public void setTargetPartNumber(String targetPartNumber) { + this.targetPartNumber = targetPartNumber; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BaselinedPartKey)) { + return false; + } + + BaselinedPartKey that = (BaselinedPartKey) o; + + return partCollectionId == that.partCollectionId && + targetPartNumber.equals(that.targetPartNumber) && + targetPartWorkspaceId.equals(that.targetPartWorkspaceId); + + } + + @Override + public int hashCode() { + int result = partCollectionId; + result = 31 * result + targetPartWorkspaceId.hashCode(); + result = 31 * result + targetPartNumber.hashCode(); + return result; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/CascadeResult.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/CascadeResult.java new file mode 100644 index 0000000000..7b203df780 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/CascadeResult.java @@ -0,0 +1,58 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.configuration; + +import java.io.Serializable; + +public class CascadeResult implements Serializable{ + + private int succeedAttempts; + private int failedAttempts; + + public CascadeResult() { + this.succeedAttempts = 0; + this.failedAttempts = 0; + } + + public void incSucceedAttempts() { + this.succeedAttempts++; + } + + public void incFailedAttempts() { + this.failedAttempts++; + } + + public int getSucceedAttempts() { + return succeedAttempts; + } + + public void setSucceedAttempts(int succeedAttempts) { + this.succeedAttempts = succeedAttempts; + } + + public int getFailedAttempts() { + return failedAttempts; + } + + public void setFailedAttempts(int failedAttempts) { + this.failedAttempts = failedAttempts; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/DocumentBaseline.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/DocumentBaseline.java new file mode 100644 index 0000000000..af568a0d6a --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/DocumentBaseline.java @@ -0,0 +1,170 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.configuration; + +import com.docdoku.core.common.Workspace; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.document.Folder; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; +import java.util.Map; + +/** + * Baselines refer to specific configurations of documents, they could be seen as + * "snapshots in time" of folders with their content. More concretely, baselines are collections + * of items (like documents and folders) at a given iteration. + * + * @author Taylor LABEJOF + * @version 2.0, 25/08/14 + * @since V2.0 + */ +@Table(name="DOCUMENTBASELINE") +@Entity +public class DocumentBaseline implements Serializable { + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Id + private int id; + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "ID") + }) + private Workspace workspace; + + @Column(nullable = false) + private String name; + + @Lob + private String description; + + @Temporal(TemporalType.TIMESTAMP) + private Date creationDate; + + @OneToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY, orphanRemoval = true) + private FolderCollection folderCollection =new FolderCollection(); + + + public DocumentBaseline() { + } + public DocumentBaseline(Workspace workspace, String name, String description) { + this.workspace = workspace; + this.name = name; + this.description = description; + this.creationDate = new Date(); + } + + public Map<BaselinedFolderKey, BaselinedFolder> getBaselinedFolders() { + return folderCollection.getBaselinedFolders(); + } + public void removeAllBaselinedFolders() { + folderCollection.removeAllBaselinedFolders(); + } + + public BaselinedFolder addBaselinedFolder(Folder folder){ + return folderCollection.addBaselinedFolder(folder); + } + public BaselinedFolder addBaselinedFolder(BaselinedFolder baselinedFolder){ + return folderCollection.addBaselinedFolder(baselinedFolder); + } + public boolean hasBasedLinedFolder(String completePath){ + return folderCollection.hasBaselinedFolder(completePath); + } + public BaselinedFolder getBaselinedFolder(String completePath){ + return folderCollection.getBaselinedFolder(completePath); + } + + public void addBaselinedDocument(DocumentIteration targetDocument){ + Folder location = targetDocument.getDocumentRevision().getLocation(); + BaselinedFolder baselinedFolder = folderCollection.getBaselinedFolder(location.getCompletePath()); + if(baselinedFolder==null) { + baselinedFolder = addBaselinedFolder(location); + } + baselinedFolder.addDocumentIteration(targetDocument); + } + public boolean hasBaselinedDocument(DocumentRevisionKey documentRevisionKey){ + if(folderCollection != null){ + return folderCollection.hasDocumentRevision(documentRevisionKey); + } + return false; + } + public DocumentIteration getBaselinedDocument(DocumentRevisionKey documentRevisionKey){ + return (folderCollection!=null) ? folderCollection.getDocumentIteration(documentRevisionKey) : null; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public Date getCreationDate() { + return (creationDate!=null) ? (Date) creationDate.clone(): null; + } + public void setCreationDate(Date creationDate) { + this.creationDate = (Date) creationDate.clone(); + } + + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + + public FolderCollection getFolderCollection() { + return folderCollection; + } + + public Workspace getWorkspace() { + return workspace; + } + public void setWorkspace(Workspace workspace) { + this.workspace = workspace; + } + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof DocumentBaseline)) { + return false; + } + + DocumentBaseline baseline = (DocumentBaseline) o; + return id == baseline.id; + } + + @Override + public int hashCode() { + return id; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/DocumentCollection.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/DocumentCollection.java new file mode 100644 index 0000000000..12face6d89 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/DocumentCollection.java @@ -0,0 +1,125 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.configuration; + +import com.docdoku.core.common.User; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevisionKey; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * This class maintains a collection of document iterations which cannot hold + * more than one {@link com.docdoku.core.document.DocumentIteration} in + * the same {@link com.docdoku.core.document.DocumentRevision}. + * + * @author Taylor LABEJOF + * @version 2.0, 25/08/14 + * @since V2.0 + */ +@Table(name="DOCUMENTCOLLECTION") +@Entity +public class DocumentCollection implements Serializable { + + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Id + private int id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + @Temporal(TemporalType.TIMESTAMP) + private Date creationDate; + + @MapKey(name="baselinedDocumentKey") + @OneToMany(mappedBy="documentCollection", cascade=CascadeType.ALL, fetch=FetchType.LAZY, orphanRemoval=true) + private Map<BaselinedDocumentKey, BaselinedDocument> baselinedDocuments = new HashMap<>(); + + public DocumentCollection() { + } + + public Map<BaselinedDocumentKey, BaselinedDocument> getBaselinedDocuments() { + return baselinedDocuments; + } + public void removeAllBaselinedDocuments() { + baselinedDocuments.clear(); + } + + public BaselinedDocument addBaselinedDocument(DocumentIteration targetDocument){ + BaselinedDocument baselinedDocument = new BaselinedDocument(this, targetDocument); + baselinedDocuments.put(baselinedDocument.getKey(),baselinedDocument); + return baselinedDocument; + } + + public BaselinedDocument getBaselinedDocument(BaselinedDocumentKey baselinedDocumentKey){ + return baselinedDocuments.get(baselinedDocumentKey); + } + public boolean hasBaselinedDocument(DocumentRevisionKey documentRevisionKey){ + BaselinedDocumentKey baselinedDocumentKey = new BaselinedDocumentKey(id,documentRevisionKey.getWorkspaceId(), documentRevisionKey.getDocumentMasterId(), documentRevisionKey.getVersion()); + return baselinedDocuments.containsKey(baselinedDocumentKey); + } + + public Date getCreationDate() { + return (creationDate!=null) ? (Date) creationDate.clone() : null; + } + public void setCreationDate(Date creationDate) { + this.creationDate = (creationDate!=null) ? (Date) creationDate.clone() : null; + } + + public User getAuthor() { + return author; + } + public void setAuthor(User author) { + this.author = author; + } + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof DocumentCollection)) { + return false; + } + + DocumentCollection collection = (DocumentCollection) o; + return id == collection.id; + } + + @Override + public int hashCode() { + return id; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/DocumentConfigSpec.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/DocumentConfigSpec.java new file mode 100644 index 0000000000..728e44dbc6 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/DocumentConfigSpec.java @@ -0,0 +1,43 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.configuration; + +import com.docdoku.core.common.User; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevision; + +import java.io.Serializable; + +/** + * @author Morgan Guimard + */ + +public abstract class DocumentConfigSpec implements Serializable{ + + public DocumentConfigSpec() { + } + public DocumentConfigSpec(User user) { + } + public DocumentConfigSpec(DocumentBaseline documentBaseline, User user) { + } + public abstract DocumentIteration filter(DocumentRevision documentRevision); +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/FolderCollection.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/FolderCollection.java new file mode 100644 index 0000000000..98a617afef --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/FolderCollection.java @@ -0,0 +1,149 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.configuration; + +import com.docdoku.core.common.User; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.document.Folder; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * This class maintains a collection of folders. + * + * FolderCollection is a foundation for the definition of {@link DocumentBaseline}. + * + * @author Taylor LABEJOF + * @version 2.0, 25/08/14 + * @since V2.0 + */ +@Table(name="FOLDERCOLLECTION") +@Entity +public class FolderCollection implements Serializable { + + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Id + private int id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + @Temporal(TemporalType.TIMESTAMP) + private Date creationDate; + + @MapKey(name="baselinedFolderKey") + @OneToMany(mappedBy="folderCollection", cascade=CascadeType.ALL, fetch=FetchType.LAZY, orphanRemoval=true) + private Map<BaselinedFolderKey, BaselinedFolder> baselinedFolders = new HashMap<>(); + + + public FolderCollection() { + } + + public Map<BaselinedFolderKey, BaselinedFolder> getBaselinedFolders() { + return baselinedFolders; + } + public void removeAllBaselinedFolders() { + baselinedFolders.clear(); + } + + public BaselinedFolder addBaselinedFolder(Folder folder){ + BaselinedFolder baselinedFolder = new BaselinedFolder(this,folder); + baselinedFolders.put(baselinedFolder.getBaselinedFolderKey(),baselinedFolder); + return baselinedFolder; + } + public BaselinedFolder addBaselinedFolder(BaselinedFolder baselinedFolder){ + baselinedFolders.put(baselinedFolder.getBaselinedFolderKey(),baselinedFolder); + return baselinedFolder; + } + + public BaselinedFolder getBaselinedFolder(String completePath){ + return baselinedFolders.get(new BaselinedFolderKey(id,completePath)); + } + public boolean hasBaselinedFolder(String completePath){ + return baselinedFolders.containsKey(new BaselinedFolderKey(id,completePath)); + } + + public DocumentIteration getDocumentIteration(DocumentRevisionKey documentRevisionKey){ + for(BaselinedFolder folder : baselinedFolders.values()){ + DocumentIteration documentIteration = folder.getDocumentIteration(documentRevisionKey); + if(documentIteration!=null){ + return documentIteration; + } + } + return null; + } + public boolean hasDocumentRevision(DocumentRevisionKey documentRevisionKey){ + for(BaselinedFolder folder : baselinedFolders.values()){ + boolean hasDocument = folder.hasDocumentRevision(documentRevisionKey); + if(hasDocument){ + return true; + } + } + return false; + } + + public Date getCreationDate() { + return (creationDate!=null)? (Date) creationDate.clone() : null; + } + public void setCreationDate(Date creationDate) { + this.creationDate = (creationDate!=null)? (Date) creationDate.clone() : null; + } + + public User getAuthor() { + return author; + } + public void setAuthor(User author) { + this.author = author; + } + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof FolderCollection)) { + return false; + } + + FolderCollection collection = (FolderCollection) o; + return id == collection.id; + } + + @Override + public int hashCode() { + return id; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/PSFilter.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/PSFilter.java new file mode 100644 index 0000000000..3b5abecec0 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/PSFilter.java @@ -0,0 +1,43 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.configuration; + +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartLink; +import com.docdoku.core.product.PartMaster; + +import java.io.Serializable; +import java.util.List; + +/** + * A PSFilter is used to select for each {@link com.docdoku.core.product.PartMaster}s + * the right {@link com.docdoku.core.product.PartIteration}s + * + * @author Morgan Guimard + */ + +public abstract class PSFilter implements Serializable{ + public PSFilter() { + } + public abstract List<PartIteration> filter(PartMaster partMaster); + public abstract List<PartLink> filter(List<PartLink> path); +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/PartCollection.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/PartCollection.java new file mode 100644 index 0000000000..dbb06bfd63 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/PartCollection.java @@ -0,0 +1,131 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.configuration; + +import com.docdoku.core.common.User; +import com.docdoku.core.product.PartIteration; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * This class maintains a collection of part iterations which cannot hold + * more than one {@link com.docdoku.core.product.PartIteration} linked + * to the same {@link com.docdoku.core.product.PartMaster}. + * + * PartCollection is a foundation for the definition of {@link ProductBaseline} + * and {@link ProductInstanceIteration}. + * + * @author Florent Garin + * @version 2.0, 25/02/14 + * @since V2.0 + */ +@Table(name="PARTCOLLECTION") +@Entity +public class PartCollection implements Serializable { + + + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Id + private int id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + @Temporal(TemporalType.TIMESTAMP) + private java.util.Date creationDate; + + @MapKey(name="baselinedPartKey") + @OneToMany(mappedBy="partCollection", cascade=CascadeType.ALL, fetch=FetchType.LAZY, orphanRemoval=true) + private Map<BaselinedPartKey, BaselinedPart> baselinedParts=new HashMap<>(); + + public PartCollection() { + } + + public void removeAllBaselinedParts() { + baselinedParts.clear(); + } + + public Map<BaselinedPartKey, BaselinedPart> getBaselinedParts() { + return baselinedParts; + } + + public void addBaselinedPart(PartIteration targetPart){ + BaselinedPart baselinedPart = new BaselinedPart(this, targetPart); + baselinedParts.put(baselinedPart.getBaselinedPartKey(),baselinedPart); + } + + public BaselinedPart getBaselinedPart(BaselinedPartKey baselinedPartKey){ + return baselinedParts.get(baselinedPartKey); + } + + public boolean hasBaselinedPart(BaselinedPartKey baselinedPartKey){ + return baselinedParts.containsKey(baselinedPartKey); + } + + public Date getCreationDate() { + return (creationDate!=null) ? (Date) creationDate.clone() : null; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = (creationDate!=null) ? (Date) creationDate.clone() : null; + } + + public User getAuthor() { + return author; + } + + public void setAuthor(User author) { + this.author = author; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PartCollection)) { + return false; + } + + PartCollection collection = (PartCollection) o; + return id == collection.id; + } + + @Override + public int hashCode() { + return id; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/PathChoice.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/PathChoice.java new file mode 100644 index 0000000000..17ff0ba4cf --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/PathChoice.java @@ -0,0 +1,57 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.configuration; + +import com.docdoku.core.product.PartLink; + +import java.util.ArrayList; +import java.util.List; + +public class PathChoice { + + private List<ResolvedPartLink> resolvedPath = new ArrayList<>(); + private PartLink partUsageLink; + + public PathChoice() { + } + + public PathChoice(List<ResolvedPartLink> resolvedPath, PartLink partUsageLink) { + this.resolvedPath = resolvedPath; + this.partUsageLink = partUsageLink; + } + + public List<ResolvedPartLink> getResolvedPath() { + return resolvedPath; + } + + public void setResolvedPath(List<ResolvedPartLink> resolvedPath) { + this.resolvedPath = resolvedPath; + } + + public PartLink getPartUsageLink() { + return partUsageLink; + } + + public void setPartUsageLink(PartLink partUsageLink) { + this.partUsageLink = partUsageLink; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/PathDataIteration.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/PathDataIteration.java new file mode 100644 index 0000000000..e817ca6528 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/PathDataIteration.java @@ -0,0 +1,198 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.configuration; + + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.FileHolder; +import com.docdoku.core.document.DocumentLink; +import com.docdoku.core.meta.InstanceAttribute; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.*; + +/** + * @author Chadid Asmae + */ +@Table(name="PATHDATAITERATION") +@Entity +@IdClass(PathDataIterationKey.class) +@NamedQueries({ + @NamedQuery(name = "PathDataIteration.findDistinctInstanceAttributes", query = "SELECT DISTINCT i FROM ProductInstanceMaster pim JOIN pim.productInstanceIterations pi JOIN pi.pathDataMasterList pdm JOIN pdm.pathDataIterations pdi JOIN pdi.instanceAttributes i WHERE pim.instanceOf.workspace.id = :workspaceId"), + @NamedQuery(name = "PathDataIteration.findLastIterationFromProductInstanceIteration", query = "SELECT DISTINCT pdi FROM ProductInstanceIteration pi JOIN pi.pathDataMasterList pdm JOIN pdm.pathDataIterations pdi WHERE pi = :productInstanceIteration AND pdi.iteration = (select max(otherPdi.iteration) from pdm.pathDataIterations otherPdi)") +}) +public class PathDataIteration implements Serializable, FileHolder { + + @Id + @ManyToOne(optional=false, fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="PATHDATAMASTER_ID", referencedColumnName="ID") + }) + private PathDataMaster pathDataMaster; + + @Id + @Column(name="ITERATION") + private int iteration; + + @Lob + private String iterationNote; + + @Temporal(TemporalType.TIMESTAMP) + private java.util.Date dateIteration; + + @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @OrderColumn(name="ATTRIBUTE_ORDER") + @JoinTable(name = "PATHDATAITERATION_ATTRIBUTE", + inverseJoinColumns = { + @JoinColumn(name = "INSTANCEATTRIBUTE_ID", referencedColumnName = "ID") + }, + joinColumns = { + @JoinColumn(name="PATHDATA_ITERATION", referencedColumnName="ITERATION"), + @JoinColumn(name="PATHDATAMASTER_ID", referencedColumnName="PATHDATAMASTER_ID") + }) + private List<InstanceAttribute> instanceAttributes = new ArrayList<>(); + + + @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinTable(name = "PATHDATAITERATION_DOCUMENTLINK", + inverseJoinColumns = { + @JoinColumn(name = "DOCUMENTLINK_ID", referencedColumnName = "ID") + }, + joinColumns = { + @JoinColumn(name="PATHDATA_ITERATION", referencedColumnName="ITERATION"), + @JoinColumn(name="PATHDATAMASTER_ID", referencedColumnName="PATHDATAMASTER_ID") + }) + private Set<DocumentLink> linkedDocuments = new HashSet<>(); + + @OneToMany(cascade = {CascadeType.REMOVE, CascadeType.REFRESH}, fetch = FetchType.EAGER) + @JoinTable(name = "PATHDATAITERATION_BINRES", + inverseJoinColumns = { + @JoinColumn(name = "ATTACHEDFILE_FULLNAME", referencedColumnName = "FULLNAME") + }, + joinColumns = { + @JoinColumn(name="PATHDATA_ITERATION", referencedColumnName="ITERATION"), + @JoinColumn(name="PATHDATAMASTER_ID", referencedColumnName="PATHDATAMASTER_ID") + }) + private Set<BinaryResource> attachedFiles = new HashSet<>(); + + public PathDataIteration() { + } + + public PathDataIteration(int iteration, PathDataMaster pathDataMaster,Date date) { + setIteration(iteration); + setPathDataMaster(pathDataMaster); + setDateIteration(date); + } + + @XmlTransient + public PathDataMaster getPathDataMaster() { + return pathDataMaster; + } + + public void setPathDataMaster(PathDataMaster pathDataMaster) { + this.pathDataMaster = pathDataMaster; + } + + public int getIteration() { + return iteration; + } + + public void setIteration(int iteration) { + this.iteration = iteration; + } + + public Date getDateIteration() { + return dateIteration; + } + + public void setDateIteration(Date dateIteration) { + this.dateIteration = dateIteration; + } + + public Set<DocumentLink> getLinkedDocuments() { + return linkedDocuments; + } + + public void setLinkedDocuments(Set<DocumentLink> linkedDocuments) { + this.linkedDocuments = linkedDocuments; + } + + public List<InstanceAttribute> getInstanceAttributes() { + return instanceAttributes; + } + + public void setInstanceAttributes(List<InstanceAttribute> instanceAttributes) { + this.instanceAttributes = instanceAttributes; + } + + public String getIterationNote() { + return iterationNote; + } + + public void setIterationNote(String iterationNote) { + this.iterationNote = iterationNote; + } + + @Override + public Set<BinaryResource> getAttachedFiles() { + return attachedFiles; + } + + public void setAttachedFiles(Set<BinaryResource> attachedFiles) { + this.attachedFiles = attachedFiles; + } + + public void addFile(BinaryResource binaryResource) { + attachedFiles.add(binaryResource); + } + + public void removeFile(BinaryResource file) { + attachedFiles.remove(file); + } + + @Override + public boolean equals(Object o) { + if (this == o){ + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PathDataIteration that = (PathDataIteration) o; + + if (iteration != that.iteration) { + return false; + } + if (pathDataMaster != null ? !pathDataMaster.equals(that.pathDataMaster) : that.pathDataMaster != null) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = pathDataMaster != null ? pathDataMaster.hashCode() : 0; + result = 31 * result + iteration; + return result; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/PathDataIterationKey.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/PathDataIterationKey.java new file mode 100644 index 0000000000..5295c80aab --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/PathDataIterationKey.java @@ -0,0 +1,87 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.configuration; + +import java.io.Serializable; + +/** + * Identity class of {@link com.docdoku.core.configuration.PathDataIteration}. + * + * @author Florent Garin + */ +public class PathDataIterationKey implements Serializable{ + + private int pathDataMaster; + private int iteration; + + public PathDataIterationKey(){ + } + + + public PathDataIterationKey(int pathDataMaster, int iteration) { + this.pathDataMaster = pathDataMaster; + this.iteration = iteration; + } + + public int getPathDataMaster() { + return pathDataMaster; + } + + public void setPathDataMaster(int pathDataMaster) { + this.pathDataMaster = pathDataMaster; + } + + public int getIteration() { + return iteration; + } + + public void setIteration(int iteration) { + this.iteration = iteration; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PathDataIterationKey that = (PathDataIterationKey) o; + + if (iteration != that.iteration) { + return false; + } + if (pathDataMaster != that.pathDataMaster) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = pathDataMaster; + result = 31 * result + iteration; + return result; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/PathDataMaster.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/PathDataMaster.java new file mode 100644 index 0000000000..e7e7c4f237 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/PathDataMaster.java @@ -0,0 +1,121 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.configuration; + + +import javax.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @author Morgan Guimard + */ +@Table(name="PATHDATAMASTER") +@Entity +@NamedQueries({ + @NamedQuery(name="PathDataMaster.findByPathIdAndProductInstanceIteration", query="SELECT p FROM PathDataMaster p JOIN ProductInstanceIteration l WHERE p member of l.pathDataMasterList and p.id = :pathId and l = :productInstanceIteration"), + @NamedQuery(name="PathDataMaster.findByPathAndProductInstanceIteration", query="SELECT p FROM PathDataMaster p JOIN ProductInstanceIteration l WHERE p member of l.pathDataMasterList and p.path = :path and l = :productInstanceIteration") +}) +public class PathDataMaster implements Serializable{ + + @GeneratedValue(strategy= GenerationType.IDENTITY) + @Id + @Column(name="ID") + private int id; + + private String path; + + @OneToMany(mappedBy = "pathDataMaster", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @OrderBy("iteration ASC") + private List<PathDataIteration> pathDataIterations = new ArrayList<>(); + + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public List<PathDataIteration> getPathDataIterations() { + return pathDataIterations; + } + + public void setPathDataIterations(List<PathDataIteration> pathDataIterations) { + this.pathDataIterations = pathDataIterations; + } + + public PathDataIteration createNextIteration() { + + PathDataIteration lastPathIteration = this.getLastIteration(); + int iteration; + if(lastPathIteration==null){ + iteration = 1; + }else{ + iteration = lastPathIteration.getIteration()+1; + } + PathDataIteration pathIteration = new PathDataIteration(iteration,this,new Date()); + this.pathDataIterations.add(pathIteration); + return pathIteration; + } + + public PathDataIteration getLastIteration() { + int index = this.pathDataIterations.size()-1; + if(index < 0) { + return null; + } else { + return this.pathDataIterations.get(index); + } + } + + @Override + public boolean equals(Object o) { + if (this == o){ + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PathDataMaster that = (PathDataMaster) o; + + if (id != that.id){ + return false; + } + + return true; + } + + @Override + public int hashCode() { + return id; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductBaseline.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductBaseline.java new file mode 100644 index 0000000000..0953d3837e --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductBaseline.java @@ -0,0 +1,310 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.configuration; + +import com.docdoku.core.common.User; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.product.ConfigurationItem; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PathToPathLink; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.*; + +/** + * Baseline refers to a specific configuration, it could be seen as + * "snapshots in time" of configurations. More concretely, baselines are collections + * of items (like parts) at a specified iteration. + * Within a baseline, there must not be two different iterations of the same part. + * Because {@link com.docdoku.core.product.PartIteration} may reference documents + * baselines capture also {@link com.docdoku.core.configuration.DocumentCollection}. + * + * @author Florent Garin + * @version 2.0, 15/05/13 + * @since V2.0 + */ +@Table(name = "PRODUCTBASELINE") +@Entity +@NamedQueries({ + @NamedQuery(name = "ProductBaseline.findByConfigurationItemId", query = "SELECT b FROM ProductBaseline b WHERE b.configurationItem.id = :ciId AND b.configurationItem.workspace.id = :workspaceId"), + @NamedQuery(name = "ProductBaseline.getBaselinesForPartRevision", query = "SELECT b FROM ProductBaseline b WHERE b.partCollection IN (SELECT bl.partCollection FROM BaselinedPart bl WHERE bl.targetPart.partRevision = :partRevision)"), + @NamedQuery(name = "ProductBaseline.findObsoletePartRevisions", query = "SELECT pr FROM PartRevision pr JOIN ProductBaseline pb JOIN BaselinedPart bp WHERE pb = :productBaseline AND pb.partCollection.id = bp.partCollection.id AND bp.targetPart.partRevision.status = com.docdoku.core.product.PartRevision.RevisionStatus.OBSOLETE AND bp.targetPart.partRevision.partMasterWorkspaceId = :workspaceId") + +}) +public class ProductBaseline implements Serializable { + + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private int id; + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "CONFIGURATIONITEM_ID", referencedColumnName = "ID"), + @JoinColumn(name = "CONFIGURATIONITEM_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private ConfigurationItem configurationItem; + + @Column(nullable = false) + private String name; + + private BaselineType type = BaselineType.LATEST; + + @Lob + private String description; + + @Temporal(TemporalType.TIMESTAMP) + private java.util.Date creationDate; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) + private PartCollection partCollection = new PartCollection(); + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) + private DocumentCollection documentCollection = new DocumentCollection(); + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + + /** + * Set of substitute links (actually their path from the root node) + * that have been included into the baseline. + * Only selected substitute links are stored as part usage links are considered as the default + * choices for baselines. + * <p> + * Paths are strings made of ordered lists of usage link ids joined by "-". + */ + @ElementCollection(fetch = FetchType.LAZY) + @CollectionTable(name = "PRODUCTBASELINE_SUBSTITUTELINK", + joinColumns = { + @JoinColumn(name = "PRODUCTBASELINE_ID", referencedColumnName = "ID") + } + ) + private Set<String> substituteLinks = new HashSet<>(); + + /** + * Set of optional usage links (actually their path from the root node) + * that have been included into the baseline. + * <p> + * Paths are strings made of ordered lists of usage link ids joined by "-". + */ + @ElementCollection(fetch = FetchType.LAZY) + @CollectionTable(name = "PRODUCTBASELINE_OPTIONALLINK", + joinColumns = { + @JoinColumn(name = "PRODUCTBASELINE_ID", referencedColumnName = "ID") + } + ) + private Set<String> optionalUsageLinks = new HashSet<>(); + + @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinTable(name = "PRODUCTBASELINE_P2PLINK", + inverseJoinColumns = { + @JoinColumn(name = "PATHTOPATHLINK_ID", referencedColumnName = "ID") + }, + joinColumns = { + @JoinColumn(name = "PRODUCTBASELINE_ID", referencedColumnName = "ID") + }) + private List<PathToPathLink> pathToPathLinks = new ArrayList<>(); + + public enum BaselineType { + LATEST, RELEASED + } + + public ProductBaseline() { + } + + public ProductBaseline(User author, ConfigurationItem configurationItem, String name, BaselineType type, String description) { + this.author = author; + this.configurationItem = configurationItem; + this.name = name; + this.type = type; + this.description = description; + this.creationDate = new Date(); + } + + public Map<BaselinedPartKey, BaselinedPart> getBaselinedParts() { + return partCollection.getBaselinedParts(); + } + + public void removeAllBaselinedParts() { + partCollection.removeAllBaselinedParts(); + } + + public void addBaselinedPart(PartIteration targetPart) { + partCollection.addBaselinedPart(targetPart); + } + + public boolean hasBasedLinedPart(String targetPartWorkspaceId, String targetPartNumber) { + return partCollection.hasBaselinedPart(new BaselinedPartKey(partCollection.getId(), targetPartWorkspaceId, targetPartNumber)); + } + + public BaselinedPart getBaselinedPart(BaselinedPartKey baselinedPartKey) { + return partCollection.getBaselinedPart(baselinedPartKey); + } + + + public Map<BaselinedDocumentKey, BaselinedDocument> getBaselinedDocuments() { + return documentCollection.getBaselinedDocuments(); + } + + public void removeAllBaselinedDocuments() { + documentCollection.removeAllBaselinedDocuments(); + } + + public void addBaselinedDocument(DocumentIteration targetDocument) { + documentCollection.addBaselinedDocument(targetDocument); + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public BaselineType getType() { + return type; + } + + public void setType(BaselineType type) { + this.type = type; + } + + public Date getCreationDate() { + return (creationDate != null) ? (Date) creationDate.clone() : null; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = (creationDate != null) ? (Date) creationDate.clone() : null; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Set<String> getOptionalUsageLinks() { + return optionalUsageLinks; + } + + public Set<String> getSubstituteLinks() { + return substituteLinks; + } + + public boolean removeOptionalUsageLink(String usageLinkPath) { + return optionalUsageLinks.remove(usageLinkPath); + } + + public boolean addOptionalUsageLink(String usageLinkPath) { + return optionalUsageLinks.add(usageLinkPath); + } + + public boolean removeSubstituteLink(String substituteLinkPath) { + return substituteLinks.remove(substituteLinkPath); + } + + public boolean addSubstituteLink(String substituteLinkPath) { + return substituteLinks.add(substituteLinkPath); + } + + public PartCollection getPartCollection() { + return partCollection; + } + + public DocumentCollection getDocumentCollection() { + return documentCollection; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public ConfigurationItem getConfigurationItem() { + return configurationItem; + } + + public void setConfigurationItem(ConfigurationItem configurationItem) { + this.configurationItem = configurationItem; + } + + public boolean hasSubstituteLink(String link) { + return substituteLinks.contains(link); + } + + public boolean isLinkOptional(String link) { + return optionalUsageLinks.contains(link); + } + + public User getAuthor() { + return author; + } + + public void setAuthor(User author) { + this.author = author; + } + + public List<PathToPathLink> getPathToPathLinks() { + return pathToPathLinks; + } + + public void setPathToPathLinks(List<PathToPathLink> pathToPathLinks) { + this.pathToPathLinks = pathToPathLinks; + } + + public void addPathToPathLink(PathToPathLink pathToPathLink) { + pathToPathLinks.add(pathToPathLink); + } + + public void removePathToPathLink(PathToPathLink pathToPathLink) { + pathToPathLinks.remove(pathToPathLink); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ProductBaseline)) { + return false; + } + + ProductBaseline productBaseline = (ProductBaseline) o; + return id == productBaseline.id; + } + + @Override + public int hashCode() { + return id; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductBaselineCreationReport.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductBaselineCreationReport.java new file mode 100644 index 0000000000..3063789ff7 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductBaselineCreationReport.java @@ -0,0 +1,72 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.configuration; + +import com.docdoku.core.product.PartRevision; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public class ProductBaselineCreationReport implements Serializable{ + ProductBaseline productBaseline; + String message; + Set<PartRevision> conflits; + + public ProductBaselineCreationReport(){ + + } + + public ProductBaselineCreationReport(ProductBaseline productBaseline) { + this.productBaseline = productBaseline; + this.message = null; + this.conflits = new HashSet<>(); + } + + public ProductBaseline getProductBaseline() { + return productBaseline; + } + public void setProductBaseline(ProductBaseline productBaseline) { + this.productBaseline = productBaseline; + } + + public String getMessage() { + return message; + } + public void setMessage(String message) { + this.message = message; + } + + public Set<PartRevision> getConflits() { + return conflits; + } + + public void setConflits(Set<PartRevision> conflits) { + this.conflits = conflits; + } + + public void addConflit(PartRevision partRevision) { + this.conflits.add(partRevision); + } + public void addConflits(Collection<PartRevision> partRevisions) { + this.conflits.addAll(partRevisions); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductConfigSpec.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductConfigSpec.java new file mode 100644 index 0000000000..1a2ac962e5 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductConfigSpec.java @@ -0,0 +1,72 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.configuration; + +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartLink; +import com.docdoku.core.product.PartMaster; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A ConfigSpec is used to select for each {@link PartMaster}s and {@link DocumentRevision}s + * the right {@link PartIteration} and {@link DocumentIteration} according to specific rules. + * + * @author Florent Garin + * @version 1.1, 30/10/11 + * @since V1.1 + */ +public abstract class ProductConfigSpec extends PSFilter implements Serializable{ + + public ProductConfigSpec() { + } + + // Config specs are strict and returns a single value + // Do not override them + @Override + public final List<PartLink> filter(List<PartLink> path) { + PartLink partLink = filterPartLink(path); + if(partLink != null){ + return Arrays.asList(partLink); + } + return new ArrayList<>(); + } + + @Override + public final List<PartIteration> filter(PartMaster partMaster) { + PartIteration partIteration = filterPartIteration(partMaster); + if(partIteration != null){ + return Arrays.asList(partIteration); + } + return new ArrayList<>(); + } + + // All config specs must implement a strict filter + public abstract PartIteration filterPartIteration(PartMaster partMaster); + public abstract PartLink filterPartLink(List<PartLink> path); + +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductConfiguration.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductConfiguration.java new file mode 100644 index 0000000000..2af498b28a --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductConfiguration.java @@ -0,0 +1,219 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.configuration; + +import com.docdoku.core.common.User; +import com.docdoku.core.product.ConfigurationItem; +import com.docdoku.core.security.ACL; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * Baseline refers to a specific configuration, it could be seen as + * "snapshots in time" of configurations. More concretely, baselines are collections + * of items (like parts) at a specified iteration. + * Within a baseline, there must not be two different iterations of the same part. + * + * @author Florent Garin + * @version 2.0, 15/05/13 + * @since V2.0 + */ +@Table(name="PRODUCTCONFIGURATION") +@Entity +@NamedQueries({ + @NamedQuery(name="ProductConfiguration.findByWorkspace",query="SELECT p FROM ProductConfiguration p WHERE p.configurationItem.workspace.id = :workspaceId"), + @NamedQuery(name="ProductConfiguration.findByConfigurationItem",query="SELECT p FROM ProductConfiguration p WHERE p.configurationItem.workspace.id = :workspaceId AND p.configurationItem.id = :configurationItemId") +}) +public class ProductConfiguration implements Serializable { + + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Id + private int id; + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "CONFIGURATIONITEM_ID", referencedColumnName = "ID"), + @JoinColumn(name = "CONFIGURATIONITEM_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private ConfigurationItem configurationItem; + + @Column(nullable = false) + private String name; + + @Lob + private String description; + + @Temporal(TemporalType.TIMESTAMP) + private Date creationDate; + + @OneToOne(orphanRemoval = true, cascade=CascadeType.ALL, fetch=FetchType.EAGER) + private ACL acl; + + /** + * Set of substitute links (actually their path from the root node) + * that have been included into the baseline. + * Only selected substitute links are stored as part usage links are considered as the default + * choices for baselines. + * + * Paths are strings made of ordered lists of usage link ids joined by "-". + */ + @ElementCollection(fetch = FetchType.LAZY) + @CollectionTable(name = "PRDCFG_SUBSTITUTELINK", + joinColumns= { + @JoinColumn(name = "PRODUCTBASELINE_ID", referencedColumnName = "ID") + } + ) + private Set<String> substituteLinks=new HashSet<>(); + + /** + * Set of optional usage links (actually their path from the root node) + * that have been included into the baseline. + * + * Paths are strings made of ordered lists of usage link ids joined by "-". + */ + @ElementCollection(fetch = FetchType.LAZY) + @CollectionTable(name = "PRDCFG_OPTIONALLINK", + joinColumns={ + @JoinColumn(name = "PRODUCTBASELINE_ID", referencedColumnName="ID") + } + ) + private Set<String> optionalUsageLinks=new HashSet<>(); + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + public ProductConfiguration() { + } + + public ProductConfiguration(User user,ConfigurationItem configurationItem, String name, String description, ACL acl) { + this.author = user; + this.configurationItem = configurationItem; + this.name = name; + this.description = description; + this.creationDate = new Date(); + this.acl = acl; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public ConfigurationItem getConfigurationItem() { + return configurationItem; + } + + public void setConfigurationItem(ConfigurationItem configurationItem) { + this.configurationItem = configurationItem; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public ACL getAcl() { + return acl; + } + + public void setAcl(ACL acl) { + this.acl = acl; + } + + public Set<String> getSubstituteLinks() { + return substituteLinks; + } + + public void setSubstituteLinks(Set<String> substituteLinks) { + this.substituteLinks = substituteLinks; + } + + public Set<String> getOptionalUsageLinks() { + return optionalUsageLinks; + } + + public void setOptionalUsageLinks(Set<String> optionalUsageLinks) { + this.optionalUsageLinks = optionalUsageLinks; + } + + public boolean hasSubstituteLink(String link){ + return substituteLinks.contains(link); + } + + public boolean isLinkOptional(String link){ + return optionalUsageLinks.contains(link); + } + + public User getAuthor() { + return author; + } + + public void setAuthor(User author) { + this.author = author; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ProductConfiguration)) { + return false; + } + + ProductConfiguration productBaseline = (ProductConfiguration) o; + return id == productBaseline.id; + } + + @Override + public int hashCode() { + return id; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductInstanceIteration.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductInstanceIteration.java new file mode 100644 index 0000000000..481012038a --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductInstanceIteration.java @@ -0,0 +1,430 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.configuration; + + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.FileHolder; +import com.docdoku.core.common.User; +import com.docdoku.core.document.DocumentLink; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PathToPathLink; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.*; + +/** + * This class represents a state, identified by its iteration, of an instance of + * a product {@link ProductInstanceMaster}. + * + * @author Florent Garin + * @version 2.0, 24/02/14 + * @since V2.0 + */ +@Table(name = "PRODUCTINSTANCEITERATION") +@IdClass(com.docdoku.core.configuration.ProductInstanceIterationKey.class) +@Entity +@NamedQueries({ + @NamedQuery(name="ProductInstanceIteration.findByProductBaseline", query = "SELECT p FROM ProductInstanceIteration p WHERE p.basedOn = :productBaseline") +}) +public class ProductInstanceIteration implements Serializable, FileHolder { + + @Id + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "PRDINSTANCEMASTER_SERIALNUMBER", referencedColumnName = "SERIALNUMBER"), + @JoinColumn(name = "CONFIGURATIONITEM_ID", referencedColumnName = "CONFIGURATIONITEM_ID"), + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private ProductInstanceMaster productInstanceMaster; + + @Id + private int iteration; + + private String iterationNote; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) + private PartCollection partCollection; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) + private DocumentCollection documentCollection = new DocumentCollection(); + + @OneToMany(cascade = {CascadeType.REMOVE, CascadeType.REFRESH}, fetch = FetchType.EAGER) + @JoinTable(name = "PRDINSTITERATION_BINRES", + inverseJoinColumns = { + @JoinColumn(name = "ATTACHEDFILE_FULLNAME", referencedColumnName = "FULLNAME") + }, + joinColumns = { + @JoinColumn(name = "PRDINSTANCEMASTER_SERIALNUMBER", referencedColumnName = "PRDINSTANCEMASTER_SERIALNUMBER"), + @JoinColumn(name = "CONFIGURATIONITEM_ID", referencedColumnName = "CONFIGURATIONITEM_ID"), + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }) + private Set<BinaryResource> attachedFiles = new HashSet<>(); + + @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinTable(name = "PRDINSTITERATION_DOCUMENTLINK", + inverseJoinColumns = { + @JoinColumn(name = "DOCUMENTLINK_ID", referencedColumnName = "ID") + }, + joinColumns = { + @JoinColumn(name = "PRDINSTANCEMASTER_SERIALNUMBER", referencedColumnName = "PRDINSTANCEMASTER_SERIALNUMBER"), + @JoinColumn(name = "CONFIGURATIONITEM_ID", referencedColumnName = "CONFIGURATIONITEM_ID"), + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }) + private Set<DocumentLink> linkedDocuments = new HashSet<>(); + + @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @OrderColumn(name = "ATTRIBUTE_ORDER") + @JoinTable(name = "PRDINSTITERATION_ATTRIBUTE", + inverseJoinColumns = { + @JoinColumn(name = "INSTANCEATTRIBUTE_ID", referencedColumnName = "ID") + }, + joinColumns = { + @JoinColumn(name = "PRDINSTANCEMASTER_SERIALNUMBER", referencedColumnName = "PRDINSTANCEMASTER_SERIALNUMBER"), + @JoinColumn(name = "CONFIGURATIONITEM_ID", referencedColumnName = "CONFIGURATIONITEM_ID"), + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }) + private List<InstanceAttribute> instanceAttributes = new ArrayList<>(); + + @ManyToOne(optional=false, fetch = FetchType.LAZY) + @JoinColumn(name = "PRODUCTBASELINE_ID", referencedColumnName = "ID") + private ProductBaseline basedOn; + + @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinTable(name = "PRDINSTITERATION_PATHDATAMSTR", + inverseJoinColumns = { + @JoinColumn(name = "PATHDATAMASTER_ID", referencedColumnName = "ID") + }, + joinColumns = { + @JoinColumn(name="PRDINSTANCEITERATION_ITERATION", referencedColumnName="ITERATION"), + @JoinColumn(name="PRDINSTANCEMASTER_SERIALNUMBER", referencedColumnName="PRDINSTANCEMASTER_SERIALNUMBER"), + @JoinColumn(name="CONFIGURATIONITEM_ID", referencedColumnName="CONFIGURATIONITEM_ID"), + @JoinColumn(name="WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + }) + private List<PathDataMaster> pathDataMasterList = new ArrayList<>(); + + /** + * Set of substitute links (actually their path from the root node) + * that have been included into the baseline. + * Only selected substitute links are stored as part usage links are considered as the default + * choices for baselines. + * <p> + * Paths are strings made of ordered lists of usage link ids joined by "-". + */ + @ElementCollection(fetch = FetchType.LAZY) + @CollectionTable(name = "PRDINSTANCEITERATION_SUBLINK", + joinColumns = { + @JoinColumn(name = "PRDINSTANCEMASTER_SERIALNUMBER", referencedColumnName = "PRDINSTANCEMASTER_SERIALNUMBER"), + @JoinColumn(name = "CONFIGURATIONITEM_ID", referencedColumnName = "CONFIGURATIONITEM_ID"), + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + } + ) + private Set<String> substituteLinks = new HashSet<>(); + + /** + * Set of optional usage links (actually their path from the root node) + * that have been included into the baseline. + * <p> + * Paths are strings made of ordered lists of usage link ids joined by "-". + */ + @ElementCollection(fetch = FetchType.LAZY) + @CollectionTable(name = "PRDINSTANCEITERATION_OPTLINK", + joinColumns = { + @JoinColumn(name = "PRDINSTANCEMASTER_SERIALNUMBER", referencedColumnName = "PRDINSTANCEMASTER_SERIALNUMBER"), + @JoinColumn(name = "CONFIGURATIONITEM_ID", referencedColumnName = "CONFIGURATIONITEM_ID"), + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + } + ) + + private Set<String> optionalUsageLinks = new HashSet<>(); + + + @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinTable(name = "PRDINSTITERATION_P2PLINK", + inverseJoinColumns = { + @JoinColumn(name = "PATHTOPATHLINK_ID", referencedColumnName = "ID") + }, + joinColumns = { + @JoinColumn(name = "PRDINSTANCEMASTER_SERIALNUMBER", referencedColumnName = "PRDINSTANCEMASTER_SERIALNUMBER"), + @JoinColumn(name = "CONFIGURATIONITEM_ID", referencedColumnName = "CONFIGURATIONITEM_ID"), + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }) + private List<PathToPathLink> pathToPathLinks = new ArrayList<>(); + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + @Temporal(TemporalType.TIMESTAMP) + private Date creationDate; + + @Temporal(TemporalType.TIMESTAMP) + private Date modificationDate; + + public ProductInstanceIteration() { + } + + public ProductInstanceIteration(ProductInstanceMaster pProductInstanceMaster, int pIteration) { + this.productInstanceMaster = pProductInstanceMaster; + this.iteration = pIteration; + } + + @XmlTransient + public ProductInstanceMaster getProductInstanceMaster() { + return productInstanceMaster; + } + + public void setProductInstanceMaster(ProductInstanceMaster productInstanceMaster) { + this.productInstanceMaster = productInstanceMaster; + } + + public String getSerialNumber() { + return this.productInstanceMaster.getSerialNumber(); + } + + public int getIteration() { + return iteration; + } + + public void setIteration(int iteration) { + this.iteration = iteration; + } + + public String getIterationNote() { + return iterationNote; + } + + public void setIterationNote(String iterationNote) { + this.iterationNote = iterationNote; + } + + public PartCollection getPartCollection() { + return partCollection; + } + + public void setPartCollection(PartCollection partCollection) { + this.partCollection = partCollection; + } + + public DocumentCollection getDocumentCollection() { + return documentCollection; + } + + public void setDocumentCollection(DocumentCollection documentCollection) { + this.documentCollection = documentCollection; + } + + public void addFile(BinaryResource pBinaryResource) { + attachedFiles.add(pBinaryResource); + } + + public Set<DocumentLink> getLinkedDocuments() { + return linkedDocuments; + } + + public void setLinkedDocuments(Set<DocumentLink> pLinkedDocuments) { + linkedDocuments = pLinkedDocuments; + } + + public List<InstanceAttribute> getInstanceAttributes() { + return instanceAttributes; + } + + public void setInstanceAttributes(List<InstanceAttribute> pInstanceAttributes) { + instanceAttributes = pInstanceAttributes; + } + + public void setAttachedFiles(Set<BinaryResource> attachedFiles) { + this.attachedFiles = attachedFiles; + } + + @Override + public Set<BinaryResource> getAttachedFiles() { + return attachedFiles; + } + + public Map<BaselinedPartKey, BaselinedPart> getBaselinedParts() { + return partCollection.getBaselinedParts(); + } + + public void addBaselinedPart(PartIteration targetPart) { + partCollection.addBaselinedPart(targetPart); + } + + public boolean hasBasedLinedPart(String targetPartWorkspaceId, String targetPartNumber) { + return partCollection.hasBaselinedPart(new BaselinedPartKey(partCollection.getId(), targetPartWorkspaceId, targetPartNumber)); + } + + public BaselinedPart getBaselinedPart(BaselinedPartKey baselinedPartKey) { + return partCollection.getBaselinedPart(baselinedPartKey); + } + + public Map<BaselinedDocumentKey, BaselinedDocument> getBaselinedDocuments() { + return documentCollection.getBaselinedDocuments(); + } + + public BaselinedDocument getBaselinedDocument(BaselinedDocumentKey baselinedDocumentKey) { + return documentCollection.getBaselinedDocument(baselinedDocumentKey); + } + + public ProductBaseline getBasedOn() { + return basedOn; + } + + public void setBasedOn(ProductBaseline basedOn) { + this.basedOn = basedOn; + } + + public List<PathDataMaster> getPathDataMasterList() { + return pathDataMasterList; + } + + public void setPathDataMasterList(List<PathDataMaster> pathDataMasterList) { + this.pathDataMasterList = pathDataMasterList; + } + + public User getUpdateAuthor(){ + return this.getPartCollection().getAuthor(); + } + + public String getUpdateAuthorName() { + User updateAuthor = getUpdateAuthor(); + if (updateAuthor == null) { + return null; + } + return updateAuthor.getName(); + } + + public Date getModificationDate() { + return (modificationDate!=null) ? (Date) modificationDate.clone() : null; + } + public void setModificationDate(Date modificationDate) { + this.modificationDate = (modificationDate!=null) ? (Date) modificationDate.clone() : null; + } + + public List<BaselinedPart> getBaselinedPartsList() { + return new ArrayList<>(this.getBaselinedParts().values()); + } + + public Set<String> getSubstituteLinks() { + return substituteLinks; + } + + public void setSubstituteLinks(Set<String> substituteLinks) { + this.substituteLinks = substituteLinks; + } + + public Set<String> getOptionalUsageLinks() { + return optionalUsageLinks; + } + + public void setOptionalUsageLinks(Set<String> optionalUsageLinks) { + this.optionalUsageLinks = optionalUsageLinks; + } + + public String getConfigurationItemId() { + return this.productInstanceMaster.getInstanceOf().getId(); + } + + + public boolean hasSubstituteLink(String link) { + return substituteLinks.contains(link); + } + + public boolean isLinkOptional(String link) { + return optionalUsageLinks.contains(link); + } + + public boolean removeFile(BinaryResource pBinaryResource) { + return attachedFiles.remove(pBinaryResource); + } + + public List<PathToPathLink> getPathToPathLinks() { + return pathToPathLinks; + } + + public void setPathToPathLinks(List<PathToPathLink> pathToPathLinks) { + this.pathToPathLinks = pathToPathLinks; + } + + public void addPathToPathLink(PathToPathLink pathToPathLink) { + pathToPathLinks.add(pathToPathLink); + } + + public void removePathToPathLink(PathToPathLink pathToPathLink) { + pathToPathLinks.remove(pathToPathLink); + } + + public User getAuthor() { + return author; + } + + public void setAuthor(User author) { + this.author = author; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + @Override + public boolean equals(Object o) { + if (this == o){ + return true; + } + if (o == null || getClass() != o.getClass()){ + return false; + } + + ProductInstanceIteration that = (ProductInstanceIteration) o; + + if (iteration != that.iteration) { + return false; + } + if (productInstanceMaster != null ? !productInstanceMaster.equals(that.productInstanceMaster) : that.productInstanceMaster != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = productInstanceMaster != null ? productInstanceMaster.hashCode() : 0; + result = 31 * result + iteration; + return result; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductInstanceIterationKey.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductInstanceIterationKey.java new file mode 100644 index 0000000000..db473f5cdf --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductInstanceIterationKey.java @@ -0,0 +1,91 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.configuration; + +import java.io.Serializable; + +/** + * Identity class of {@link ProductInstanceIteration} objects. + * + * @author Florent Garin + */ +public class ProductInstanceIterationKey implements Serializable { + + private ProductInstanceMasterKey productInstanceMaster; + private int iteration; + + public ProductInstanceIterationKey() { + } + + public ProductInstanceIterationKey(String serialNumber, String pWorkspaceId, String pId, int pIteration) { + this(new ProductInstanceMasterKey(serialNumber, pWorkspaceId, pId), pIteration); + } + + public ProductInstanceIterationKey(ProductInstanceMasterKey pProductInstanceMaster, int pIteration) { + productInstanceMaster = pProductInstanceMaster; + iteration = pIteration; + } + + public int getIteration() { + return iteration; + } + + public void setIteration(int iteration) { + this.iteration = iteration; + } + + public ProductInstanceMasterKey getProductInstanceMaster() { + return productInstanceMaster; + } + + public void setProductInstanceMaster(ProductInstanceMasterKey productInstanceMaster) { + this.productInstanceMaster = productInstanceMaster; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ProductInstanceIterationKey that = (ProductInstanceIterationKey) o; + + return iteration == that.iteration && productInstanceMaster.equals(that.productInstanceMaster); + + } + + @Override + public int hashCode() { + int result = productInstanceMaster.hashCode(); + result = 31 * result + iteration; + return result; + } + + @Override + public String toString() { + return productInstanceMaster + "-" + iteration; + } + + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductInstanceMaster.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductInstanceMaster.java new file mode 100644 index 0000000000..662d22e17d --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductInstanceMaster.java @@ -0,0 +1,165 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.configuration; + +import com.docdoku.core.product.ConfigurationItem; +import com.docdoku.core.security.ACL; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * This class is an instance of a product, its main attributes are + * the serial number and the configuration item it is an instance of. + * + * The composition of the instance may vary according to modifications + * applied on it so to track this evolution the part collection is kept + * on several {@link ProductInstanceIteration}. + * + * @author Florent Garin + * @version 2.0, 24/02/14 + * @since V2.0 + */ +@Table(name="PRODUCTINSTANCEMASTER") +@IdClass(com.docdoku.core.configuration.ProductInstanceMasterKey.class) +@Entity +@NamedQueries({ + @NamedQuery(name="ProductInstanceMaster.findByPathData", query="SELECT p.productInstanceMaster FROM ProductInstanceIteration p WHERE :pathDataMasterList member of p.pathDataMasterList"), + @NamedQuery(name="ProductInstanceMaster.findByConfigurationItemId", query="SELECT pim FROM ProductInstanceMaster pim WHERE pim.instanceOf.id = :ciId AND pim.instanceOf.workspace.id = :workspaceId"), + @NamedQuery(name="ProductInstanceMaster.findByPart", query="SELECT DISTINCT pim FROM ProductInstanceMaster pim JOIN ProductBaseline pb JOIN BaselinedPart bp WHERE pim.instanceOf = pb.configurationItem AND pb.partCollection = bp.partCollection AND bp.targetPart.partRevision = :partRevision ORDER BY pb.configurationItem.id") +}) +public class ProductInstanceMaster implements Serializable { + + + @Column(name="SERIALNUMBER", length = 100) + @Id + private String serialNumber; + + @Id + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "CONFIGURATIONITEM_ID", referencedColumnName = "ID"), + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private ConfigurationItem instanceOf; + + @OneToMany(mappedBy = "productInstanceMaster", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @OrderBy("iteration ASC") + private List<ProductInstanceIteration> productInstanceIterations = new ArrayList<>(); + + @OneToOne(orphanRemoval = true, cascade=CascadeType.ALL, fetch=FetchType.EAGER) + private ACL acl; + + public ProductInstanceMaster() { + } + + public ProductInstanceMaster(ConfigurationItem configurationItem, String serialNumber) { + this.instanceOf = configurationItem; + this.serialNumber = serialNumber; + } + + public ConfigurationItem getInstanceOf() { + return instanceOf; + } + public void setInstanceOf(ConfigurationItem instanceOf) { + this.instanceOf = instanceOf; + } + + public String getSerialNumber() { + return serialNumber; + } + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public List<ProductInstanceIteration> getProductInstanceIterations() { + return productInstanceIterations; + } + + public ProductInstanceIteration createNextIteration() { + ProductInstanceIteration lastProductInstanceIteration = getLastIteration(); + int iteration; + if(lastProductInstanceIteration==null){ + iteration = 1; + }else{ + iteration = lastProductInstanceIteration.getIteration()+1; + } + ProductInstanceIteration productInstanceIteration = new ProductInstanceIteration(this,iteration); + this.productInstanceIterations.add(productInstanceIteration); + return productInstanceIteration; + } + public void removeIteration(ProductInstanceIteration prodInstI){ + this.productInstanceIterations.remove(prodInstI); + } + + public ProductInstanceIteration getLastIteration() { + int index = productInstanceIterations.size()-1; + if(index < 0) + return null; + else + return productInstanceIterations.get(index); + } + + public ACL getAcl() { + return acl; + } + + public void setAcl(ACL acl) { + this.acl = acl; + } + + public void setProductInstanceIterations(List<ProductInstanceIteration> productInstanceIterations) { + this.productInstanceIterations = productInstanceIterations; + } + + public String getIdentifier(){ + return instanceOf.getId() + "-" + serialNumber; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()){ + return false; + } + + ProductInstanceMaster that = (ProductInstanceMaster) o; + + if (instanceOf != null ? !instanceOf.equals(that.instanceOf) : that.instanceOf != null){ + return false; + } + if (serialNumber != null ? !serialNumber.equals(that.serialNumber) : that.serialNumber != null){ + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = serialNumber != null ? serialNumber.hashCode() : 0; + result = 31 * result + (instanceOf != null ? instanceOf.hashCode() : 0); + return result; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductInstanceMasterKey.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductInstanceMasterKey.java new file mode 100644 index 0000000000..711f439fe5 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/ProductInstanceMasterKey.java @@ -0,0 +1,93 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.configuration; + +import com.docdoku.core.product.ConfigurationItemKey; + +import java.io.Serializable; + +/** + * Identity class of {@link ProductInstanceMaster} objects. + * + * @author Florent Garin + */ +public class ProductInstanceMasterKey implements Serializable { + + private ConfigurationItemKey instanceOf; + private String serialNumber; + + + public ProductInstanceMasterKey() { + } + + public ProductInstanceMasterKey(String serialNumber, String pWorkspaceId, String pId) { + this.serialNumber=serialNumber; + this.instanceOf=new ConfigurationItemKey(pWorkspaceId,pId); + } + + public ProductInstanceMasterKey(String serialNumber, ConfigurationItemKey ciKey) { + this.serialNumber=serialNumber; + this.instanceOf=ciKey; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public ConfigurationItemKey getInstanceOf() { + return instanceOf; + } + + public void setInstanceOf(ConfigurationItemKey instanceOf) { + this.instanceOf = instanceOf; + } + + @Override + public String toString() { + return instanceOf + "-" + serialNumber; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ProductInstanceMasterKey that = (ProductInstanceMasterKey) o; + + return instanceOf.equals(that.instanceOf) && serialNumber.equals(that.serialNumber); + + } + + @Override + public int hashCode() { + int result = instanceOf.hashCode(); + result = 31 * result + serialNumber.hashCode(); + return result; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/configuration/ResolvedPartLink.java b/docdoku-common/src/main/java/com/docdoku/core/configuration/ResolvedPartLink.java new file mode 100644 index 0000000000..7f35638c95 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/configuration/ResolvedPartLink.java @@ -0,0 +1,57 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.configuration; + +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartLink; + +/** + * + * @author Morgan Guimard + * @version 2.0, 07/21/15 + * @since 2.0 + */ +public class ResolvedPartLink { + + private PartIteration partIteration; + private PartLink partLink; + + public ResolvedPartLink(PartIteration partIteration, PartLink partLink) { + this.partIteration = partIteration; + this.partLink = partLink; + } + + public PartIteration getPartIteration() { + return partIteration; + } + + public void setPartIteration(PartIteration partIteration) { + this.partIteration = partIteration; + } + + public PartLink getPartLink() { + return partLink; + } + + public void setPartLink(PartLink partLink) { + this.partLink = partLink; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/DocumentIteration.java b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentIteration.java new file mode 100644 index 0000000000..03c40a4ff7 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentIteration.java @@ -0,0 +1,347 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.document; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.FileHolder; +import com.docdoku.core.common.User; +import com.docdoku.core.meta.InstanceAttribute; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.*; + +/** + * This DocumentIteration class represents the iterated part of a document. + * The iteration attribute indicates the order in which the modifications + * have been made on the document. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name = "DOCUMENTITERATION") +@javax.persistence.IdClass(com.docdoku.core.document.DocumentIterationKey.class) +@NamedQueries ({ + @NamedQuery(name="DocumentIteration.findByBinaryResource", query = "SELECT d FROM DocumentIteration d WHERE :binaryResource member of d.attachedFiles") +}) +@javax.persistence.Entity +public class DocumentIteration implements Serializable, FileHolder, Comparable<DocumentIteration>, Cloneable { + + @Id + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "DOCUMENTMASTER_ID", referencedColumnName = "DOCUMENTMASTER_ID"), + @JoinColumn(name = "DOCUMENTREVISION_VERSION", referencedColumnName = "VERSION"), + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private DocumentRevision documentRevision; + + @javax.persistence.Id + private int iteration; + + @OneToMany(cascade = {CascadeType.REMOVE, CascadeType.REFRESH}, fetch = FetchType.EAGER) + @JoinTable(name = "DOCUMENTITERATION_BINRES", + inverseJoinColumns = { + @JoinColumn(name = "ATTACHEDFILE_FULLNAME", referencedColumnName = "FULLNAME") + }, + joinColumns = { + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "DOCUMENTMASTER_ID", referencedColumnName = "DOCUMENTMASTER_ID"), + @JoinColumn(name = "DOCUMENTREVISION_VERSION", referencedColumnName = "DOCUMENTREVISION_VERSION"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }) + private Set<BinaryResource> attachedFiles = new HashSet<>(); + private String revisionNote; + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date creationDate; + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date modificationDate; + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date checkInDate; + + @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinTable(name = "DOCUMENTITERATION_DOCUMENTLINK", + inverseJoinColumns = { + @JoinColumn(name = "DOCUMENTLINK_ID", referencedColumnName = "ID") + }, + joinColumns = { + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "DOCUMENTMASTER_ID", referencedColumnName = "DOCUMENTMASTER_ID"), + @JoinColumn(name = "DOCUMENTREVISION_VERSION", referencedColumnName = "DOCUMENTREVISION_VERSION"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }) + private Set<DocumentLink> linkedDocuments = new HashSet<>(); + + @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @OrderColumn(name="ATTRIBUTE_ORDER") + @JoinTable(name = "DOCUMENTITERATION_ATTRIBUTE", + inverseJoinColumns = { + @JoinColumn(name = "INSTANCEATTRIBUTE_ID", referencedColumnName = "ID") + }, + joinColumns = { + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "DOCUMENTMASTER_ID", referencedColumnName = "DOCUMENTMASTER_ID"), + @JoinColumn(name = "DOCUMENTREVISION_VERSION", referencedColumnName = "DOCUMENTREVISION_VERSION"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }) + private List<InstanceAttribute> instanceAttributes = new ArrayList<>(); + + public DocumentIteration() { + } + + public DocumentIteration(DocumentRevision pDocumentRevision, User pAuthor) { + DocumentIteration lastDoc = pDocumentRevision.getLastIteration(); + int newIteration = 1; + + if (lastDoc != null) { + newIteration = lastDoc.getIteration() + 1; + Date lastModificationDate = lastDoc.modificationDate; + modificationDate = lastModificationDate; + } + + setDocumentRevision(pDocumentRevision); + iteration = newIteration; + author = pAuthor; + checkInDate = null; + } + + public void setDocumentRevision(DocumentRevision documentRevision) { + this.documentRevision = documentRevision; + } + + + public String getWorkspaceId() { + return documentRevision==null?"":documentRevision.getWorkspaceId(); + } + + public String getId() { + return documentRevision==null?"":documentRevision.getId(); + } + + public String getVersion() { + return documentRevision==null?"":documentRevision.getVersion(); + } + + public String getDocumentMasterId() { + return getId(); + } + + public int getIteration() { + return iteration; + } + + public void setRevisionNote(String pRevisionNote) { + revisionNote = pRevisionNote; + } + + public String getRevisionNote() { + return revisionNote; + } + + public void setAttachedFiles(Set<BinaryResource> attachedFiles) { + this.attachedFiles = attachedFiles; + } + + public void setIteration(int iteration) { + this.iteration = iteration; + } + + public boolean removeFile(BinaryResource pBinaryResource) { + return attachedFiles.remove(pBinaryResource); + } + + public void addFile(BinaryResource pBinaryResource) { + attachedFiles.add(pBinaryResource); + } + + @Override + public Set<BinaryResource> getAttachedFiles() { + return attachedFiles; + } + + public DocumentRevisionKey getDocumentRevisionKey() { + return documentRevision==null?new DocumentRevisionKey(new DocumentMasterKey("",""),""):documentRevision.getKey(); + } + public DocumentIterationKey getKey() { + return new DocumentIterationKey(getDocumentRevisionKey(),iteration); + } + + + public void setAuthor(User pAuthor) { + author = pAuthor; + } + + public User getAuthor() { + return author; + } + + @XmlTransient + public DocumentRevision getDocumentRevision() { + return documentRevision; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = (creationDate!=null) ? (Date) creationDate.clone() : null; + } + + public Date getCreationDate() { + return (creationDate!=null) ? (Date) creationDate.clone() : null; + } + + public void setModificationDate(Date modificationDate) { + this.modificationDate = (modificationDate!=null) ? (Date) modificationDate.clone() : null; + } + + public Date getModificationDate() { + return (modificationDate!=null) ? (Date) modificationDate.clone() : null; + } + + public void setCheckInDate(Date checkInDate) { + this.checkInDate = (checkInDate!=null) ? (Date) checkInDate.clone() : null; + } + + public Date getCheckInDate() { + return (checkInDate!=null) ? (Date) checkInDate.clone() : null; + } + + public Set<DocumentLink> getLinkedDocuments() { + return linkedDocuments; + } + + public void setLinkedDocuments(Set<DocumentLink> pLinkedDocuments) { + linkedDocuments=pLinkedDocuments; + } + + public List<InstanceAttribute> getInstanceAttributes() { + return instanceAttributes; + } + + public void setInstanceAttributes(List<InstanceAttribute> pInstanceAttributes) { + instanceAttributes=pInstanceAttributes; + } + + public String getTitle() { + return documentRevision==null ? "" : this.documentRevision.getTitle(); + } + + @Override + public String toString() { + return documentRevision + "-" + iteration; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + getWorkspaceId().hashCode(); + hash = 31 * hash + getId().hashCode(); + hash = 31 * hash + getVersion().hashCode(); + hash = 31 * hash + iteration; + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof DocumentIteration)) { + return false; + } + DocumentIteration docI = (DocumentIteration) pObj; + return docI.getId().equals(getId()) && + docI.getWorkspaceId().equals(getWorkspaceId()) && + docI.getVersion().equals(getVersion()) && + docI.iteration==iteration; + } + + + + + @Override + public int compareTo(DocumentIteration pDoc) { + + int wksComp = getWorkspaceId().compareTo(pDoc.getWorkspaceId()); + if (wksComp != 0) { + return wksComp; + } + int docmIdComp = getId().compareTo(pDoc.getId()); + if (docmIdComp != 0) { + return docmIdComp; + } + int docmVersionComp = getVersion().compareTo(pDoc.getVersion()); + if (docmVersionComp != 0) { + return docmVersionComp; + } else { + return iteration - pDoc.iteration; + } + } + + /** + * perform a deep clone operation + */ + @Override + public DocumentIteration clone() { + DocumentIteration clone = null; + try { + clone = (DocumentIteration) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + //perform a deep copy + clone.attachedFiles = new HashSet<>(attachedFiles); + + Set<DocumentLink> clonedLinks = new HashSet<>(); + for (DocumentLink link : linkedDocuments) { + DocumentLink clonedLink = link.clone(); + clonedLinks.add(clonedLink); + } + clone.linkedDocuments = clonedLinks; + + //perform a deep copy + List<InstanceAttribute> clonedInstanceAttributes = new ArrayList<>(); + for (InstanceAttribute attribute : instanceAttributes) { + InstanceAttribute clonedAttribute = attribute.clone(); + clonedInstanceAttributes.add(clonedAttribute); + } + clone.instanceAttributes = clonedInstanceAttributes; + + if (creationDate != null) { + clone.creationDate = (Date) creationDate.clone(); + } + if (modificationDate != null) { + clone.modificationDate = (Date) modificationDate.clone(); + } + if (checkInDate != null) { + clone.checkInDate = (Date) checkInDate.clone(); + } + return clone; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/DocumentIterationKey.java b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentIterationKey.java new file mode 100644 index 0000000000..835d215d1f --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentIterationKey.java @@ -0,0 +1,100 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.document; + +import java.io.Serializable; + +/** + * Identity class of {@link DocumentIteration} objects. + * + * @author Florent Garin + */ +public class DocumentIterationKey implements Serializable { + + private DocumentRevisionKey documentRevision; + private int iteration; + + public DocumentIterationKey() { + } + + public DocumentIterationKey(String pWorkspaceId, String pId, String pVersion, int pIteration) { + documentRevision= new DocumentRevisionKey(pWorkspaceId, pId, pVersion); + iteration=pIteration; + } + + public DocumentIterationKey(DocumentRevisionKey pDocumentRevisionKey, int pIteration) { + documentRevision=pDocumentRevisionKey; + iteration=pIteration; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + documentRevision.hashCode(); + hash = 31 * hash + iteration; + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof DocumentIterationKey)) + return false; + DocumentIterationKey key = (DocumentIterationKey) pObj; + return key.documentRevision.equals(documentRevision) && key.iteration==iteration; + } + + @Override + public String toString() { + return documentRevision + "-" + iteration; + } + + public int getIteration(){ + return iteration; + } + + public void setIteration(int pIteration){ + iteration=pIteration; + } + + public DocumentRevisionKey getDocumentRevision() { + return documentRevision; + } + + public void setDocumentRevision(DocumentRevisionKey documentRevision) { + this.documentRevision = documentRevision; + } + + + public String getWorkspaceId() { + return documentRevision.getDocumentMaster().getWorkspace(); + } + + public String getDocumentMasterId() { + return documentRevision.getDocumentMaster().getId(); + } + + public String getDocumentRevisionVersion(){ + return documentRevision.getVersion(); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/DocumentIterationLink.java b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentIterationLink.java new file mode 100644 index 0000000000..109d61ca6c --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentIterationLink.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.document; + +import java.io.Serializable; + +public class DocumentIterationLink implements Serializable { + private DocumentLink documentLink; + private DocumentIteration documentIteration; + + public DocumentIterationLink() { + } + + public DocumentIterationLink(DocumentLink documentLink, DocumentIteration documentIteration) { + this.documentLink = documentLink; + this.documentIteration = documentIteration; + } + + public DocumentLink getDocumentLink() { + return documentLink; + } + + public void setDocumentLink(DocumentLink documentLink) { + this.documentLink = documentLink; + } + + public DocumentIteration getDocumentIteration() { + return documentIteration; + } + + public void setDocumentIteration(DocumentIteration documentIteration) { + this.documentIteration = documentIteration; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/DocumentLink.java b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentLink.java new file mode 100644 index 0000000000..81a1bce31a --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentLink.java @@ -0,0 +1,183 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.document; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; + +/** + * This is a link class used to connect an object to a {@link DocumentRevision}. + * Documents are not linked directly but rather through this class to get + * a loosely coupling and to carry additional information. + * + * @author Florent Garin + * @version 1.1, 28/01/13 + * @since V1.0 + */ +@Table(name="DOCUMENTLINK") +@javax.persistence.Entity +@NamedQueries ({ + @NamedQuery(name="DocumentLink.findDocumentOwner", query = "SELECT d FROM DocumentIteration d WHERE :link MEMBER OF d.linkedDocuments"), + @NamedQuery(name="DocumentLink.findPartOwner", query = "SELECT p FROM PartIteration p WHERE :link MEMBER OF p.linkedDocuments"), + @NamedQuery(name="DocumentLink.findProductInstanceIteration", query = "SELECT p FROM ProductInstanceIteration p JOIN p.linkedDocuments dl where dl.targetDocument = :documentRevision"), + @NamedQuery(name="DocumentLink.findPathData", query = "SELECT p FROM PathDataIteration p JOIN p.linkedDocuments dl where dl.targetDocument = :documentRevision"), + @NamedQuery(name="DocumentLink.findInverseDocumentLinks", query = "SELECT d FROM DocumentIteration d JOIN d.linkedDocuments dl where dl.targetDocument = :documentRevision"), + @NamedQuery(name="DocumentLink.findInversePartLinks", query = "SELECT p FROM PartIteration p JOIN p.linkedDocuments dl where dl.targetDocument = :documentRevision") +}) +public class DocumentLink implements Serializable, Cloneable{ + + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Id + private int id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name="TARGET_DOCUMENTMASTER_ID", referencedColumnName="DOCUMENTMASTER_ID"), + @JoinColumn(name="TARGET_DOCREVISION_VERSION", referencedColumnName="VERSION"), + @JoinColumn(name="TARGET_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private DocumentRevision targetDocument; + + @Column(name = "TARGET_DOCUMENTMASTER_ID", length=100, insertable = false, updatable = false) + private String targetDocumentMasterId =""; + + @Column(name = "TARGET_DOCREVISION_VERSION", length=10, insertable = false, updatable = false) + private String targetDocumentVersion =""; + + @Column(name = "TARGET_WORKSPACE_ID", length=100, insertable = false, updatable = false) + private String targetDocumentWorkspaceId=""; + + @Column(name="COMMENTDATA") + private String comment; + + + public DocumentLink() { + } + + public DocumentLink(DocumentRevision pTargetDocument, String pComment){ + setTargetDocument(pTargetDocument); + comment=pComment; + } + + public DocumentLink(DocumentRevision pTargetDocument){ + setTargetDocument(pTargetDocument); + } + + public void setId(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public String getComment() { + return comment; + } + + @XmlTransient + public DocumentRevision getTargetDocument() { + return targetDocument; + } + + public DocumentRevisionKey getTargetDocumentKey(){ + return new DocumentRevisionKey(targetDocumentWorkspaceId, targetDocumentMasterId, targetDocumentVersion); + } + + public String getTargetDocumentMasterId() { + return targetDocumentMasterId; + } + + public String getTargetDocumentVersion() { + return targetDocumentVersion; + } + + public String getTargetDocumentWorkspaceId() { + return targetDocumentWorkspaceId; + } + + public void setTargetDocumentMasterId(String targetDocumentMasterId) { + this.targetDocumentMasterId = targetDocumentMasterId; + } + + public void setTargetDocumentVersion(String targetDocumentVersion) { + this.targetDocumentVersion = targetDocumentVersion; + } + + public void setTargetDocumentWorkspaceId(String targetDocumentWorkspaceId) { + this.targetDocumentWorkspaceId = targetDocumentWorkspaceId; + } + + + + public void setTargetDocument(DocumentRevision targetDocument) { + this.targetDocument = targetDocument; + targetDocumentMasterId =targetDocument.getId(); + targetDocumentVersion =targetDocument.getVersion(); + targetDocumentWorkspaceId=targetDocument.getWorkspaceId(); + } + + + @Override + public String toString() { + return targetDocumentMasterId +"-"+ targetDocumentVersion; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof DocumentLink)) { + return false; + } + DocumentLink link = (DocumentLink) pObj; + return link.id == id; + } + + @Override + public int hashCode() { + return id; + } + + + @Override + public DocumentLink clone() { + DocumentLink clone; + try { + clone = (DocumentLink) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + + return clone; + } + + public String getDocumentTitle() { + return this.getTargetDocument().getTitle(); + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/DocumentMaster.java b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentMaster.java new file mode 100644 index 0000000000..2a222de262 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentMaster.java @@ -0,0 +1,237 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.document; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Version; +import com.docdoku.core.common.Workspace; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * This class holds the unchanging aspects of a document. + * From that object, we can navigate to all the revisions and then + * the iterations of the document where most of the data sits. + * + * @author Florent Garin + * @version 1.1, 23/01/12 + * @since V1.0 + */ +@Table(name="DOCUMENTMASTER") +@IdClass(com.docdoku.core.document.DocumentMasterKey.class) +@Entity +@NamedQueries ({ + @NamedQuery(name="DocumentMaster.findByWorkspace", query="SELECT dm FROM DocumentMaster dm WHERE dm.workspace.id = :workspaceId ORDER BY dm.creationDate DESC") +}) +public class DocumentMaster implements Serializable, Comparable<DocumentMaster> { + + @Column(name="ID", length=100) + @Id + private String id=""; + + @Id + @ManyToOne(optional=false, fetch=FetchType.EAGER) + private Workspace workspace; + + @ManyToOne(fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="AUTHOR_LOGIN", referencedColumnName="LOGIN"), + @JoinColumn(name="AUTHOR_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private User author; + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date creationDate; + + private String type; + + @OneToMany(mappedBy = "documentMaster", cascade=CascadeType.ALL, fetch=FetchType.EAGER) + @OrderBy("version ASC") + private List<DocumentRevision> documentRevisions = new ArrayList<>(); + + private boolean attributesLocked; + + public DocumentMaster() { + } + + public DocumentMaster(Workspace pWorkspace, + String pId, + User pAuthor) { + this(pWorkspace, pId); + author = pAuthor; + } + + private DocumentMaster(Workspace pWorkspace, String pId) { + id=pId; + setWorkspace(pWorkspace); + } + + public void setAuthor(User pAuthor) { + author = pAuthor; + } + public User getAuthor() { + return author; + } + + public void setCreationDate(Date pCreationDate) { + creationDate = pCreationDate; + } + public Date getCreationDate() { + return creationDate; + } + + public void setId(String id) { + this.id = id; + } + public String getId(){ + return id; + } + + public List<DocumentRevision> getDocumentRevisions() { + return documentRevisions; + } + public void setDocumentRevisions(List<DocumentRevision> documentRevisions) { + this.documentRevisions = documentRevisions; + } + + public DocumentRevision getDocumentRevision(String version) { + for(DocumentRevision documentRevision : documentRevisions){ + if(documentRevision.getVersion().equals(version)){ + return documentRevision; + } + } + return null; + } + + + public DocumentRevision getLastRevision() { + int index = documentRevisions.size()-1; + if(index < 0) { + return null; + } else { + return documentRevisions.get(index); + } + } + + public DocumentRevision removeLastRevision() { + int index = documentRevisions.size()-1; + if(index < 0) { + return null; + } else { + return documentRevisions.remove(index); + } + } + + public void removeRevision(DocumentRevision documentRevision) { + documentRevisions.remove(documentRevision); + } + + public DocumentRevision createNextRevision(User pUser){ + DocumentRevision lastRev=getLastRevision(); + Version version; + if(lastRev==null) { + version = new Version("A"); + } else{ + version = new Version(lastRev.getVersion()); + version.increase(); + } + + DocumentRevision rev = new DocumentRevision(this,version,pUser); + documentRevisions.add(rev); + return rev; + } + + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + + public DocumentMasterKey getKey() { + return new DocumentMasterKey(getWorkspaceId(), id); + } + + public String getWorkspaceId() { + return workspace == null ? "" : workspace.getId(); + } + + + + public void setWorkspace(Workspace pWorkspace){ + workspace=pWorkspace; + } + public Workspace getWorkspace(){ + return workspace; + } + + + + + @Override + public String toString() { + return id; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof DocumentMaster)) { + return false; + } + DocumentMaster docM = (DocumentMaster) pObj; + return docM.id.equals(id) && docM.getWorkspaceId().equals(getWorkspaceId()); + + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + getWorkspaceId().hashCode(); + hash = 31 * hash + id.hashCode(); + return hash; + } + + @Override + public int compareTo(DocumentMaster pDocM) { + int wksComp = getWorkspaceId().compareTo(pDocM.getWorkspaceId()); + if (wksComp != 0) { + return wksComp; + } else { + return id.compareTo(pDocM.id); + } + + } + + public boolean isAttributesLocked() { + return attributesLocked; + } + + public void setAttributesLocked(boolean attributesLocked) { + this.attributesLocked = attributesLocked; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/DocumentMasterKey.java b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentMasterKey.java new file mode 100644 index 0000000000..5144fd3deb --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentMasterKey.java @@ -0,0 +1,116 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.document; + +import java.io.Serializable; + +/** + * Identity class of {@link DocumentMaster} objects. + * + * @author Florent Garin + */ +public class DocumentMasterKey implements Serializable, Comparable<DocumentMasterKey>, Cloneable { + + private String workspace; + private String id; + + + public DocumentMasterKey() { + } + + public DocumentMasterKey(String pWorkspaceId, String pId) { + workspace = pWorkspaceId; + id = pId; + } + + + public String getWorkspace() { + return workspace; + } + + public void setWorkspace(String pWorkspaceId) { + workspace = pWorkspaceId; + } + + public String getId() { + return id; + } + + public void setId(String pId) { + id = pId; + } + + + @Override + public String toString() { + return workspace + "-" + id; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DocumentMasterKey that = (DocumentMasterKey) o; + + if (!id.equals(that.id)) { + return false; + } + if (!workspace.equals(that.workspace)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = workspace.hashCode(); + result = 31 * result + id.hashCode(); + return result; + } + + + public int compareTo(DocumentMasterKey pKey) { + int wksComp = workspace.compareTo(pKey.workspace); + if (wksComp != 0) { + return wksComp; + } else { + return id.compareTo(pKey.id); + } + } + + @Override + public DocumentMasterKey clone() { + DocumentMasterKey clone; + try { + clone = (DocumentMasterKey) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + return clone; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/DocumentMasterTemplate.java b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentMasterTemplate.java new file mode 100644 index 0000000000..f9be4beba4 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentMasterTemplate.java @@ -0,0 +1,305 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.document; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.FileHolder; +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.meta.InstanceAttributeTemplate; +import com.docdoku.core.security.ACL; +import com.docdoku.core.workflow.WorkflowModel; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.*; + +/** + * A model object from which we can create a {@link DocumentMaster}. + * Creating a document through a model offers the ability to enforce a input + * mask for the document ID, as well as some insuring that the starting + * iteration defines some custom attributes or has some specific binary files. + * + * @author Florent Garin + * @version 1.1, 23/01/12 + * @since V1.0 + */ +@Table(name="DOCUMENTMASTERTEMPLATE") +@javax.persistence.IdClass(com.docdoku.core.document.DocumentMasterTemplateKey.class) +@javax.persistence.Entity +@NamedQueries({ + @NamedQuery(name="DocumentMasterTemplate.findWhereLOV", query="SELECT d FROM DocumentMasterTemplate d WHERE EXISTS ( SELECT i FROM InstanceAttributeTemplate i, ListOfValuesAttributeTemplate il WHERE i member of d.attributeTemplates AND i = il AND il.lov.name = :lovName AND il.lov.workspaceId = :workspace_id)"), + @NamedQuery(name="DocumentMasterTemplate.findWhereWorkflowModel", query="SELECT d FROM DocumentMasterTemplate d WHERE :workflowModel = d.workflowModel") +}) +public class DocumentMasterTemplate implements Serializable, FileHolder, Comparable<DocumentMasterTemplate> { + + @Column(length=100) + @javax.persistence.Id + private String id=""; + + @javax.persistence.Column(name = "WORKSPACE_ID", length=100, nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String workspaceId=""; + + private boolean idGenerated; + + private String documentType; + + private String mask; + + @OneToOne(orphanRemoval = true, cascade=CascadeType.ALL, fetch=FetchType.EAGER) + private ACL acl; + + @OneToMany(cascade={CascadeType.REMOVE,CascadeType.REFRESH}, fetch=FetchType.EAGER) + @JoinTable(name="DOCUMENTMASTERTEMPLATE_BINRES", + inverseJoinColumns={ + @JoinColumn(name="ATTACHEDFILE_FULLNAME", referencedColumnName="FULLNAME") + }, + joinColumns={ + @JoinColumn(name="WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name="DOCUMENTMASTERTEMPLATE_ID", referencedColumnName="ID") + } + ) + private Set<BinaryResource> attachedFiles = new HashSet<>(); + + @OrderColumn(name="ATTR_ORDER") + @OneToMany(cascade={CascadeType.ALL}, fetch=FetchType.EAGER, orphanRemoval = true) + @JoinTable(name="DOCUMENTMASTERTEMPLATE_ATTR", + inverseJoinColumns={ + @JoinColumn(name="INSTANCEATTRIBUTETEMPLATE_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name="DOCUMENTMASTERTEMPLATE_ID", referencedColumnName="ID") + } + ) + private List<InstanceAttributeTemplate> attributeTemplates=new ArrayList<>(); + + private boolean attributesLocked; + + @ManyToOne(fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="AUTHOR_LOGIN", referencedColumnName="LOGIN"), + @JoinColumn(name="AUTHOR_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private User author; + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date creationDate; + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date modificationDate; + + @javax.persistence.ManyToOne(optional=false, fetch=FetchType.EAGER) + private Workspace workspace; + + @ManyToOne(fetch=FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name="WORKFLOWMODEL_ID", referencedColumnName="ID"), + @JoinColumn(name="WORKFLOWMODEL_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private WorkflowModel workflowModel; + + + @Column(name = "WORKFLOWMODEL_ID", length=100, insertable = false, updatable = false) + private String workflowModelId; + + + + public DocumentMasterTemplate() { + } + + public DocumentMasterTemplate(Workspace pWorkspace, String pId, User pAuthor, String pDocumentType, String pMask) { + id=pId; + setWorkspace(pWorkspace); + author = pAuthor; + mask = pMask; + documentType=pDocumentType; + } + + public String getDocumentType() { + return documentType; + } + + public void setDocumentType(String documentType) { + this.documentType = documentType; + } + + public String getWorkflowModelId() { + return workflowModelId; + } + + public void setWorkflowModelId(String workflowModelId) { + this.workflowModelId = workflowModelId; + } + + public WorkflowModel getWorkflowModel() { + return workflowModel; + } + + public void setWorkflowModel(WorkflowModel workflowModel) { + this.workflowModel = workflowModel; + if (workflowModel == null) { + setWorkflowModelId(null); + } else { + setWorkflowModelId(workflowModel.getId()); + } + } + + public String getMask(){ + return mask; + } + + public void setMask(String pMask){ + mask=pMask; + } + + public void setAttachedFiles(Set<BinaryResource> attachedFiles) { + this.attachedFiles = attachedFiles; + } + + + public void setId(String id) { + this.id = id; + } + + public boolean isIdGenerated() { + return idGenerated; + } + + public void setIdGenerated(boolean idGenerated) { + this.idGenerated = idGenerated; + } + + public boolean removeFile(BinaryResource pBinaryResource){ + return attachedFiles.remove(pBinaryResource); + } + + public void addFile(BinaryResource pBinaryResource){ + attachedFiles.add(pBinaryResource); + } + @Override + public Set<BinaryResource> getAttachedFiles() { + return attachedFiles; + } + + public List<InstanceAttributeTemplate> getAttributeTemplates() { + return attributeTemplates; + } + + public void setAttributeTemplates(List<InstanceAttributeTemplate> pAttributeTemplates) { + attributeTemplates=pAttributeTemplates; + } + + public boolean isAttributesLocked() { + return attributesLocked; + } + + public void setAttributesLocked(boolean attributesLocked) { + this.attributesLocked = attributesLocked; + } + + public void setAuthor(User pAuthor) { + author = pAuthor; + } + + public User getAuthor() { + return author; + } + + public void setCreationDate(Date pCreationDate) { + creationDate = pCreationDate; + } + + public Date getCreationDate() { + return creationDate; + } + + public Date getModificationDate() { + return modificationDate; + } + + public void setModificationDate(Date modificationDate) { + this.modificationDate = modificationDate; + } + + public void setWorkspace(Workspace pWorkspace){ + workspace=pWorkspace; + workspaceId=workspace.getId(); + } + public ACL getAcl() { + return acl; + } + + public void setAcl(ACL acl) { + this.acl = acl; + } + public Workspace getWorkspace(){ + return workspace; + } + + public String getId(){ + return id; + } + + public String getWorkspaceId(){ + return workspaceId; + } + + public DocumentMasterTemplateKey getKey() { + return new DocumentMasterTemplateKey(workspaceId, id); + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof DocumentMasterTemplate)) { + return false; + } + DocumentMasterTemplate template = (DocumentMasterTemplate) pObj; + return template.id.equals(id) && template.workspaceId.equals(workspaceId); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + id.hashCode(); + return hash; + } + + @Override + public String toString() { + return id; + } + + public int compareTo(DocumentMasterTemplate pTemplate) { + int wksComp = workspaceId.compareTo(pTemplate.workspaceId); + if (wksComp != 0) { + return wksComp; + } else { + return id.compareTo(pTemplate.id); + } + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/DocumentMasterTemplateKey.java b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentMasterTemplateKey.java new file mode 100644 index 0000000000..94483b8f01 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentMasterTemplateKey.java @@ -0,0 +1,83 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.document; + +import java.io.Serializable; + +/** + * + * @author Florent Garin + */ +public class DocumentMasterTemplateKey implements Serializable { + + private String workspaceId; + private String id; + + public DocumentMasterTemplateKey() { + } + + public DocumentMasterTemplateKey(String pWorkspaceId, String pId) { + workspaceId=pWorkspaceId; + id=pId; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + id.hashCode(); + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof DocumentMasterTemplateKey)) { + return false; + } + DocumentMasterTemplateKey key = (DocumentMasterTemplateKey) pObj; + return key.id.equals(id) && key.workspaceId.equals(workspaceId); + } + + @Override + public String toString() { + return workspaceId + "-" + id; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String pWorkspaceId) { + workspaceId = pWorkspaceId; + } + + public String getId() { + return id; + } + + public void setId(String pId) { + id = pId; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/DocumentRevision.java b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentRevision.java new file mode 100644 index 0000000000..e52a846eec --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentRevision.java @@ -0,0 +1,562 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.document; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Version; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.security.ACL; +import com.docdoku.core.workflow.Workflow; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.*; + +/** + * This class stands between {@link DocumentMaster} and {@link DocumentIteration}. + * It represents a formal revision of a document and can have an attached workflow. + * + * @author Florent Garin + * @version 2.0, 11/01/14 + * @since V2.0 + */ +@Table(name="DOCUMENTREVISION") +@IdClass(DocumentRevisionKey.class) +@Entity +@NamedQueries ({ + @NamedQuery(name="DocumentRevision.findLinks", query = "SELECT l FROM DocumentLink l WHERE l.targetDocument = :target"), + @NamedQuery(name="DocumentRevision.findWithAssignedTasksForUser", query="SELECT d FROM DocumentRevision d, Task t LEFT JOIN t.assignedUsers au LEFT JOIN t.assignedGroups ag LEFT JOIN ag.users agu WHERE t.activity.workflow = d.workflow AND d.workflow IS NOT NULL AND d.documentMasterWorkspaceId = :workspaceId AND ((au.login = :login AND au.workspaceId = :workspaceId) OR (agu.login = :login AND agu.workspaceId = :workspaceId))"), + @NamedQuery(name="DocumentRevision.findWithOpenedTasksForUser", query="SELECT d FROM DocumentRevision d, Task t LEFT JOIN t.assignedUsers au LEFT JOIN t.assignedGroups ag LEFT JOIN ag.users agu WHERE t.activity.workflow = d.workflow AND d.workflow IS NOT NULL AND d.documentMasterWorkspaceId = :workspaceId AND ((au.login = :login AND au.workspaceId = :workspaceId) OR (agu.login = :login AND agu.workspaceId = :workspaceId)) AND t.status = com.docdoku.core.workflow.Task.Status.IN_PROGRESS"), + @NamedQuery(name="DocumentRevision.findByReferenceOrTitle", query="SELECT d FROM DocumentRevision d WHERE (d.documentMasterId LIKE :id OR d.title LIKE :title) AND d.documentMasterWorkspaceId = :workspaceId"), + @NamedQuery(name="DocumentRevision.countByWorkspace", query="SELECT COUNT(d) FROM DocumentRevision d WHERE d.documentMasterWorkspaceId = :workspaceId"), + @NamedQuery(name="DocumentRevision.findByWorkspace.filterUserACLEntry", query="SELECT dr FROM DocumentRevision dr WHERE dr.documentMasterWorkspaceId = :workspaceId and (dr.acl is null or exists(SELECT au from ACLUserEntry au WHERE au.principal = :user AND au.permission not like com.docdoku.core.security.ACL.Permission.FORBIDDEN AND au.acl = dr.acl)) AND dr.location.completePath NOT LIKE :excludedFolders ORDER BY dr.documentMasterId ASC"), + @NamedQuery(name="DocumentRevision.countByWorkspace.filterUserACLEntry", query="SELECT count(dr) FROM DocumentRevision dr WHERE dr.documentMasterWorkspaceId = :workspaceId and (dr.acl is null or exists(SELECT au from ACLUserEntry au WHERE au.principal = :user AND au.permission not like com.docdoku.core.security.ACL.Permission.FORBIDDEN AND au.acl = dr.acl)) AND dr.location.completePath NOT LIKE :excludedFolders"), + @NamedQuery(name="DocumentRevision.findByWorkflow", query="SELECT d FROM DocumentRevision d WHERE d.workflow = :workflow") +}) +public class DocumentRevision implements Serializable, Comparable<DocumentRevision> { + + + @Id + @ManyToOne(optional=false, fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="DOCUMENTMASTER_ID", referencedColumnName="ID"), + @JoinColumn(name="WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private DocumentMaster documentMaster; + + @Column(length=10) + @Id + private String version=""; + + @ManyToOne(fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="AUTHOR_LOGIN", referencedColumnName="LOGIN"), + @JoinColumn(name="AUTHOR_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private User author; + + @Temporal(TemporalType.TIMESTAMP) + private Date creationDate; + + private String title; + + @Lob + private String description; + + @OneToMany(mappedBy = "documentRevision", cascade=CascadeType.ALL, fetch=FetchType.EAGER) + @OrderBy("iteration ASC") + private List<DocumentIteration> documentIterations = new ArrayList<>(); + + @ManyToOne(fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="CHECKOUTUSER_LOGIN", referencedColumnName="LOGIN"), + @JoinColumn(name="CHECKOUTUSER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private User checkOutUser; + + @Temporal(TemporalType.TIMESTAMP) + private Date checkOutDate; + + @OneToOne(orphanRemoval=true, cascade=CascadeType.ALL, fetch=FetchType.EAGER) + private Workflow workflow; + + @OrderBy("abortedDate") + @OneToMany(orphanRemoval=true, cascade= CascadeType.ALL, fetch= FetchType.EAGER) + @JoinTable(name="DOCUMENT_ABORTED_WORKFLOW", + inverseJoinColumns={ + @JoinColumn(name="WORKFLOW_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="DOCUMENTMASTER_ID", referencedColumnName="DOCUMENTMASTER_ID"), + @JoinColumn(name="DOCUMENTREVISION_VERSION", referencedColumnName="VERSION"), + @JoinColumn(name="DOCUMENTMASTER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private List<Workflow> abortedWorkflows=new ArrayList<>(); + + @ManyToOne(fetch=FetchType.EAGER) + private Folder location; + + @ManyToMany(fetch=FetchType.EAGER) + @JoinTable(name="DOCUMENTREVISION_TAG", + inverseJoinColumns={ + @JoinColumn(name="TAG_LABEL", referencedColumnName="LABEL"), + @JoinColumn(name="TAG_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }, + joinColumns={ + @JoinColumn(name="DOCUMENTMASTER_ID", referencedColumnName="DOCUMENTMASTER_ID"), + @JoinColumn(name="DOCUMENTREVISION_VERSION", referencedColumnName="VERSION"), + @JoinColumn(name="DOCUMENTMASTER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private Set<Tag> tags=new HashSet<>(); + + + @Column(name = "DOCUMENTMASTER_ID", nullable = false, insertable = false, updatable = false) + private String documentMasterId=""; + + @Column(name = "WORKSPACE_ID", nullable = false, insertable = false, updatable = false) + private String documentMasterWorkspaceId=""; + + + @OneToOne(orphanRemoval = true, cascade=CascadeType.ALL, fetch=FetchType.EAGER) + private ACL acl; + + private boolean publicShared; + + private RevisionStatus status=RevisionStatus.WIP; + + + + @Embedded + @AttributeOverrides({ + @AttributeOverride( + name="statusModificationDate", + column=@Column(name="RELEASE_DATE")) + }) + @AssociationOverrides({ + @AssociationOverride( + name="statusChangeAuthor", + joinColumns={ + @JoinColumn(name="RELEASE_USER_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name="RELEASE_USER_WORKSPACE", referencedColumnName = "WORKSPACE_ID") + }) + }) + private StatusChange releaseStatusChange; + + @Embedded + @AttributeOverrides({ + @AttributeOverride( + name="statusModificationDate", + column=@Column(name="OBSOLETE_DATE")) + }) + @AssociationOverrides({ + @AssociationOverride( + name="statusChangeAuthor", + joinColumns={ + @JoinColumn(name="OBSOLETE_USER_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name="OBSOLETE_USER_WORKSPACE", referencedColumnName = "WORKSPACE_ID") + }) + }) + private StatusChange obsoleteStatusChange; + + public enum RevisionStatus { + WIP, RELEASED, OBSOLETE + } + + public DocumentRevision() { + } + public DocumentRevision(DocumentMaster pDocumentMaster, + String pStringVersion, + User pAuthor) { + this(pDocumentMaster); + version=pStringVersion; + author = pAuthor; + } + public DocumentRevision(DocumentMaster pDocumentMaster, + Version pVersion, + User pAuthor) { + this(pDocumentMaster); + version=pVersion.toString(); + author = pAuthor; + } + public DocumentRevision(DocumentMaster pDocumentMaster, User pAuthor) { + this(pDocumentMaster); + version = new Version().toString(); + author = pAuthor; + } + private DocumentRevision(DocumentMaster pDocumentMaster) { + setDocumentMaster(pDocumentMaster); + } + + @XmlTransient + public DocumentMaster getDocumentMaster() { + return documentMaster; + } + public void setDocumentMaster(DocumentMaster documentMaster) { + this.documentMaster = documentMaster; + setDocumentMasterId(documentMaster.getId()); + setDocumentMasterWorkspaceId(documentMaster.getWorkspaceId()); + } + + public DocumentRevisionKey getKey() { + return new DocumentRevisionKey(getDocumentMasterKey(), version); + } + + public User getCheckOutUser() { + return checkOutUser; + } + public void setCheckOutUser(User pCheckOutUser) { + checkOutUser = pCheckOutUser; + } + public boolean isCheckedOut() { + return checkOutUser != null; + } + public boolean isCheckedOutBy(String pUser) { + return checkOutUser != null && checkOutUser.getLogin().equals(pUser); + } + + public Date getCheckOutDate() { + return (checkOutDate!=null) ? (Date) checkOutDate.clone() : null; + } + public void setCheckOutDate(Date checkOutDate) { + this.checkOutDate = (checkOutDate!=null) ? (Date) checkOutDate.clone() : null; + } + + public User getAuthor() { + return author; + } + public void setAuthor(User pAuthor) { + author = pAuthor; + } + + public Date getCreationDate() { + return (creationDate!=null) ? (Date) creationDate.clone() : null; + } + public void setCreationDate(Date creationDate) { + this.creationDate =(creationDate!=null) ? (Date) creationDate.clone(): null; + } + + + public String getVersion() { + return version; + } + public void setVersion(String version) { + this.version = version; + } + + public String getType() { + return documentMaster == null ? "":documentMaster.getType(); + } + + public Workflow getWorkflow() { + return workflow; + } + public void setWorkflow(Workflow pWorkflow) { + workflow = pWorkflow; + } + + public Integer getWorkflowId(){ + return hasWorkflow() ? workflow.getId() : null; + } + + public boolean hasWorkflow() { + return workflow != null; + } + + public String getLifeCycleState() { + if (workflow != null) { + return workflow.getLifeCycleState(); + } else { + return null; + } + } + + public ACL getACL() { + return acl; + } + public void setACL(ACL acl) { + this.acl = acl; + } + + public List<Workflow> getAbortedWorkflows() { + return abortedWorkflows; + } + + public void addAbortedWorkflows(Workflow abortedWorkflow) { + this.abortedWorkflows.add(abortedWorkflow); + } + + public List<DocumentIteration> getDocumentIterations() { + return documentIterations; + } + public void setDocumentIterations(List<DocumentIteration> documentIterations) { + this.documentIterations = documentIterations; + } + public DocumentIteration getIteration(int pIteration) { + return documentIterations.get(pIteration-1); + } + + public DocumentIteration createNextIteration(User pUser){ + DocumentIteration doc = new DocumentIteration(this, pUser); + documentIterations.add(doc); + return doc; + } + public DocumentIteration getLastIteration() { + int index = documentIterations.size()-1; + if(index < 0) { + return null; + } else { + return documentIterations.get(index); + } + } + public DocumentIteration getLastCheckedInIteration() { + int index; + if(isCheckedOut()){ + index = documentIterations.size()-2; + }else{ + index = documentIterations.size()-1; + } + if(index < 0) { + return null; + }else { + return documentIterations.get(index); + } + } + public DocumentIteration removeLastIteration() { + int index = documentIterations.size()-1; + if(index < 0) { + return null; + } else { + return documentIterations.remove(index); + } + } + /** + * Remove the iterations following the lastIterationWanted + * @param lastIterationWanted The new last iteration number + */ + public void removeFollowingIterations(int lastIterationWanted){ + DocumentIteration documentIteration; + do{ + documentIteration = getLastIteration(); + if(documentIteration.getIteration()>lastIterationWanted){ + documentIteration = removeLastIteration(); + } + }while(documentIteration!=null && documentIteration.getIteration()>lastIterationWanted); + } + + public int getNumberOfIterations() { + return documentIterations.size(); + } + + public void setDescription(String pDescription) { + description = pDescription; + } + public String getDescription() { + return description; + } + + public DocumentMasterKey getDocumentMasterKey() { + return documentMaster==null?new DocumentMasterKey("",""):documentMaster.getKey(); + } + + public String getWorkspaceId() { + return documentMaster==null?"":documentMaster.getWorkspaceId(); + } + + public String getId() { + return documentMaster==null?"":documentMaster.getId(); + } + + + public void setDocumentMasterId(String pDocumentMasterId) { + documentMasterId=pDocumentMasterId; + } + + public void setDocumentMasterWorkspaceId(String pDocumentMasterWorkspaceId) { + documentMasterWorkspaceId=pDocumentMasterWorkspaceId; + } + + public String getDocumentMasterId() { + return documentMasterId; + } + public String getDocumentMasterWorkspaceId() { + return documentMasterWorkspaceId; + } + + public boolean isPublicShared() { + return publicShared; + } + public void setPublicShared(boolean publicShared) { + this.publicShared = publicShared; + } + + public RevisionStatus getStatus() { + return status; + } + public void setStatus(RevisionStatus status) { + this.status = status; + } + + public boolean isReleased(){ + return status==RevisionStatus.RELEASED; + } + public boolean isObsolete(){ + return status==RevisionStatus.OBSOLETE; + } + public boolean release(User user){ + if(this.status==RevisionStatus.WIP){ + this.status=RevisionStatus.RELEASED; + StatusChange statusChange = new StatusChange(); + statusChange.setStatusChangeAuthor(user); + statusChange.setStatusModificationDate(new Date()); + this.setReleaseStatusChange(statusChange); + return true; + }else{ + return false; + } + + } + public boolean markAsObsolete(User user){ + if(this.status==RevisionStatus.RELEASED){ + this.status=RevisionStatus.OBSOLETE; + StatusChange statusChange = new StatusChange(); + statusChange.setStatusChangeAuthor(user); + statusChange.setStatusModificationDate(new Date()); + this.setObsoleteStatusChange(statusChange); + return true; + }else{ + return false; + } + + } + + public void setTitle(String pTitle) { + title = pTitle; + } + public String getTitle() { + return title; + } + + public boolean isCheckedOutBy(User pUser) { + return isCheckedOutBy(pUser.getLogin()); + } + + public Set<Tag> getTags() { + return tags; + } + public void setTags(Set<Tag> pTags) { + tags.retainAll(pTags); + pTags.removeAll(tags); + tags.addAll(pTags); + } + public boolean addTag(Tag pTag){ + return tags.add(pTag); + } + public boolean removeTag(Tag pTag){ + return tags.remove(pTag); + } + + public boolean isAttributesLocked(){ + if (this.documentMaster != null){ + return this.documentMaster.isAttributesLocked(); + } + return false; + } + + public StatusChange getObsoleteStatusChange() { + return obsoleteStatusChange; + } + + public void setObsoleteStatusChange(StatusChange statusChange) { + this.obsoleteStatusChange = statusChange; + } + + public StatusChange getReleaseStatusChange() { + return releaseStatusChange; + } + + public void setReleaseStatusChange(StatusChange statusChange) { + this.releaseStatusChange = statusChange; + } + + public User getObsoleteAuthor() { + return obsoleteStatusChange == null ? null : obsoleteStatusChange.getStatusChangeAuthor(); + } + + public Date getObsoleteDate() { + return obsoleteStatusChange == null ? null : obsoleteStatusChange.getStatusModificationDate(); + } + public User getReleaseAuthor() { + return releaseStatusChange == null ? null : releaseStatusChange.getStatusChangeAuthor(); + } + + public Date getReleaseDate() { + return releaseStatusChange == null ? null : releaseStatusChange.getStatusModificationDate(); + } + + @Override + public String toString() { + return documentMaster.getId() + "-" + version; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof DocumentRevision)) { + return false; + } + DocumentRevision docR = (DocumentRevision) pObj; + return docR.getId().equals(getId()) && + docR.getWorkspaceId().equals(getWorkspaceId()) && + docR.version.equals(version); + + } + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + getWorkspaceId().hashCode(); + hash = 31 * hash + getId().hashCode(); + hash = 31 * hash + version.hashCode(); + return hash; + } + public int compareTo(DocumentRevision pDocR) { + int wksComp = getWorkspaceId().compareTo(pDocR.getWorkspaceId()); + if (wksComp != 0) { + return wksComp; + } + int idComp = getId().compareTo(pDocR.getId()); + if (idComp != 0) { + return idComp; + } else { + return version.compareTo(pDocR.version); + } + } + + public Folder getLocation() { + return location; + } + + public void setLocation(Folder pLocation) { + location = pLocation; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/DocumentRevisionKey.java b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentRevisionKey.java new file mode 100644 index 0000000000..7520a0bbfd --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/DocumentRevisionKey.java @@ -0,0 +1,121 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.document; + + +import java.io.Serializable; + +/** + * Identity class of {@link DocumentRevision} objects. + * + * @author Florent Garin + */ +public class DocumentRevisionKey implements Serializable, Comparable<DocumentRevisionKey>, Cloneable { + + private DocumentMasterKey documentMaster; + private String version; + + + public DocumentRevisionKey() { + } + + public DocumentRevisionKey(String pWorkspaceId, String pId, String pVersion) { + documentMaster = new DocumentMasterKey(pWorkspaceId, pId); + version = pVersion; + } + + public DocumentRevisionKey(DocumentMasterKey pDocumentMasterKey, String pVersion) { + documentMaster = pDocumentMasterKey; + version = pVersion; + } + + + public String getWorkspaceId() { + return documentMaster.getWorkspace(); + } + + public String getDocumentMasterId() { + return documentMaster.getId(); + } + + public DocumentMasterKey getDocumentMaster() { + return documentMaster; + } + + public void setDocumentMaster(DocumentMasterKey documentMaster) { + this.documentMaster = documentMaster; + } + + + public String getVersion() { + return version; + } + + public void setVersion(String pVersion) { + version = pVersion; + } + + @Override + public String toString() { + return documentMaster + "-" + version; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof DocumentRevisionKey)) { + return false; + } + DocumentRevisionKey key = (DocumentRevisionKey) pObj; + return key.documentMaster.equals(documentMaster) && key.version.equals(version); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + documentMaster.hashCode(); + hash = 31 * hash + version.hashCode(); + return hash; + } + + public int compareTo(DocumentRevisionKey pKey) { + int wksMaster = documentMaster.compareTo(pKey.documentMaster); + if (wksMaster != 0) { + return wksMaster; + } else { + return version.compareTo(pKey.version); + } + } + + @Override + public DocumentRevisionKey clone() { + DocumentRevisionKey clone; + try { + clone = (DocumentRevisionKey) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + return clone; + } + +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/Folder.java b/docdoku-common/src/main/java/com/docdoku/core/document/Folder.java new file mode 100644 index 0000000000..88174e5731 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/Folder.java @@ -0,0 +1,212 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.document; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import java.io.Serializable; +import java.util.Stack; + +/** + * The {@link Folder} class is the unitary element of the tree structure. + * Like in a regular file system, folder may contain other folders or documents. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="FOLDER") +@Entity +public class Folder implements Serializable, Comparable<Folder> { + + @Column(length=255) + @javax.persistence.Id + private String completePath=""; + + @ManyToOne + private Folder parentFolder; + + public Folder() { + } + + public Folder(String pCompletePath) { + completePath = pCompletePath; + if (!isRoot() && !isHome()){ + int index = completePath.lastIndexOf('/'); + parentFolder = new Folder(completePath.substring(0, index)); + } + } + + public Folder(String pParentFolderPath, String pShortName) { + this(pParentFolderPath + "/" + pShortName); + } + + public String getWorkspaceId(){ + return Folder.parseWorkspaceId(completePath); + } + + public static String parseWorkspaceId(String pCompletePath){ + if(!pCompletePath.contains("/")) { + return pCompletePath; + }else{ + int index = pCompletePath.indexOf('/'); + return pCompletePath.substring(0, index); + } + } + + public boolean isRoot() { + return !completePath.contains("/"); + } + + public boolean isHome() { + try { + int index = completePath.lastIndexOf('/'); + return completePath.charAt(index+1) == '~'; + } catch (IndexOutOfBoundsException pIOOBEx) { + return false; + } + } + + public boolean isPrivate() { + try { + int index = completePath.indexOf('/'); + return completePath.charAt(index+1) == '~'; + } catch (IndexOutOfBoundsException pIOOBEx) { + return false; + } + } + + public String getOwner() { + String owner = null; + if (isPrivate()) { + int beginIndex = completePath.indexOf('/'); + int endIndex = completePath.indexOf("/", beginIndex+1); + if(endIndex==-1) { + endIndex = completePath.length(); + } + + owner = completePath.substring(beginIndex+2, endIndex); + } + return owner; + } + + @Override + public String toString() { + return completePath; + } + + public String getCompletePath() { + return completePath; + } + + public Folder getParentFolder() { + return parentFolder; + } + + public Folder[] getAllFolders() { + Folder currentFolder = this; + Stack<Folder> foldersStack = new Stack<>(); + + while (!(currentFolder == null)) { + foldersStack.push(currentFolder); + currentFolder = currentFolder.getParentFolder(); + } + + Folder[] folders = new Folder[foldersStack.size()]; + + int i = 0; + + while(!foldersStack.empty()){ + folders[i++] = foldersStack.pop(); + } + + return folders; + } + + public String getShortName() { + if(isRoot()) { + return completePath; + } + + int index = completePath.lastIndexOf('/'); + return completePath.substring(index + 1); + } + + public String getRoutePath() { + int index = completePath.indexOf('/'); + + if (index == -1) { + return ""; + } else { + return completePath.substring(index + 1).replaceAll("/", ":"); + } + } + + public String getFoldersPath() { + String path = getRoutePath(); + + if (path != null && path.length() > 0) { + return "folders/" + path; + } else { + return "folders"; + } + } + + public static Folder createRootFolder(String pWorkspaceId) { + return new Folder(pWorkspaceId); + } + + public static Folder createHomeFolder(String pWorkspaceId, String pLogin) { + return new Folder(pWorkspaceId + "/~" + pLogin); + } + + public Folder createSubFolder(String pShortName) { + Folder subFolder = new Folder(); + subFolder.completePath=completePath + "/" + pShortName; + subFolder.parentFolder=this; + return subFolder; + } + + + @Override + public int hashCode() { + return completePath.hashCode(); + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof Folder)) { + return false; + } + Folder folder = (Folder) pObj; + return folder.completePath.equals(completePath); + } + + @Override + public int compareTo(Folder pFolder) { + return completePath.compareTo(pFolder.completePath); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/IterationChangeSubscription.java b/docdoku-common/src/main/java/com/docdoku/core/document/IterationChangeSubscription.java new file mode 100644 index 0000000000..d75e6283ec --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/IterationChangeSubscription.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.document; + +import com.docdoku.core.common.User; + +import javax.persistence.Entity; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; + +/** + * A concrete implementation of a subscription. + * With the use of this class, the user manifests his interest of being informed + * when a new iteration has been made on a specific document. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="ITERATIONCHANGESUBSCRIPTION") +@Entity +@NamedQueries({ + @NamedQuery(name="IterationChangeSubscription.findSubscriptionByUserAndDocRevision", query="SELECT s FROM IterationChangeSubscription s WHERE s.subscriber = :user AND s.observedDocumentRevision = :docR"), +}) +public class IterationChangeSubscription extends Subscription{ + + + public IterationChangeSubscription() { + } + + public IterationChangeSubscription (User pSubscriber, DocumentRevision pObservedElement){ + super(pSubscriber,pObservedElement); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/StateChangeSubscription.java b/docdoku-common/src/main/java/com/docdoku/core/document/StateChangeSubscription.java new file mode 100644 index 0000000000..483726509d --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/StateChangeSubscription.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.document; + +import com.docdoku.core.common.User; + +import javax.persistence.Entity; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; + +/** + * Subscription on the event that is triggered when the state of the workflow + * attached to the document has changed. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="STATECHANGESUBSCRIPTION") +@Entity +@NamedQueries({ + @NamedQuery(name="StateChangeSubscription.findSubscriptionByUserAndDocRevision", query="SELECT s FROM StateChangeSubscription s WHERE s.subscriber = :user AND s.observedDocumentRevision = :docR"), +}) +public class StateChangeSubscription extends Subscription{ + + + public StateChangeSubscription() { + } + + public StateChangeSubscription (User pSubscriber, DocumentRevision pObservedElement){ + super(pSubscriber,pObservedElement); + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/StatusChange.java b/docdoku-common/src/main/java/com/docdoku/core/document/StatusChange.java new file mode 100644 index 0000000000..a91b5b2c28 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/StatusChange.java @@ -0,0 +1,61 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.document; + +import com.docdoku.core.common.User; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; + +/** + * @author Elisabel Généreux + */ +@Embeddable +public class StatusChange implements Serializable{ + + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="USER_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name="USER_WORKSPACE",referencedColumnName = "WORKSPACE_ID")}) + private User statusChangeAuthor; + + @Temporal(TemporalType.TIMESTAMP) + private Date statusModificationDate; + + public Date getStatusModificationDate() { + return statusModificationDate; + } + + public void setStatusModificationDate(Date statusModificationDate) { + this.statusModificationDate = statusModificationDate; + } + + public User getStatusChangeAuthor() { + return statusChangeAuthor; + } + + public void setStatusChangeAuthor(User statusChangeAuthor) { + this.statusChangeAuthor = statusChangeAuthor; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/Subscription.java b/docdoku-common/src/main/java/com/docdoku/core/document/Subscription.java new file mode 100644 index 0000000000..7ab2c9f459 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/Subscription.java @@ -0,0 +1,137 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.document; + +import com.docdoku.core.common.User; + +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.ManyToOne; +import java.io.Serializable; + +/** + * Abstract class for defining subscription made by users on documents. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@javax.persistence.IdClass(com.docdoku.core.document.SubscriptionKey.class) +@javax.persistence.MappedSuperclass +public abstract class Subscription implements Serializable{ + + + @ManyToOne(optional=false, fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="SUBSCRIBER_LOGIN", referencedColumnName="LOGIN"), + @JoinColumn(name="SUBSCRIBER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + protected User subscriber; + + @ManyToOne(optional=false, fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="DOCUMENTMASTER_ID", referencedColumnName="DOCUMENTMASTER_ID"), + @JoinColumn(name="DOCUMENTREVISION_VERSION", referencedColumnName="VERSION"), + @JoinColumn(name="DOCUMENTMASTER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + protected DocumentRevision observedDocumentRevision; + + @javax.persistence.Column(name = "SUBSCRIBER_WORKSPACE_ID", length=100, nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String subscriberWorkspaceId=""; + + @javax.persistence.Column(name = "SUBSCRIBER_LOGIN", nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String subscriberLogin=""; + + + @javax.persistence.Column(name = "DOCUMENTMASTER_WORKSPACE_ID", length=100, nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String observedDocumentRevisionWorkspaceId =""; + + @javax.persistence.Column(name = "DOCUMENTREVISION_VERSION", length=10, nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String observedDocumentRevisionVersion =""; + + @javax.persistence.Column(name = "DOCUMENTMASTER_ID", length=100, nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String observedDocumentRevisionId =""; + + + public Subscription() { + } + + public Subscription (User pSubscriber, DocumentRevision pObservedElement){ + setSubscriber(pSubscriber); + setObservedDocumentRevision(pObservedElement); + } + + public SubscriptionKey getKey(){ + return new SubscriptionKey(subscriberWorkspaceId, subscriberLogin, observedDocumentRevisionWorkspaceId, observedDocumentRevisionId, observedDocumentRevisionVersion); + } + + public DocumentRevision getObservedDocumentRevision() { + return observedDocumentRevision; + } + + public User getSubscriber() { + return subscriber; + } + + public String getSubscriberLogin() { + return subscriberLogin; + } + + + public String getSubscriberWorkspaceId() { + return subscriberWorkspaceId; + } + + public String getObservedDocumentRevisionId() { + return observedDocumentRevisionId; + } + + public String getObservedDocumentRevisionVersion() { + return observedDocumentRevisionVersion; + } + + public String getObservedDocumentRevisionWorkspaceId() { + return observedDocumentRevisionWorkspaceId; + } + + + public void setObservedDocumentRevision(DocumentRevision pObservedDocumentRevision) { + observedDocumentRevision = pObservedDocumentRevision; + observedDocumentRevisionId = observedDocumentRevision.getId(); + observedDocumentRevisionVersion = observedDocumentRevision.getVersion(); + observedDocumentRevisionWorkspaceId = observedDocumentRevision.getWorkspaceId(); + + } + + public void setSubscriber(User pSubscriber) { + subscriber = pSubscriber; + subscriberLogin=subscriber.getLogin(); + subscriberWorkspaceId=subscriber.getWorkspaceId(); + } + + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/document/SubscriptionKey.java b/docdoku-common/src/main/java/com/docdoku/core/document/SubscriptionKey.java new file mode 100644 index 0000000000..3fb023f6e4 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/document/SubscriptionKey.java @@ -0,0 +1,114 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.document; + +import java.io.Serializable; + +/** + * + * @author Florent Garin + */ +public class SubscriptionKey implements Serializable { + + private String subscriberWorkspaceId; + private String subscriberLogin; + private String observedDocumentRevisionWorkspaceId; + private String observedDocumentRevisionVersion; + private String observedDocumentRevisionId; + + public SubscriptionKey() { + } + + public SubscriptionKey(String pSubscriberWorkspaceId, String pSubscriberLogin, String pObservedDocumentRevisionWorkspaceId, String pObservedDocumentRevisionId, String pObservedDocumentRevisionVersion) { + subscriberWorkspaceId = pSubscriberWorkspaceId; + subscriberLogin = pSubscriberLogin; + observedDocumentRevisionWorkspaceId = pObservedDocumentRevisionWorkspaceId; + observedDocumentRevisionId = pObservedDocumentRevisionId; + observedDocumentRevisionVersion = pObservedDocumentRevisionVersion; + } + + public String getObservedDocumentRevisionId() { + return observedDocumentRevisionId; + } + + public String getObservedDocumentRevisionVersion() { + return observedDocumentRevisionVersion; + } + + public String getObservedDocumentRevisionWorkspaceId() { + return observedDocumentRevisionWorkspaceId; + } + + public String getSubscriberLogin() { + return subscriberLogin; + } + + public String getSubscriberWorkspaceId() { + return subscriberWorkspaceId; + } + + public void setObservedDocumentRevisionId(String observedDocumentRevisionId) { + this.observedDocumentRevisionId = observedDocumentRevisionId; + } + + public void setObservedDocumentRevisionVersion(String observedDocumentRevisionVersion) { + this.observedDocumentRevisionVersion = observedDocumentRevisionVersion; + } + + public void setObservedDocumentRevisionWorkspaceId(String observedDocumentRevisionWorkspaceId) { + this.observedDocumentRevisionWorkspaceId = observedDocumentRevisionWorkspaceId; + } + + public void setSubscriberLogin(String subscriberLogin) { + this.subscriberLogin = subscriberLogin; + } + + public void setSubscriberWorkspaceId(String subscriberWorkspaceId) { + this.subscriberWorkspaceId = subscriberWorkspaceId; + } + + @Override + public String toString() { + return subscriberWorkspaceId + "-" + subscriberLogin + "/" + observedDocumentRevisionWorkspaceId + "-" + observedDocumentRevisionId + "-" + observedDocumentRevisionVersion; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof SubscriptionKey)) { + return false; + } + SubscriptionKey key = (SubscriptionKey) pObj; + return key.subscriberWorkspaceId.equals(subscriberWorkspaceId) && key.subscriberLogin.equals(subscriberLogin) && key.observedDocumentRevisionId.equals(observedDocumentRevisionId) && key.observedDocumentRevisionWorkspaceId.equals(observedDocumentRevisionWorkspaceId) && key.observedDocumentRevisionVersion.equals(observedDocumentRevisionVersion); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + subscriberWorkspaceId.hashCode(); + hash = 31 * hash + subscriberLogin.hashCode(); + hash = 31 * hash + observedDocumentRevisionWorkspaceId.hashCode(); + hash = 31 * hash + observedDocumentRevisionId.hashCode(); + hash = 31 * hash + observedDocumentRevisionVersion.hashCode(); + return hash; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/AccessRightException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/AccessRightException.java new file mode 100644 index 0000000000..08135c4a34 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/AccessRightException.java @@ -0,0 +1,59 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.User; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class AccessRightException extends ApplicationException { + private final String mName; + + public AccessRightException(String pMessage) { + super(pMessage); + mName=null; + } + + public AccessRightException(Locale pLocale, User pUser) { + this(pLocale, pUser.toString()); + } + + public AccessRightException(Locale pLocale, Account pAccount) { + this(pLocale, pAccount.toString()); + } + + public AccessRightException(Locale pLocale, String pName) { + super(pLocale); + mName=pName; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mName); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/AccountAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/AccountAlreadyExistsException.java new file mode 100644 index 0000000000..8d6018d9b2 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/AccountAlreadyExistsException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class AccountAlreadyExistsException extends EntityAlreadyExistsException { + private final String mLogin; + + public AccountAlreadyExistsException(String pMessage) { + super(pMessage); + mLogin = null; + } + + public AccountAlreadyExistsException(Locale pLocale, String pLogin) { + this(pLocale, pLogin, null); + } + + public AccountAlreadyExistsException(Locale pLocale, String pLogin, Throwable pCause) { + super(pLocale, pCause); + mLogin=pLogin; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mLogin); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/AccountNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/AccountNotFoundException.java new file mode 100644 index 0000000000..e76744bdfe --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/AccountNotFoundException.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class AccountNotFoundException extends EntityNotFoundException { + private final String mLogin; + + + public AccountNotFoundException(String pMessage) { + super(pMessage); + mLogin=null; + } + + public AccountNotFoundException(Locale pLocale, String pLogin) { + this(pLocale, pLogin, null); + } + + public AccountNotFoundException(Locale pLocale, String pLogin, Throwable pCause) { + super(pLocale, pCause); + mLogin=pLogin; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mLogin); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ApplicationException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ApplicationException.java new file mode 100644 index 0000000000..18d1366791 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ApplicationException.java @@ -0,0 +1,84 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * + * @author Florent Garin + */ +public abstract class ApplicationException extends Exception{ + + private static final String DEFAULT_BUNDLE_NAME = "com.docdoku.core.i18n.LocalStrings"; + private ResourceBundle mResourceBundle; + + public ApplicationException(String pMessage, Throwable pCause, String pBundleName) { + super(pMessage, pCause); + mResourceBundle=ResourceBundle.getBundle(pBundleName, Locale.getDefault()); + } + + public ApplicationException(String pMessage) { + super(pMessage); + mResourceBundle=ResourceBundle.getBundle(DEFAULT_BUNDLE_NAME, Locale.getDefault()); + } + + public ApplicationException(String pMessage, Throwable pCause) { + super(pMessage, pCause); + mResourceBundle=ResourceBundle.getBundle(DEFAULT_BUNDLE_NAME, Locale.getDefault()); + } + + public ApplicationException(Locale pLocale) { + super(); + mResourceBundle=ResourceBundle.getBundle(DEFAULT_BUNDLE_NAME, pLocale); + } + + public ApplicationException(Locale pLocale, Throwable pCause) { + super(pCause); + mResourceBundle=ResourceBundle.getBundle(DEFAULT_BUNDLE_NAME, pLocale); + } + public void setLocale(Locale pLocale){ + mResourceBundle=ResourceBundle.getBundle(DEFAULT_BUNDLE_NAME, pLocale); + } + + protected String getBundleDefaultMessage(){ + return getBundleMessage(getClass().getSimpleName()); + } + + protected String getBundleMessage(String pKey){ + return mResourceBundle.getString(pKey); + } + + @Override + public String getMessage() { + String detailMessage=super.getMessage(); + return detailMessage==null?getLocalizedMessage():detailMessage; + } + + @Override + public abstract String getLocalizedMessage(); + + @Override + public String toString() { + return getMessage(); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/BaselineNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/BaselineNotFoundException.java new file mode 100644 index 0000000000..5028f3ef9f --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/BaselineNotFoundException.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class BaselineNotFoundException extends EntityNotFoundException { + private final int mBaseline; + + + public BaselineNotFoundException(String pMessage) { + super(pMessage); + mBaseline = -1; + } + + public BaselineNotFoundException(Locale pLocale, int pBaseline) { + this(pLocale, pBaseline, null); + } + + public BaselineNotFoundException(Locale pLocale, int pBaseline, Throwable pCause) { + super(pLocale, pCause); + mBaseline=pBaseline; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mBaseline); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/BaselinedFolderAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/BaselinedFolderAlreadyExistsException.java new file mode 100644 index 0000000000..0b2ffb783f --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/BaselinedFolderAlreadyExistsException.java @@ -0,0 +1,57 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.configuration.BaselinedFolder; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * Exception thrown when trying to add a folder into a baseline which already has it. + * + * @author Taylor LABEJOF + * @version 2.0, 05/09/14 + * @since V2.0 + */ +public class BaselinedFolderAlreadyExistsException extends EntityAlreadyExistsException { + private final BaselinedFolder mFolder; + + public BaselinedFolderAlreadyExistsException(String pMessage) { + super(pMessage); + mFolder = null; + } + + public BaselinedFolderAlreadyExistsException(Locale pLocale, BaselinedFolder pFolder) { + this(pLocale, pFolder, null); + } + + public BaselinedFolderAlreadyExistsException(Locale pLocale, BaselinedFolder pFolder, Throwable pCause) { + super(pLocale, pCause); + mFolder=pFolder; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message, mFolder); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ChangeIssueNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ChangeIssueNotFoundException.java new file mode 100644 index 0000000000..85f435ab92 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ChangeIssueNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class ChangeIssueNotFoundException extends EntityNotFoundException { + private final int mChange; + + public ChangeIssueNotFoundException(String pMessage) { + super(pMessage); + mChange = -1; + } + + public ChangeIssueNotFoundException(Locale pLocale, int pChange) { + this(pLocale, pChange, null); + } + + public ChangeIssueNotFoundException(Locale pLocale, int pChange, Throwable pCause) { + super(pLocale, pCause); + mChange =pChange; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message, mChange); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ChangeOrderNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ChangeOrderNotFoundException.java new file mode 100644 index 0000000000..900d52d69a --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ChangeOrderNotFoundException.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class ChangeOrderNotFoundException extends EntityNotFoundException { + private final int mChange; + + + public ChangeOrderNotFoundException(String pMessage) { + super(pMessage); + mChange = -1; + } + + public ChangeOrderNotFoundException(Locale pLocale, int pChange) { + this(pLocale, pChange, null); + } + + public ChangeOrderNotFoundException(Locale pLocale, int pChange, Throwable pCause) { + super(pLocale, pCause); + mChange =pChange; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message, mChange); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ChangeRequestNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ChangeRequestNotFoundException.java new file mode 100644 index 0000000000..f03f0b0dbf --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ChangeRequestNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class ChangeRequestNotFoundException extends EntityNotFoundException { + private final int mChange; + + public ChangeRequestNotFoundException(String pMessage) { + super(pMessage); + mChange = -1; + } + + public ChangeRequestNotFoundException(Locale pLocale, int pChange) { + this(pLocale, pChange, null); + } + + public ChangeRequestNotFoundException(Locale pLocale, int pChange, Throwable pCause) { + super(pLocale, pCause); + mChange =pChange; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message, mChange); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ConfigurationItemAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ConfigurationItemAlreadyExistsException.java new file mode 100644 index 0000000000..44a495edab --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ConfigurationItemAlreadyExistsException.java @@ -0,0 +1,56 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + + +import com.docdoku.core.product.ConfigurationItem; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class ConfigurationItemAlreadyExistsException extends EntityAlreadyExistsException { + private final ConfigurationItem mConfigurationItem; + + + public ConfigurationItemAlreadyExistsException(String pMessage) { + super(pMessage); + mConfigurationItem = null; + } + + public ConfigurationItemAlreadyExistsException(Locale pLocale, ConfigurationItem pConfigurationItem) { + this(pLocale, pConfigurationItem, null); + } + + public ConfigurationItemAlreadyExistsException(Locale pLocale, ConfigurationItem pConfigurationItem, Throwable pCause) { + super(pLocale, pCause); + mConfigurationItem=pConfigurationItem; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mConfigurationItem); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ConfigurationItemNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ConfigurationItemNotFoundException.java new file mode 100644 index 0000000000..238a1c38bf --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ConfigurationItemNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class ConfigurationItemNotFoundException extends EntityNotFoundException { + private final String mCIId; + + public ConfigurationItemNotFoundException(String pMessage) { + super(pMessage); + mCIId = null; + } + + public ConfigurationItemNotFoundException(Locale pLocale, String pCIID) { + this(pLocale, pCIID, null); + } + + public ConfigurationItemNotFoundException(Locale pLocale, String pCIId, Throwable pCause) { + super(pLocale, pCause); + mCIId=pCIId; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mCIId); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ConvertedResourceException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ConvertedResourceException.java new file mode 100644 index 0000000000..25a9a8ce41 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ConvertedResourceException.java @@ -0,0 +1,35 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.util.Locale; + +public class ConvertedResourceException extends ApplicationException { + + public ConvertedResourceException(Locale pLocale, Throwable pCause) { + super(pLocale, pCause); + } + + @Override + public String getLocalizedMessage() { + return getBundleDefaultMessage(); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/CreationException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/CreationException.java new file mode 100644 index 0000000000..b07c0c67e5 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/CreationException.java @@ -0,0 +1,47 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class CreationException extends ApplicationException { + + public CreationException(String pMessage) { + super(pMessage); + } + + public CreationException(Locale pLocale) { + this(pLocale, null); + } + + public CreationException(Locale pLocale, Throwable pCause) { + super(pLocale, pCause); + } + + @Override + public String getLocalizedMessage() { + return getBundleDefaultMessage(); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentIterationNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentIterationNotFoundException.java new file mode 100644 index 0000000000..985babe3f5 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentIterationNotFoundException.java @@ -0,0 +1,68 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.document.DocumentIterationKey; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class DocumentIterationNotFoundException extends EntityNotFoundException { + private final String mDocMId; + private final String mDocRStringVersion; + private final Integer mDocIStringIteration; + + public DocumentIterationNotFoundException(String pMessage) { + super(pMessage); + mDocMId=null; + mDocRStringVersion=null; + mDocIStringIteration=null; + } + + public DocumentIterationNotFoundException(Locale pLocale, DocumentIterationKey pKey) { + this(pLocale, pKey, null); + } + + public DocumentIterationNotFoundException(Locale pLocale, DocumentIterationKey pKey, Throwable pCause) { + this(pLocale, pKey.getDocumentMasterId(), pKey.getDocumentRevisionVersion(), pKey.getIteration(), pCause); + } + + public DocumentIterationNotFoundException(Locale pLocale, + String pDocMId, + String pDocRStringVersion, + int pDocIStringIteration, + Throwable pCause) { + super(pLocale, pCause); + mDocMId=pDocMId; + mDocRStringVersion=pDocRStringVersion; + mDocIStringIteration=pDocIStringIteration; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mDocMId,mDocRStringVersion,mDocIStringIteration); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentMasterAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentMasterAlreadyExistsException.java new file mode 100644 index 0000000000..28f71545fa --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentMasterAlreadyExistsException.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.document.DocumentMaster; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class DocumentMasterAlreadyExistsException extends EntityAlreadyExistsException { + private final DocumentMaster mDocM; + + + public DocumentMasterAlreadyExistsException(String pMessage) { + super(pMessage); + mDocM = null; + } + + public DocumentMasterAlreadyExistsException(Locale pLocale, DocumentMaster pDocM) { + this(pLocale, pDocM, null); + } + + public DocumentMasterAlreadyExistsException(Locale pLocale, DocumentMaster pDocM, Throwable pCause) { + super(pLocale, pCause); + mDocM=pDocM; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mDocM); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentMasterTemplateAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentMasterTemplateAlreadyExistsException.java new file mode 100644 index 0000000000..1fa0897c3e --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentMasterTemplateAlreadyExistsException.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.document.DocumentMasterTemplate; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class DocumentMasterTemplateAlreadyExistsException extends EntityAlreadyExistsException { + private final DocumentMasterTemplate mDocMTemplate; + + + public DocumentMasterTemplateAlreadyExistsException(String pMessage) { + super(pMessage); + mDocMTemplate=null; + } + + public DocumentMasterTemplateAlreadyExistsException(Locale pLocale, DocumentMasterTemplate pDocMTemplate) { + this(pLocale, pDocMTemplate, null); + } + + public DocumentMasterTemplateAlreadyExistsException(Locale pLocale, DocumentMasterTemplate pDocMTemplate, Throwable pCause) { + super(pLocale, pCause); + mDocMTemplate=pDocMTemplate; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mDocMTemplate); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentMasterTemplateNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentMasterTemplateNotFoundException.java new file mode 100644 index 0000000000..8216b6b2e8 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentMasterTemplateNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class DocumentMasterTemplateNotFoundException extends EntityNotFoundException { + private final String mDocMTemplateId; + + public DocumentMasterTemplateNotFoundException(String pMessage) { + super(pMessage); + mDocMTemplateId=null; + } + + public DocumentMasterTemplateNotFoundException(Locale pLocale, String pDocMTemplateID) { + this(pLocale, pDocMTemplateID, null); + } + + public DocumentMasterTemplateNotFoundException(Locale pLocale, String pDocMTemplateId, Throwable pCause) { + super(pLocale, pCause); + mDocMTemplateId=pDocMTemplateId; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mDocMTemplateId); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentRevisionAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentRevisionAlreadyExistsException.java new file mode 100644 index 0000000000..f3d8d3cb2a --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentRevisionAlreadyExistsException.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.document.DocumentRevision; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class DocumentRevisionAlreadyExistsException extends EntityAlreadyExistsException { + private final DocumentRevision mDocR; + + + public DocumentRevisionAlreadyExistsException(String pMessage) { + super(pMessage); + mDocR=null; + } + + public DocumentRevisionAlreadyExistsException(Locale pLocale, DocumentRevision pDocR) { + this(pLocale, pDocR, null); + } + + public DocumentRevisionAlreadyExistsException(Locale pLocale, DocumentRevision pDocR, Throwable pCause) { + super(pLocale, pCause); + mDocR=pDocR; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mDocR); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentRevisionNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentRevisionNotFoundException.java new file mode 100644 index 0000000000..d6302c1eb2 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/DocumentRevisionNotFoundException.java @@ -0,0 +1,70 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.common.Version; +import com.docdoku.core.document.DocumentRevisionKey; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class DocumentRevisionNotFoundException extends EntityNotFoundException { + private final String mDocMId; + private final String mDocRStringVersion; + + public DocumentRevisionNotFoundException(String pMessage) { + super(pMessage); + mDocMId=null; + mDocRStringVersion=null; + } + + public DocumentRevisionNotFoundException(Locale pLocale, DocumentRevisionKey pDocRPK) { + this(pLocale, pDocRPK, null); + } + + public DocumentRevisionNotFoundException(Locale pLocale, DocumentRevisionKey pDocRPK, Throwable pCause) { + this(pLocale, pDocRPK.getDocumentMaster().getId(), pDocRPK.getVersion(), pCause); + } + + public DocumentRevisionNotFoundException(Locale pLocale, String pDocMID, Version pDocRVersion) { + this(pLocale, pDocMID, pDocRVersion.toString(), null); + } + + public DocumentRevisionNotFoundException(Locale pLocale, String pDocMId, String pDocRStringVersion) { + this(pLocale, pDocMId, pDocRStringVersion, null); + } + + public DocumentRevisionNotFoundException(Locale pLocale, String pDocMId, String pDocRStringVersion, Throwable pCause) { + super(pLocale, pCause); + mDocMId=pDocMId; + mDocRStringVersion=pDocRStringVersion; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mDocMId,mDocRStringVersion); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ESIndexAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ESIndexAlreadyExistsException.java new file mode 100644 index 0000000000..5b8687e845 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ESIndexAlreadyExistsException.java @@ -0,0 +1,40 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.exceptions; + +import java.util.Locale; + +/** + * Exception when trying to create a existing index. + * @author Taylor LABEJOF + */ +public class ESIndexAlreadyExistsException extends EntityAlreadyExistsException { + private final String mKey; + + public ESIndexAlreadyExistsException(Locale pLocale) { + super(pLocale); + mKey="IndexAlreadyExistsException"; + } + + @Override + public String getLocalizedMessage() { + return mKey==null?null:getBundleMessage(mKey); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ESIndexNamingException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ESIndexNamingException.java new file mode 100644 index 0000000000..08883ea272 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ESIndexNamingException.java @@ -0,0 +1,39 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.exceptions; + +import java.util.Locale; + +/** + * Exception when trying to create a index with a wrong Name. + * @author Taylor LABEJOF + */ +public class ESIndexNamingException extends ApplicationException { + private final String mKey; + + public ESIndexNamingException(Locale pLocale) { + super(pLocale); + mKey="IndexNamingException"; + } + @Override + public String getLocalizedMessage() { + return mKey==null?null:getBundleMessage(mKey); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ESServerException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ESServerException.java new file mode 100644 index 0000000000..402d9e59cd --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ESServerException.java @@ -0,0 +1,44 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.exceptions; + +import java.util.Locale; + +/** + * Exception on the Indexer Server. + * @author Taylor LABEJOF + */ +public class ESServerException extends ApplicationException { + private final String mKey; + + public ESServerException(String pMessage) { + super(pMessage); + mKey = null; + } + public ESServerException(Locale pLocale, String pKey) { + super(pLocale); + mKey=pKey; + } + + @Override + public String getLocalizedMessage() { + return mKey==null?null:getBundleMessage(mKey); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/EntityAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/EntityAlreadyExistsException.java new file mode 100644 index 0000000000..cea3857e0d --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/EntityAlreadyExistsException.java @@ -0,0 +1,41 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.util.Locale; + +/** + * @author Taylor LABEJOF + */ +public abstract class EntityAlreadyExistsException extends ApplicationException { + + public EntityAlreadyExistsException(String pMessage) { + super(pMessage); + } + + public EntityAlreadyExistsException(Locale pLocale) { + super(pLocale); + } + + public EntityAlreadyExistsException(Locale pLocale, Throwable pCause) { + super(pLocale, pCause); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/EntityConstraintException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/EntityConstraintException.java new file mode 100644 index 0000000000..88f4588d0f --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/EntityConstraintException.java @@ -0,0 +1,49 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + + +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class EntityConstraintException extends ApplicationException{ + private final String mKey; + + public EntityConstraintException(String pMessage) { + super(pMessage); + mKey=null; + } + + public EntityConstraintException(Locale pLocale, String pKey) { + super(pLocale); + mKey=pKey; + } + + @Override + public String getLocalizedMessage() { + return mKey==null?null:getBundleMessage(mKey); + } + +} + diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/EntityNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/EntityNotFoundException.java new file mode 100644 index 0000000000..aef32823b2 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/EntityNotFoundException.java @@ -0,0 +1,41 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.util.Locale; + +/** + * @author Taylor LABEJOF + */ +public abstract class EntityNotFoundException extends ApplicationException { + + public EntityNotFoundException(String pMessage) { + super(pMessage); + } + + public EntityNotFoundException(Locale pLocale) { + super(pLocale); + } + + public EntityNotFoundException(Locale pLocale, Throwable pCause) { + super(pLocale, pCause); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/FileAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/FileAlreadyExistsException.java new file mode 100644 index 0000000000..a9f78b53b3 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/FileAlreadyExistsException.java @@ -0,0 +1,63 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.common.BinaryResource; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class FileAlreadyExistsException extends EntityAlreadyExistsException { + private final String mFullName; + + + public FileAlreadyExistsException(String pMessage) { + super(pMessage); + mFullName=null; + } + + public FileAlreadyExistsException(Locale pLocale, BinaryResource pFile) { + this(pLocale,pFile.getFullName()); + } + + public FileAlreadyExistsException(Locale pLocale, String pFullName) { + this(pLocale,pFullName, null); + } + + public FileAlreadyExistsException(Locale pLocale, BinaryResource pFile, Throwable pCause) { + this(pLocale,pFile.getFullName(),pCause); + } + + public FileAlreadyExistsException(Locale pLocale, String pFullName, Throwable pCause) { + super(pLocale,pCause); + mFullName=pFullName; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mFullName); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/FileNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/FileNotFoundException.java new file mode 100644 index 0000000000..65a79e686a --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/FileNotFoundException.java @@ -0,0 +1,48 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class FileNotFoundException extends EntityNotFoundException { + private final String mFullName; + + public FileNotFoundException(String pMessage) { + super(pMessage); + mFullName=null; + } + + public FileNotFoundException(Locale pLocale, String pFullName) { + super(pLocale); + mFullName=pFullName; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mFullName); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/FolderAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/FolderAlreadyExistsException.java new file mode 100644 index 0000000000..633635b792 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/FolderAlreadyExistsException.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.document.Folder; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class FolderAlreadyExistsException extends EntityAlreadyExistsException { + private final Folder mFolder; + + + public FolderAlreadyExistsException(String pMessage) { + super(pMessage); + mFolder=null; + } + + public FolderAlreadyExistsException(Locale pLocale, Folder pFolder) { + this(pLocale, pFolder, null); + } + + public FolderAlreadyExistsException(Locale pLocale, Folder pFolder, Throwable pCause) { + super(pLocale, pCause); + mFolder=pFolder; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mFolder); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/FolderNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/FolderNotFoundException.java new file mode 100644 index 0000000000..06d642e254 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/FolderNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class FolderNotFoundException extends EntityNotFoundException { + private final String mCompletePath; + + public FolderNotFoundException(String pMessage) { + super(pMessage); + mCompletePath=null; + } + + public FolderNotFoundException(Locale pLocale, String pCompletePath) { + this(pLocale, pCompletePath, null); + } + + public FolderNotFoundException(Locale pLocale, String pCompletePath, Throwable pCause) { + super(pLocale, pCause); + mCompletePath=pCompletePath; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mCompletePath); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/GCMAccountAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/GCMAccountAlreadyExistsException.java new file mode 100644 index 0000000000..dc150b18a3 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/GCMAccountAlreadyExistsException.java @@ -0,0 +1,47 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class GCMAccountAlreadyExistsException extends EntityAlreadyExistsException { + private final String mLogin; + + public GCMAccountAlreadyExistsException(Locale pLocale, String pLogin) { + this(pLocale, pLogin, null); + } + + public GCMAccountAlreadyExistsException(Locale pLocale, String pLogin, Throwable pCause) { + super(pLocale, pCause); + mLogin=pLogin; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mLogin); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/GCMAccountNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/GCMAccountNotFoundException.java new file mode 100644 index 0000000000..322e9cf86e --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/GCMAccountNotFoundException.java @@ -0,0 +1,48 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class GCMAccountNotFoundException extends EntityNotFoundException { + private final String mLogin; + + + public GCMAccountNotFoundException(Locale pLocale, String pLogin) { + this(pLocale, pLogin, null); + } + + public GCMAccountNotFoundException(Locale pLocale, String pLogin, Throwable pCause) { + super(pLocale, pCause); + mLogin=pLogin; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mLogin); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/LayerNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/LayerNotFoundException.java new file mode 100644 index 0000000000..216de279f0 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/LayerNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class LayerNotFoundException extends EntityNotFoundException { + private final int mLayer; + + public LayerNotFoundException(String pMessage) { + super(pMessage); + mLayer=-1; + } + + public LayerNotFoundException(Locale pLocale, int pLayer) { + this(pLocale, pLayer, null); + } + + public LayerNotFoundException(Locale pLocale, int pLayer, Throwable pCause) { + super(pLocale, pCause); + mLayer=pLayer; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mLayer); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ListOfValuesAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ListOfValuesAlreadyExistsException.java new file mode 100644 index 0000000000..9e890a5fb4 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ListOfValuesAlreadyExistsException.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.meta.ListOfValues; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class ListOfValuesAlreadyExistsException extends EntityAlreadyExistsException { + private final ListOfValues mLov; + + + public ListOfValuesAlreadyExistsException(String pMessage) { + super(pMessage); + mLov=null; + } + + public ListOfValuesAlreadyExistsException(Locale pLocale, ListOfValues pLov) { + this(pLocale, pLov, null); + } + + public ListOfValuesAlreadyExistsException(Locale pLocale, ListOfValues pLov, Throwable pCause) { + super(pLocale, pCause); + mLov=pLov; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mLov); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ListOfValuesNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ListOfValuesNotFoundException.java new file mode 100644 index 0000000000..1c139c3fe4 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ListOfValuesNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class ListOfValuesNotFoundException extends EntityNotFoundException { + private final String mName; + + public ListOfValuesNotFoundException(String pMessage) { + super(pMessage); + mName=null; + } + + public ListOfValuesNotFoundException(Locale pLocale, String pName) { + this(pLocale, pName, null); + } + + public ListOfValuesNotFoundException(Locale pLocale, String pName, Throwable pCause) { + super(pLocale, pCause); + mName=pName; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mName); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/MarkerNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/MarkerNotFoundException.java new file mode 100644 index 0000000000..3df2275bc6 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/MarkerNotFoundException.java @@ -0,0 +1,44 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +public class MarkerNotFoundException extends EntityNotFoundException { + private final int mMarker; + + public MarkerNotFoundException(Locale pLocale, int pMarker) { + this(pLocale, pMarker, null); + } + + public MarkerNotFoundException(Locale pLocale, int pMarker, Throwable pCause) { + super(pLocale, pCause); + mMarker = pMarker; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message, mMarker); + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/MilestoneAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/MilestoneAlreadyExistsException.java new file mode 100644 index 0000000000..d54c8bfda1 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/MilestoneAlreadyExistsException.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class MilestoneAlreadyExistsException extends EntityAlreadyExistsException { + private final String mTitle; + + + public MilestoneAlreadyExistsException(String pMessage) { + super(pMessage); + mTitle=null; + } + + public MilestoneAlreadyExistsException(Locale pLocale, String pLogin) { + this(pLocale, pLogin, null); + } + + public MilestoneAlreadyExistsException(Locale pLocale, String pTitle, Throwable pCause) { + super(pLocale, pCause); + mTitle=pTitle; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mTitle); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/MilestoneNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/MilestoneNotFoundException.java new file mode 100644 index 0000000000..f45bd74abd --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/MilestoneNotFoundException.java @@ -0,0 +1,64 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Taylor LABEJOF + */ +public class MilestoneNotFoundException extends EntityNotFoundException { + private final int mChange; + private final String mTitle; + + public MilestoneNotFoundException(String pMessage) { + super(pMessage); + mTitle=null; + mChange=-1; + } + + public MilestoneNotFoundException(Locale pLocale, int pChange) { + this(pLocale, pChange, null); + } + + public MilestoneNotFoundException(Locale pLocale, int pChange, Throwable pCause) { + super(pLocale, pCause); + mChange =pChange; + mTitle=null; + } + + public MilestoneNotFoundException(Locale pLocale, String pTitle) { + super(pLocale, null); + mTitle = pTitle; + mChange = -1; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + if(mTitle!=null){ + return MessageFormat.format(message, mTitle); + } + return MessageFormat.format(message, "N° "+mChange); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/NotAllowedException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/NotAllowedException.java new file mode 100644 index 0000000000..97561d9cc8 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/NotAllowedException.java @@ -0,0 +1,61 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class NotAllowedException extends ApplicationException { + private final String mKey; + private final String mName; + + public NotAllowedException(String pMessage) { + super(pMessage); + mKey=null; + mName=null; + } + + public NotAllowedException(Locale pLocale, String pKey) { + super(pLocale); + mKey=pKey; + mName=null; + } + + public NotAllowedException(Locale pLocale, String pKey, String pName) { + super(pLocale); + mKey=pKey; + mName=pName; + } + + @Override + public String getLocalizedMessage() { + if (mKey == null) { + return null; + } + + String message = getBundleMessage(mKey); + return MessageFormat.format(message, mName); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/OrganizationAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/OrganizationAlreadyExistsException.java new file mode 100644 index 0000000000..70dfdf9faa --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/OrganizationAlreadyExistsException.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.common.Organization; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class OrganizationAlreadyExistsException extends EntityAlreadyExistsException { + private final Organization mOrganization; + + + public OrganizationAlreadyExistsException(String pMessage) { + super(pMessage); + mOrganization=null; + } + + public OrganizationAlreadyExistsException(Locale pLocale, Organization pOrganization) { + this(pLocale, pOrganization, null); + } + + public OrganizationAlreadyExistsException(Locale pLocale, Organization pOrganization, Throwable pCause) { + super(pLocale, pCause); + mOrganization=pOrganization; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mOrganization); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/OrganizationNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/OrganizationNotFoundException.java new file mode 100644 index 0000000000..22b4804259 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/OrganizationNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class OrganizationNotFoundException extends EntityNotFoundException { + private final String mName; + + public OrganizationNotFoundException(String pMessage) { + super(pMessage); + mName=null; + } + + public OrganizationNotFoundException(Locale pLocale, String pName) { + this(pLocale, pName, null); + } + + public OrganizationNotFoundException(Locale pLocale, String pName, Throwable pCause) { + super(pLocale, pCause); + mName=pName; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mName); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartIterationNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartIterationNotFoundException.java new file mode 100644 index 0000000000..442b7198df --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartIterationNotFoundException.java @@ -0,0 +1,74 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + + +import com.docdoku.core.common.Version; +import com.docdoku.core.product.PartIterationKey; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class PartIterationNotFoundException extends EntityNotFoundException { + private final String mPartMNumber; + private final String mPartRStringVersion; + private final int mPartIIteration; + + public PartIterationNotFoundException(String pMessage) { + super(pMessage); + mPartMNumber=null; + mPartRStringVersion=null; + mPartIIteration=-1; + } + + public PartIterationNotFoundException(Locale pLocale, PartIterationKey pPartIPK) { + this(pLocale, pPartIPK, null); + } + + public PartIterationNotFoundException(Locale pLocale, PartIterationKey pPartIPK, Throwable pCause) { + this(pLocale, pPartIPK.getPartMasterNumber(), pPartIPK.getPartRevision().getVersion(), pPartIPK.getIteration(), pCause); + } + + public PartIterationNotFoundException(Locale pLocale, String pPartMNumber, Version pPartRVersion, int pPartIIteration) { + this(pLocale, pPartMNumber, pPartRVersion.toString(), pPartIIteration, null); + } + + public PartIterationNotFoundException(Locale pLocale, String pPartMNumber, String pPartRStringVersion, int pPartIIteration) { + this(pLocale, pPartMNumber, pPartRStringVersion, pPartIIteration, null); + } + + public PartIterationNotFoundException(Locale pLocale, String pPartMNumber, String pPartRStringVersion, int pPartIIteration, Throwable pCause) { + super(pLocale, pCause); + mPartMNumber=pPartMNumber; + mPartRStringVersion=pPartRStringVersion; + mPartIIteration=pPartIIteration; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mPartMNumber,mPartRStringVersion, mPartIIteration); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartMasterAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartMasterAlreadyExistsException.java new file mode 100644 index 0000000000..32b064fcda --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartMasterAlreadyExistsException.java @@ -0,0 +1,56 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.product.PartMaster; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class PartMasterAlreadyExistsException extends EntityAlreadyExistsException { + private final PartMaster mPartMaster; + + + public PartMasterAlreadyExistsException(String pMessage) { + super(pMessage); + mPartMaster = null; + } + + + public PartMasterAlreadyExistsException(Locale pLocale, PartMaster pPartMaster) { + this(pLocale, pPartMaster, null); + } + + public PartMasterAlreadyExistsException(Locale pLocale, PartMaster pPartMaster, Throwable pCause) { + super(pLocale, pCause); + mPartMaster=pPartMaster; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mPartMaster); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartMasterNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartMasterNotFoundException.java new file mode 100644 index 0000000000..be24673462 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartMasterNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class PartMasterNotFoundException extends EntityNotFoundException { + private final String mPartM; + + public PartMasterNotFoundException(String pMessage) { + super(pMessage); + mPartM=null; + } + + public PartMasterNotFoundException(Locale pLocale, String pPartM) { + this(pLocale, pPartM, null); + } + + public PartMasterNotFoundException(Locale pLocale, String pPartM, Throwable pCause) { + super(pLocale, pCause); + mPartM=pPartM; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mPartM); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartMasterTemplateAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartMasterTemplateAlreadyExistsException.java new file mode 100644 index 0000000000..2a8be210da --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartMasterTemplateAlreadyExistsException.java @@ -0,0 +1,56 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.product.PartMasterTemplate; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class PartMasterTemplateAlreadyExistsException extends EntityAlreadyExistsException { + private final PartMasterTemplate mPartMTemplate; + + + public PartMasterTemplateAlreadyExistsException(String pMessage) { + super(pMessage); + mPartMTemplate=null; + } + + + public PartMasterTemplateAlreadyExistsException(Locale pLocale, PartMasterTemplate pPartMTemplate) { + this(pLocale, pPartMTemplate, null); + } + + public PartMasterTemplateAlreadyExistsException(Locale pLocale, PartMasterTemplate pPartMTemplate, Throwable pCause) { + super(pLocale, pCause); + mPartMTemplate=pPartMTemplate; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mPartMTemplate); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartMasterTemplateNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartMasterTemplateNotFoundException.java new file mode 100644 index 0000000000..afd9a1a13a --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartMasterTemplateNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class PartMasterTemplateNotFoundException extends EntityNotFoundException { + private final String mPartMTemplateId; + + public PartMasterTemplateNotFoundException(String pMessage) { + super(pMessage); + mPartMTemplateId=null; + } + + public PartMasterTemplateNotFoundException(Locale pLocale, String pPartMTemplateID) { + this(pLocale, pPartMTemplateID, null); + } + + public PartMasterTemplateNotFoundException(Locale pLocale, String pPartMTemplateId, Throwable pCause) { + super(pLocale, pCause); + mPartMTemplateId=pPartMTemplateId; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mPartMTemplateId); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartRevisionAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartRevisionAlreadyExistsException.java new file mode 100644 index 0000000000..a9455a98ce --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartRevisionAlreadyExistsException.java @@ -0,0 +1,56 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.product.PartRevision; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class PartRevisionAlreadyExistsException extends EntityAlreadyExistsException { + private final PartRevision mPartR; + + + public PartRevisionAlreadyExistsException(String pMessage) { + super(pMessage); + mPartR = null; + } + + + public PartRevisionAlreadyExistsException(Locale pLocale, PartRevision pPartR) { + this(pLocale, pPartR, null); + } + + public PartRevisionAlreadyExistsException(Locale pLocale, PartRevision pPartR, Throwable pCause) { + super(pLocale, pCause); + mPartR=pPartR; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mPartR); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartRevisionNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartRevisionNotFoundException.java new file mode 100644 index 0000000000..83b3b2f3e8 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartRevisionNotFoundException.java @@ -0,0 +1,71 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + + +import com.docdoku.core.common.Version; +import com.docdoku.core.product.PartRevisionKey; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class PartRevisionNotFoundException extends EntityNotFoundException { + private final String mPartMNumber; + private final String mPartRStringVersion; + + public PartRevisionNotFoundException(String pMessage) { + super(pMessage); + mPartMNumber=null; + mPartRStringVersion=null; + } + + public PartRevisionNotFoundException(Locale pLocale, PartRevisionKey pPartRPK) { + this(pLocale, pPartRPK, null); + } + + public PartRevisionNotFoundException(Locale pLocale, PartRevisionKey pPartRPK, Throwable pCause) { + this(pLocale, pPartRPK.getPartMaster().getNumber(), pPartRPK.getVersion(), pCause); + } + + public PartRevisionNotFoundException(Locale pLocale, String pPartMNumber, Version pPartRVersion) { + this(pLocale, pPartMNumber, pPartRVersion.toString(), null); + } + + public PartRevisionNotFoundException(Locale pLocale, String pPartMNumber, String pPartRStringVersion) { + this(pLocale, pPartMNumber, pPartRStringVersion, null); + } + + public PartRevisionNotFoundException(Locale pLocale, String pPartMNumber, String pPartRStringVersion, Throwable pCause) { + super(pLocale, pCause); + mPartMNumber=pPartMNumber; + mPartRStringVersion=pPartRStringVersion; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mPartMNumber,mPartRStringVersion); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartRevisionNotReleasedException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartRevisionNotReleasedException.java new file mode 100644 index 0000000000..cdf9c6c8c4 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartRevisionNotReleasedException.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class PartRevisionNotReleasedException extends ApplicationException { + private final String mCIId; + + + public PartRevisionNotReleasedException(String pMessage) { + super(pMessage); + mCIId=null; + } + + public PartRevisionNotReleasedException(Locale pLocale, String pCIID) { + this(pLocale, pCIID, null); + } + + public PartRevisionNotReleasedException(Locale pLocale, String pCIId, Throwable pCause) { + super(pLocale, pCause); + mCIId=pCIId; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mCIId); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartUsageLinkNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartUsageLinkNotFoundException.java new file mode 100644 index 0000000000..aa38a9db72 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PartUsageLinkNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class PartUsageLinkNotFoundException extends EntityNotFoundException { + private final int mPartUsageLink; + + public PartUsageLinkNotFoundException(String pMessage) { + super(pMessage); + mPartUsageLink = -1; + } + + public PartUsageLinkNotFoundException(Locale pLocale, int pPartUsageLink) { + this(pLocale, pPartUsageLink, null); + } + + public PartUsageLinkNotFoundException(Locale pLocale, int pPartUsageLink, Throwable pCause) { + super(pLocale, pCause); + mPartUsageLink=pPartUsageLink; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mPartUsageLink); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PasswordRecoveryRequestNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PasswordRecoveryRequestNotFoundException.java new file mode 100644 index 0000000000..fe7379650f --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PasswordRecoveryRequestNotFoundException.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class PasswordRecoveryRequestNotFoundException extends EntityNotFoundException { + private final String mPasswordRRUuid; + + + public PasswordRecoveryRequestNotFoundException(String pMessage) { + super(pMessage); + mPasswordRRUuid=null; + } + + public PasswordRecoveryRequestNotFoundException(Locale pLocale, String pPasswordRRUuid) { + this(pLocale, pPasswordRRUuid, null); + } + + public PasswordRecoveryRequestNotFoundException(Locale pLocale, String pPasswordRRUuid, Throwable pCause) { + super(pLocale, pCause); + mPasswordRRUuid=pPasswordRRUuid; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mPasswordRRUuid); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathDataAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathDataAlreadyExistsException.java new file mode 100644 index 0000000000..c89bbb180f --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathDataAlreadyExistsException.java @@ -0,0 +1,56 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.configuration.PathDataMaster; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class PathDataAlreadyExistsException extends EntityAlreadyExistsException { + + private final PathDataMaster pathDataMaster; + + public PathDataAlreadyExistsException(String pMessage) { + super(pMessage); + pathDataMaster = null; + } + + + public PathDataAlreadyExistsException(Locale pLocale, PathDataMaster pPathDataMaster) { + this(pLocale, pPathDataMaster, null); + } + + public PathDataAlreadyExistsException(Locale pLocale, PathDataMaster pPathDataMaster, Throwable pCause) { + super(pLocale, pCause); + pathDataMaster = pPathDataMaster; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message, pathDataMaster); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathDataMasterNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathDataMasterNotFoundException.java new file mode 100644 index 0000000000..7600ac3d80 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathDataMasterNotFoundException.java @@ -0,0 +1,67 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class PathDataMasterNotFoundException extends EntityNotFoundException { + private final Integer mPathDataMasterId; + private final String mPathDataMasterPath; + + public PathDataMasterNotFoundException(String pMessage) { + super(pMessage); + mPathDataMasterId=null; + mPathDataMasterPath=null; + } + public PathDataMasterNotFoundException(Locale pLocale, String pPathDataMasterPath) { + this(pLocale, pPathDataMasterPath, null); + } + + public PathDataMasterNotFoundException(Locale pLocale, String pPathDataMasterPath, Throwable pCause) { + super(pLocale, pCause); + mPathDataMasterPath=pPathDataMasterPath; + mPathDataMasterId=null; + } + + public PathDataMasterNotFoundException(Locale pLocale, Integer pPathDataMasterId) { + this(pLocale, pPathDataMasterId, null); + } + + public PathDataMasterNotFoundException(Locale pLocale, Integer pPathDataMasterId, Throwable pCause) { + super(pLocale, pCause); + mPathDataMasterId=pPathDataMasterId; + mPathDataMasterPath=null; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + if(mPathDataMasterPath!=null) + return MessageFormat.format(message,mPathDataMasterPath); + else + return MessageFormat.format(message,mPathDataMasterId); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathToPathCyclicException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathToPathCyclicException.java new file mode 100644 index 0000000000..e72285b5bd --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathToPathCyclicException.java @@ -0,0 +1,39 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class PathToPathCyclicException extends ApplicationException { + + public PathToPathCyclicException(Locale pLocale) { + super(pLocale); + } + + @Override + public String getLocalizedMessage() { + return getBundleDefaultMessage(); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathToPathLinkAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathToPathLinkAlreadyExistsException.java new file mode 100644 index 0000000000..fcbb01d573 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathToPathLinkAlreadyExistsException.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.product.PathToPathLink; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class PathToPathLinkAlreadyExistsException extends EntityAlreadyExistsException { + + private final PathToPathLink mPathToPathLink; + + public PathToPathLinkAlreadyExistsException(String pMessage) { + super(pMessage); + mPathToPathLink =null; + } + + public PathToPathLinkAlreadyExistsException(Locale pLocale, PathToPathLink pPathToPathLink) { + this(pLocale, pPathToPathLink, null); + } + + public PathToPathLinkAlreadyExistsException(Locale pLocale, PathToPathLink pPathToPathLink, Throwable pCause) { + super(pLocale, pCause); + mPathToPathLink = pPathToPathLink; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message, mPathToPathLink); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathToPathLinkNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathToPathLinkNotFoundException.java new file mode 100644 index 0000000000..88286beaa3 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/PathToPathLinkNotFoundException.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class PathToPathLinkNotFoundException extends EntityNotFoundException { + + private final int mPathToPathLinkId; + + public PathToPathLinkNotFoundException(String pMessage) { + super(pMessage); + mPathToPathLinkId =0; + } + + public PathToPathLinkNotFoundException(Locale pLocale, int pPathToPathLinkId) { + this(pLocale, pPathToPathLinkId, null); + } + + public PathToPathLinkNotFoundException(Locale pLocale, int pPathToPathLinkId, Throwable pCause) { + super(pLocale, pCause); + mPathToPathLinkId =pPathToPathLinkId; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message, mPathToPathLinkId); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ProductConfigurationNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ProductConfigurationNotFoundException.java new file mode 100644 index 0000000000..3976c7f99b --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ProductConfigurationNotFoundException.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class ProductConfigurationNotFoundException extends EntityNotFoundException { + private final int mProductConfigurationId; + + + public ProductConfigurationNotFoundException(String pMessage) { + super(pMessage); + mProductConfigurationId = -1; + } + + public ProductConfigurationNotFoundException(Locale pLocale, int pProductConfigurationId) { + this(pLocale, pProductConfigurationId, null); + } + + public ProductConfigurationNotFoundException(Locale pLocale, int pProductConfigurationId, Throwable pCause) { + super(pLocale, pCause); + mProductConfigurationId =pProductConfigurationId; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message, mProductConfigurationId); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ProductInstanceAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ProductInstanceAlreadyExistsException.java new file mode 100644 index 0000000000..f021df73f0 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ProductInstanceAlreadyExistsException.java @@ -0,0 +1,58 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.configuration.ProductInstanceMaster; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Taylor LABEJOF + */ +public class ProductInstanceAlreadyExistsException extends EntityAlreadyExistsException { + private final ProductInstanceMaster productInstanceMaster; + + + public ProductInstanceAlreadyExistsException(String pMessage) { + super(pMessage); + productInstanceMaster=null; + } + + + public ProductInstanceAlreadyExistsException(Locale pLocale, ProductInstanceMaster productInstanceMaster) { + this(pLocale, productInstanceMaster, null); + } + + public ProductInstanceAlreadyExistsException(Locale pLocale, ProductInstanceMaster productInstanceMaster, Throwable pCause) { + super(pLocale, pCause); + this.productInstanceMaster=productInstanceMaster; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,productInstanceMaster); + } + + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ProductInstanceIterationNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ProductInstanceIterationNotFoundException.java new file mode 100644 index 0000000000..89f7756877 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ProductInstanceIterationNotFoundException.java @@ -0,0 +1,54 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.configuration.ProductInstanceIterationKey; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class ProductInstanceIterationNotFoundException extends EntityNotFoundException { + private final ProductInstanceIterationKey mProductInstanceIteration; + + public ProductInstanceIterationNotFoundException(String pMessage) { + super(pMessage); + mProductInstanceIteration=null; + } + + public ProductInstanceIterationNotFoundException(Locale pLocale, ProductInstanceIterationKey pProductInstanceMaster) { + this(pLocale, pProductInstanceMaster, null); + } + + public ProductInstanceIterationNotFoundException(Locale pLocale, ProductInstanceIterationKey pProductInstanceMaster, Throwable pCause) { + super(pLocale, pCause); + mProductInstanceIteration=pProductInstanceMaster; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mProductInstanceIteration); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/ProductInstanceMasterNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ProductInstanceMasterNotFoundException.java new file mode 100644 index 0000000000..7f584192a7 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/ProductInstanceMasterNotFoundException.java @@ -0,0 +1,54 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.configuration.ProductInstanceMasterKey; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class ProductInstanceMasterNotFoundException extends EntityNotFoundException { + private final ProductInstanceMasterKey mProductInstanceMaster; + + public ProductInstanceMasterNotFoundException(String pMessage) { + super(pMessage); + mProductInstanceMaster=null; + } + + public ProductInstanceMasterNotFoundException(Locale pLocale, ProductInstanceMasterKey pProductInstanceMaster) { + this(pLocale, pProductInstanceMaster, null); + } + + public ProductInstanceMasterNotFoundException(Locale pLocale, ProductInstanceMasterKey pProductInstanceMaster, Throwable pCause) { + super(pLocale, pCause); + mProductInstanceMaster=pProductInstanceMaster; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mProductInstanceMaster); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/QueryAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/QueryAlreadyExistsException.java new file mode 100644 index 0000000000..fafeeab5a6 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/QueryAlreadyExistsException.java @@ -0,0 +1,48 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.query.Query; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * @author Morgan Guimard + */ +public class QueryAlreadyExistsException extends EntityAlreadyExistsException { + private final Query mQuery; + + public QueryAlreadyExistsException(Locale pLocale, Query query) { + this(pLocale, query, null); + } + + public QueryAlreadyExistsException(Locale pLocale, Query pQuery, Throwable pCause) { + super(pLocale, pCause); + mQuery=pQuery; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mQuery.getName()); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/RoleAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/RoleAlreadyExistsException.java new file mode 100644 index 0000000000..356b354ce7 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/RoleAlreadyExistsException.java @@ -0,0 +1,48 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.workflow.Role; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * @author Taylor LABEJOF + */ +public class RoleAlreadyExistsException extends EntityAlreadyExistsException { + private final Role mRole; + + public RoleAlreadyExistsException(Locale pLocale, Role pRole) { + this(pLocale, pRole, null); + } + + public RoleAlreadyExistsException(Locale pLocale, Role pRole, Throwable pCause) { + super(pLocale, pCause); + mRole=pRole; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mRole.getName()); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/RoleNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/RoleNotFoundException.java new file mode 100644 index 0000000000..c43ae03deb --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/RoleNotFoundException.java @@ -0,0 +1,49 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.exceptions; + +import com.docdoku.core.workflow.RoleKey; + +import java.text.MessageFormat; +import java.util.Locale; + + +/** + * @author Morgan Guimard + */ +public class RoleNotFoundException extends EntityNotFoundException { + private final RoleKey mRoleKey; + + public RoleNotFoundException(String pMessage) { + super(pMessage); + mRoleKey=null; + } + + public RoleNotFoundException(Locale pLocale, RoleKey pRoleKey) { + super(pLocale); + mRoleKey=pRoleKey; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message, mRoleKey); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/SharedEntityNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/SharedEntityNotFoundException.java new file mode 100644 index 0000000000..be4bf34954 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/SharedEntityNotFoundException.java @@ -0,0 +1,46 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * @author Morgan Guimard + */ +public class SharedEntityNotFoundException extends EntityNotFoundException { + private final String uuid; + + public SharedEntityNotFoundException(String pMessage) { + super(pMessage); + uuid=null; + } + + public SharedEntityNotFoundException(Locale pLocale, String pUuid) { + super(pLocale); + uuid=pUuid; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message, uuid); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/StorageException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/StorageException.java new file mode 100644 index 0000000000..a5a9958b81 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/StorageException.java @@ -0,0 +1,33 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +public class StorageException extends Exception { + + public StorageException(String message) { + super(message); + } + + public StorageException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/TagAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/TagAlreadyExistsException.java new file mode 100644 index 0000000000..df5189aab7 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/TagAlreadyExistsException.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.meta.Tag; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class TagAlreadyExistsException extends EntityAlreadyExistsException { + private final Tag mTag; + + + public TagAlreadyExistsException(String pMessage) { + super(pMessage); + mTag=null; + } + + public TagAlreadyExistsException(Locale pLocale, Tag pTag) { + this(pLocale, pTag, null); + } + + public TagAlreadyExistsException(Locale pLocale, Tag pTag, Throwable pCause) { + super(pLocale, pCause); + mTag=pTag; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mTag); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/TagNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/TagNotFoundException.java new file mode 100644 index 0000000000..138490726c --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/TagNotFoundException.java @@ -0,0 +1,50 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.meta.TagKey; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class TagNotFoundException extends EntityNotFoundException { + private final TagKey mTagKey; + + public TagNotFoundException(String pMessage) { + super(pMessage); + mTagKey=null; + } + + public TagNotFoundException(Locale pLocale, TagKey pTagKey) { + super(pLocale); + mTagKey=pTagKey; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mTagKey); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/TaskNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/TaskNotFoundException.java new file mode 100644 index 0000000000..04ace37133 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/TaskNotFoundException.java @@ -0,0 +1,50 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.workflow.TaskKey; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class TaskNotFoundException extends EntityNotFoundException { + private final TaskKey mTaskKey; + + public TaskNotFoundException(String pMessage) { + super(pMessage); + mTaskKey=null; + } + + public TaskNotFoundException(Locale pLocale, TaskKey pTaskKey) { + super(pLocale); + mTaskKey=pTaskKey; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mTaskKey); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserAlreadyExistsException.java new file mode 100644 index 0000000000..a77975f6c7 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserAlreadyExistsException.java @@ -0,0 +1,56 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.common.User; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class UserAlreadyExistsException extends EntityAlreadyExistsException { + private final User mUser; + + + public UserAlreadyExistsException(String pMessage) { + super(pMessage); + mUser=null; + } + + + public UserAlreadyExistsException(Locale pLocale, User pUser) { + this(pLocale, pUser, null); + } + + public UserAlreadyExistsException(Locale pLocale, User pUser, Throwable pCause) { + super(pLocale, pCause); + mUser=pUser; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mUser); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserGroupAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserGroupAlreadyExistsException.java new file mode 100644 index 0000000000..b58ab3657b --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserGroupAlreadyExistsException.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.common.UserGroup; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class UserGroupAlreadyExistsException extends EntityAlreadyExistsException { + private final UserGroup mUserGroup; + + + public UserGroupAlreadyExistsException(String pMessage) { + super(pMessage); + mUserGroup=null; + } + + public UserGroupAlreadyExistsException(Locale pLocale, UserGroup pUserGroup) { + this(pLocale, pUserGroup, null); + } + + public UserGroupAlreadyExistsException(Locale pLocale, UserGroup pUserGroup, Throwable pCause) { + super(pLocale, pCause); + mUserGroup=pUserGroup; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mUserGroup); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserGroupNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserGroupNotFoundException.java new file mode 100644 index 0000000000..637ac11cef --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserGroupNotFoundException.java @@ -0,0 +1,51 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + + +import com.docdoku.core.common.UserGroupKey; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class UserGroupNotFoundException extends EntityNotFoundException { + private final UserGroupKey mKey; + + public UserGroupNotFoundException(String pMessage) { + super(pMessage); + mKey=null; + } + + public UserGroupNotFoundException(Locale pLocale, UserGroupKey pKey) { + super(pLocale); + mKey=pKey; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mKey); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserNotActiveException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserNotActiveException.java new file mode 100644 index 0000000000..49cbf023f4 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserNotActiveException.java @@ -0,0 +1,54 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.common.User; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class UserNotActiveException extends ApplicationException { + private final String mName; + + public UserNotActiveException(String pMessage) { + super(pMessage); + mName=null; + } + + public UserNotActiveException(Locale pLocale, User pUser) { + this(pLocale, pUser.toString()); + } + + public UserNotActiveException(Locale pLocale, String pName) { + super(pLocale); + mName=pName; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mName); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserNotFoundException.java new file mode 100644 index 0000000000..d87c8ce682 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/UserNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class UserNotFoundException extends EntityNotFoundException { + private final String mLogin; + + public UserNotFoundException(String pMessage) { + super(pMessage); + mLogin=null; + } + + public UserNotFoundException(Locale pLocale, String pLogin) { + this(pLocale, pLogin, null); + } + + public UserNotFoundException(Locale pLocale, String pLogin, Throwable pCause) { + super(pLocale, pCause); + mLogin=pLogin; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mLogin); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/VaultException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/VaultException.java new file mode 100644 index 0000000000..15a58e042c --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/VaultException.java @@ -0,0 +1,33 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +/** + * + * @author Florent Garin + */ +public class VaultException extends RuntimeException { + + public VaultException(Throwable pCause) { + super(pCause); + } + +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkflowModelAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkflowModelAlreadyExistsException.java new file mode 100644 index 0000000000..7491dfe958 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkflowModelAlreadyExistsException.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.workflow.WorkflowModel; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class WorkflowModelAlreadyExistsException extends EntityAlreadyExistsException { + private final WorkflowModel mWorkflowModel; + + + public WorkflowModelAlreadyExistsException(String pMessage) { + super(pMessage); + mWorkflowModel=null; + } + + public WorkflowModelAlreadyExistsException(Locale pLocale, WorkflowModel pWorkflowModel) { + this(pLocale, pWorkflowModel, null); + } + + public WorkflowModelAlreadyExistsException(Locale pLocale, WorkflowModel pWorkflowModel, Throwable pCause) { + super(pLocale, pCause); + mWorkflowModel=pWorkflowModel; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mWorkflowModel); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkflowModelNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkflowModelNotFoundException.java new file mode 100644 index 0000000000..7389d1cf6c --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkflowModelNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class WorkflowModelNotFoundException extends EntityNotFoundException { + private final String mID; + + public WorkflowModelNotFoundException(String pMessage) { + super(pMessage); + mID=null; + } + + public WorkflowModelNotFoundException(Locale pLocale, String pID) { + this(pLocale, pID, null); + } + + public WorkflowModelNotFoundException(Locale pLocale, String pID, Throwable pCause) { + super(pLocale, pCause); + mID=pID; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mID); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkflowNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkflowNotFoundException.java new file mode 100644 index 0000000000..b9a25ba405 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkflowNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +public class WorkflowNotFoundException extends EntityNotFoundException { + private final int mID; + + public WorkflowNotFoundException(String pMessage) { + super(pMessage); + mID=-1; + } + + public WorkflowNotFoundException(Locale pLocale, int pID) { + this(pLocale, pID, null); + } + + public WorkflowNotFoundException(Locale pLocale, int pID, Throwable pCause) { + super(pLocale, pCause); + mID=pID; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mID); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkspaceAlreadyExistsException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkspaceAlreadyExistsException.java new file mode 100644 index 0000000000..73446f0cb1 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkspaceAlreadyExistsException.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import com.docdoku.core.common.Workspace; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class WorkspaceAlreadyExistsException extends EntityAlreadyExistsException { + private final Workspace mWorkspace; + + + public WorkspaceAlreadyExistsException(String pMessage) { + super(pMessage); + mWorkspace=null; + } + + public WorkspaceAlreadyExistsException(Locale pLocale, Workspace pWorkspace) { + this(pLocale, pWorkspace, null); + } + + public WorkspaceAlreadyExistsException(Locale pLocale, Workspace pWorkspace, Throwable pCause) { + super(pLocale, pCause); + mWorkspace=pWorkspace; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mWorkspace); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkspaceNotFoundException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkspaceNotFoundException.java new file mode 100644 index 0000000000..d9bcba0583 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/WorkspaceNotFoundException.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * + * @author Florent Garin + */ +public class WorkspaceNotFoundException extends EntityNotFoundException { + private final String mID; + + public WorkspaceNotFoundException(String pMessage) { + super(pMessage); + mID=null; + } + + public WorkspaceNotFoundException(Locale pLocale, String pID) { + this(pLocale, pID, null); + } + + public WorkspaceNotFoundException(Locale pLocale, String pID, Throwable pCause) { + super(pLocale, pCause); + mID=pID; + } + + @Override + public String getLocalizedMessage() { + String message = getBundleDefaultMessage(); + return MessageFormat.format(message,mID); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/exceptions/WrongInputException.java b/docdoku-common/src/main/java/com/docdoku/core/exceptions/WrongInputException.java new file mode 100644 index 0000000000..b3324fd590 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/exceptions/WrongInputException.java @@ -0,0 +1,47 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.exceptions; + +import java.util.Locale; + +/** + * + * @author Elisabel Généreux + */ +public class WrongInputException extends ApplicationException { + + public WrongInputException(String pMessage) { + super(pMessage); + } + + public WrongInputException(Locale pLocale) { + this(pLocale, null); + } + + public WrongInputException(Locale pLocale, Throwable pCause) { + super(pLocale, pCause); + } + + @Override + public String getLocalizedMessage() { + return getBundleDefaultMessage(); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/gcm/GCMAccount.java b/docdoku-common/src/main/java/com/docdoku/core/gcm/GCMAccount.java new file mode 100644 index 0000000000..5adae3f5c1 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/gcm/GCMAccount.java @@ -0,0 +1,84 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.gcm; + +import com.docdoku.core.common.Account; + +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; +import java.io.Serializable; + +@Table(name="GCMACCOUNT") +@javax.persistence.Entity +public class GCMAccount implements Serializable { + + @Id + @OneToOne + private Account account; + + @NotNull + private String gcmId; + + public GCMAccount() { + } + + public GCMAccount(Account account, String gcmId) { + this.account = account; + this.gcmId = gcmId; + } + + public Account getAccount() { + return account; + } + + public void setAccount(Account account) { + this.account = account; + } + + public String getGcmId() { + return gcmId; + } + + public void setGcmId(String gcmId) { + this.gcmId = gcmId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + GCMAccount that = (GCMAccount) o; + return account.equals(that.account) && gcmId.equals(that.gcmId); + } + + @Override + public int hashCode() { + int result = account.hashCode(); + result = 31 * result + gcmId.hashCode(); + return result; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/log/DocumentLog.java b/docdoku-common/src/main/java/com/docdoku/core/log/DocumentLog.java new file mode 100644 index 0000000000..c685cd5698 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/log/DocumentLog.java @@ -0,0 +1,123 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.log; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; + +/** + * The DocumentLog class represents an entry in the log + * table that keeps track of all activities around a specific document. + * + * @author Florent Garin + * @version 1.1, 22/09/11 + * @since V1.1 + */ +@Table(name="DOCUMENTLOG") +@javax.persistence.Entity +@NamedQueries ({ + @NamedQuery(name="findLogByDocumentAndUserAndEvent", query="SELECT l FROM DocumentLog l WHERE l.userLogin = :userLogin AND l.documentWorkspaceId = :documentWorkspaceId AND l.documentId = :documentId AND l.documentVersion = :documentVersion AND l.documentIteration = :documentIteration AND l.event = :event ORDER BY l.logDate") +}) +public class DocumentLog implements Serializable { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private int id; + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date logDate; + private String documentWorkspaceId; + private String documentId; + private String documentVersion; + private int documentIteration; + private String userLogin; + private String event; + private String info; + + + public DocumentLog() { + } + + public int getDocumentIteration() { + return documentIteration; + } + + public void setDocumentIteration(int documentIteration) { + this.documentIteration = documentIteration; + } + + public String getDocumentId() { + return documentId; + } + + public void setDocumentId(String documentId) { + this.documentId = documentId; + } + + public String getDocumentVersion() { + return documentVersion; + } + + public void setDocumentVersion(String documentVersion) { + this.documentVersion = documentVersion; + } + + public String getDocumentWorkspaceId() { + return documentWorkspaceId; + } + + public void setDocumentWorkspaceId(String documentWorkspaceId) { + this.documentWorkspaceId = documentWorkspaceId; + } + + public String getEvent() { + return event; + } + + public void setEvent(String event) { + this.event = event; + } + + public Date getLogDate() { + return logDate; + } + + public void setLogDate(Date logDate) { + this.logDate = logDate; + } + + public String getUserLogin() { + return userLogin; + } + + public void setUserLogin(String userLogin) { + this.userLogin = userLogin; + } + + public String getInfo() { + return info; + } + + public void setInfo(String info) { + this.info = info; + } + + +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/log/PartLog.java b/docdoku-common/src/main/java/com/docdoku/core/log/PartLog.java new file mode 100644 index 0000000000..d6bbea5db0 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/log/PartLog.java @@ -0,0 +1,128 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.log; + + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; + +/** + * The PartLog class represents an entry in the log + * table that keeps track of all activities around a specific part. + * + * @author Florent Garin + * @version 1.1, 12/03/13 + * @since V1.1 + */ +@Table(name="PARTLOG") +@javax.persistence.Entity +@NamedQueries ({ + @NamedQuery(name="findLogByPartAndUserAndEvent", query="SELECT l FROM PartLog l WHERE l.userLogin = :userLogin AND l.partWorkspaceId = :partWorkspaceId AND l.partNumber = :partNumber AND l.partVersion = :partVersion AND l.partIteration = :partIteration AND l.event = :event ORDER BY l.logDate") +}) +public class PartLog implements Serializable { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private int id; + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date logDate; + private String partWorkspaceId; + private String partNumber; + private String partVersion; + private int partIteration; + private String userLogin; + private String event; + private String info; + + + public PartLog() { + } + + public int getId() { + return id; + } + + public int getPartIteration() { + return partIteration; + } + + public void setPartIteration(int partIteration) { + this.partIteration = partIteration; + } + + public String getPartNumber() { + return partNumber; + } + + public void setPartNumber(String partNumber) { + this.partNumber = partNumber; + } + + public String getPartVersion() { + return partVersion; + } + + public void setPartVersion(String partVersion) { + this.partVersion = partVersion; + } + + public String getPartWorkspaceId() { + return partWorkspaceId; + } + + public void setPartWorkspaceId(String partWorkspaceId) { + this.partWorkspaceId = partWorkspaceId; + } + + public String getEvent() { + return event; + } + + public void setEvent(String event) { + this.event = event; + } + + public Date getLogDate() { + return logDate; + } + + public void setLogDate(Date logDate) { + this.logDate = logDate; + } + + public String getUserLogin() { + return userLogin; + } + + public void setUserLogin(String userLogin) { + this.userLogin = userLogin; + } + + public String getInfo() { + return info; + } + + public void setInfo(String info) { + this.info = info; + } + + +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/log/WorkspaceLog.java b/docdoku-common/src/main/java/com/docdoku/core/log/WorkspaceLog.java new file mode 100644 index 0000000000..f7fad7e15a --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/log/WorkspaceLog.java @@ -0,0 +1,101 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.log; + + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; + +/** + * The WorkspaceLog class represents an entry in the log + * table that keeps track of all activities around a specific workspace. + * + * @author Florent Garin + * @version 1.1, 12/03/13 + * @since V1.1 + */ +@Table(name="WORKSPACELOG") +@javax.persistence.Entity +@NamedQueries ({ + @NamedQuery(name="findLogByWorkspaceAndUserAndEvent", query="SELECT l FROM WorkspaceLog l WHERE l.userLogin = :userLogin AND l.workspaceId = :workspaceId AND l.event = :event ORDER BY l.logDate") +}) +public class WorkspaceLog implements Serializable { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private int id; + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date logDate; + private String workspaceId; + private String userLogin; + private String event; + private String info; + + + public WorkspaceLog() { + } + + public int getId() { + return id; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String partWorkspaceId) { + this.workspaceId = partWorkspaceId; + } + + public String getEvent() { + return event; + } + + public void setEvent(String event) { + this.event = event; + } + + public Date getLogDate() { + return logDate; + } + + public void setLogDate(Date logDate) { + this.logDate = logDate; + } + + public String getUserLogin() { + return userLogin; + } + + public void setUserLogin(String userLogin) { + this.userLogin = userLogin; + } + + public String getInfo() { + return info; + } + + public void setInfo(String info) { + this.info = info; + } + + +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/DefaultAttributeTemplate.java b/docdoku-common/src/main/java/com/docdoku/core/meta/DefaultAttributeTemplate.java new file mode 100644 index 0000000000..55b234f486 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/DefaultAttributeTemplate.java @@ -0,0 +1,97 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.meta; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * A generic implementation of {@link InstanceAttributeTemplate} that can instantiate + * simple primitive based attributes. + * + * @author Florent Garin + * @version 1.1, 23/01/12 + * @since V1.0 + */ +@Table(name="DEFAULTIATTRIBUTETEMPLATE") +@Entity +public class DefaultAttributeTemplate extends InstanceAttributeTemplate { + + + private AttributeType attributeType; + + public DefaultAttributeTemplate() { + } + + public DefaultAttributeTemplate(String pName, AttributeType pAttributeType) { + super(pName); + attributeType = pAttributeType; + } + + + @Override + public AttributeType getAttributeType() { + return attributeType; + } + + public void setAttributeType(AttributeType attributeType) { + this.attributeType = attributeType; + } + + @Override + public InstanceAttribute createInstanceAttribute() { + InstanceAttribute attr = null; + if(attributeType!=null){ + switch (attributeType) { + case TEXT: + attr = new InstanceTextAttribute(); + break; + case NUMBER: + attr = new InstanceNumberAttribute(); + break; + case BOOLEAN: + attr = new InstanceBooleanAttribute(); + break; + case DATE: + attr = new InstanceDateAttribute(); + break; + case URL: + attr = new InstanceURLAttribute(); + break; + case LONG_TEXT: + attr = new InstanceLongTextAttribute(); + break; + default: + return null; + } + + attr.setName(name); + attr.setMandatory(mandatory); + attr.setLocked(locked); + } + + return attr; + } + + @Override + public String toString() { + return name + "-" + attributeType; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceAttribute.java b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceAttribute.java new file mode 100644 index 0000000000..c1bee55cd9 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceAttribute.java @@ -0,0 +1,133 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.meta; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlSeeAlso; +import java.io.Serializable; + +/** + * Base class for all instance attributes. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name = "INSTANCEATTRIBUTE") +@XmlSeeAlso({InstanceTextAttribute.class, InstanceLongTextAttribute.class, InstanceNumberAttribute.class, InstanceDateAttribute.class, InstanceBooleanAttribute.class, InstanceURLAttribute.class, InstanceListOfValuesAttribute.class}) +@Inheritance() +@Entity +public abstract class InstanceAttribute implements Serializable, Cloneable { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private int id; + + protected String name = ""; + + protected boolean mandatory; + + protected boolean locked; + + public InstanceAttribute() { + } + + public InstanceAttribute(String pName, boolean pMandatory) { + name = pName; + mandatory = pMandatory; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getNameWithoutWhiteSpace() { + return this.name.replaceAll(" ", "_"); + } + + public boolean isMandatory() { + return mandatory; + } + + public void setMandatory(boolean mandatory) { + this.mandatory = mandatory; + } + + public boolean isLocked() { + return locked; + } + + public void setLocked(boolean locked) { + this.locked = locked; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof InstanceAttribute)) { + return false; + } + InstanceAttribute attribute = (InstanceAttribute) pObj; + return attribute.id == id; + } + + @Override + public String toString() { + return name; + } + + @Override + public InstanceAttribute clone() { + try { + return (InstanceAttribute) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + public void setId(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public abstract Object getValue(); + + public abstract boolean setValue(Object pValue); + + public boolean isValueEquals(Object pValue) { + Object value = getValue(); + return value != null && value.equals(pValue); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceAttributeDescriptor.java b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceAttributeDescriptor.java new file mode 100644 index 0000000000..20f4c53b65 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceAttributeDescriptor.java @@ -0,0 +1,141 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.meta; + +import java.io.Serializable; +import java.util.List; + +/** + * Wraps type and name for attributes facilities. + * + * @author Morgan Guimard + */ +public class InstanceAttributeDescriptor implements Serializable { + + private String name; + private Type type; + private List<NameValuePair> lovItems; + + public enum Type { + TEXT, NUMBER, DATE, BOOLEAN, URL, LOV, LONG_TEXT + } + + public InstanceAttributeDescriptor() { + } + + public InstanceAttributeDescriptor(InstanceAttribute o) { + + this.name = o.getName(); + + if(o instanceof InstanceDateAttribute){ + type = Type.DATE; + } + else if(o instanceof InstanceNumberAttribute){ + type = Type.NUMBER; + } + else if(o instanceof InstanceTextAttribute){ + type = Type.TEXT; + } + else if(o instanceof InstanceURLAttribute){ + type = Type.URL; + } + else if(o instanceof InstanceBooleanAttribute){ + type = Type.BOOLEAN; + } + else if(o instanceof InstanceListOfValuesAttribute){ + type = Type.LOV; + lovItems = ((InstanceListOfValuesAttribute) o).getItems(); + } + else if(o instanceof InstanceLongTextAttribute){ + type = Type.LONG_TEXT; + } + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public List<NameValuePair> getLovItems() { + return lovItems; + } + + public void setLovItems(List<NameValuePair> lovItems) { + this.lovItems = lovItems; + } + + public String getStringType(){ + String typeAsString = ""; + switch (type){ + case LOV: + typeAsString = "LOV"; + break; + case DATE: + typeAsString = "DATE"; + break; + case NUMBER: + typeAsString = "NUMBER"; + break; + case URL: + typeAsString = "URL"; + break; + case BOOLEAN: + typeAsString = "BOOLEAN"; + break; + case LONG_TEXT: + typeAsString = "LONG_TEXT"; + break; + default : + typeAsString = "TEXT"; + break; + } + return typeAsString; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof InstanceAttributeDescriptor)){ + return false; + } + InstanceAttributeDescriptor other = (InstanceAttributeDescriptor) o; + return other.name.equals(name) && other.type.equals(type); + } + + + @Override + public int hashCode() { + return (name+type).hashCode(); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceAttributeTemplate.java b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceAttributeTemplate.java new file mode 100644 index 0000000000..532ad1962c --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceAttributeTemplate.java @@ -0,0 +1,133 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.meta; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlSeeAlso; +import java.io.Serializable; + +/** + * This class holds the definition of the custom attribute of the documents and parts + * @author the template. + * + * @author Florent Garin + * @version 1.1, 23/01/12 + * @since V1.0 + */ +@Table(name="INSTANCEATTRIBUTETEMPLATE") +@XmlSeeAlso({DefaultAttributeTemplate.class, ListOfValuesAttributeTemplate.class}) +@Inheritance() +@Entity +public abstract class InstanceAttributeTemplate implements Serializable, Cloneable { + + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Id + protected int id; + + @Column(length=100) + protected String name = ""; + + protected boolean mandatory; + + protected boolean locked; + + public enum AttributeType { + + TEXT, NUMBER, DATE, BOOLEAN, URL, LOV, LONG_TEXT + } + + + public InstanceAttributeTemplate() { + } + + public InstanceAttributeTemplate(String pName) { + name = pName; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isMandatory() { + return mandatory; + } + + public void setMandatory(boolean mandatory) { + this.mandatory = mandatory; + } + + public boolean isLocked() { + return locked; + } + + public void setLocked(boolean locked) { + this.locked = locked; + } + + public abstract InstanceAttribute createInstanceAttribute(); + + public abstract AttributeType getAttributeType(); + + @Override + public InstanceAttributeTemplate clone() { + try { + return (InstanceAttributeTemplate) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + InstanceAttributeTemplate that = (InstanceAttributeTemplate) o; + + return id == that.id; + + } + + @Override + public int hashCode() { + return id; + } + + @Override + public String toString() { + return name; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceBooleanAttribute.java b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceBooleanAttribute.java new file mode 100644 index 0000000000..7bdc7e1cfc --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceBooleanAttribute.java @@ -0,0 +1,63 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.meta; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * Defines a boolean type custom attribute of a document, part, product and other objects. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="INSTANCEBOOLEANATTRIBUTE") +@Entity +public class InstanceBooleanAttribute extends InstanceAttribute{ + + private boolean booleanValue; + + public InstanceBooleanAttribute() { + } + + public InstanceBooleanAttribute(String pName, boolean pValue, boolean pMandatory) { + super(pName, pMandatory); + setBooleanValue(pValue); + } + + @Override + public Boolean getValue() { + return booleanValue; + } + @Override + public boolean setValue(Object pValue) { + booleanValue=Boolean.parseBoolean(pValue + ""); + return true; + } + + public boolean isBooleanValue() { + return booleanValue; + } + public void setBooleanValue(boolean booleanValue) { + this.booleanValue = booleanValue; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceDateAttribute.java b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceDateAttribute.java new file mode 100644 index 0000000000..d77af8efe7 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceDateAttribute.java @@ -0,0 +1,107 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.meta; + +import javax.persistence.Entity; +import javax.persistence.Table; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +/** + * Defines a date type custom attribute of a document, part, product and other objects. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="INSTANCEDATEATTRIBUTE") +@Entity +public class InstanceDateAttribute extends InstanceAttribute{ + + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date dateValue; + + public InstanceDateAttribute() { + } + + public InstanceDateAttribute(String pName, Date pValue, boolean pMandatory) { + super(pName, pMandatory); + setDateValue(pValue); + } + + @Override + public Date getValue() { + return dateValue; + } + @Override + public boolean setValue(Object pValue) { + if(pValue instanceof Date){ + dateValue=(Date)pValue; + return true; + }else if(pValue instanceof String){ + try { + //TODO: could use DateAdpater instead + TimeZone tz = TimeZone.getTimeZone("UTC"); + DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + df.setTimeZone(tz); + Date date = df.parse((String) pValue); + dateValue = date; + return true; + } catch (ParseException pe) { + try { + dateValue = new Date(Long.parseLong((String) pValue)); + return true; + }catch(NumberFormatException nfe){ + return false; + } + } + + }else{ + dateValue=null; + return false; + } + } + + public Date getDateValue() { + return dateValue; + } + public void setDateValue(Date dateValue) { + this.dateValue = dateValue; + } + + + /** + * perform a deep clone operation + */ + @Override + public InstanceDateAttribute clone() { + InstanceDateAttribute clone; + clone = (InstanceDateAttribute) super.clone(); + + if(dateValue!=null) + clone.dateValue = (Date) dateValue.clone(); + return clone; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceListOfValuesAttribute.java b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceListOfValuesAttribute.java new file mode 100644 index 0000000000..9bacc41a31 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceListOfValuesAttribute.java @@ -0,0 +1,113 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.meta; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlTransient; +import java.util.ArrayList; +import java.util.List; + +/** + * Defines a closed-ended type custom attribute of a document, part, product and other objects. + * + * @author Florent Garin + * @version 2.0, 27/02/15 + * @since V2.0 + */ +@Table(name="INSTANCELOVATTRIBUTE") +@Entity +public class InstanceListOfValuesAttribute extends InstanceAttribute{ + + + private int indexValue; + + @ElementCollection(fetch = FetchType.EAGER) + @OrderColumn(name = "NAMEVALUE_ORDER") + @CollectionTable( + name = "ATTRIBUTE_NAMEVALUE", + joinColumns = { + @JoinColumn(name = "ATTRIBUTE_ID", referencedColumnName = "ID") + } + ) + private List<NameValuePair> items=new ArrayList<>(); + + public InstanceListOfValuesAttribute() { + } + + public InstanceListOfValuesAttribute(String pName, int pValue, boolean pMandatory) { + super(pName, pMandatory); + setIndexValue(pValue); + } + @Override + public Integer getValue() { + return indexValue; + } + @Override + public boolean setValue(Object pValue) { + try{ + int index=Integer.parseInt(pValue + ""); + return setIndexValue(index); + }catch(NumberFormatException ex){ + return false; + } + } + + @XmlElement(name="indexValue") + private int getUncheckedIndexValue(){ + return indexValue; + } + + private void setUncheckedIndexValue(int value){ + indexValue = value; + } + + @XmlTransient + public int getIndexValue() { + return indexValue; + } + + public boolean setIndexValue(int indexValue) { + if(indexValue < 0 || indexValue > items.size()-1) + return false; + else { + this.indexValue=indexValue; + return true; + } + } + + public void setItems(List<NameValuePair> items) { + this.items = items; + } + + public List<NameValuePair> getItems() { + return items; + } + + public String getSelectedName(){ + return items.get(indexValue).getName(); + } + + public String getSelectedValue(){ + return items.get(indexValue).getValue(); + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceLongTextAttribute.java b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceLongTextAttribute.java new file mode 100644 index 0000000000..7ccf8bf01e --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceLongTextAttribute.java @@ -0,0 +1,67 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.meta; + +import javax.persistence.Entity; +import javax.persistence.Lob; +import javax.persistence.Table; + +/** + * Defines a text type custom attribute of a document, part, product and other objects. + * + * + * @author Florent Garin + * @version 2.0, 29/08/16 + * @since V2.0 + */ +@Table(name="INSTANCELONGTEXTATTRIBUTE") +@Entity +public class InstanceLongTextAttribute extends InstanceAttribute{ + + + @Lob + private String longTextValue; + + public InstanceLongTextAttribute() { + } + + public InstanceLongTextAttribute(String pName, String pValue, boolean pMandatory) { + super(pName, pMandatory); + setLongTextValue(pValue); + } + + @Override + public String getValue() { + return longTextValue; + } + @Override + public boolean setValue(Object pValue) { + longTextValue = pValue != null ? pValue + "" : ""; + return true; + } + + public String getLongTextValue() { + return longTextValue; + } + public void setLongTextValue(String longTextValue) { + this.longTextValue = longTextValue; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceNumberAttribute.java b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceNumberAttribute.java new file mode 100644 index 0000000000..0950453ad7 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceNumberAttribute.java @@ -0,0 +1,69 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.meta; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * Defines a numerical custom attribute of a document, part, product and other objects. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="INSTANCENUMBERATTRIBUTE") +@Entity +public class InstanceNumberAttribute extends InstanceAttribute{ + + + + private float numberValue; + + public InstanceNumberAttribute() { + } + + public InstanceNumberAttribute(String pName, float pValue, boolean pMandatory) { + super(pName, pMandatory); + setNumberValue(pValue); + } + + @Override + public Float getValue() { + return numberValue; + } + @Override + public boolean setValue(Object pValue) { + try{ + numberValue=Float.parseFloat(pValue + ""); + return true; + }catch(NumberFormatException ex){ + return false; + } + } + + public float getNumberValue() { + return numberValue; + } + public void setNumberValue(float numberValue) { + this.numberValue = numberValue; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceTextAttribute.java b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceTextAttribute.java new file mode 100644 index 0000000000..f35aa21102 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceTextAttribute.java @@ -0,0 +1,65 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.meta; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * Defines a text type custom attribute of a document, part, product and other objects. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="INSTANCETEXTATTRIBUTE") +@Entity +public class InstanceTextAttribute extends InstanceAttribute{ + + + + private String textValue; + + public InstanceTextAttribute() { + } + + public InstanceTextAttribute(String pName, String pValue, boolean pMandatory) { + super(pName, pMandatory); + setTextValue(pValue); + } + + @Override + public String getValue() { + return textValue; + } + @Override + public boolean setValue(Object pValue) { + textValue = pValue != null ? pValue + "" : ""; + return true; + } + + public String getTextValue() { + return textValue; + } + public void setTextValue(String textValue) { + this.textValue = textValue; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceURLAttribute.java b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceURLAttribute.java new file mode 100644 index 0000000000..585be94652 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/InstanceURLAttribute.java @@ -0,0 +1,63 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.meta; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * Defines an URL type custom attribute of a document, part, product and other objects. + * + * @author Emmanuel Nhan + * @version 1.0 23/07/2009 + * @since V1.0 + */ +@Table(name="INSTANCEURLATTRIBUTE") +@Entity +public class InstanceURLAttribute extends InstanceAttribute { + + private String urlValue; + + public InstanceURLAttribute() { + } + + public InstanceURLAttribute(String pName, String pValue, boolean pMandatory) { + super(pName, pMandatory); + setUrlValue(pValue); + } + + @Override + public String getValue() { + return urlValue; + } + @Override + public boolean setValue(Object pValue) { + urlValue = pValue != null ? pValue + "" : ""; + return true; + } + + public String getUrlValue() { + return urlValue; + } + public void setUrlValue(String urlValue) { + this.urlValue = urlValue; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/ListOfValues.java b/docdoku-common/src/main/java/com/docdoku/core/meta/ListOfValues.java new file mode 100644 index 0000000000..84d336d03b --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/ListOfValues.java @@ -0,0 +1,127 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.meta; + +import com.docdoku.core.common.Workspace; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * A list of values is basically a named collection of name-value pair. + * + * @author Florent Garin + * @version 2.0, 27/02/15 + * @since V2.0 + */ +@Table(name = "LOV") +@javax.persistence.IdClass(ListOfValuesKey.class) +@javax.persistence.Entity +public class ListOfValues implements Serializable { + + @Column(name = "WORKSPACE_ID", length = 100, nullable = false, insertable = false, updatable = false) + @Id + private String workspaceId = ""; + + @Column(length = 100) + @Id + private String name = ""; + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + private Workspace workspace; + + @ElementCollection(fetch = FetchType.EAGER) + @OrderColumn(name = "NAMEVALUE_ORDER") + @CollectionTable( + name = "LOV_NAMEVALUE", + joinColumns = {@JoinColumn(name = "LOV_NAME", referencedColumnName = "NAME"), + @JoinColumn(name = "LOV_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + } + ) + private List<NameValuePair> values=new ArrayList<>(); + + public ListOfValues() { + } + + public ListOfValues(Workspace pWorkspace, String pName) { + setWorkspace(pWorkspace); + name = pName; + } + + public void setWorkspace(Workspace pWorkspace) { + workspace = pWorkspace; + workspaceId = workspace.getId(); + } + + public String getName() { + return name; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setName(String name) { + this.name = name; + } + + public Workspace getWorkspace() { + return workspace; + } + + public List<NameValuePair> getValues() { + return values; + } + + public void setValues(List<NameValuePair> values) { + this.values = values; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + name.hashCode(); + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof ListOfValues)) { + return false; + } + ListOfValues lov = (ListOfValues) pObj; + + return lov.workspaceId.equals(workspaceId) + && lov.name.equals(name); + } + + @Override + public String toString() { + return name; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/ListOfValuesAttributeTemplate.java b/docdoku-common/src/main/java/com/docdoku/core/meta/ListOfValuesAttributeTemplate.java new file mode 100644 index 0000000000..727768f9c5 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/ListOfValuesAttributeTemplate.java @@ -0,0 +1,89 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.meta; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; + +/** + * {@link InstanceAttributeTemplate} implementation that can instantiate list of values based + * attributes. + * + * @author Florent Garin + * @version 2.0, 02/03/15 + * @since V2.0 + */ +@Table(name="ILOVATTRIBUTETEMPLATE") +@Entity +public class ListOfValuesAttributeTemplate extends InstanceAttributeTemplate { + + + @JoinColumns({ + @JoinColumn(name = "LOV_NAME", referencedColumnName = "NAME"), + @JoinColumn(name = "LOV_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + @ManyToOne(optional = false, fetch = FetchType.EAGER) + private ListOfValues lov; + + public ListOfValuesAttributeTemplate() { + } + + public ListOfValuesAttributeTemplate(String pName, ListOfValues pLov) { + super(pName); + lov=pLov; + } + + @Override + public AttributeType getAttributeType() { + return AttributeType.LOV; + } + + public ListOfValues getLov() { + return lov; + } + + public void setLov(ListOfValues lov) { + this.lov = lov; + } + + public String getLovName(){ + return this.lov != null ? this.lov.getName():null; + } + + @Override + public InstanceAttribute createInstanceAttribute() { + InstanceListOfValuesAttribute attr = new InstanceListOfValuesAttribute(); + List<NameValuePair> items = new ArrayList<>(lov.getValues()); + attr.setItems(items); + attr.setName(name); + attr.setMandatory(mandatory); + attr.setLocked(locked); + + return attr; + } + + + + @Override + public String toString() { + return name + "-" + lov; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/ListOfValuesKey.java b/docdoku-common/src/main/java/com/docdoku/core/meta/ListOfValuesKey.java new file mode 100644 index 0000000000..4021d7895d --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/ListOfValuesKey.java @@ -0,0 +1,83 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.meta; + +import java.io.Serializable; + +/** + * + * @author Florent Garin + */ +public class ListOfValuesKey implements Serializable { + + private String workspaceId; + private String name; + + public ListOfValuesKey() { + } + + public ListOfValuesKey(String pWorkspaceId, String pName) { + workspaceId=pWorkspaceId; + name =pName; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + name.hashCode(); + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof ListOfValuesKey)) { + return false; + } + ListOfValuesKey key = (ListOfValuesKey) pObj; + return key.workspaceId.equals(workspaceId) && key.name.equals(name); + } + + @Override + public String toString() { + return name; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String pWorkspaceId) { + workspaceId = pWorkspaceId; + } + + + public String getName(){ + return name; + } + + public void setName(String pName){ + name =pName; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/NameValuePair.java b/docdoku-common/src/main/java/com/docdoku/core/meta/NameValuePair.java new file mode 100644 index 0000000000..715510b18a --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/NameValuePair.java @@ -0,0 +1,61 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.meta; + +import javax.persistence.Embeddable; +import java.io.Serializable; + +/** + * Embeddable name-value pair. + * + * @author Florent Garin + * @version 2.0, 27/02/15 + * @since V2.0 + */ +@Embeddable +public class NameValuePair implements Serializable { + + private String name, value; + + public NameValuePair() { + } + + public NameValuePair(String name, String value) { + this.name = name; + this.value = value; + } + + public void setName(String name) { + this.name = name; + } + + public void setValue(String value) { + this.value = value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/Tag.java b/docdoku-common/src/main/java/com/docdoku/core/meta/Tag.java new file mode 100644 index 0000000000..90d953e619 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/Tag.java @@ -0,0 +1,109 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.meta; + +import com.docdoku.core.common.Workspace; + +import javax.persistence.Column; +import javax.persistence.FetchType; +import javax.persistence.Table; +import java.io.Serializable; + +/** + * A tag is just a label pinned on an entity. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="TAG") +@javax.persistence.IdClass(TagKey.class) +@javax.persistence.Entity +public class Tag implements Serializable { + + @javax.persistence.Column(name = "WORKSPACE_ID", length=100, nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String workspaceId=""; + + @Column(length=100) + @javax.persistence.Id + private String label=""; + + @javax.persistence.ManyToOne(optional=false, fetch=FetchType.EAGER) + private Workspace workspace; + + public Tag() { + } + + public Tag(Workspace pWorkspace, String pLabel) { + setWorkspace(pWorkspace); + label=pLabel; + } + + public void setWorkspace(Workspace pWorkspace){ + workspace=pWorkspace; + workspaceId=workspace.getId(); + } + + public String getLabel() { + return label; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setLabel(String label) { + this.label = label; + } + + public Workspace getWorkspace() { + return workspace; + } + + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + label.hashCode(); + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof Tag)) { + return false; + } + Tag tag = (Tag) pObj; + + return tag.workspaceId.equals(workspaceId) + && tag.label.equals(label); + } + + @Override + public String toString() { + return label; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/meta/TagKey.java b/docdoku-common/src/main/java/com/docdoku/core/meta/TagKey.java new file mode 100644 index 0000000000..ca363abf51 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/meta/TagKey.java @@ -0,0 +1,83 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.meta; + +import java.io.Serializable; + +/** + * + * @author Florent Garin + */ +public class TagKey implements Serializable { + + private String workspaceId; + private String label; + + public TagKey() { + } + + public TagKey(String pWorkspaceId, String pLabel) { + workspaceId=pWorkspaceId; + label=pLabel; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + label.hashCode(); + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof TagKey)) { + return false; + } + TagKey key = (TagKey) pObj; + return key.workspaceId.equals(workspaceId) && key.label.equals(label); + } + + @Override + public String toString() { + return label; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String pWorkspaceId) { + workspaceId = pWorkspaceId; + } + + + public String getLabel(){ + return label; + } + + public void setLabel(String pLabel){ + label=pLabel; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/CADInstance.java b/docdoku-common/src/main/java/com/docdoku/core/product/CADInstance.java new file mode 100644 index 0000000000..d43f0997d4 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/CADInstance.java @@ -0,0 +1,198 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.product; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; +import java.io.Serializable; + +/** + * Represents a CAD instance of a specific part defined in a + * {@link PartUsageLink} {@link PartSubstituteLink}. + * Using its attributes: translation and orientation on the three axis we'll be + * able to create the mesh to render it. + * + * @author Florent Garin + * @version 1.1, 20/07/12 + * @since V1.1 + */ +@Table(name="CADINSTANCE") +@Entity +public class CADInstance implements Serializable, Cloneable { + + public enum RotationType { + ANGLE,MATRIX + }; + + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Id + private int id; + + + /** + * Translation on x axis. + */ + private double tx; + + /** + * Translation on y axis. + */ + private double ty; + + /** + * Translation on z axis. + */ + private double tz; + + /** + * Radian orientation on x axis. + */ + private double rx; + + /** + * Radian orientation on y axis. + */ + private double ry; + + /** + * Radian orientation on z axis. + */ + private double rz; + + /** + * Rotation matrix + */ + @Embedded + private RotationMatrix rotationMatrix; + + @Enumerated(EnumType.STRING) + @NotNull + private RotationType rotationType; + + + public CADInstance() { + } + + public CADInstance(double tx, double ty, double tz, double rx, double ry, double rz) { + this.tx = tx; + this.ty = ty; + this.tz = tz; + this.rx = rx; + this.ry = ry; + this.rz = rz; + rotationType = RotationType.ANGLE; + this.rotationMatrix = new RotationMatrix(new double[9]); + } + + public CADInstance(RotationMatrix rotationMatrix, double tx, double ty, double tz) { + rotationType = RotationType.MATRIX; + this.tx = tx; + this.ty = ty; + this.tz = tz; + this.rotationMatrix = rotationMatrix; + } + + + public double getRx() { + return rx; + } + + public double getRy() { + return ry; + } + + public double getRz() { + return rz; + } + + public double getTx() { + return tx; + } + + public double getTy() { + return ty; + } + + public double getTz() { + return tz; + } + + public void setRx(double rx) { + this.rx = rx; + } + + public void setRy(double ry) { + this.ry = ry; + } + + public void setRz(double rz) { + this.rz = rz; + } + + public void setTx(double tx) { + this.tx = tx; + } + + public void setTy(double ty) { + this.ty = ty; + } + + public void setTz(double tz) { + this.tz = tz; + } + + public RotationMatrix getRotationMatrix() { + return rotationMatrix; + } + + public void setRotationMatrix(RotationMatrix rotationMatrix) { + this.rotationMatrix = rotationMatrix; + } + + public RotationType getRotationType() { + return rotationType; + } + + public void setRotationType(RotationType rotationType) { + this.rotationType = rotationType; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + @Override + public CADInstance clone() { + CADInstance clone; + try { + clone = (CADInstance) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + return clone; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/Component.java b/docdoku-common/src/main/java/com/docdoku/core/product/Component.java new file mode 100644 index 0000000000..1811e9d849 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/Component.java @@ -0,0 +1,104 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.product; + +import com.docdoku.core.common.User; + +import java.io.Serializable; +import java.util.List; + +/** + * @author Morgan Guimard + * + * Helping class to construct a descriptive tree of a resolved configuration. + */ + +public class Component implements Serializable { + + private User user; + private PartMaster partMaster; + private PartIteration retainedIteration; + private List<PartLink> path; + private List<Component> components; + private boolean isVirtual = false; + + public Component() { + } + + public Component(User user, PartMaster partMaster, List<PartLink> path, List<Component> components) { + this.user = user; + this.partMaster = partMaster; + this.path = path; + this.components = components; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + + public List<PartLink> getPath() { + return path; + } + public PartLink getPartLink() { + return path.get(path.size()-1); + } + public void setPath(List<PartLink> path) { + this.path = path; + } + + public List<Component> getComponents() { + return components; + } + + public void setComponents(List<Component> components) { + this.components = components; + } + public void addComponent(Component component) { + this.components.add(component); + } + public PartMaster getPartMaster() { + return partMaster; + } + + public void setPartMaster(PartMaster partMaster) { + this.partMaster = partMaster; + } + + public PartIteration getRetainedIteration() { + return retainedIteration; + } + + public void setRetainedIteration(PartIteration retainedIteration) { + this.retainedIteration = retainedIteration; + } + + public boolean isVirtual() { + return isVirtual; + } + + public void setVirtual(boolean isVirtual) { + this.isVirtual = isVirtual; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/ConfigurationItem.java b/docdoku-common/src/main/java/com/docdoku/core/product/ConfigurationItem.java new file mode 100644 index 0000000000..770c710813 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/ConfigurationItem.java @@ -0,0 +1,198 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.product; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * This class represents an entire product or some portion + * than is planned for production. + * Configuration management is done using this class that controls + * the composition of constituents for actual units of production. + * + * All the effectivities that reference a given ConfigurationItem + * must be of the same type. + * Application logic should insure it's not possible to mix effectivity + * types for the same configuration item. + * + * @author Florent Garin + * @version 1.1, 31/10/11 + * @since V1.1 + */ +@Table(name="CONFIGURATIONITEM") +@NamedQueries({ + @NamedQuery(name="ConfigurationItem.getConfigurationItemsInWorkspace",query="SELECT DISTINCT ci FROM ConfigurationItem ci WHERE ci.workspace.id = :workspaceId"), + @NamedQuery(name="ConfigurationItem.getEffectivities",query="SELECT e FROM Effectivity e WHERE e.configurationItem = :configurationItem"), + @NamedQuery(name="ConfigurationItem.findByDesignItem",query="SELECT c FROM ConfigurationItem c WHERE c.designItem = :designItem"), + @NamedQuery(name="ConfigurationItem.findByPathToPathLink",query="SELECT c FROM ConfigurationItem c WHERE :pathToPathLink member of c.pathToPathLinks") +}) +@javax.persistence.IdClass(com.docdoku.core.product.ConfigurationItemKey.class) +@Entity +public class ConfigurationItem implements Serializable { + + @Column(length = 100) + @Id + private String id = ""; + + @Id + @ManyToOne(optional = false, fetch = FetchType.EAGER) + private Workspace workspace; + + + @Lob + private String description; + + /** + * The top level of the design of the configuration item + * which is the context for effectivities. + */ + @ManyToOne(fetch= FetchType.LAZY, optional=false) + @JoinColumns({ + @JoinColumn(name = "PARTMASTER_PARTNUMBER", referencedColumnName = "PARTNUMBER"), + @JoinColumn(name = "PARTMASTER_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private PartMaster designItem; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinTable(name = "CONFIGURATIONITEM_P2PLINK", + inverseJoinColumns = { + @JoinColumn(name = "PATHTOPATHLINK_ID", referencedColumnName = "ID") + }, + joinColumns = { + @JoinColumn(name="CONFIGURATIONITEM_ID", referencedColumnName="ID"), + @JoinColumn(name="WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private List<PathToPathLink> pathToPathLinks =new ArrayList<>(); + + public ConfigurationItem() { + } + + public ConfigurationItem(User author,Workspace pWorkspace, String pId, String pDescription) { + this.author = author; + this.workspace=pWorkspace; + this.id=pId; + this.description=pDescription; + } + + public PartMaster getDesignItem() { + return designItem; + } + + public void setDesignItem(PartMaster designItem) { + this.designItem = designItem; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Workspace getWorkspace() { + return workspace; + } + + public String getWorkspaceId() { + return workspace == null ? "" : workspace.getId(); + } + + public void setWorkspace(Workspace workspace) { + this.workspace = workspace; + } + + public ConfigurationItemKey getKey(){ + return new ConfigurationItemKey(getWorkspaceId(),getId()); + } + + public User getAuthor() { + return author; + } + + public void setAuthor(User author) { + this.author = author; + } + + public List<PathToPathLink> getPathToPathLinks() { + return pathToPathLinks; + } + + public void setPathToPathLinks(List<PathToPathLink> pathToPathLinks) { + this.pathToPathLinks = pathToPathLinks; + } + + public void addPathToPathLink(PathToPathLink pathToPathLink) { + pathToPathLinks.add(pathToPathLink); + } + + public void removePathToPathLink(PathToPathLink pathToPathLink) { + pathToPathLinks.remove(pathToPathLink); + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof ConfigurationItem)) { + return false; + } + ConfigurationItem ci = (ConfigurationItem) pObj; + return ci.id.equals(id) && ci.getWorkspaceId().equals(getWorkspaceId()); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + getWorkspaceId().hashCode(); + hash = 31 * hash + id.hashCode(); + return hash; + } + + @Override + public String toString() { + return id; + } + + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/ConfigurationItemKey.java b/docdoku-common/src/main/java/com/docdoku/core/product/ConfigurationItemKey.java new file mode 100644 index 0000000000..93611aec73 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/ConfigurationItemKey.java @@ -0,0 +1,84 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import java.io.Serializable; + +/** + * Identity class of {@link ConfigurationItem}. + * objects. + * + * @author Florent Garin + */ +public class ConfigurationItemKey implements Serializable { + + private String workspace; + private String id; + + public ConfigurationItemKey() { + } + + public ConfigurationItemKey(String pWorkspaceId, String pId) { + workspace=pWorkspaceId; + id=pId; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspace.hashCode(); + hash = 31 * hash + id.hashCode(); + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof ConfigurationItemKey)) { + return false; + } + ConfigurationItemKey key = (ConfigurationItemKey) pObj; + return key.id.equals(id) && key.workspace.equals(workspace); + } + + @Override + public String toString() { + return workspace + "-" + id; + } + + public String getWorkspace() { + return workspace; + } + + public void setWorkspace(String pWorkspace) { + workspace = pWorkspace; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/Conversion.java b/docdoku-common/src/main/java/com/docdoku/core/product/Conversion.java new file mode 100644 index 0000000000..c3148fb5c4 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/Conversion.java @@ -0,0 +1,99 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; + +@Entity +@IdClass(PartIterationKey.class) +public class Conversion implements Serializable { + + @Id + @OneToOne(optional = false, fetch = FetchType.EAGER) + private PartIteration partIteration; + + @Temporal(TemporalType.TIMESTAMP) + private Date startDate; + + @Temporal(TemporalType.TIMESTAMP) + private Date endDate; + + private boolean pending; + + private boolean succeed; + + public Conversion() { + } + + public Conversion(PartIteration partIteration) { + this(new Date(), null, true, false, partIteration); + } + + public Conversion(Date startDate, Date endDate, boolean pending, boolean succeed, PartIteration partIteration) { + this.startDate = startDate; + this.endDate = endDate; + this.pending = pending; + this.succeed = succeed; + this.partIteration = partIteration; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public boolean isPending() { + return pending; + } + + public void setPending(boolean pending) { + this.pending = pending; + } + + public boolean isSucceed() { + return succeed; + } + + public void setSucceed(boolean succeed) { + this.succeed = succeed; + } + + public PartIteration getPartIteration() { + return partIteration; + } + + public void setPartIteration(PartIteration partIteration) { + this.partIteration = partIteration; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/DateBasedEffectivity.java b/docdoku-common/src/main/java/com/docdoku/core/product/DateBasedEffectivity.java new file mode 100644 index 0000000000..e841ae9dbe --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/DateBasedEffectivity.java @@ -0,0 +1,75 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.product; + +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; + +/** + * DateBasedEffectivity indicates that an item is effective while a + * configuration item is being produced during a date range. + * + * @author Florent Garin + * @version 1.1, 18/10/11 + * @since V1.1 + */ +@Table(name="DATEBASEDEFFECTIVITY") +@Entity +public class DateBasedEffectivity extends Effectivity{ + + /** + * The date and/or time when the effectivity starts. + */ + @Temporal(TemporalType.TIMESTAMP) + private Date startDate; + + /** + * The date and/or time when the effectivity ends. + * If a value for this attribute is not set, + * then the effectivity has no defined end. + */ + @Temporal(TemporalType.TIMESTAMP) + private Date endDate; + + public DateBasedEffectivity() { + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/Effectivity.java b/docdoku-common/src/main/java/com/docdoku/core/product/Effectivity.java new file mode 100644 index 0000000000..acbbd21c82 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/Effectivity.java @@ -0,0 +1,99 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.product; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlSeeAlso; +import java.io.Serializable; + +/** + * An Effectivity is an abstract subclass which is + * a kind of qualification object. + * + * Effectivities are primarily applied to {@link PartRevision} objects. + * + * + * @author Florent Garin + * @version 1.1, 14/10/11 + * @since V1.1 + */ +@Table(name="EFFECTIVITY") +@XmlSeeAlso({DateBasedEffectivity.class, SerialNumberBasedEffectivity.class, LotBasedEffectivity.class}) +@Inheritance() +@Entity +@NamedQuery(name="Effectivity.removeEffectivitiesFromConfigurationItem",query="DELETE FROM Effectivity e WHERE e.configurationItem.id = :configurationItemId AND e.configurationItem.workspace.id = :workspaceId") + +public abstract class Effectivity implements Serializable { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private int id; + protected String name; + + @Lob + private String description; + + @ManyToOne(optional = true, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "CONFIGURATIONITEM_ID", referencedColumnName = "ID"), + @JoinColumn(name = "CONFIGURATIONITEM_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private ConfigurationItem configurationItem; + + + public Effectivity() { + } + + public Effectivity(String pName) { + name = pName; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + /* + * May be optional for a DateBasedEffectivity. + */ + public void setConfigurationItem(ConfigurationItem configurationItem) { + this.configurationItem = configurationItem; + } + + public ConfigurationItem getConfigurationItem() { + return configurationItem; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/Geometry.java b/docdoku-common/src/main/java/com/docdoku/core/product/Geometry.java new file mode 100644 index 0000000000..e985938b7b --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/Geometry.java @@ -0,0 +1,179 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.product; + +import com.docdoku.core.common.BinaryResource; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.util.Date; + +/** + * Wraps a CAD file providing quality information. + * Higher quality is more faces are defined. + * This CAD file is not the native one used by the authoring tool but the generated one + * used for the in-browser visualizer. + * + * @author Florent Garin + * @version 1.1, 20/07/12 + * @since V1.1 + */ +@Table(name="GEOMETRY") +@Entity +public class Geometry extends BinaryResource{ + + /** + * Starts at 0, smaller is greater. + */ + private int quality; + + + //Bounding box + @Column(name="X_MIN") + private double xMin = 0; + + @Column(name="Y_MIN") + private double yMin = 0; + + @Column(name="Z_MIN") + private double zMin = 0; + + @Column(name="X_MAX") + private double xMax = 0; + + @Column(name="Y_MAX") + private double yMax = 0; + + @Column(name="Z_MAX") + private double zMax = 0; + + public Geometry() { + } + + public Geometry(int pQuality, String pFullName, long pContentLength, Date pLastModified) { + super(pFullName, pContentLength, pLastModified); + this.quality=pQuality; + } + + public int getQuality() { + return quality; + } + + public void setQuality(int quality) { + this.quality = quality; + } + + public void setBox(double x1, double y1, double z1, double x2, double y2, double z2) { + xMin = Math.min(x1, x2); + xMax = Math.max(x1, x2); + yMin = Math.min(y1, y2); + yMax = Math.max(y1, y2); + zMin = Math.min(z1, z2); + zMax = Math.max(z1, z2); + } + + public double getxMin() { + return xMin; + } + + public void setxMin(double xMin) { + this.xMin = xMin; + } + + public double getyMin() { + return yMin; + } + + public void setyMin(double yMin) { + this.yMin = yMin; + } + + public double getzMin() { + return zMin; + } + + public void setzMin(double zMin) { + this.zMin = zMin; + } + + public double getxMax() { + return xMax; + } + + public void setxMax(double xMax) { + this.xMax = xMax; + } + + public double getyMax() { + return yMax; + } + + public void setyMax(double yMax) { + this.yMax = yMax; + } + + public double getzMax() { + return zMax; + } + + public void setzMax(double zMax) { + this.zMax = zMax; + } + + @Override + public int compareTo(BinaryResource pBinaryResource) { + if (!(pBinaryResource instanceof Geometry)) { + return super.compareTo(pBinaryResource); + } + + int ret = quality - ((Geometry) pBinaryResource).quality; + if(ret == 0){ + return fullName.compareTo(((Geometry) pBinaryResource).fullName); + } + return ret; + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Geometry)) { + return false; + } + if (!super.equals(o)) { + return false; + } + + Geometry geometry = (Geometry) o; + return quality == geometry.quality; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + quality; + return result; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/Import.java b/docdoku-common/src/main/java/com/docdoku/core/product/Import.java new file mode 100644 index 0000000000..4c8e9125a2 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/Import.java @@ -0,0 +1,159 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import com.docdoku.core.common.User; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +@Entity +@Table(name = "IMPORT") +public class Import implements Serializable { + + @Column(length = 255) + @Id + private String id=""; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "USER_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "USER_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User user; + + @ElementCollection + @CollectionTable(name="IMPORT_WARNING", + joinColumns = { + @JoinColumn(name = "IMPORT_ID", referencedColumnName = "ID") + }) + private List<String> warnings ; + + @ElementCollection + @CollectionTable(name="IMPORT_ERROR", joinColumns = { + @JoinColumn(name = "IMPORT_ID", referencedColumnName = "ID") + }) + private List<String> errors ; + + private String fileName; + + @Temporal(TemporalType.TIMESTAMP) + private Date startDate; + + @Temporal(TemporalType.TIMESTAMP) + private Date endDate; + + private boolean pending; + + private boolean succeed; + + public Import() { + } + + public Import(User user, String fileName) { + this(user, fileName, new Date(), null, true, false); + } + + public Import(User user, String fileName, Date startDate, Date endDate, boolean pending, boolean succeed) { + this.user=user; + this.fileName=fileName; + this.id=UUID.randomUUID().toString(); + this.startDate = startDate; + this.endDate = endDate; + this.pending = pending; + this.succeed = succeed; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public boolean isPending() { + return pending; + } + + public void setPending(boolean pending) { + this.pending = pending; + } + + public boolean isSucceed() { + return succeed; + } + + public void setSucceed(boolean succeed) { + this.succeed = succeed; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public List<String> getWarnings() { + return warnings; + } + + public void setWarnings(List<String> warnings) { + this.warnings = warnings; + } + + public List<String> getErrors() { + return errors; + } + + public void setErrors(List<String> errors) { + this.errors = errors; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/ImportPreview.java b/docdoku-common/src/main/java/com/docdoku/core/product/ImportPreview.java new file mode 100644 index 0000000000..01a27a154d --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/ImportPreview.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import java.util.ArrayList; +import java.util.List; + +/** + * A class to stock dry run result, a preview of the import + * @author Laurent Le Van + * @since 29/06/2016 + */ +public class ImportPreview { + + /** + * Part revisions which will be checked out + */ + private List<PartRevision> partRevsToCheckout; + + public ImportPreview(){ + this.partRevsToCheckout = new ArrayList<>(); + } + + public ImportPreview(List<PartRevision> partRevisions){ + this.partRevsToCheckout = partRevisions; + } + + public List<PartRevision> getPartRevsToCheckout(){ + return partRevsToCheckout; + } + + public void setPartRevisions(List<PartRevision> partRevsToCheckout){ + this.partRevsToCheckout = partRevsToCheckout; + } + + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/ImportResult.java b/docdoku-common/src/main/java/com/docdoku/core/product/ImportResult.java new file mode 100644 index 0000000000..8d9ac279fe --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/ImportResult.java @@ -0,0 +1,106 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class ImportResult { + + private File importedFile; + private String originalFileName; + private List<String> warnings = new ArrayList<>(); + private List<String> errors = new ArrayList<>(); + private String stdOutput; + private String errorOutput; + + + public ImportResult(File importedFile) { + this.importedFile = importedFile; + } + + public ImportResult(File importedFile, List<String> warnings, List<String> errors) { + this.importedFile = importedFile; + this.warnings = warnings; + this.errors=errors; + } + + public ImportResult(File importedFile, String originalFileName, List<String> warnings, List<String> errors) { + this.importedFile = importedFile; + this.originalFileName = originalFileName; + this.warnings = warnings; + this.errors = errors; + } + + public ImportResult(File importedFile, List<String> warnings, List<String> errors, String stdOutput, String errorOutput) { + this.importedFile = importedFile; + this.warnings = warnings; + this.errors=errors; + this.stdOutput = stdOutput; + this.errorOutput = errorOutput; + } + + public File getImportedFile() { + return importedFile; + } + + public void setImportedFile(File importedFile) { + this.importedFile = importedFile; + } + + public List<String> getWarnings() { + return warnings; + } + + public void setWarnings(List<String> warnings) { + this.warnings = warnings; + } + + public List<String> getErrors() { + return errors; + } + + public void setErrors(List<String> errors) { + this.errors = errors; + } + + public String getStdOutput() { + return stdOutput; + } + + public void setStdOutput(String stdOutput) { + this.stdOutput = stdOutput; + } + + public String getErrorOutput() { + return errorOutput; + } + + public void setErrorOutput(String errorOutput) { + this.errorOutput = errorOutput; + } + + public boolean isSucceed() { + return errors == null || errors.isEmpty(); + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/Layer.java b/docdoku-common/src/main/java/com/docdoku/core/product/Layer.java new file mode 100644 index 0000000000..25b1e25d3c --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/Layer.java @@ -0,0 +1,157 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.product; + +import com.docdoku.core.common.User; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * A Layer is a collection of {@link Marker}s that can be manipulated as a whole. + * Layers belong to a {@link ConfigurationItem}. + * + * @author Florent Garin + * @version 1.1, 14/08/12 + * @since V1.1 + */ +@Table(name="LAYER") +@Entity +@NamedQueries({ + @NamedQuery(name="Layer.findLayersByConfigurationItem",query="SELECT DISTINCT l FROM Layer l WHERE l.configurationItem.id = :configurationItemId AND l.configurationItem.workspace.id = :workspaceId"), + @NamedQuery(name="Layer.removeLayersFromConfigurationItem",query="DELETE FROM Layer l WHERE l.configurationItem.id = :configurationItemId AND l.configurationItem.workspace.id = :workspaceId") +}) +public class Layer implements Serializable{ + + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Id + private int id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + @Temporal(TemporalType.TIMESTAMP) + private java.util.Date creationDate; + + private String name; + + @ManyToMany(fetch= FetchType.LAZY) + @JoinTable(name="LAYER_MARKER", + inverseJoinColumns={ + @JoinColumn(name="MARKER_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="LAYER_ID", referencedColumnName="ID") + }) + private Set<Marker> markers = new HashSet<Marker>(); + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "CONFIGURATIONITEM_ID", referencedColumnName = "ID"), + @JoinColumn(name = "CONFIGURATIONITEM_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private ConfigurationItem configurationItem; + + private String color; + + public Layer() { + } + + public Layer(String pName, User pAuthor, ConfigurationItem pConfigurationItem, String color) { + this.name=pName; + this.author=pAuthor; + this.configurationItem=pConfigurationItem; + this.color=color; + } + + public User getAuthor() { + return author; + } + + public void setAuthor(User author) { + this.author = author; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public Set<Marker> getMarkers() { + return markers; + } + + public void setMarkers(Set<Marker> markers) { + this.markers = markers; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public ConfigurationItem getConfigurationItem() { + return configurationItem; + } + + public void setConfigurationItem(ConfigurationItem configurationItem) { + this.configurationItem = configurationItem; + } + + public void addMarker(Marker marker) { + this.markers.add(marker); + } + + public void removeMarker(Marker marker) { + this.markers.remove(marker); + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/LotBasedEffectivity.java b/docdoku-common/src/main/java/com/docdoku/core/product/LotBasedEffectivity.java new file mode 100644 index 0000000000..56ccbd6348 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/LotBasedEffectivity.java @@ -0,0 +1,74 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.product; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * LotBasedEffectivity indicates that an item is effective while a + * configuration item is being produced in a specified lot. + * + * @author Florent Garin + * @version 1.1, 18/10/11 + * @since V1.1 + */ +@Table(name="LOTBASEDEFFECTIVITY") +@Entity +public class LotBasedEffectivity extends Effectivity{ + + /** + * The identification of the first batch of items + * that the effectivity applies to. + */ + private String startLotId; + + + /** + * The identification of the last batch of items + * that the effectivity applies to. + * This value is optional. + */ + private String endLotId; + + public LotBasedEffectivity() { + } + + public String getStartLotId() { + return startLotId; + } + + public void setStartLotId(String startLotId) { + this.startLotId = startLotId; + } + + + public String getEndLotId() { + return endLotId; + } + + public void setEndLotId(String endLotId) { + this.endLotId = endLotId; + } + + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/Marker.java b/docdoku-common/src/main/java/com/docdoku/core/product/Marker.java new file mode 100644 index 0000000000..b98ec6f4b9 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/Marker.java @@ -0,0 +1,196 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.product; + +import com.docdoku.core.common.User; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * Represents a marker on the 3D scene, actually a {@link ConfigurationItem}. + * May be attached to one or several {@link PartMaster}s. + * + * @author Florent Garin + * @version 1.1, 14/08/12 + * @since V1.1 + */ +@Table(name="MARKER") +@Entity +public class Marker implements Serializable{ + + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Id + private int id; + + @OneToMany(orphanRemoval=true, cascade= CascadeType.ALL, fetch= FetchType.EAGER) + @JoinTable(name="MARKER_EFFECTIVITY", + inverseJoinColumns={ + @JoinColumn(name="EFFECTIVITY_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="MARKER_ID", referencedColumnName="ID") + }) + private Set<Effectivity> effectivities = new HashSet<Effectivity>(); + + @ManyToMany(fetch= FetchType.LAZY) + @JoinTable(name="MARKER_PARTMASTER", + inverseJoinColumns={ + @JoinColumn(name="RELATEDPART_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name="RELATEDPART_PARTNUMBER", referencedColumnName="PARTNUMBER") + }, + joinColumns={ + @JoinColumn(name="MARKER_ID", referencedColumnName="ID") + }) + private Set<PartMaster> relatedParts = new HashSet<PartMaster>(); + + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + @Temporal(TemporalType.TIMESTAMP) + private java.util.Date creationDate; + + private String title; + + @Lob + private String description; + + /** + * Position on x axis. + */ + private double x; + + /** + * Position on y axis. + */ + private double y; + + /** + * Position on z axis. + */ + private double z; + + public Marker() { + } + + public Marker(String pTitle, User pAuthor, String pDescription, double pX, double pY, double pZ) { + this.title=pTitle; + this.author=pAuthor; + this.description=pDescription; + this.x=pX; + this.y=pY; + this.z=pZ; + } + + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Set<Effectivity> getEffectivities() { + return effectivities; + } + + public void setEffectivities(Set<Effectivity> effectivities) { + this.effectivities = effectivities; + } + + public Set<PartMaster> getRelatedParts() { + return relatedParts; + } + + public void setRelatedParts(Set<PartMaster> relatedParts) { + this.relatedParts = relatedParts; + } + + + public void setX(double x) { + this.x = x; + } + + public double getX() { + return x; + } + + public void setY(double y) { + this.y = y; + } + + public double getY() { + return y; + } + + public void setZ(double z) { + this.z = z; + } + + public double getZ() { + return z; + } + + public User getAuthor() { + return author; + } + + public void setAuthor(User author) { + this.author = author; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/PartAlternateLink.java b/docdoku-common/src/main/java/com/docdoku/core/product/PartAlternateLink.java new file mode 100644 index 0000000000..ea37d0dab2 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/PartAlternateLink.java @@ -0,0 +1,81 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.product; + +import javax.persistence.*; +import java.io.Serializable; + +/** + * + * An Alternate object is a part that is interchangeable with another part + * with respect to function and physical properties. + * + * Beware that this link is neither transitive nor even bidirectional. + * That means if we want to express that two parts are alternates of each other + * we must create two links. + * + * @author Florent Garin + * @version 1.1, 15/10/11 + * @since V1.1 + */ +@Embeddable +public class PartAlternateLink implements Serializable { + + + private String referenceDescription; + + @Column(name="COMMENTDATA") + private String comment; + + @ManyToOne(optional=false, fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="ALTERNATE_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name="ALTERNATE_PARTNUMBER", referencedColumnName="PARTNUMBER")}) + private PartMaster alternate; + + + public PartAlternateLink() { + } + + public PartMaster getAlternate() { + return alternate; + } + + public void setAlternate(PartMaster alternate) { + this.alternate = alternate; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public String getReferenceDescription() { + return referenceDescription; + } + + public void setReferenceDescription(String referenceDescription) { + this.referenceDescription = referenceDescription; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/PartIteration.java b/docdoku-common/src/main/java/com/docdoku/core/product/PartIteration.java new file mode 100644 index 0000000000..558b4168b6 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/PartIteration.java @@ -0,0 +1,469 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.FileHolder; +import com.docdoku.core.common.User; +import com.docdoku.core.document.DocumentLink; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.InstanceAttributeTemplate; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.*; + +/** + * This class encapsulates the various states of a part whereas its unchanging + * attributes are hold on a {@link PartMaster}. + * + * @author Florent Garin + * @version 1.1, 18/05/11 + * @since V1.1 + */ +@Table(name="PARTITERATION") +@IdClass(com.docdoku.core.product.PartIterationKey.class) +@NamedQueries({ + @NamedQuery(name="PartIteration.findUsedByAsSubstitute", query="SELECT p FROM PartIteration p JOIN p.components l JOIN l.substitutes s WHERE s.substitute = :partMaster"), + @NamedQuery(name="PartIteration.findUsedByAsComponent", query="SELECT p FROM PartIteration p JOIN p.components l WHERE l.component = :partMaster"), + @NamedQuery(name="PartIteration.findDistinctInstanceAttributes", query="SELECT DISTINCT i FROM InstanceAttribute i LEFT JOIN PartIteration p WHERE p.partRevision.partMaster.workspace.id = :workspaceId AND i member of p.instanceAttributes"), + @NamedQuery(name="PartIteration.findWhereLOV", query="SELECT p FROM PartIteration p WHERE EXISTS ( SELECT i FROM InstanceAttributeTemplate i, ListOfValuesAttributeTemplate il WHERE i member of p.instanceAttributeTemplates AND i = il AND il.lov.name = :lovName AND il.lov.workspaceId = :workspace_id)") +}) +@Entity +public class PartIteration implements Serializable, FileHolder, Comparable<PartIteration>, Cloneable { + + @Id + @ManyToOne(optional=false, fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="PARTMASTER_PARTNUMBER", referencedColumnName="PARTMASTER_PARTNUMBER"), + @JoinColumn(name="PARTREVISION_VERSION", referencedColumnName="VERSION"), + @JoinColumn(name="WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private PartRevision partRevision; + + @Id + private int iteration; + + @OneToMany(orphanRemoval=true, cascade = {CascadeType.REMOVE, CascadeType.REFRESH}, fetch = FetchType.EAGER) + @JoinTable(name="PARTITERATION_GEOMETRY", inverseJoinColumns = { + @JoinColumn(name = "GEOMETRY_FULLNAME", referencedColumnName = "FULLNAME") + }, + joinColumns = { + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "PARTMASTER_PARTNUMBER", referencedColumnName = "PARTMASTER_PARTNUMBER"), + @JoinColumn(name = "PARTREVISION_VERSION", referencedColumnName = "PARTREVISION_VERSION"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }) + private Set<Geometry> geometries = new HashSet<>(); + + @OneToOne(orphanRemoval=true, cascade=CascadeType.ALL, fetch=FetchType.EAGER) + private BinaryResource nativeCADFile; + + @OneToMany(orphanRemoval = true, cascade=CascadeType.ALL, fetch=FetchType.EAGER) + @JoinTable(name="PARTITERATION_DOCUMENTLINK", + inverseJoinColumns={ + @JoinColumn(name="DOCUMENTLINK_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name="PARTMASTER_PARTNUMBER", referencedColumnName="PARTMASTER_PARTNUMBER"), + @JoinColumn(name="PARTREVISION_VERSION", referencedColumnName="PARTREVISION_VERSION"), + @JoinColumn(name="ITERATION", referencedColumnName="ITERATION") + }) + private Set<DocumentLink> linkedDocuments=new HashSet<>(); + + + @OneToMany(orphanRemoval=true, cascade = {CascadeType.REMOVE, CascadeType.REFRESH}, fetch = FetchType.EAGER) + @JoinTable(name="PARTITERATION_BINRES", inverseJoinColumns = { + @JoinColumn(name = "ATTACHEDFILE_FULLNAME", referencedColumnName = "FULLNAME") + }, + joinColumns = { + @JoinColumn(name = "WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "PARTMASTER_PARTNUMBER", referencedColumnName = "PARTMASTER_PARTNUMBER"), + @JoinColumn(name = "PARTREVISION_VERSION", referencedColumnName = "PARTREVISION_VERSION"), + @JoinColumn(name = "ITERATION", referencedColumnName = "ITERATION") + }) + private Set<BinaryResource> attachedFiles = new HashSet<>(); + + private String iterationNote; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + @Temporal(TemporalType.TIMESTAMP) + private Date creationDate; + + @Temporal(TemporalType.TIMESTAMP) + private Date modificationDate; + + @Temporal(TemporalType.TIMESTAMP) + private Date checkInDate; + + @OneToMany(orphanRemoval=true, cascade=CascadeType.ALL, fetch=FetchType.EAGER) + @OrderColumn(name="ATTRIBUTE_ORDER") + @JoinTable(name="PARTITERATION_ATTRIBUTE", + inverseJoinColumns={ + @JoinColumn(name="INSTANCEATTRIBUTE_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name="PARTMASTER_PARTNUMBER", referencedColumnName="PARTMASTER_PARTNUMBER"), + @JoinColumn(name="PARTREVISION_VERSION", referencedColumnName="PARTREVISION_VERSION"), + @JoinColumn(name="ITERATION", referencedColumnName="ITERATION") + }) + private List<InstanceAttribute> instanceAttributes=new ArrayList<>(); + + @OneToMany(orphanRemoval=true, cascade=CascadeType.ALL, fetch=FetchType.EAGER) + @OrderColumn(name="ATTRIBUTE_ORDER") + @JoinTable(name="PARTITERATION_PATHDATA_ATTR", + inverseJoinColumns={ + @JoinColumn(name="INSTANCEATTRIBUTE_TEMPLATE_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name="PARTMASTER_PARTNUMBER", referencedColumnName="PARTMASTER_PARTNUMBER"), + @JoinColumn(name="PARTREVISION_VERSION", referencedColumnName="PARTREVISION_VERSION"), + @JoinColumn(name="ITERATION", referencedColumnName="ITERATION") + }) + private List<InstanceAttributeTemplate> instanceAttributeTemplates=new ArrayList<>(); + + @OrderColumn(name="COMPONENT_ORDER") + @ManyToMany(fetch=FetchType.LAZY) + @JoinTable(name="PARTITERATION_PARTUSAGELINK", + inverseJoinColumns={ + @JoinColumn(name="COMPONENT_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name="PARTMASTER_PARTNUMBER", referencedColumnName="PARTMASTER_PARTNUMBER"), + @JoinColumn(name="PARTREVISION_VERSION", referencedColumnName="PARTREVISION_VERSION"), + @JoinColumn(name="ITERATION", referencedColumnName="ITERATION") + }) + private List<PartUsageLink> components=new LinkedList<>(); + + /* + private Type type; + public enum Type {COMPONENT, INSEPARABLE_ASSEMBLY, SEPARABLE_ASSEMBLY} + */ + + private Source source; + public enum Source { + MAKE, BUY + } + + public PartIteration() { + } + + public PartIteration(PartRevision pPartRevision, User pAuthor) { + PartIteration lastPart = pPartRevision.getLastIteration(); + int newIteration = 1; + + if (lastPart != null) { + newIteration = lastPart.getIteration() + 1; + Date lastModificationDate = lastPart.modificationDate; + setModificationDate(lastModificationDate); + } + + setPartRevision(pPartRevision); + iteration = newIteration; + author = pAuthor; + checkInDate = null; + } + + public PartIteration(PartRevision pPartRevision, int pIteration, User pAuthor) { + this(pPartRevision, pAuthor); + iteration = pIteration; + } + + public String getWorkspaceId() { + return partRevision==null?"":partRevision.getWorkspaceId(); + } + + public Set<Geometry> getGeometries() { + return geometries; + } + public List<Geometry> getSortedGeometries() { + List<Geometry> geometriesList = new ArrayList<>(geometries); + Collections.sort(geometriesList); + return geometriesList; + } + + public void addGeometry(Geometry pGeometry){ + geometries.add(pGeometry); + } + public boolean removeGeometry(Geometry pGeometry){ + return geometries.remove(pGeometry); + } + + public String getNumber() { + return getPartNumber(); + } + public String getPartNumber() { + return partRevision==null?"":partRevision.getPartNumber(); + } + + public String getPartName() { + return partRevision==null?"":partRevision.getPartName(); + } + + public String getVersion() { + return getPartVersion(); + } + public String getPartVersion() { + return partRevision==null?"":partRevision.getVersion(); + } + + public User getAuthor() { + return author; + } + public void setAuthor(User author) { + this.author = author; + } + + public String getIterationNote() { + return iterationNote; + } + public void setIterationNote(String iterationNote) { + this.iterationNote = iterationNote; + } + + public BinaryResource getNativeCADFile() { + return nativeCADFile; + } + public void setNativeCADFile(BinaryResource nativeCADFile) { + this.nativeCADFile = nativeCADFile; + } + + @Override + public Set<BinaryResource> getAttachedFiles() { + return attachedFiles; + } + public void setAttachedFiles(Set<BinaryResource> attachedFiles) { + this.attachedFiles = attachedFiles; + } + + public void addAttachedFile(BinaryResource pBinaryResource){ + attachedFiles.add(pBinaryResource); + } + public boolean removeAttachedFile(BinaryResource pBinaryResource){ + return attachedFiles.remove(pBinaryResource); + } + + + public Set<DocumentLink> getLinkedDocuments() { + return linkedDocuments; + } + + public void setLinkedDocuments(Set<DocumentLink> pLinkedDocuments) { + linkedDocuments=pLinkedDocuments; + } + + public Date getCreationDate() { + return (creationDate!=null) ? (Date) creationDate.clone() : null; + } + public void setCreationDate(Date creationDate) { + this.creationDate = (creationDate!=null) ? (Date) creationDate.clone() : null; + } + + public Date getModificationDate() { + return (modificationDate!=null) ? (Date) modificationDate.clone() : null; + } + public void setModificationDate(Date modificationDate) { + this.modificationDate = (modificationDate!=null) ? (Date) modificationDate.clone() : null; + } + + public Date getCheckInDate() { + return (checkInDate!=null) ? (Date) checkInDate.clone() : null; + } + + public void setCheckInDate(Date checkInDate) { + this.checkInDate = (checkInDate!=null) ? (Date) checkInDate.clone() : null; + } + + public List<InstanceAttribute> getInstanceAttributes() { + return instanceAttributes; + } + + public void setInstanceAttributes(List<InstanceAttribute> instanceAttributes) { + this.instanceAttributes = instanceAttributes; + } + + public int getIteration() { + return iteration; + } + public void setIteration(int iteration) { + this.iteration = iteration; + } + + @XmlTransient + public PartRevision getPartRevision() { + return partRevision; + } + public void setPartRevision(PartRevision partRevision) { + this.partRevision = partRevision; + } + + public List<PartUsageLink> getComponents() { + return components; + } + + public void setComponents(List<PartUsageLink> components) { + this.components = components; + } + + public PartRevisionKey getPartRevisionKey() { + return partRevision==null?new PartRevisionKey(new PartMasterKey("",""),""):partRevision.getKey(); + } + public PartIterationKey getKey() { + return new PartIterationKey(getPartRevisionKey(),iteration); + } + + public Source getSource() { + return source; + } + + public void setSource(Source source) { + this.source = source; + } + + public boolean isAssembly(){ + return components != null && !components.isEmpty(); + } + + public boolean isLastIteration(){ + return equals(partRevision.getLastIteration()); + } + + public String getName() { + return partRevision==null ? "" : this.partRevision.getPartName(); + } + + public List<InstanceAttributeTemplate> getInstanceAttributeTemplates() { + return instanceAttributeTemplates; + } + + public void setInstanceAttributeTemplates(List<InstanceAttributeTemplate> instanceAttributeTemplates) { + this.instanceAttributeTemplates = instanceAttributeTemplates; + } + + @Override + public String toString() { + return partRevision + "-" + iteration; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + getWorkspaceId().hashCode(); + hash = 31 * hash + getPartNumber().hashCode(); + hash = 31 * hash + getPartVersion().hashCode(); + hash = 31 * hash + iteration; + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof PartIteration)){ + return false; + } + PartIteration partI = (PartIteration) pObj; + return partI.getPartNumber().equals(getPartNumber()) && + partI.getWorkspaceId().equals(getWorkspaceId()) && + partI.getPartVersion().equals(getPartVersion()) && + partI.iteration==iteration; + } + + @Override + public int compareTo(PartIteration pPart) { + + int wksComp = getWorkspaceId().compareTo(pPart.getWorkspaceId()); + if (wksComp != 0) { + return wksComp; + } + int mpartNumberComp = getPartNumber().compareTo(pPart.getPartNumber()); + if (mpartNumberComp != 0) { + return mpartNumberComp; + } + int mpartVersionComp = getPartVersion().compareTo(pPart.getPartVersion()); + if (mpartVersionComp != 0) { + return mpartVersionComp; + }else { + return iteration - pPart.iteration; + } + } + + /** + * perform a deep clone operation + */ + @Override + public PartIteration clone() { + PartIteration clone; + try { + clone = (PartIteration) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + //perform a deep copy + clone.attachedFiles = new HashSet<>(attachedFiles); + + Set<DocumentLink> clonedLinks = new HashSet<>(); + for (DocumentLink link : linkedDocuments) { + DocumentLink clonedLink = link.clone(); + clonedLinks.add(clonedLink); + } + clone.linkedDocuments = clonedLinks; + + //perform a deep copy + List<InstanceAttribute> clonedInstanceAttributes = new ArrayList<>(); + for (InstanceAttribute attribute : instanceAttributes) { + InstanceAttribute clonedAttribute = attribute.clone(); + clonedInstanceAttributes.add(clonedAttribute); + } + clone.instanceAttributes = clonedInstanceAttributes; + + //perform a deep copy + List<InstanceAttributeTemplate> clonedInstanceAttributeTemplates = new ArrayList<>(); + for (InstanceAttributeTemplate attribute : instanceAttributeTemplates) { + InstanceAttributeTemplate clonedAttribute = attribute.clone(); + clonedInstanceAttributeTemplates.add(clonedAttribute); + } + clone.instanceAttributeTemplates = clonedInstanceAttributeTemplates; + + if (creationDate != null) { + clone.creationDate = (Date) creationDate.clone(); + } + if (modificationDate != null) { + clone.modificationDate = (Date) modificationDate.clone(); + } + if (checkInDate != null) { + clone.checkInDate = (Date) checkInDate.clone(); + } + return clone; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/PartIterationKey.java b/docdoku-common/src/main/java/com/docdoku/core/product/PartIterationKey.java new file mode 100644 index 0000000000..862084d2aa --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/PartIterationKey.java @@ -0,0 +1,97 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import java.io.Serializable; + +/** + * Identity class of {@link PartIteration} objects. + * + * @author Florent Garin + */ +public class PartIterationKey implements Serializable { + + private PartRevisionKey partRevision; + private int iteration; + + public PartIterationKey() { + } + + public PartIterationKey(String pWorkspaceId, String pNumber, String pVersion, int pIteration) { + partRevision= new PartRevisionKey(pWorkspaceId, pNumber, pVersion); + iteration=pIteration; + } + + public PartIterationKey(PartRevisionKey pPartRevisionKey, int pIteration) { + partRevision=pPartRevisionKey; + iteration=pIteration; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + partRevision.hashCode(); + hash = 31 * hash + iteration; + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof PartIterationKey)) { + return false; + } + PartIterationKey key = (PartIterationKey) pObj; + return key.partRevision.equals(partRevision) && key.iteration==iteration; + } + + @Override + public String toString() { + return partRevision + "-" + iteration; + } + + public PartRevisionKey getPartRevision() { + return partRevision; + } + public void setPartRevision(PartRevisionKey partRevision) { + this.partRevision = partRevision; + } + + public int getIteration(){ + return iteration; + } + public void setIteration(int pIteration){ + iteration=pIteration; + } + + public String getWorkspaceId() { + return partRevision.getPartMaster().getWorkspace(); + } + public String getPartMasterNumber() { + return partRevision.getPartMaster().getNumber(); + } + + public String getPartRevisionVersion() { + return partRevision.getVersion(); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/PartLink.java b/docdoku-common/src/main/java/com/docdoku/core/product/PartLink.java new file mode 100644 index 0000000000..810b66a7ae --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/PartLink.java @@ -0,0 +1,39 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.product; + +import java.util.List; + +/** + * @author Morgan Guimard + */ +public interface PartLink { + int getId(); + Character getCode(); + String getFullId(); + double getAmount(); + String getUnit(); + String getComment(); + boolean isOptional(); + PartMaster getComponent(); + List<PartSubstituteLink> getSubstitutes(); + String getReferenceDescription(); + List<CADInstance> getCadInstances(); +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/PartLinkList.java b/docdoku-common/src/main/java/com/docdoku/core/product/PartLinkList.java new file mode 100644 index 0000000000..90d201fa7a --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/PartLinkList.java @@ -0,0 +1,56 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.product; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Elisabel Généreux + */ +public class PartLinkList { + + private List<PartLink> path = new ArrayList<>(); + + public PartLinkList(List<PartLink> path) { + this.path = path; + } + + public List<PartLink> getPath() { + return path; + } + + public void setPath(List<PartLink> path) { + this.path = path; + } + + public int size() { + return this.path.size(); + } + + public boolean isEmpty() { + return this.path.isEmpty(); + } + + public PartLink[] toArray() { + return path.toArray(new PartLink[this.size()]); + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/PartMaster.java b/docdoku-common/src/main/java/com/docdoku/core/product/PartMaster.java new file mode 100644 index 0000000000..559ef5f051 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/PartMaster.java @@ -0,0 +1,284 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.product; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Version; +import com.docdoku.core.common.Workspace; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +/** + * This class holds the unchanging aspects of a part. + * It contains a collection of part revisions which themselves reference + * a collection of part iterations which wrap the subsequent changes + * operated on the part. + * + * @author Florent Garin + * @version 1.1, 18/05/11 + * @since V1.1 + */ +@Table(name = "PARTMASTER") +@IdClass(PartMasterKey.class) +@Entity +@NamedQueries({ + @NamedQuery(name = "PartMaster.findByNameOrNumber", query = "SELECT pm FROM PartMaster pm WHERE (pm.name LIKE :partName OR pm.number LIKE :partNumber) AND pm.workspace.id = :workspaceId"), + @NamedQuery(name = "PartMaster.findByWorkspace", query = "SELECT pm FROM PartMaster pm WHERE pm.workspace.id = :workspaceId ORDER BY pm.creationDate DESC") +}) +public class PartMaster implements Serializable { + + @Column(name = "PARTNUMBER", length = 100) + @Id + private String number = ""; + + @Id + @ManyToOne(optional = false, fetch = FetchType.EAGER) + private Workspace workspace; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + + @OrderColumn(name = "ALTERNATE_ORDER") + @CollectionTable(name = "PARTMASTER_ALTERNATE", joinColumns = { + @JoinColumn(name = "PARTMASTER_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "PARTMASTER_PARTNUMBER", referencedColumnName = "PARTNUMBER") + }) + @ElementCollection(fetch = FetchType.LAZY) + private List<PartAlternateLink> alternates = new LinkedList<>(); + + @Temporal(TemporalType.TIMESTAMP) + private java.util.Date creationDate; + + private String name; + + private String type; + + + @OneToMany(mappedBy = "partMaster", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @OrderBy("version ASC") + private List<PartRevision> partRevisions = new ArrayList<>(); + + private boolean standardPart; + + private boolean attributesLocked; + + public PartMaster() { + } + + public PartMaster(Workspace pWorkspace, + String pNumber, + User pAuthor) { + this(pWorkspace, pNumber); + author = pAuthor; + } + + public PartMaster(Workspace pWorkspace, String pNumber) { + number = pNumber; + setWorkspace(pWorkspace); + } + + public User getAuthor() { + return author; + } + + public void setAuthor(User author) { + this.author = author; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public List<PartAlternateLink> getAlternates() { + return alternates; + } + + public void setAlternates(List<PartAlternateLink> alternates) { + this.alternates = alternates; + } + + public List<PartRevision> getPartRevisions() { + return partRevisions; + } + + public void setPartRevisions(List<PartRevision> partRevisions) { + this.partRevisions = partRevisions; + } + + public boolean isStandardPart() { + return standardPart; + } + + public void setStandardPart(boolean standardPart) { + this.standardPart = standardPart; + } + + + public PartRevision getLastRevision() { + int index = partRevisions.size() - 1; + if (index < 0) { + return null; + } else { + return partRevisions.get(index); + } + } + + public PartRevision removeLastRevision() { + int index = partRevisions.size() - 1; + if (index < 0) { + return null; + } else { + return partRevisions.remove(index); + } + } + + public List<PartRevision> getAllReleasedRevisions() { + List<PartRevision> releasedRevisions = new ArrayList<>(); + for (int index = partRevisions.size() - 1; index >= 0; index--) { + PartRevision partRevision = partRevisions.get(index); + if (partRevision.isReleased()) { + releasedRevisions.add(partRevision); + } + } + return releasedRevisions; + } + + public PartRevision getLastReleasedRevision() { + for (int index = partRevisions.size() - 1; index >= 0; index--) { + PartRevision partRevision = partRevisions.get(index); + if (partRevision.isReleased()) { + return partRevision; + } + } + return null; + } + + public void removeRevision(PartRevision partR) { + this.partRevisions.remove(partR); + } + + public PartRevision createNextRevision(User pUser) { + PartRevision lastRev = getLastRevision(); + Version version; + if (lastRev == null) { + version = new Version("A"); + } else { + version = new Version(lastRev.getVersion()); + version.increase(); + } + + PartRevision rev = new PartRevision(this, version, pUser); + partRevisions.add(rev); + return rev; + } + + public Workspace getWorkspace() { + return workspace; + } + + public void setWorkspace(Workspace workspace) { + this.workspace = workspace; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + public PartMasterKey getKey() { + return new PartMasterKey(getWorkspaceId(), number); + } + + public String getWorkspaceId() { + return workspace == null ? "" : workspace.getId(); + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public boolean isAttributesLocked() { + return attributesLocked; + } + + public void setAttributesLocked(boolean attributesLocked) { + this.attributesLocked = attributesLocked; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof PartMaster)) { + return false; + } + + PartMaster partM = (PartMaster) pObj; + return partM.number.equals(number) && + partM.getWorkspaceId().equals(getWorkspaceId()); + + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + getWorkspaceId().hashCode(); + hash = 31 * hash + number.hashCode(); + return hash; + } + + + @Override + public String toString() { + return number; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/PartMasterKey.java b/docdoku-common/src/main/java/com/docdoku/core/product/PartMasterKey.java new file mode 100644 index 0000000000..8f174858b1 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/PartMasterKey.java @@ -0,0 +1,106 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import java.io.Serializable; + +/** + * Identity class of {@link PartMaster} objects. + * + * @author Florent Garin + */ +public class PartMasterKey implements Serializable, Comparable<PartMasterKey>, Cloneable { + + private String workspace; + private String number; + + + public PartMasterKey() { + } + + public PartMasterKey(String pWorkspaceId, String pNumber) { + workspace=pWorkspaceId; + number=pNumber; + } + + public String getWorkspace() { + return workspace; + } + + public void setWorkspace(String workspace) { + this.workspace = workspace; + } + + + public String getNumber() { + return number; + } + + public void setNumber(String pNumber) { + number = pNumber; + } + + + @Override + public String toString() { + return workspace + "-" + number; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof PartMasterKey)) { + return false; + } + PartMasterKey key = (PartMasterKey) pObj; + return key.number.equals(number) && key.workspace.equals(workspace); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspace.hashCode(); + hash = 31 * hash + number.hashCode(); + return hash; + } + + public int compareTo(PartMasterKey pKey) { + int wksComp = workspace.compareTo(pKey.workspace); + if (wksComp != 0) { + return wksComp; + }else { + return number.compareTo(pKey.number); + } + } + + @Override + public PartMasterKey clone() { + PartMasterKey clone; + try { + clone = (PartMasterKey) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + return clone; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/PartMasterTemplate.java b/docdoku-common/src/main/java/com/docdoku/core/product/PartMasterTemplate.java new file mode 100644 index 0000000000..8aab057d10 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/PartMasterTemplate.java @@ -0,0 +1,311 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.meta.InstanceAttributeTemplate; +import com.docdoku.core.security.ACL; +import com.docdoku.core.workflow.WorkflowModel; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * A model object from which we can create a {@link PartMaster}. + * Creating a part through a model offers the ability to enforce a input + * mask for the part ID, as well as some insuring that the starting + * iteration defines some custom attributes or has some specific binary files. + * + * @author Florent Garin + * @version 1.1, 23/01/12 + * @since V1.0 + */ +@Table(name="PARTMASTERTEMPLATE") +@IdClass(PartMasterTemplateKey.class) +@Entity +@NamedQueries({ + @NamedQuery(name="PartMasterTemplate.findWhereLOV", query="SELECT p FROM PartMasterTemplate p WHERE EXISTS ( SELECT i FROM InstanceAttributeTemplate i, ListOfValuesAttributeTemplate il WHERE i member of p.attributeTemplates AND i = il AND il.lov.name = :lovName AND il.lov.workspaceId = :workspace_id)"), + @NamedQuery(name="PartMasterTemplate.findWhereWorkflowModel", query="SELECT p FROM PartMasterTemplate p WHERE :workflowModel = p.workflowModel") +}) +public class PartMasterTemplate implements Serializable, Comparable<PartMasterTemplate> { + + @Column(length=100) + @Id + private String id=""; + + @Column(name = "WORKSPACE_ID", length=100, nullable = false, insertable = false, updatable = false) + @Id + private String workspaceId=""; + + private boolean idGenerated; + + private String partType; + + private String mask; + + @OneToOne(orphanRemoval = true, cascade=CascadeType.ALL, fetch=FetchType.EAGER) + private ACL acl; + + @OneToOne(orphanRemoval=true, cascade = {CascadeType.REMOVE, CascadeType.REFRESH}, fetch = FetchType.EAGER) + private BinaryResource attachedFile; + + @OrderColumn(name="ATTR_ORDER") + @OneToMany(cascade={CascadeType.ALL}, fetch=FetchType.EAGER, orphanRemoval = true) + @JoinTable(name="PARTMASTERTEMPLATE_ATTR", + inverseJoinColumns={ + @JoinColumn(name="INSTANCEATTRIBUTETEMPLATE_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name="PARTMASTERTEMPLATE_ID", referencedColumnName="ID") + } + ) + private List<InstanceAttributeTemplate> attributeTemplates=new ArrayList<>(); + + private boolean attributesLocked; + + @ManyToOne(fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="AUTHOR_LOGIN", referencedColumnName="LOGIN"), + @JoinColumn(name="AUTHOR_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private User author; + + @Temporal(TemporalType.TIMESTAMP) + private Date creationDate; + + @Temporal(TemporalType.TIMESTAMP) + private Date modificationDate; + + @ManyToOne(optional=false, fetch=FetchType.EAGER) + private Workspace workspace; + + + @ManyToOne(fetch=FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name="WORKFLOWMODEL_ID", referencedColumnName="ID"), + @JoinColumn(name="WORKFLOWMODEL_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private WorkflowModel workflowModel; + + + @Column(name = "WORKFLOWMODEL_ID", length=100, insertable = false, updatable = false) + private String workflowModelId; + + @OrderColumn(name="ATTR_ORDER") + @OneToMany(cascade={CascadeType.ALL}, fetch=FetchType.EAGER, orphanRemoval = true) + @JoinTable(name="PARTMASTERTPL_INSTANCE_ATTR", + inverseJoinColumns={ + @JoinColumn(name="INSTANCEATTRIBUTETEMPLATE_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name="PARTMASTERTEMPLATE_ID", referencedColumnName="ID") + } + ) + private List<InstanceAttributeTemplate> attributeInstanceTemplates = new ArrayList<>(); + + + public PartMasterTemplate() { + } + + public PartMasterTemplate(Workspace pWorkspace, String pId, User pAuthor, String pPartType, String pMask, boolean pAttributesLocked) { + id=pId; + setWorkspace(pWorkspace); + author = pAuthor; + mask = pMask; + partType=pPartType; + attributesLocked=pAttributesLocked; + } + + public String getPartType() { + return partType; + } + + public void setPartType(String partType) { + this.partType = partType; + } + + public String getWorkflowModelId() { + return workflowModelId; + } + + public void setWorkflowModelId(String workflowModelId) { + this.workflowModelId = workflowModelId; + } + + public WorkflowModel getWorkflowModel() { + return workflowModel; + } + + public void setWorkflowModel(WorkflowModel workflowModel) { + this.workflowModel = workflowModel; + if (workflowModel == null) { + setWorkflowModelId(null); + } else { + setWorkflowModelId(workflowModel.getId()); + } + } + + public String getMask(){ + return mask; + } + + public void setMask(String pMask){ + mask=pMask; + } + + public void setId(String id) { + this.id = id; + } + + public boolean isIdGenerated() { + return idGenerated; + } + + public void setIdGenerated(boolean idGenerated) { + this.idGenerated = idGenerated; + } + + public BinaryResource getAttachedFile() { + return attachedFile; + } + + public void setAttachedFile(BinaryResource attachedFile) { + this.attachedFile = attachedFile; + } + + public ACL getAcl() { + return acl; + } + + public void setAcl(ACL acl) { + this.acl = acl; + } + + public List<InstanceAttributeTemplate> getAttributeTemplates() { + return attributeTemplates; + } + + public void setAttributeTemplates(List<InstanceAttributeTemplate> pAttributeTemplates) { + attributeTemplates=pAttributeTemplates; + } + + public boolean isAttributesLocked() { + return attributesLocked; + } + + public void setAttributesLocked(boolean attributesLocked) { + this.attributesLocked = attributesLocked; + } + + public void setAuthor(User pAuthor) { + author = pAuthor; + } + + public User getAuthor() { + return author; + } + + public void setCreationDate(Date pCreationDate) { + creationDate = pCreationDate; + } + + public Date getCreationDate() { + return creationDate; + } + + public Date getModificationDate() { + return modificationDate; + } + + public void setModificationDate(Date modificationDate) { + this.modificationDate = modificationDate; + } + + public void setWorkspace(Workspace pWorkspace){ + workspace=pWorkspace; + workspaceId=workspace.getId(); + } + + public Workspace getWorkspace(){ + return workspace; + } + + public String getId(){ + return id; + } + + public String getWorkspaceId(){ + return workspaceId; + } + + public PartMasterTemplateKey getKey() { + return new PartMasterTemplateKey(workspaceId, id); + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof PartMasterTemplate)) { + return false; + } + PartMasterTemplate template = (PartMasterTemplate) pObj; + return template.id.equals(id) && template.workspaceId.equals(workspaceId); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + id.hashCode(); + return hash; + } + + @Override + public String toString() { + return id; + } + + public int compareTo(PartMasterTemplate pTemplate) { + int wksComp = workspaceId.compareTo(pTemplate.workspaceId); + if (wksComp != 0) { + return wksComp; + } else { + return id.compareTo(pTemplate.id); + } + } + + public void setAttributeInstanceTemplates(List<InstanceAttributeTemplate> attributeInstanceTemplates) { + this.attributeInstanceTemplates = attributeInstanceTemplates; + } + + public List<InstanceAttributeTemplate> getAttributeInstanceTemplates() { + return attributeInstanceTemplates; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/PartMasterTemplateKey.java b/docdoku-common/src/main/java/com/docdoku/core/product/PartMasterTemplateKey.java new file mode 100644 index 0000000000..65de3219b4 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/PartMasterTemplateKey.java @@ -0,0 +1,83 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import java.io.Serializable; + +/** + * + * @author Florent Garin + */ +public class PartMasterTemplateKey implements Serializable { + + private String workspaceId; + private String id; + + public PartMasterTemplateKey() { + } + + public PartMasterTemplateKey(String pWorkspaceId, String pId) { + workspaceId=pWorkspaceId; + id=pId; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + id.hashCode(); + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof PartMasterTemplateKey)) { + return false; + } + PartMasterTemplateKey key = (PartMasterTemplateKey) pObj; + return key.id.equals(id) && key.workspaceId.equals(workspaceId); + } + + @Override + public String toString() { + return workspaceId + "-" + id; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String pWorkspaceId) { + workspaceId = pWorkspaceId; + } + + public String getId() { + return id; + } + + public void setId(String pId) { + id = pId; + } + +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/PartRevision.java b/docdoku-common/src/main/java/com/docdoku/core/product/PartRevision.java new file mode 100644 index 0000000000..5896a70da0 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/PartRevision.java @@ -0,0 +1,572 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.product; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Version; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.security.ACL; +import com.docdoku.core.workflow.Workflow; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.*; + +/** + * This class stands between {@link PartMaster} and {@link PartIteration}. + * Its main purpose is to hold effectivities. It represents a formal revision of a part. + * + * @author Florent Garin + * @version 1.1, 31/10/11 + * @since V1.1 + */ +@Table(name="PARTREVISION") +@IdClass(PartRevisionKey.class) +@Entity +@NamedQueries({ + @NamedQuery(name="PartRevision.findByWorkspace", query="SELECT pr FROM PartRevision pr WHERE pr.partMaster.workspace.id = :workspaceId ORDER BY pr.partMaster.number ASC"), + @NamedQuery(name="PartRevision.findByWorkspace.filterUserACLEntry", query="SELECT pr FROM PartRevision pr WHERE pr.partMaster.workspace.id = :workspaceId and (pr.acl is null or exists(SELECT au from ACLUserEntry au WHERE au.principal = :user AND au.permission not like com.docdoku.core.security.ACL.Permission.FORBIDDEN AND au.acl = pr.acl)) ORDER BY pr.partMaster.number ASC"), + @NamedQuery(name="PartRevision.countByWorkspace.filterUserACLEntry", query="SELECT count(pr) FROM PartRevision pr WHERE pr.partMaster.workspace.id = :workspaceId and (pr.acl is null or exists(SELECT au from ACLUserEntry au WHERE au.principal = :user AND au.permission not like com.docdoku.core.security.ACL.Permission.FORBIDDEN AND au.acl = pr.acl))"), + @NamedQuery(name="PartRevision.countByWorkspace", query="SELECT count(pr) FROM PartRevision pr WHERE pr.partMasterWorkspaceId = :workspaceId"), + @NamedQuery(name="PartRevision.findByReferenceOrName", query="SELECT pr FROM PartRevision pr WHERE (pr.partMaster.number LIKE :partNumber OR pr.partMaster.name LIKE :partName) AND pr.partMaster.workspace.id = :workspaceId"), + @NamedQuery(name="PartRevision.findByWorkflow", query="SELECT p FROM PartRevision p WHERE p.workflow = :workflow") +}) +public class PartRevision implements Serializable, Comparable<PartRevision> { + + + @Id + @ManyToOne(optional=false, fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="PARTMASTER_PARTNUMBER", referencedColumnName="PARTNUMBER"), + @JoinColumn(name="WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private PartMaster partMaster; + + @Column(length=10) + @Id + private String version=""; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + @Temporal(TemporalType.TIMESTAMP) + private java.util.Date creationDate; + + @Lob + private String description; + + @OneToMany(orphanRemoval=true, cascade= CascadeType.ALL, fetch= FetchType.EAGER) + @JoinTable(name="PARTREVISION_EFFECTIVITY", + inverseJoinColumns={ + @JoinColumn(name="EFFECTIVITY_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="PARTMASTER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + @JoinColumn(name="PARTMASTER_PARTNUMBER", referencedColumnName="PARTMASTER_PARTNUMBER"), + @JoinColumn(name="PARTREVISION_VERSION", referencedColumnName="VERSION") + }) + private Set<Effectivity> effectivities = new HashSet<>(); + + + @OneToMany(mappedBy = "partRevision", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @OrderBy("iteration ASC") + private List<PartIteration> partIterations = new ArrayList<>(); + + + @ManyToOne(fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="CHECKOUTUSER_LOGIN", referencedColumnName="LOGIN"), + @JoinColumn(name="CHECKOUTUSER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private User checkOutUser; + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date checkOutDate; + + @OneToOne(orphanRemoval=true, cascade=CascadeType.ALL, fetch=FetchType.EAGER) + private Workflow workflow; + + @OrderBy("abortedDate") + @OneToMany(orphanRemoval=true, cascade= CascadeType.ALL, fetch= FetchType.EAGER) + @JoinTable(name="PART_ABORTED_WORKFLOW", + inverseJoinColumns={ + @JoinColumn(name="WORKFLOW_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="PARTMASTER_PARTNUMBER", referencedColumnName="PARTMASTER_PARTNUMBER"), + @JoinColumn(name="PARTREVISION_VERSION", referencedColumnName="VERSION"), + @JoinColumn(name="PARTMASTER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private List<Workflow> abortedWorkflows=new ArrayList<>(); + + @Column(name = "PARTMASTER_PARTNUMBER", nullable = false, insertable = false, updatable = false) + private String partMasterNumber=""; + + @Column(name = "WORKSPACE_ID", nullable = false, insertable = false, updatable = false) + private String partMasterWorkspaceId=""; + + @OneToOne(orphanRemoval = true, cascade=CascadeType.ALL, fetch=FetchType.EAGER) + private ACL acl; + + @ManyToMany(fetch=FetchType.EAGER) + @JoinTable(name="PARTREVISION_TAG", + inverseJoinColumns={ + @JoinColumn(name="TAG_LABEL", referencedColumnName="LABEL"), + @JoinColumn(name="TAG_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }, + joinColumns={ + @JoinColumn(name="PARTMASTER_PARTNUMBER", referencedColumnName="PARTMASTER_PARTNUMBER"), + @JoinColumn(name="PARTREVISION_VERSION", referencedColumnName="VERSION"), + @JoinColumn(name="PARTMASTER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private Set<Tag> tags=new HashSet<>(); + + private boolean publicShared; + + private RevisionStatus status=RevisionStatus.WIP; + + + + @Embedded + @AttributeOverrides({ + @AttributeOverride( + name="statusModificationDate", + column=@Column(name="RELEASE_DATE")) + }) + @AssociationOverrides({ + @AssociationOverride( + name="statusChangeAuthor", + joinColumns={ + @JoinColumn(name="RELEASE_USER_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name="RELEASE_USER_WORKSPACE", referencedColumnName = "WORKSPACE_ID") + }) + }) + private StatusChange releaseStatusChange; + + @Embedded + @AttributeOverrides({ + @AttributeOverride( + name="statusModificationDate", + column=@Column(name="OBSOLETE_DATE")) + }) + @AssociationOverrides({ + @AssociationOverride( + name="statusChangeAuthor", + joinColumns={ + @JoinColumn(name="OBSOLETE_USER_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name="OBSOLETE_USER_WORKSPACE", referencedColumnName = "WORKSPACE_ID") + }) + }) + private StatusChange obsoleteStatusChange; + + public enum RevisionStatus { + WIP, RELEASED, OBSOLETE + } + + public PartRevision(){ + } + + public PartRevision(PartMaster pPartMaster, + String pStringVersion, + User pAuthor) { + this(pPartMaster); + version=pStringVersion; + author = pAuthor; + } + + public PartRevision(PartMaster pPartMaster, + Version pVersion, + User pAuthor) { + this(pPartMaster); + version=pVersion.toString(); + author = pAuthor; + } + + public PartRevision(PartMaster pPartMaster, User pAuthor) { + this(pPartMaster); + version = new Version().toString(); + author = pAuthor; + } + + private PartRevision(PartMaster pPartMaster) { + setPartMaster(pPartMaster); + } + + public void setPartMaster(PartMaster partMaster) { + this.partMaster = partMaster; + setPartMasterNumber(partMaster.getNumber()); + setPartMasterWorkspaceId(partMaster.getWorkspaceId()); + } + + @XmlTransient + public PartMaster getPartMaster() { + return partMaster; + } + + public PartRevisionKey getKey() { + return new PartRevisionKey(getPartMasterKey(),version); + } + + public boolean isCheckedOut() { + return checkOutUser != null; + } + + public User getAuthor() { + return author; + } + public void setAuthor(User author) { + this.author = author; + } + + public Date getCreationDate() { + return (creationDate!=null) ? (Date) creationDate.clone() : null; + } + public void setCreationDate(Date creationDate) { + this.creationDate = (creationDate!=null) ? (Date) creationDate.clone() : null; + } + + public Date getCheckOutDate() { + return (checkOutDate!=null) ? (Date) checkOutDate.clone() : null; + } + public void setCheckOutDate(Date checkOutDate) { + this.checkOutDate = (checkOutDate!=null) ? (Date) checkOutDate.clone() : null; + } + + public User getCheckOutUser() { + return checkOutUser; + } + public void setCheckOutUser(User checkOutUser) { + this.checkOutUser = checkOutUser; + } + + public String getVersion() { + return version; + } + public void setVersion(String version) { + this.version = version; + } + + public String getType() { + return partMaster.getType(); + } + + public Workflow getWorkflow() { + return workflow; + } + public void setWorkflow(Workflow pWorkflow) { + workflow = pWorkflow; + } + + public String getLifeCycleState() { + if (workflow != null) { + return workflow.getLifeCycleState(); + }else { + return null; + } + } + + public boolean hasWorkflow() { + return workflow != null; + } + + public ACL getACL() { + return acl; + } + public void setACL(ACL acl) { + this.acl = acl; + } + + public Set<Tag> getTags() { + return tags; + } + + public void setTags(Set<Tag> pTags) { + if (pTags != null){ + tags.retainAll(pTags); + pTags.removeAll(tags); + tags.addAll(pTags); + } + + } + + public boolean addTag(Tag pTag) { + return tags.add(pTag); + } + public boolean removeTag(Tag pTag){ + return tags.remove(pTag); + } + public List<Workflow> getAbortedWorkflows() { + return abortedWorkflows; + } + public void addAbortedWorkflows(Workflow abortedWorkflow) { + this.abortedWorkflows.add(abortedWorkflow); + } + + public PartIteration createNextIteration(User pUser){ + PartIteration part = new PartIteration(this, pUser); + partIterations.add(part); + return part; + } + + public List<PartIteration> getPartIterations() { + return partIterations; + } + public void setPartIterations(List<PartIteration> partIterations) { + this.partIterations = partIterations; + } + + public PartIteration getLastIteration() { + int index = partIterations.size()-1; + if(index < 0) { + return null; + } else { + return partIterations.get(index); + } + } + + public PartIteration getLastCheckedInIteration() { + int index; + if(isCheckedOut()){ + index = partIterations.size()-2; + }else{ + index = partIterations.size()-1; + } + if(index < 0) { + return null; + }else { + return partIterations.get(index); + } + } + + public int getLastIterationNumber() { + if(this.getLastIteration()!=null){ + return this.getLastIteration().getIteration(); + }else{ + return 0; + } + } + + public PartIteration removeLastIteration() { + int index = partIterations.size()-1; + if(index < 0) { + return null; + }else { + return partIterations.remove(index); + } + } + + public PartIteration getIteration(int pIteration) { + return partIterations.get(pIteration-1); + } + + public int getNumberOfIterations() { + return partIterations.size(); + } + + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + + public PartMasterKey getPartMasterKey() { + return partMaster==null?new PartMasterKey("",""):partMaster.getKey(); + } + + public String getWorkspaceId() { + return partMaster==null?"":partMaster.getWorkspaceId(); + } + + public String getPartNumber() { + return partMaster==null?"":partMaster.getNumber(); + } + + public String getPartName() { + return partMaster==null?"":partMaster.getName(); + } + + + public String getPartMasterNumber() { + return partMasterNumber; + } + + public void setPartMasterNumber(String pPartMasterNumber) { + partMasterNumber=pPartMasterNumber; + } + + public String getPartMasterWorkspaceId() { + return partMasterWorkspaceId; + } + + public void setPartMasterWorkspaceId(String pPartMasterWorkspaceId) { + partMasterWorkspaceId = pPartMasterWorkspaceId; + } + + public Set<Effectivity> getEffectivities() { + return effectivities; + } + public void setEffectivities(Set<Effectivity> effectivities) { + this.effectivities = effectivities; + } + + public boolean isPublicShared() { + return publicShared; + } + public void setPublicShared(boolean publicShared) { + this.publicShared = publicShared; + } + + public RevisionStatus getStatus() { + return status; + } + public void setStatus(RevisionStatus status) { + this.status = status; + } + + public boolean isReleased(){ + return status==RevisionStatus.RELEASED; + } + public boolean isObsolete(){ + return status==RevisionStatus.OBSOLETE; + } + public boolean release(User user){ + if(this.status==RevisionStatus.WIP){ + this.status=RevisionStatus.RELEASED; + StatusChange statusChange = new StatusChange(); + statusChange.setStatusChangeAuthor(user); + statusChange.setStatusModificationDate(new Date()); + this.setReleaseStatusChange(statusChange); + return true; + }else{ + return false; + } + + } + public boolean markAsObsolete(User user){ + if(this.status==RevisionStatus.RELEASED){ + this.status=RevisionStatus.OBSOLETE; + StatusChange statusChange = new StatusChange(); + statusChange.setStatusChangeAuthor(user); + statusChange.setStatusModificationDate(new Date()); + this.setObsoleteStatusChange(statusChange); + return true; + }else{ + return false; + } + + } + + public boolean isAttributesLocked(){ + if (this.partMaster != null){ + return this.partMaster.isAttributesLocked(); + } + return false; + } + + public StatusChange getObsoleteStatusChange() { + return obsoleteStatusChange; + } + + public void setObsoleteStatusChange(StatusChange statusChange) { + this.obsoleteStatusChange = statusChange; + } + + public StatusChange getReleaseStatusChange() { + return releaseStatusChange; + } + + public void setReleaseStatusChange(StatusChange statusChange) { + this.releaseStatusChange = statusChange; + } + + public User getObsoleteAuthor() { + return obsoleteStatusChange == null ? null : obsoleteStatusChange.getStatusChangeAuthor(); + } + + public Date getObsoleteDate() { + return obsoleteStatusChange == null ? null : obsoleteStatusChange.getStatusModificationDate(); + } + public User getReleaseAuthor() { + return releaseStatusChange == null ? null : releaseStatusChange.getStatusChangeAuthor(); + } + + public Date getReleaseDate() { + return releaseStatusChange == null ? null : releaseStatusChange.getStatusModificationDate(); + } + + @Override + public String toString() { + return getPartNumber() + "-" + version; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof PartRevision)) { + return false; + } + PartRevision partR = (PartRevision) pObj; + return partR.getPartNumber().equals(getPartNumber()) && partR.getWorkspaceId().equals(getWorkspaceId()) && partR.version.equals(version); + + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + getWorkspaceId().hashCode(); + hash = 31 * hash + getPartNumber().hashCode(); + hash = 31 * hash + version.hashCode(); + return hash; + } + + @Override + public int compareTo(PartRevision pPartR) { + int wksComp = getWorkspaceId().compareTo(pPartR.getWorkspaceId()); + if (wksComp != 0) { + return wksComp; + } + int numberComp = getPartNumber().compareTo(pPartR.getPartNumber()); + if (numberComp != 0) { + return numberComp; + } else { + return version.compareTo(pPartR.version); + } + } + + public PartIteration getLastAccessibleIteration(User user) { + if(isCheckoutByAnotherUser(user)) { + return partIterations.size() <= 1 ? null : partIterations.get(partIterations.size() -2); + } else { + return getLastIteration(); + } + } + + private boolean isCheckoutByAnotherUser(User user) { + return isCheckedOut() && !getCheckOutUser().equals(user); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/PartRevisionKey.java b/docdoku-common/src/main/java/com/docdoku/core/product/PartRevisionKey.java new file mode 100644 index 0000000000..1488148828 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/PartRevisionKey.java @@ -0,0 +1,117 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import java.io.Serializable; + +/** + * Identity class of {@link PartRevision} objects. + * + * @author Florent Garin + */ +public class PartRevisionKey implements Serializable, Comparable<PartRevisionKey>, Cloneable { + + private PartMasterKey partMaster; + private String version; + + + public PartRevisionKey() { + } + + public PartRevisionKey(String pWorkspaceId, String pNumber, String pVersion) { + partMaster = new PartMasterKey(pWorkspaceId, pNumber); + version = pVersion; + } + + public PartRevisionKey(PartMasterKey pPartMasterKey, String pVersion) { + partMaster = pPartMasterKey; + version = pVersion; + } + + public PartMasterKey getPartMaster() { + return partMaster; + } + + public void setPartMaster(PartMasterKey partMaster) { + this.partMaster = partMaster; + } + + public String getWorkspaceId() { + return partMaster.getWorkspace(); + } + + public String getPartMasterNumber() { + return partMaster.getNumber(); + } + + public String getVersion() { + return version; + } + + public void setVersion(String pVersion) { + version = pVersion; + } + + @Override + public String toString() { + return partMaster + "-" + version; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof PartRevisionKey)) { + return false; + } + PartRevisionKey key = (PartRevisionKey) pObj; + return key.partMaster.equals(partMaster) && key.version.equals(version); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + partMaster.hashCode(); + hash = 31 * hash + version.hashCode(); + return hash; + } + + public int compareTo(PartRevisionKey pKey) { + int wksMaster = partMaster.compareTo(pKey.partMaster); + if (wksMaster != 0) { + return wksMaster; + } else { + return version.compareTo(pKey.version); + } + } + + @Override + public PartRevisionKey clone() { + PartRevisionKey clone = null; + try { + clone = (PartRevisionKey) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + return clone; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/PartSubstituteLink.java b/docdoku-common/src/main/java/com/docdoku/core/product/PartSubstituteLink.java new file mode 100644 index 0000000000..accda0c205 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/PartSubstituteLink.java @@ -0,0 +1,184 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.product; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.LinkedList; +import java.util.List; + +/** + * This class is related to a {@link PartUsageLink} + * to indicate a replacement part that could be used instead. + * + * @author Florent Garin + * @version 1.1, 16/10/11 + * @since V1.1 + */ +@Table(name="PARTSUBSTITUTELINK") +@Entity +@NamedQueries({ + @NamedQuery(name="PartSubstituteLink.findBySubstitute",query="SELECT u FROM PartSubstituteLink u WHERE u.substitute.number LIKE :partNumber AND u.substitute.workspace.id = :workspaceId"), +}) +public class PartSubstituteLink implements Serializable, Cloneable, PartLink { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private int id; + private double amount; + private String unit; + + + private String referenceDescription; + + @Column(name="COMMENTDATA") + private String comment; + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "SUBSTITUTE_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "SUBSTITUTE_PARTNUMBER", referencedColumnName = "PARTNUMBER") + }) + private PartMaster substitute; + + @OrderColumn(name = "CADINSTANCE_ORDER") + @JoinTable(name = "PARTSUBSTITUTELINK_CADINSTANCE", + inverseJoinColumns = { + @JoinColumn(name = "CADINSTANCE_ID", referencedColumnName = "ID") + }, + joinColumns = { + @JoinColumn(name = "PARTSUBSTITUTELINK_ID", referencedColumnName = "ID") + }) + @OneToMany(orphanRemoval=true, cascade=CascadeType.ALL, fetch=FetchType.LAZY) + private List<CADInstance> cadInstances = new LinkedList<CADInstance>(); + + public PartSubstituteLink() { + } + + @Override + public int getId() { + return id; + } + + @Override + public double getAmount() { + return amount; + } + + @Override + public String getUnit() { + return unit; + } + + @Override + public String getComment() { + return comment; + } + + @Override + public boolean isOptional() { + // A substitute cannot be optional + return false; + } + + @Override + public PartMaster getComponent() { + return substitute; + } + + @Override + public List<PartSubstituteLink> getSubstitutes() { + // A substitute cannot have substitutes + return null; + } + + @Override + public String getReferenceDescription() { + return referenceDescription; + } + + @Override + public Character getCode() { + return 's'; + } + + @Override + public String getFullId() { + return getCode()+""+getId(); + } + + public PartMaster getSubstitute() { + return substitute; + } + + @Override + public List<CADInstance> getCadInstances() { + return cadInstances; + } + + public void setId(int id) { + this.id = id; + } + + public void setAmount(double amount) { + this.amount = amount; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + public void setReferenceDescription(String referenceDescription) { + this.referenceDescription = referenceDescription; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public void setSubstitute(PartMaster substitute) { + this.substitute = substitute; + } + + public void setCadInstances(List<CADInstance> cadInstances) { + this.cadInstances = cadInstances; + } + + @Override + public PartSubstituteLink clone() { + PartSubstituteLink clone = null; + try { + clone = (PartSubstituteLink) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + + //perform a deep copy + List<CADInstance> clonedCADInstances = new LinkedList<CADInstance>(); + for (CADInstance cadInstance : cadInstances) { + CADInstance clonedCADInstance = cadInstance.clone(); + clonedCADInstances.add(clonedCADInstance); + } + clone.cadInstances = clonedCADInstances; + + return clone; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/PartUsageLink.java b/docdoku-common/src/main/java/com/docdoku/core/product/PartUsageLink.java new file mode 100644 index 0000000000..10347aa39c --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/PartUsageLink.java @@ -0,0 +1,215 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.product; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.LinkedList; +import java.util.List; + +/** + * A link between an assembly represented as {@link PartIteration} + * and a part represented as {@link PartMaster}. + * + * @author Florent Garin + * @version 1.1, 15/10/11 + * @since V1.1 + */ +@Table(name = "PARTUSAGELINK") +@Entity +@NamedQueries({ + @NamedQuery(name="PartUsageLink.findByComponent",query="SELECT u FROM PartUsageLink u WHERE u.component.number LIKE :partNumber AND u.component.workspace.id = :workspaceId"), + @NamedQuery(name="PartUsageLink.getPartOwner",query="SELECT p FROM PartIteration p WHERE :usage MEMBER OF p.components"), + @NamedQuery(name="PartUsageLink.findOrphans",query="SELECT p FROM PartUsageLink p WHERE NOT EXISTS (SELECT pi FROM PartIteration pi WHERE p member of pi.components) ") +}) +public class PartUsageLink implements Serializable, Cloneable, PartLink { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private int id; + private double amount; + private String unit; + + private String referenceDescription; + + @Column(name = "COMMENTDATA") + private String comment; + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "COMPONENT_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID"), + @JoinColumn(name = "COMPONENT_PARTNUMBER", referencedColumnName = "PARTNUMBER") + }) + private PartMaster component; + @OrderColumn(name = "PARTSUBSTITUTE_ORDER") + @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinTable(name = "PUSAGELINK_PSUBSTITUTELINK", + inverseJoinColumns = { + @JoinColumn(name = "PARTSUBSTITUTE_ID", referencedColumnName = "ID") + }, + joinColumns = { + @JoinColumn(name = "PARTUSAGELINK_ID", referencedColumnName = "ID") + }) + private List<PartSubstituteLink> substitutes = new LinkedList<>(); + + @OrderColumn(name = "CADINSTANCE_ORDER") + @JoinTable(name = "PARTUSAGELINK_CADINSTANCE", + inverseJoinColumns = { + @JoinColumn(name = "CADINSTANCE_ID", referencedColumnName = "ID") + }, + joinColumns = { + @JoinColumn(name = "PARTUSAGELINK_ID", referencedColumnName = "ID") + }) + @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List<CADInstance> cadInstances = new LinkedList<>(); + + private boolean optional; + + public PartUsageLink() { + } + + public PartUsageLink(PartMaster pComponent, double pAmount, String pUnit, boolean pOptional) { + component=pComponent; + amount=pAmount; + unit=pUnit; + optional=pOptional; + } + + @Override + public int getId() { + return id; + } + + @Override + public double getAmount() { + return amount; + } + + @Override + public String getUnit() { + return unit; + } + + @Override + public String getComment() { + return comment; + } + + @Override + public boolean isOptional() { + return optional; + } + + @Override + public PartMaster getComponent() { + return component; + } + + @Override + public List<PartSubstituteLink> getSubstitutes() { + return substitutes; + } + + @Override + public String getReferenceDescription() { + return referenceDescription; + } + + @Override + public Character getCode() { + return 'u'; + } + + @Override + public String getFullId() { + return getCode()+""+getId(); + } + + @Override + public List<CADInstance> getCadInstances() { + return cadInstances; + } + + public void setId(int id) { + this.id = id; + } + + public void setAmount(double amount) { + this.amount = amount; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + public void setReferenceDescription(String referenceDescription) { + this.referenceDescription = referenceDescription; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public void setComponent(PartMaster component) { + this.component = component; + } + + public void setSubstitutes(List<PartSubstituteLink> substitutes) { + this.substitutes = substitutes; + } + + public void setCadInstances(List<CADInstance> cadInstances) { + this.cadInstances = cadInstances; + } + + public void setOptional(boolean optional) { + this.optional = optional; + } + + @Override + public PartUsageLink clone() { + PartUsageLink clone; + try { + clone = (PartUsageLink) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + + //perform a deep copy + List<PartSubstituteLink> clonedSubstitutes = new LinkedList<>(); + for (PartSubstituteLink substitute : substitutes) { + PartSubstituteLink clonedSubstitute = substitute.clone(); + clonedSubstitutes.add(clonedSubstitute); + } + clone.substitutes = clonedSubstitutes; + + List<CADInstance> clonedCADInstances = new LinkedList<>(); + for (CADInstance cadInstance : cadInstances) { + CADInstance clonedCADInstance = cadInstance.clone(); + clonedCADInstances.add(clonedCADInstance); + } + clone.cadInstances = clonedCADInstances; + + return clone; + } + + public void addSubstitute(PartSubstituteLink partSubstituteLink) { + substitutes.add(partSubstituteLink); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/PathToPathLink.java b/docdoku-common/src/main/java/com/docdoku/core/product/PathToPathLink.java new file mode 100644 index 0000000000..7d8b9e8efc --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/PathToPathLink.java @@ -0,0 +1,154 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import javax.persistence.*; +import java.io.Serializable; + +/** + * @author Morgan Guimard + * @version 2.0, 29/04/15 + * @since V2.0 + */ +@Table(name="PATHTOPATHLINK") +@Entity +@NamedQueries({ + @NamedQuery(name="PathToPathLink.findPathToPathLinkTypesByProductInstanceIteration", query="SELECT DISTINCT(p.type) FROM ProductInstanceIteration pi JOIN pi.pathToPathLinks p WHERE pi = :productInstanceIteration"), + @NamedQuery(name="PathToPathLink.findPathToPathLinkByProductInstanceIteration", query="SELECT DISTINCT p FROM ProductInstanceIteration pi JOIN pi.pathToPathLinks p WHERE pi = :productInstanceIteration"), + @NamedQuery(name="PathToPathLink.findPathToPathLinkTypesByProductBaseline", query="SELECT DISTINCT(p.type) FROM ProductBaseline pb JOIN pb.pathToPathLinks p WHERE pb = :productBaseline"), + @NamedQuery(name="PathToPathLink.findPathToPathLinkTypesByProduct", query="SELECT DISTINCT(p.type) FROM ConfigurationItem ci JOIN ci.pathToPathLinks p WHERE ci = :configurationItem"), + @NamedQuery(name="PathToPathLink.findNextPathToPathLinkInProduct", query="SELECT DISTINCT p FROM ConfigurationItem ci JOIN ci.pathToPathLinks p WHERE ci = :configurationItem AND p.sourcePath = :targetPath AND p.type = :type"), + @NamedQuery(name="PathToPathLink.findPathToPathLinkBySourceAndTargetInProductInstance", query="SELECT DISTINCT p FROM ProductInstanceIteration pi JOIN pi.pathToPathLinks p WHERE pi = :productInstanceIteration AND (p.sourcePath = :source AND p.targetPath = :target OR p.sourcePath = :target AND p.targetPath = :source)"), + @NamedQuery(name="PathToPathLink.findPathToPathLinkBySourceAndTargetInBaseline", query="SELECT DISTINCT p FROM ProductBaseline pb JOIN pb.pathToPathLinks p WHERE pb = :baseline AND (p.sourcePath = :source AND p.targetPath = :target OR p.sourcePath = :target AND p.targetPath = :source)"), + @NamedQuery(name="PathToPathLink.findPathToPathLinkBySourceAndTargetInProduct", query="SELECT DISTINCT p FROM ConfigurationItem ci JOIN ci.pathToPathLinks p WHERE ci = :configurationItem AND (p.sourcePath = :source AND p.targetPath = :target OR p.sourcePath = :target AND p.targetPath = :source)"), + @NamedQuery(name="PathToPathLink.findSamePathToPathLinkInProduct", query="SELECT DISTINCT p FROM ConfigurationItem ci JOIN ci.pathToPathLinks p WHERE ci = :configurationItem AND p.sourcePath = :sourcePath AND p.targetPath = :targetPath AND p.type = :type"), + @NamedQuery(name="PathToPathLink.findPathToPathLinksForGivenProductInstanceIterationAndType", query="SELECT DISTINCT p FROM ProductInstanceIteration pi JOIN pi.pathToPathLinks p WHERE pi = :productInstanceIteration AND p.type = :type"), + @NamedQuery(name="PathToPathLink.findRootPathToPathLinkForGivenProductInstanceIterationAndType", query="SELECT DISTINCT p FROM ProductInstanceIteration pi JOIN pi.pathToPathLinks p WHERE p.type = :type AND pi = :productInstanceIteration AND p.sourcePath not in (SELECT _p.targetPath FROM PathToPathLink _p WHERE _p member of pi.pathToPathLinks AND _p.type = :type)"), + @NamedQuery(name="PathToPathLink.findRootPathToPathLinkForGivenProductBaselineAndType", query="SELECT DISTINCT p FROM ProductBaseline pb JOIN pb.pathToPathLinks p WHERE p.type = :type AND pb = :productBaseline AND p.sourcePath not in (SELECT _p.targetPath FROM PathToPathLink _p WHERE _p member of pb.pathToPathLinks AND _p.type = :type)"), + @NamedQuery(name="PathToPathLink.findRootPathToPathLinkForGivenProductAndType", query="SELECT DISTINCT p FROM ConfigurationItem ci JOIN ci.pathToPathLinks p WHERE p.type = :type AND ci = :configurationItem AND p.sourcePath not in (SELECT _p.targetPath FROM PathToPathLink _p WHERE _p member of ci.pathToPathLinks AND _p.type = :type)"), + @NamedQuery(name="PathToPathLink.findPathToPathLinkByPathListInProduct", query="SELECT DISTINCT p FROM ConfigurationItem ci JOIN ci.pathToPathLinks p WHERE ci = :configurationItem AND p.sourcePath in :paths AND p.targetPath in :paths"), + @NamedQuery(name="PathToPathLink.findSourcesPathToPathLinkInProduct", query="SELECT DISTINCT p FROM ConfigurationItem ci JOIN ci.pathToPathLinks p WHERE ci = :configurationItem AND p.sourcePath = :source AND p.type = :type"), + @NamedQuery(name="PathToPathLink.findSourcesPathToPathLinkInProductBaseline", query="SELECT DISTINCT p FROM ProductBaseline pb JOIN pb.pathToPathLinks p WHERE pb = :productBaseline AND p.sourcePath = :source AND p.type = :type"), + @NamedQuery(name="PathToPathLink.findLinksWherePartialPathIsPresent", query="SELECT DISTINCT p FROM PathToPathLink p WHERE p.targetPath LIKE :endOfChain OR p.targetPath LIKE :inChain OR p.sourcePath LIKE :endOfChain OR p.sourcePath LIKE :inChain"), + @NamedQuery(name="PathToPathLink.isSourceInProductInstanceContext", query="SELECT p FROM PathToPathLink p JOIN ProductInstanceIteration pi WHERE pi = :productInstanceIteration AND p member of pi.pathToPathLinks AND p.sourcePath = :path"), + @NamedQuery(name="PathToPathLink.isTargetInProductInstanceContext", query="SELECT p FROM PathToPathLink p JOIN ProductInstanceIteration pi WHERE pi = :productInstanceIteration AND p member of pi.pathToPathLinks AND p.targetPath = :path"), + @NamedQuery(name="PathToPathLink.isSourceInConfigurationItemContext", query="SELECT p FROM PathToPathLink p JOIN ConfigurationItem ci WHERE ci = :configurationItem AND p member of ci.pathToPathLinks AND p.sourcePath = :path"), + @NamedQuery(name="PathToPathLink.isTargetInConfigurationItemContext", query="SELECT p FROM PathToPathLink p JOIN ConfigurationItem ci WHERE ci = :configurationItem AND p member of ci.pathToPathLinks AND p.targetPath = :path") +}) +public class PathToPathLink implements Serializable, Cloneable{ + + @GeneratedValue(strategy= GenerationType.IDENTITY) + @Id + @Column(name="ID") + private int id; + + private String type; + private String sourcePath; + private String targetPath; + + @Lob + private String description; + + public PathToPathLink() { + } + + public PathToPathLink(String type, String sourcePath, String targetPath, String description) { + this.type = type; + this.sourcePath = sourcePath; + this.targetPath = targetPath; + this.description = description; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getSourcePath() { + return sourcePath; + } + + public void setSourcePath(String pathFrom) { + this.sourcePath = pathFrom; + } + + public String getTargetPath() { + return targetPath; + } + + public void setTargetPath(String pathTo) { + this.targetPath = pathTo; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public boolean equals(Object o) { + if (this == o){ + return true; + } + if (o == null || getClass() != o.getClass()){ + return false; + } + + PathToPathLink that = (PathToPathLink) o; + + if (id != that.id){ + return false; + } + + return true; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public PathToPathLink clone(){ + PathToPathLink clone; + try { + clone = (PathToPathLink) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + return clone; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/RotationMatrix.java b/docdoku-common/src/main/java/com/docdoku/core/product/RotationMatrix.java new file mode 100644 index 0000000000..f735d23c80 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/RotationMatrix.java @@ -0,0 +1,128 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import javax.persistence.Embeddable; +import javax.persistence.Transient; + +/** + * @author Charles Fallourd on 01/03/16. + */ +@Embeddable +public class RotationMatrix { + + private double m00, m01, m02, m10, m11, m12, m20, m21, m22; + + public RotationMatrix() { + + } + + public RotationMatrix(double[] values) { + if(values!=null) { + m00 = values[0]; + m01 = values[1]; + m02 = values[2]; + m10 = values[3]; + m11 = values[4]; + m12 = values[5]; + m20 = values[6]; + m21 = values[7]; + m22 = values[8]; + } + } + + public double getM00() { + return m00; + } + + public void setM00(double m00) { + this.m00 = m00; + } + + public double getM01() { + return m01; + } + + public void setM01(double m01) { + this.m01 = m01; + } + + public double getM02() { + return m02; + } + + public void setM02(double m02) { + this.m02 = m02; + } + + public double getM10() { + return m10; + } + + public void setM10(double m10) { + this.m10 = m10; + } + + public double getM11() { + return m11; + } + + public void setM11(double m11) { + this.m11 = m11; + } + + public double getM12() { + return m12; + } + + public void setM12(double m12) { + this.m12 = m12; + } + + public double getM20() { + return m20; + } + + public void setM20(double m20) { + this.m20 = m20; + } + + public double getM21() { + return m21; + } + + public void setM21(double m21) { + this.m21 = m21; + } + + public double getM22() { + return m22; + } + + public void setM22(double m22) { + this.m22 = m22; + } + + @Transient + public double[] getValues() { + return new double[]{ m00, m01, m02, m10, m11, m12, m20, m21, m22 }; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/SerialNumberBasedEffectivity.java b/docdoku-common/src/main/java/com/docdoku/core/product/SerialNumberBasedEffectivity.java new file mode 100644 index 0000000000..f4db2ed1c6 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/SerialNumberBasedEffectivity.java @@ -0,0 +1,72 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + + +package com.docdoku.core.product; + +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * SerialNumberBasedEffectivity indicates that an item is effective while a + * configuration item is being produced in a range of serial numbered units. + * + * @author Florent Garin + * @version 1.1, 18/10/11 + * @since V1.1 + */ +@Table(name="SERIALNUMBERBASEDEFFECTIVITY") +@Entity +public class SerialNumberBasedEffectivity extends Effectivity{ + + /** + * The serial number of the first item that the effectivity applies to. + */ + private String startNumber; + + /** + * The serial number of the last item that the effectivity applies to. + * This value is optional. + */ + private String endNumber; + + public SerialNumberBasedEffectivity() { + } + + public String getStartNumber() { + return startNumber; + } + + public void setStartNumber(String startNumber) { + this.startNumber = startNumber; + } + + + public String getEndNumber() { + return endNumber; + } + + public void setEndNumber(String endNumber) { + this.endNumber = endNumber; + } + + + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/product/StatusChange.java b/docdoku-common/src/main/java/com/docdoku/core/product/StatusChange.java new file mode 100644 index 0000000000..049fd40cc5 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/product/StatusChange.java @@ -0,0 +1,61 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import com.docdoku.core.common.User; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; + +/** + * @author Charles Fallourd + */ +@Embeddable +public class StatusChange implements Serializable{ + + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="USER_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name="USER_WORKSPACE",referencedColumnName = "WORKSPACE_ID")}) + private User statusChangeAuthor; + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private java.util.Date statusModificationDate; + + public java.util.Date getStatusModificationDate() { + return statusModificationDate; + } + + public void setStatusModificationDate(Date statusModificationDate) { + this.statusModificationDate = statusModificationDate; + } + + public User getStatusChangeAuthor() { + return statusChangeAuthor; + } + + public void setStatusChangeAuthor(User statusChangeAuthor) { + this.statusChangeAuthor = statusChangeAuthor; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/query/DocumentSearchQuery.java b/docdoku-common/src/main/java/com/docdoku/core/query/DocumentSearchQuery.java new file mode 100644 index 0000000000..822bbaafe0 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/query/DocumentSearchQuery.java @@ -0,0 +1,63 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.query; + +import java.io.Serializable; +import java.util.Date; + +/** + * Wraps data needed to perform a query on documents. + * + * @author Florent Garin + * @version 2.0, 03/01/2014 + * @since V2.0 + */ +public class DocumentSearchQuery extends SearchQuery implements Serializable{ + protected String docMId; + protected String title; + + public DocumentSearchQuery(){ + + } + public DocumentSearchQuery(String workspaceId, String fullText, String docMId, String title, String version, String author, String type, Date creationDateFrom, Date creationDateTo, Date modificationDateFrom, Date modificationDateTo, SearchQuery.AbstractAttributeQuery[] attributes, String[] tags, String content){ + super(workspaceId,fullText,version,author,type,creationDateFrom,creationDateTo,modificationDateFrom,modificationDateTo,attributes,tags,content); + this.docMId=docMId; + this.title=title; + this.content=content; + } + + //Getter + public String getDocMId() { + return docMId; + } + public String getTitle() { + return title; + } + + //Setter + public void setDocMId(String docMId) { + this.docMId = docMId; + } + public void setTitle(String title) { + this.title = title; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/query/PartSearchQuery.java b/docdoku-common/src/main/java/com/docdoku/core/query/PartSearchQuery.java new file mode 100644 index 0000000000..3515807787 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/query/PartSearchQuery.java @@ -0,0 +1,71 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.query; + +import java.io.Serializable; +import java.util.Date; + +/** + * Wraps data needed to perform a query on part revisions. + * + * @author Morgan Guimard + * @version 2.0, 03/01/2014 + * @since V2.0 + */ +public class PartSearchQuery extends SearchQuery implements Serializable{ + protected String partNumber; + protected String name; + protected Boolean standardPart; + + public PartSearchQuery(){ + + } + public PartSearchQuery(String workspaceId, String fullText, String partNumber, String name, String version, + String author, String type, Date creationDateFrom, Date creationDateTo, Date modificationDateFrom, + Date modificationDateTo, SearchQuery.AbstractAttributeQuery[] attributes,String[] tags, Boolean standardPart, String content){ + super(workspaceId,fullText,version,author,type,creationDateFrom,creationDateTo,modificationDateFrom, + modificationDateTo,attributes,tags,content); + this.partNumber=partNumber; + this.name=name; + this.standardPart=standardPart; + } + + //Getter + public String getPartNumber() { + return partNumber; + } + public String getName() { + return name; + } + public Boolean isStandardPart() { + return standardPart; + } + //Setter + public void setPartNumber(String partNumber) { + this.partNumber = partNumber; + } + public void setName(String name) { + this.name = name; + } + public void setStandardPart(Boolean standardPart) { + this.standardPart = standardPart; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/query/Query.java b/docdoku-common/src/main/java/com/docdoku/core/query/Query.java new file mode 100644 index 0000000000..048c39d14c --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/query/Query.java @@ -0,0 +1,185 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.query; + +import com.docdoku.core.common.User; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Wraps data needed to perform a custom query on database. + * + * @author Morgan Guimard + */ +@Table(name="QUERY") +@Entity +@NamedQueries({ + @NamedQuery(name="Query.findByWorkspace",query="SELECT q FROM Query q WHERE q.author.workspaceId = :workspaceId"), + @NamedQuery(name="Query.findByWorkspaceAndWorkspace",query="SELECT q FROM Query q WHERE q.author.workspaceId = :workspaceId AND q.name = :name") +}) +public class Query implements Serializable { + + @GeneratedValue(strategy= GenerationType.IDENTITY) + @Id + private int id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + private String name; + + @Temporal(TemporalType.TIMESTAMP) + private Date creationDate; + + @OneToOne(orphanRemoval = true, cascade=CascadeType.ALL, fetch=FetchType.EAGER) + @JoinColumn(name="QUERYRULE_ID") + private QueryRule queryRule; + + @ElementCollection(fetch = FetchType.LAZY) + @CollectionTable(name = "QUERY_SELECTS", + joinColumns= { + @JoinColumn(name = "QUERY_ID", referencedColumnName = "ID") + } + ) + private List<String> selects=new ArrayList<>(); + + @ElementCollection(fetch = FetchType.LAZY) + @CollectionTable(name = "QUERY_ORDER_BY", + joinColumns= { + @JoinColumn(name = "QUERY_ID", referencedColumnName = "ID") + } + ) + private List<String> orderByList=new ArrayList<>(); + + @ElementCollection(fetch = FetchType.LAZY) + @CollectionTable(name = "QUERY_GROUPED_BY", + joinColumns= { + @JoinColumn(name = "QUERY_ID", referencedColumnName = "ID") + } + ) + private List<String> groupedByList=new ArrayList<>(); + + + @OneToMany(mappedBy = "parentQuery", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List<QueryContext> contexts =new ArrayList<>(); + + public Query() { + } + + public Query(User author, String name, Date creationDate, QueryRule queryRule, List<String> selects, List<String> orderByList, List<String> groupedByList, List<QueryContext> contexts) { + this.author = author; + this.name = name; + this.creationDate = creationDate; + this.queryRule = queryRule; + this.selects = selects; + this.orderByList = orderByList; + this.groupedByList = groupedByList; + this.contexts = contexts; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public User getAuthor() { + return author; + } + + public void setAuthor(User author) { + this.author = author; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public void setQueryRule(QueryRule queryRule) { + this.queryRule = queryRule; + } + + public QueryRule getQueryRule() { + return queryRule; + } + + public void setRules(QueryRule queryRule) { + this.queryRule = queryRule; + } + + public List<String> getSelects() { + return selects; + } + + public void setSelects(List<String> selects) { + this.selects = selects; + } + + public List<String> getOrderByList() { + return orderByList; + } + + public void setOrderByList(List<String> orderByList) { + this.orderByList = orderByList; + } + + public List<String> getGroupedByList() { + return groupedByList; + } + + public void setGroupedByList(List<String> groupedByList) { + this.groupedByList = groupedByList; + } + + public List<QueryContext> getContexts() { + return contexts; + } + + public void setContexts(List<QueryContext> contexts) { + this.contexts = contexts; + } + + public boolean hasContext() { + return !contexts.isEmpty(); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/query/QueryContext.java b/docdoku-common/src/main/java/com/docdoku/core/query/QueryContext.java new file mode 100644 index 0000000000..0f6809fa2a --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/query/QueryContext.java @@ -0,0 +1,89 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.query; + +import javax.persistence.*; +import java.io.Serializable; + +@Table(name="QUERYCONTEXT") +@Entity +public class QueryContext implements Serializable{ + + @GeneratedValue(strategy= GenerationType.IDENTITY) + @Id + private int id; + + private String workspaceId; + + private String serialNumber; + + private String configurationItemId; + + @ManyToOne(optional=false, fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="QUERY_ID", referencedColumnName="ID"), + + }) + private Query parentQuery; + + public QueryContext() { + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getConfigurationItemId() { + return configurationItemId; + } + + public void setConfigurationItemId(String configurationItemId) { + this.configurationItemId = configurationItemId; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public Query getParentQuery() { + return parentQuery; + } + + public void setParentQuery(Query parentQuery) { + this.parentQuery = parentQuery; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/query/QueryField.java b/docdoku-common/src/main/java/com/docdoku/core/query/QueryField.java new file mode 100644 index 0000000000..6df3e686d0 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/query/QueryField.java @@ -0,0 +1,58 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.query; + +/** + * + * @author Morgan Guimard + */ +public class QueryField { + + public static final String PART_MASTER_NUMBER = "pm.number"; + public static final String PART_MASTER_NAME = "pm.name"; + public static final String PART_MASTER_TYPE = "pm.type"; + public static final String PART_MASTER_IS_STANDARD = "pm.standardPart"; + + public static final String PART_REVISION_PART_KEY = "pr.partKey"; + public static final String PART_REVISION_VERSION = "pr.version"; + public static final String PART_REVISION_MODIFICATION_DATE = "pr.modificationDate"; + public static final String PART_REVISION_CHECKIN_DATE = "pr.checkInDate"; + public static final String PART_REVISION_CHECKOUT_DATE = "pr.checkOutDate"; + public static final String PART_REVISION_CREATION_DATE = "pr.creationDate"; + public static final String PART_REVISION_LIFECYCLE_STATE = "pr.lifeCycleState"; + public static final String PART_REVISION_STATUS = "pr.status"; + public static final String PART_ITERATION_LINKED_DOCUMENTS = "pr.linkedDocuments"; + public static final String PART_REVISION_ATTRIBUTES_PREFIX = "attr-"; + public static final String PATH_DATA_ATTRIBUTES_PREFIX = "pd-attr-"; + + public static final String AUTHOR_LOGIN = "author.login"; + public static final String AUTHOR_NAME = "author.name"; + + public static final String CTX_SERIAL_NUMBER = "ctx.serialNumber"; + public static final String CTX_PRODUCT_ID = "ctx.productId"; + public static final String CTX_DEPTH = "ctx.depth"; + public static final String CTX_AMOUNT = "ctx.amount"; + public static final String CTX_P2P_SOURCE = "ctx.p2p.source"; + public static final String CTX_P2P_TARGET = "ctx.p2p.target"; + + private QueryField() { + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/query/QueryResultRow.java b/docdoku-common/src/main/java/com/docdoku/core/query/QueryResultRow.java new file mode 100644 index 0000000000..e07079b1f2 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/query/QueryResultRow.java @@ -0,0 +1,144 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.query; + +import com.docdoku.core.configuration.PathDataIteration; +import com.docdoku.core.product.PartLink; +import com.docdoku.core.product.PartLinkList; +import com.docdoku.core.product.PartRevision; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Morgan Guimard + */ +public class QueryResultRow { + + private PartRevision partRevision; + private int depth; + private Map<String, List<PartLinkList>> sources = new HashMap<>(); + private Map<String, List<PartLinkList>> targets = new HashMap<>(); + private PathDataIteration pathDataIteration; + private double[] results; + private QueryContext context; + private double amount; + private String path; + + public QueryResultRow() { + } + + public QueryResultRow(PartRevision partRevision) { + this.partRevision = partRevision; + } + + public QueryResultRow(PartRevision partRevision, int depth, double[] results) { + this.partRevision = partRevision; + this.depth = depth; + this.results = results; + } + + public PartRevision getPartRevision() { + return partRevision; + } + + public void setPartRevision(PartRevision partRevision) { + this.partRevision = partRevision; + } + + public int getDepth() { + return depth; + } + + public void setDepth(int depth) { + this.depth = depth; + } + + public double[] getResults() { + return results; + } + + public void setResults(double[] results) { + this.results = results; + } + + public QueryContext getContext() { + return context; + } + + public void setContext(QueryContext context) { + this.context = context; + } + + public double getAmount() { + return amount; + } + + public void setAmount(double amount) { + this.amount = amount; + } + + public Map<String, List<PartLinkList>> getSources() { + return sources; + } + + public void setSources(Map<String, List<PartLinkList>> sources) { + this.sources = sources; + } + + public Map<String, List<PartLinkList>> getTargets() { + return targets; + } + + public void setTargets(Map<String, List<PartLinkList>> targets) { + this.targets = targets; + } + + public void addTarget(String type, List<PartLink> targetPath) { + List<PartLinkList> paths = targets.get(type) != null ? targets.get(type) : new ArrayList<>(); + paths.add(new PartLinkList(targetPath)); + targets.put(type, paths); + } + + public void addSource(String type, List<PartLink> sourcePath) { + List<PartLinkList> paths = sources.get(type) != null ? sources.get(type) : new ArrayList<>(); + paths.add(new PartLinkList(sourcePath)); + sources.put(type, paths); + } + + public PathDataIteration getPathDataIteration() { + return pathDataIteration; + } + + public void setPathDataIteration(PathDataIteration pathDataIteration) { + this.pathDataIteration = pathDataIteration; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/query/QueryRule.java b/docdoku-common/src/main/java/com/docdoku/core/query/QueryRule.java new file mode 100644 index 0000000000..7b4cf0a3e0 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/query/QueryRule.java @@ -0,0 +1,142 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.query; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Morgan Guimard + */ +@Table(name="QUERYRULE") +@Entity +public class QueryRule implements Serializable { + + @GeneratedValue(strategy= GenerationType.IDENTITY) + @Id + private int qid; + + @Column(name="COND") + private String condition; + private String id; + private String field; + private String type; + private String operator; + + @Column(name="VALUE") + @OrderColumn(name="VALUE_ORDER") + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "QUERYRULE_VALUES", + joinColumns= { + @JoinColumn(name = "QUERYRULE_ID", referencedColumnName = "QID") + } + ) + private List<String> values=new ArrayList<>(); + + @ManyToOne + @JoinColumn(name = "PARENT_QUERY_RULE") + private QueryRule parentQueryRule; + + @OneToMany(mappedBy = "parentQueryRule", cascade=CascadeType.ALL, fetch=FetchType.EAGER) + @OrderBy("qid ASC") + private List<QueryRule> subQueryRules; + + public QueryRule() { + } + + public int getQid() { + return qid; + } + + public void setQid(int qid) { + this.qid = qid; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getCondition() { + return condition; + } + + public void setCondition(String condition) { + this.condition = condition; + } + + public String getField() { + return field; + } + + public void setField(String field) { + this.field = field; + } + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public List<String> getValues() { + return values; + } + + public void setValues(List<String> values) { + this.values = values; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public QueryRule getParentQueryRule() { + return parentQueryRule; + } + + public void setParentQueryRule(QueryRule parentQueryRule) { + this.parentQueryRule = parentQueryRule; + } + + public List<QueryRule> getSubQueryRules() { + return subQueryRules; + } + + public void setSubQueryRules(List<QueryRule> subQueryRules) { + this.subQueryRules = subQueryRules; + } + + public boolean hasSubRules() { + return getSubQueryRules() != null && !getSubQueryRules().isEmpty(); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/query/SearchQuery.java b/docdoku-common/src/main/java/com/docdoku/core/query/SearchQuery.java new file mode 100644 index 0000000000..c1a5e5c08d --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/query/SearchQuery.java @@ -0,0 +1,362 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.query; + +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.InstanceDateAttribute; + +import javax.xml.bind.annotation.XmlSeeAlso; +import java.io.Serializable; +import java.util.Date; + +/** + * Wraps data needed to perform a query on document or part revisions. + * @author Taylor LABEJOF + */ +public class SearchQuery implements Serializable { + + protected String workspaceId; + protected String fullText; + protected String version; + protected String author; + protected String type; + protected Date creationDateFrom; + protected Date creationDateTo; + protected Date modificationDateFrom; + protected Date modificationDateTo; + protected AbstractAttributeQuery[] attributes; + protected String[] tags; + protected String content; + + public SearchQuery(){ + + } + public SearchQuery(String workspaceId, String fullText, String version, String author, String type, + Date creationDateFrom, Date creationDateTo, Date modificationDateFrom, Date modificationDateTo, + AbstractAttributeQuery[] attributes, String[] tags, String content) { + this.workspaceId = workspaceId; + this.fullText =fullText; + this.version = version; + this.author = author; + this.type = type; + this.creationDateFrom = (creationDateFrom!=null) ? (Date) creationDateFrom.clone() : null; + this.creationDateTo = (creationDateTo!=null) ? (Date) creationDateTo.clone() : null; + this.modificationDateFrom = (modificationDateFrom!=null) ? (Date) modificationDateFrom.clone() : null; + this.modificationDateTo = (modificationDateTo!=null) ? (Date) modificationDateTo.clone() : null; + this.attributes = attributes; + this.tags = tags; + this.content = content; + } + + // Getter + public String getWorkspaceId() { + return workspaceId; + } + public String getFullText() { + return fullText; + } + public String getVersion() { + return version; + } + public String getAuthor() { + return author; + } + public String getType() { + return type; + } + public String[] getTags() { + return tags; + } + public Date getCreationDateFrom() { + return (creationDateFrom!=null) ? (Date) creationDateFrom.clone() : null; + } + public Date getCreationDateTo() { + return (creationDateTo!=null) ? (Date) creationDateTo.clone() : null; + } + public Date getModificationDateFrom() { + return (modificationDateFrom!=null) ? (Date) modificationDateFrom.clone() : null; + } + public Date getModificationDateTo() { + return (modificationDateTo!=null) ? (Date) modificationDateTo.clone() : null; + } + public AbstractAttributeQuery[] getAttributes() { + return attributes; + } + public String getContent() { + return content; + } + + //Setter + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + public void setFullText(String fullText){ + this.fullText = fullText; + } + public void setVersion(String version) { + this.version = version; + } + public void setAuthor(String author) { + this.author = author; + } + public void setType(String type) { + this.type = type; + } + public void setTags(String[] tags) { + this.tags = tags; + } + public void setCreationDateFrom(Date creationDateFrom) { + this.creationDateFrom = (creationDateFrom!=null) ? (Date) creationDateFrom.clone() : null; + } + public void setCreationDateTo(Date creationDateTo) { + this.creationDateTo = (creationDateTo!=null) ? (Date) creationDateTo.clone() : null; + } + public void setModificationDateFrom(Date modificationDateFrom) { + this.modificationDateFrom = (modificationDateFrom!=null) ? (Date) modificationDateFrom.clone() : null; + } + public void setModificationDateTo(Date modificationDateTo) { + this.modificationDateTo = (modificationDateTo!=null) ? (Date) modificationDateTo.clone() : null; + } + + public void setAttributes(AbstractAttributeQuery[] attributes) { + this.attributes = attributes; + } + public void setContent(String content) { + this.content = content; + } + + @XmlSeeAlso({TextAttributeQuery.class, NumberAttributeQuery.class, DateAttributeQuery.class, BooleanAttributeQuery.class, URLAttributeQuery.class}) + public abstract static class AbstractAttributeQuery implements Serializable{ + protected String name; + + public String getName() { + return name; + } + public String getNameWithoutWhiteSpace(){ + return this.name.replaceAll(" ","_"); + } + public void setName(String name) { + this.name = name; + } + public AbstractAttributeQuery(){ + + } + public AbstractAttributeQuery(String name){ + this.name=name; + } + public abstract boolean attributeMatches(InstanceAttribute attr); + public abstract boolean hasValue(); + @Override + public abstract String toString(); + } + + public static class TextAttributeQuery extends AbstractAttributeQuery{ + private String textValue; + public TextAttributeQuery(){ + + } + public TextAttributeQuery(String name, String value){ + super(name); + this.textValue=value; + } + public String getTextValue() { + return textValue; + } + public void setTextValue(String textValue) { + this.textValue = textValue; + } + @Override + public boolean attributeMatches(InstanceAttribute attr){ + return attr.isValueEquals(textValue); + } + + @Override + public boolean hasValue() { + return !textValue.isEmpty(); + } + + @Override + public String toString() { + return textValue; + } + } + public static class NumberAttributeQuery extends AbstractAttributeQuery{ + private Float numberValue; + public NumberAttributeQuery(){ + + } + public NumberAttributeQuery(String name, Float value){ + super(name); + this.numberValue=value; + } + public float getNumberValue() { + return numberValue; + } + public void setNumberValue(float numberValue) { + this.numberValue = numberValue; + } + @Override + public boolean attributeMatches(InstanceAttribute attr){ + return attr.isValueEquals(numberValue); + } + + @Override + public boolean hasValue() { + return numberValue != null; + } + + @Override + public String toString() { + return numberValue.toString(); + } + } + public static class BooleanAttributeQuery extends AbstractAttributeQuery{ + private boolean booleanValue; + public BooleanAttributeQuery(){ + + } + public BooleanAttributeQuery(String name, boolean value){ + super(name); + this.booleanValue=value; + } + public boolean isBooleanValue() { + return booleanValue; + } + public void setBooleanValue(boolean booleanValue) { + this.booleanValue = booleanValue; + } + @Override + public boolean attributeMatches(InstanceAttribute attr){ + return attr.isValueEquals(booleanValue); + } + + @Override + public boolean hasValue() { + //by default boolean attribute must have a value + return true; + } + + @Override + public String toString() { + return ""+booleanValue; + } + } + public static class URLAttributeQuery extends AbstractAttributeQuery{ + private String urlValue; + public URLAttributeQuery(){ + + } + public URLAttributeQuery(String name, String value){ + super(name); + this.urlValue=value; + } + + public void setUrlValue(String urlValue) { + this.urlValue = urlValue; + } + public String getUrlValue() { + return urlValue; + } + @Override + public boolean attributeMatches(InstanceAttribute attr){ + return attr.isValueEquals(urlValue); + } + + @Override + public boolean hasValue() { + return !urlValue.isEmpty(); + } + + @Override + public String toString() { + return urlValue; + } + } + public static class DateAttributeQuery extends AbstractAttributeQuery{ + private Date date; + public DateAttributeQuery(){ + + } + public DateAttributeQuery(String name, Date date){ + super(name); + this.date = date; + } + public Date getDate() { + return (date !=null) ? (Date) date.clone() : null; + } + public void setDate(Date date) { + this.date = (date !=null) ? (Date) date.clone() : null; + } + + @Override + public boolean attributeMatches(InstanceAttribute attr) { + if (attr instanceof InstanceDateAttribute) { + InstanceDateAttribute dateAttr = (InstanceDateAttribute) attr; + Date dateValue = dateAttr.getDateValue(); + if( date !=null) { + return dateValue.equals(date); + } + } + return false; + } + + @Override + public boolean hasValue() { + return date != null; + } + + @Override + public String toString() { + return date.toString(); + } + } + public static class LovAttributeQuery extends AbstractAttributeQuery{ + private String lovValue; + public LovAttributeQuery(){ + + } + public LovAttributeQuery(String name, String value){ + super(name); + this.lovValue=value; + } + + public void setLovValue(String lovValue) { + this.lovValue = lovValue; + } + public String getLovValue() { + return lovValue; + } + @Override + public boolean attributeMatches(InstanceAttribute attr){ + return attr.isValueEquals(lovValue); + } + + @Override + public boolean hasValue() { + return !lovValue.isEmpty(); + } + + @Override + public String toString() { + return lovValue; + } + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/security/ACL.java b/docdoku-common/src/main/java/com/docdoku/core/security/ACL.java new file mode 100644 index 0000000000..f655ce98fd --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/security/ACL.java @@ -0,0 +1,198 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.security; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * This class can be attached to any entity so that an access control + * list will be applied. + * In that way, the default access rights defined at the workspace level will be + * overridden. + * + * @author Florent Garin + * @version 1.1, 17/07/09 + * @since V1.1 + */ +@Table(name="ACL") +@Entity +@NamedQueries ({ + @NamedQuery(name="ACL.removeUserEntries", query = "DELETE FROM ACLUserEntry a WHERE a.acl.id = :aclId"), + @NamedQuery(name="ACL.removeUserGroupEntries", query = "DELETE FROM ACLUserGroupEntry a WHERE a.acl.id = :aclId") +}) +public class ACL implements Serializable, Cloneable{ + + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Id + private int id; + + @OneToMany(cascade=CascadeType.ALL, mappedBy="acl", fetch=FetchType.EAGER) + @MapKey(name="principal") + private Map<User,ACLUserEntry> userEntries=new HashMap<User,ACLUserEntry>(); + + @OneToMany(cascade=CascadeType.ALL, mappedBy="acl", fetch=FetchType.EAGER) + @MapKey(name="principal") + private Map<UserGroup,ACLUserGroupEntry> groupEntries=new HashMap<UserGroup,ACLUserGroupEntry>(); + + public enum Permission{ + FORBIDDEN, + READ_ONLY, + FULL_ACCESS + } + + private boolean enabled=true; + + public ACL(){ + + } + + public void setId(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + public boolean hasReadAccess(User user){ + ACLUserEntry userAccess=userEntries.get(user); + if(userAccess!=null) + return !userAccess.getPermission().equals(Permission.FORBIDDEN); + else{ + for(Map.Entry<UserGroup, ACLUserGroupEntry> entry:groupEntries.entrySet()){ + if(entry.getKey().isMember(user) && !entry.getValue().getPermission().equals(Permission.FORBIDDEN)) { + return true; + } + } + } + return false; + } + + public boolean hasWriteAccess(User user){ + ACLUserEntry userAccess=userEntries.get(user); + if(userAccess!=null) + return userAccess.getPermission().equals(Permission.FULL_ACCESS); + else{ + for(Map.Entry<UserGroup, ACLUserGroupEntry> entry:groupEntries.entrySet()){ + if(entry.getKey().isMember(user) && entry.getValue().getPermission().equals(Permission.FULL_ACCESS)) { + return true; + } + } + } + return false; + } + + public void addEntry(User user, Permission perm){ + userEntries.put(user, new ACLUserEntry(this,user,perm)); + } + + public void addEntry(UserGroup group, Permission perm){ + groupEntries.put(group, new ACLUserGroupEntry(this,group,perm)); + } + + public void removeEntry(User user){ + userEntries.remove(user); + } + + public void removeEntry(UserGroup group){ + groupEntries.remove(group); + } + + public Map<User, ACLUserEntry> getUserEntries() { + return userEntries; + } + + public void setUserEntries(Map<User, ACLUserEntry> userEntries) { + this.userEntries = userEntries; + } + + public Map<UserGroup, ACLUserGroupEntry> getGroupEntries() { + return groupEntries; + } + + public void setGroupEntries(Map<UserGroup, ACLUserGroupEntry> groupEntries) { + this.groupEntries = groupEntries; + } + + /** + * perform a deep clone operation + */ + @Override + public ACL clone() { + ACL clone; + try { + clone = (ACL) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + //perform a deep copy + Map<User,ACLUserEntry> clonedUserEntries = new HashMap<>(); + for (Map.Entry<User,ACLUserEntry> entry : userEntries.entrySet()) { + ACLUserEntry aclEntry = entry.getValue().clone(); + aclEntry.setACL(clone); + clonedUserEntries.put(entry.getKey(),aclEntry); + } + clone.userEntries = clonedUserEntries; + + //perform a deep copy + Map<UserGroup,ACLUserGroupEntry> clonedGroupEntries = new HashMap<>(); + for (Map.Entry<UserGroup,ACLUserGroupEntry> entry : groupEntries.entrySet()) { + ACLUserGroupEntry aclEntry = entry.getValue().clone(); + aclEntry.setACL(clone); + clonedGroupEntries.put(entry.getKey(),aclEntry); + } + clone.groupEntries = clonedGroupEntries; + return clone; + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ACL)) { + return false; + } + ACL acl = (ACL) obj; + return acl.id == id; + } + + @Override + public int hashCode() { + return id; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/security/ACLUserEntry.java b/docdoku-common/src/main/java/com/docdoku/core/security/ACLUserEntry.java new file mode 100644 index 0000000000..6843d5aa34 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/security/ACLUserEntry.java @@ -0,0 +1,107 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.security; + +import com.docdoku.core.common.User; +import com.docdoku.core.security.ACL.Permission; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; + +/** + * Class that belongs to the ACL classe and makes the mapping between a user + * and a associated permission. + * + * @author Florent Garin + * @version 1.1, 17/07/09 + * @since V1.1 + */ +@Table(name="ACLUSERENTRY") +@Entity +@IdClass(com.docdoku.core.security.ACLUserEntryKey.class) +public class ACLUserEntry implements Serializable, Cloneable { + + @Id + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumn(name="ACL_ID", referencedColumnName="ID") + protected ACL acl; + + @Id + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "PRINCIPAL_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "PRINCIPAL_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User principal; + + private Permission permission; + + public ACLUserEntry() { + } + + public ACLUserEntry(ACL acl, User principal, Permission permission) { + setACL(acl); + setPrincipal(principal); + setPermission(permission); + } + + public void setACL(ACL pACL) { + this.acl = pACL; + } + + @XmlTransient + public ACL getAcl() { + return acl; + } + + public void setPermission(Permission permission) { + this.permission = permission; + } + + public void setPrincipal(User pPrincipal) { + this.principal = pPrincipal; + } + + public Permission getPermission() { + return permission; + } + + public User getPrincipal() { + return principal; + } + + public String getPrincipalLogin() { + return principal.getLogin(); + } + + @Override + public ACLUserEntry clone() { + ACLUserEntry clone = null; + try { + clone = (ACLUserEntry) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + return clone; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/security/ACLUserEntryKey.java b/docdoku-common/src/main/java/com/docdoku/core/security/ACLUserEntryKey.java new file mode 100644 index 0000000000..6bf44f335f --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/security/ACLUserEntryKey.java @@ -0,0 +1,83 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.security; + +import com.docdoku.core.common.UserKey; + +import java.io.Serializable; + +/** + * + * @author Florent Garin + */ +public class ACLUserEntryKey implements Serializable { + + private UserKey principal; + private int acl; + + public ACLUserEntryKey() { + } + + public ACLUserEntryKey(int acl, UserKey principal) { + this.acl = acl; + this.principal = principal; + } + + public int getAcl() { + return acl; + } + + public UserKey getPrincipal() { + return principal; + } + + public void setAcl(int aclId) { + this.acl = aclId; + } + + public void setPrincipal(UserKey principal) { + this.principal = principal; + } + + @Override + public String toString() { + return acl + "/" + principal ; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof ACLUserEntryKey)) { + return false; + } + ACLUserEntryKey key = (ACLUserEntryKey) pObj; + return key.acl==acl && key.principal.equals(principal); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + principal.hashCode(); + hash = 31 * hash + acl; + return hash; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/security/ACLUserGroupEntry.java b/docdoku-common/src/main/java/com/docdoku/core/security/ACLUserGroupEntry.java new file mode 100644 index 0000000000..78412ed4ca --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/security/ACLUserGroupEntry.java @@ -0,0 +1,108 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.security; + +import com.docdoku.core.common.UserGroup; +import com.docdoku.core.security.ACL.Permission; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; + +/** + * Class that belongs to the ACL classe and makes the mapping between a group + * and a associated permission. + * + * @author Florent Garin + * @version 1.1, 17/07/09 + * @since V1.1 + */ +@Table(name="ACLUSERGROUPENTRY") +@Entity +@IdClass(com.docdoku.core.security.ACLUserGroupEntryKey.class) +public class ACLUserGroupEntry implements Serializable, Cloneable { + + @Id + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumn(name="ACL_ID", referencedColumnName="ID") + protected ACL acl; + + @Id + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "PRINCIPAL_ID", referencedColumnName = "ID"), + @JoinColumn(name = "PRINCIPAL_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private UserGroup principal; + + private Permission permission; + + public ACLUserGroupEntry(){ + + } + + public ACLUserGroupEntry(ACL acl, UserGroup principal, Permission permission) { + setACL(acl); + setPrincipal(principal); + setPermission(permission); + } + + public void setACL(ACL pACL) { + this.acl = pACL; + } + + @XmlTransient + public ACL getAcl() { + return acl; + } + + public void setPermission(Permission permission) { + this.permission = permission; + } + + public void setPrincipal(UserGroup pPrincipal) { + this.principal = pPrincipal; + } + + public Permission getPermission() { + return permission; + } + + public UserGroup getPrincipal() { + return principal; + } + + public String getPrincipalId() { + return principal.getId(); + } + + + @Override + public ACLUserGroupEntry clone() { + ACLUserGroupEntry clone = null; + try { + clone = (ACLUserGroupEntry) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + return clone; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/security/ACLUserGroupEntryKey.java b/docdoku-common/src/main/java/com/docdoku/core/security/ACLUserGroupEntryKey.java new file mode 100644 index 0000000000..d9dd8c14d7 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/security/ACLUserGroupEntryKey.java @@ -0,0 +1,83 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.security; + +import com.docdoku.core.common.UserGroupKey; + +import java.io.Serializable; + +/** + * + * @author Florent Garin + */ +public class ACLUserGroupEntryKey implements Serializable { + + private UserGroupKey principal; + private int acl; + + public ACLUserGroupEntryKey() { + } + + public ACLUserGroupEntryKey(int acl, UserGroupKey principal) { + this.acl = acl; + this.principal = principal; + } + + public int getAcl() { + return acl; + } + + public UserGroupKey getPrincipal() { + return principal; + } + + public void setAcl(int aclId) { + this.acl = aclId; + } + + public void setPrincipal(UserGroupKey principal) { + this.principal = principal; + } + + @Override + public String toString() { + return acl + "/" + principal; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof ACLUserGroupEntryKey)) { + return false; + } + ACLUserGroupEntryKey key = (ACLUserGroupEntryKey) pObj; + return key.acl==acl && key.principal.equals(principal); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + principal.hashCode(); + hash = 31 * hash + acl; + return hash; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/security/Credential.java b/docdoku-common/src/main/java/com/docdoku/core/security/Credential.java new file mode 100644 index 0000000000..a9a3bda60e --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/security/Credential.java @@ -0,0 +1,93 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.security; + +import javax.persistence.Table; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Useful class for storing credential, login/password pair, to the persistence + * storage. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="CREDENTIAL") +@javax.persistence.Entity +public class Credential implements java.io.Serializable { + + + @javax.persistence.Id + private String login=""; + + private String password; + + private static final Logger LOGGER = Logger.getLogger(Credential.class.getName()); + + public Credential() { + } + + public static Credential createCredential(String pLogin, String pClearPassword){ + Credential credential = new Credential(); + credential.login = pLogin; + try { + credential.password=md5Sum(pClearPassword); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException pEx) { + LOGGER.log(Level.SEVERE, null, pEx); + } + return credential; + } + + private static String md5Sum(String pText) throws NoSuchAlgorithmException, UnsupportedEncodingException { + byte[] digest = MessageDigest.getInstance("MD5").digest(pText.getBytes("UTF-8")); + StringBuffer hexString = new StringBuffer(); + for (byte aDigest : digest) { + String hex = Integer.toHexString(0xFF & aDigest); + if (hex.length() == 1) { + hexString.append("0" + hex); + } else { + hexString.append(hex); + } + } + return hexString.toString(); + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/security/PasswordRecoveryRequest.java b/docdoku-common/src/main/java/com/docdoku/core/security/PasswordRecoveryRequest.java new file mode 100644 index 0000000000..e7c3bbb300 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/security/PasswordRecoveryRequest.java @@ -0,0 +1,93 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.security; + +import javax.persistence.Column; +import javax.persistence.Table; +import java.util.UUID; + +/** + * Represents a password recovery request. This class makes the link between + * the UUID of the request and the user who asked it. + * + * @author Florent Garin + * @version 1.0, 01/04/11 + * @since V1.0 + */ +@Table(name="PASSWORDRECOVERYREQUEST") +@javax.persistence.Entity +public class PasswordRecoveryRequest implements java.io.Serializable { + + @Column(length = 255) + @javax.persistence.Id + private String uuid=""; + + private String login; + + + public PasswordRecoveryRequest() { + } + + + public static PasswordRecoveryRequest createPasswordRecoveryRequest(String login){ + PasswordRecoveryRequest passwdRR = new PasswordRecoveryRequest(); + passwdRR.setLogin(login); + passwdRR.setUuid(UUID.randomUUID().toString()); + return passwdRR; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String toString() { + return uuid; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof PasswordRecoveryRequest)) + return false; + PasswordRecoveryRequest passwdRR = (PasswordRecoveryRequest) pObj; + return passwdRR.uuid.equals(uuid); + } + + @Override + public int hashCode() { + return uuid.hashCode(); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/security/UserGroupMapping.java b/docdoku-common/src/main/java/com/docdoku/core/security/UserGroupMapping.java new file mode 100644 index 0000000000..d0db5f574f --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/security/UserGroupMapping.java @@ -0,0 +1,72 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.security; + +import javax.persistence.Table; + +/** + * Useful class for adding users to a security group. + * Actually, all users belong to, and only to, the "users" group. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="USERGROUPMAPPING") +@javax.persistence.Entity +public class UserGroupMapping implements java.io.Serializable { + + @javax.persistence.Id + private String login=""; + private String groupName; + + public static final String REGULAR_USER_ROLE_ID="users"; + public static final String ADMIN_ROLE_ID ="admin"; + public static final String GUEST_PROXY_ROLE_ID ="guest-proxy"; + + public UserGroupMapping() { + } + + public UserGroupMapping(String pLogin) { + this(pLogin,REGULAR_USER_ROLE_ID); + } + + public UserGroupMapping(String pLogin, String pRole) { + login=pLogin; + groupName=pRole; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/security/WorkspaceUserGroupMembership.java b/docdoku-common/src/main/java/com/docdoku/core/security/WorkspaceUserGroupMembership.java new file mode 100644 index 0000000000..bf291fc35c --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/security/WorkspaceUserGroupMembership.java @@ -0,0 +1,107 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.security; + +import com.docdoku.core.common.UserGroup; +import com.docdoku.core.common.Workspace; + +import javax.persistence.*; +import java.io.Serializable; + +/** + * Class that holds information on how a specific user group belongs to a + * workspace. + * + * @author Florent Garin + * @version 1.1, 08/07/09 + * @since V1.1 + */ +@Table(name="WORKSPACEUSERGROUPMEMBERSHIP") +@javax.persistence.IdClass(com.docdoku.core.security.WorkspaceUserGroupMembershipKey.class) +@javax.persistence.Entity +public class WorkspaceUserGroupMembership implements Serializable { + + @javax.persistence.Column(name = "WORKSPACE_ID", length=100, nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String workspaceId = ""; + @javax.persistence.ManyToOne(optional = false, fetch = FetchType.EAGER) + private Workspace workspace; + @javax.persistence.Column(name = "MEMBER_WORKSPACE_ID", length=100, nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String memberWorkspaceId = ""; + @javax.persistence.Column(name = "MEMBER_ID", nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String memberId = ""; + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "MEMBER_ID", referencedColumnName = "ID"), + @JoinColumn(name = "MEMBER_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private UserGroup member; + + private boolean readOnly; + + public WorkspaceUserGroupMembership() { + } + + public WorkspaceUserGroupMembership(Workspace pWorkspace, UserGroup pMember) { + setWorkspace(pWorkspace); + setMember(pMember); + } + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + public boolean isReadOnly() { + return readOnly; + } + + public void setWorkspace(Workspace pWorkspace) { + workspace = pWorkspace; + workspaceId = workspace.getId(); + } + + public void setMember(UserGroup pMember) { + this.member = pMember; + this.memberId=member.getId(); + this.memberWorkspaceId=member.getWorkspaceId(); + } + + public String getWorkspaceId() { + return workspaceId; + } + + public Workspace getWorkspace() { + return workspace; + } + + public UserGroup getMember() { + return member; + } + + public String getMemberId() { + return memberId; + } + + public String getMemberWorkspaceId() { + return memberWorkspaceId; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/security/WorkspaceUserGroupMembershipKey.java b/docdoku-common/src/main/java/com/docdoku/core/security/WorkspaceUserGroupMembershipKey.java new file mode 100644 index 0000000000..bb1700f31f --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/security/WorkspaceUserGroupMembershipKey.java @@ -0,0 +1,95 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.security; + +import java.io.Serializable; + +/** + * + * @author Florent Garin + */ +public class WorkspaceUserGroupMembershipKey implements Serializable { + + private String memberWorkspaceId; + private String memberId; + private String workspaceId; + + public WorkspaceUserGroupMembershipKey() { + } + + public WorkspaceUserGroupMembershipKey(String pWorkspaceId, String pMemberWorkspaceId, String pMemberId) { + workspaceId = pWorkspaceId; + memberWorkspaceId = pMemberWorkspaceId; + memberId = pMemberId; + } + + public String getMemberId() { + return memberId; + } + + public String getMemberWorkspaceId() { + return memberWorkspaceId; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setMemberId(String memberId) { + this.memberId = memberId; + } + + public void setMemberWorkspaceId(String memberWorkspaceId) { + this.memberWorkspaceId = memberWorkspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + + + @Override + public String toString() { + return workspaceId + "/" + memberWorkspaceId + "-" + memberId; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof WorkspaceUserGroupMembershipKey)) { + return false; + } + WorkspaceUserGroupMembershipKey key = (WorkspaceUserGroupMembershipKey) pObj; + return key.workspaceId.equals(workspaceId) && key.memberWorkspaceId.equals(memberWorkspaceId) && key.memberId.equals(memberId); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + memberWorkspaceId.hashCode(); + hash = 31 * hash + memberId.hashCode(); + + return hash; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/security/WorkspaceUserMembership.java b/docdoku-common/src/main/java/com/docdoku/core/security/WorkspaceUserMembership.java new file mode 100644 index 0000000000..bb0ba198ff --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/security/WorkspaceUserMembership.java @@ -0,0 +1,112 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.security; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; + +import javax.persistence.*; +import java.io.Serializable; + +/** + * Class that holds information on how a specific user belongs to a workspace. + * + * + * @author Florent Garin + * @version 1.1, 08/07/09 + * @since V1.1 + */ +@Table(name="WORKSPACEUSERMEMBERSHIP") +@javax.persistence.IdClass(com.docdoku.core.security.WorkspaceUserMembershipKey.class) +@javax.persistence.Entity +@NamedQueries({ + @NamedQuery(name="findCommonWorkspacesForGivenUsers", query="SELECT w FROM Workspace w WHERE EXISTS (SELECT u1.workspace FROM User u1, User u2 WHERE u1.workspace = u2.workspace AND u1.login = :userLogin1 AND u2.login = :userLogin2 AND u1.workspace IS NOT NULL)") +}) +public class WorkspaceUserMembership implements Serializable { + + @javax.persistence.Column(name = "WORKSPACE_ID", length=100, nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String workspaceId = ""; + @javax.persistence.ManyToOne(optional = false, fetch = FetchType.EAGER) + private Workspace workspace; + @javax.persistence.Column(name = "MEMBER_WORKSPACE_ID", length=100, nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String memberWorkspaceId = ""; + @javax.persistence.Column(name = "MEMBER_LOGIN", nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String memberLogin = ""; + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "MEMBER_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "MEMBER_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User member; + + private boolean readOnly; + + public WorkspaceUserMembership() { + } + + public WorkspaceUserMembership(Workspace pWorkspace, User pMember) { + setWorkspace(pWorkspace); + setMember(pMember); + } + + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + public boolean isReadOnly() { + return readOnly; + } + + public void setWorkspace(Workspace pWorkspace) { + workspace = pWorkspace; + workspaceId = workspace.getId(); + } + + public String getWorkspaceId() { + return workspaceId; + } + + public Workspace getWorkspace() { + return workspace; + } + + public void setMember(User pMember) { + this.member = pMember; + this.memberLogin=member.getLogin(); + this.memberWorkspaceId=member.getWorkspaceId(); + } + + public String getMemberLogin() { + return memberLogin; + } + + + public User getMember() { + return member; + } + + public String getMemberWorkspaceId() { + return memberWorkspaceId; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/security/WorkspaceUserMembershipKey.java b/docdoku-common/src/main/java/com/docdoku/core/security/WorkspaceUserMembershipKey.java new file mode 100644 index 0000000000..d75b08da50 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/security/WorkspaceUserMembershipKey.java @@ -0,0 +1,95 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.security; + +import java.io.Serializable; + +/** + * + * @author Florent Garin + */ +public class WorkspaceUserMembershipKey implements Serializable { + + private String memberWorkspaceId; + private String memberLogin; + private String workspaceId; + + public WorkspaceUserMembershipKey() { + } + + public WorkspaceUserMembershipKey(String pWorkspaceId, String pMemberWorkspaceId, String pMemberLogin) { + workspaceId = pWorkspaceId; + memberWorkspaceId = pMemberWorkspaceId; + memberLogin = pMemberLogin; + } + + public String getMemberLogin() { + return memberLogin; + } + + public String getMemberWorkspaceId() { + return memberWorkspaceId; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setMemberLogin(String memberLogin) { + this.memberLogin = memberLogin; + } + + public void setMemberWorkspaceId(String memberWorkspaceId) { + this.memberWorkspaceId = memberWorkspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + + + @Override + public String toString() { + return workspaceId + "/" + memberWorkspaceId + "-" + memberLogin; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof WorkspaceUserMembershipKey)) { + return false; + } + WorkspaceUserMembershipKey key = (WorkspaceUserMembershipKey) pObj; + return key.workspaceId.equals(workspaceId) && key.memberWorkspaceId.equals(memberWorkspaceId) && key.memberLogin.equals(memberLogin); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + memberWorkspaceId.hashCode(); + hash = 31 * hash + memberLogin.hashCode(); + + return hash; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IAccountManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IAccountManagerLocal.java new file mode 100644 index 0000000000..12b4ecb2c2 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IAccountManagerLocal.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.Organization; +import com.docdoku.core.exceptions.*; + +/** + * + * @author Elisabel Généreux + */ +public interface IAccountManagerLocal { + + /** + * Get the account matching a login. ONLY USE IN LOCAL. + * @param pLogin Login you search + * @return The account matching the login + * @throws AccountNotFoundException If no account was found + */ + Account getAccount(String pLogin) throws AccountNotFoundException; + String getRole(String pLogin); + + Account createAccount(String pLogin, String pName, String pEmail, String pLanguage, String pPassword, String pTimeZone) throws AccountAlreadyExistsException, CreationException; + Account updateAccount(String pName, String pEmail, String pLanguage, String pPassword, String pTimeZone) throws AccountNotFoundException; + + Account getMyAccount() throws AccountNotFoundException; + + Account checkAdmin(Organization pOrganization) throws AccessRightException, AccountNotFoundException; + Account checkAdmin(String pOrganizationName) throws AccessRightException, AccountNotFoundException, OrganizationNotFoundException; + + void setGCMAccount(String gcmId) throws AccountNotFoundException, GCMAccountAlreadyExistsException, CreationException; + void deleteGCMAccount() throws AccountNotFoundException, GCMAccountNotFoundException; + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/ICascadeActionManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/ICascadeActionManagerLocal.java new file mode 100644 index 0000000000..47a2e92760 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/ICascadeActionManagerLocal.java @@ -0,0 +1,37 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.services; + +import com.docdoku.core.configuration.CascadeResult; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.ConfigurationItemKey; + +/** + * @author Charles Fallourd on 10/02/16. + */ +public interface ICascadeActionManagerLocal { + + CascadeResult cascadeCheckout(ConfigurationItemKey configurationItemKey, String path) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException, PartUsageLinkNotFoundException, ConfigurationItemNotFoundException; + + CascadeResult cascadeUndocheckout(ConfigurationItemKey configurationItemKey, String path) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException, PartUsageLinkNotFoundException, ConfigurationItemNotFoundException; + + CascadeResult cascadeCheckin(ConfigurationItemKey configurationItemKey, String path, String iterationNote) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartMasterNotFoundException, EntityConstraintException, NotAllowedException, PartUsageLinkNotFoundException, ConfigurationItemNotFoundException; +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IChangeManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IChangeManagerLocal.java new file mode 100644 index 0000000000..1af5aad819 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IChangeManagerLocal.java @@ -0,0 +1,93 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.change.*; +import com.docdoku.core.document.DocumentIterationKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartIterationKey; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * + * @author Florent Garin + */ +public interface IChangeManagerLocal { + ChangeIssue getChangeIssue(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException; + List<ChangeIssue> getChangeIssues(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + List<ChangeIssue> getIssuesWithReference(String workspaceId, String q, int maxResults) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + ChangeIssue createChangeIssue(String pWorkspaceId, String name, String description, String initiator, ChangeItem.Priority priority, String assignee, ChangeItem.Category category) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException; + ChangeIssue updateChangeIssue(int pId, String pWorkspaceId, String description, ChangeItem.Priority priority, String assignee, ChangeItem.Category category) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException; + void deleteChangeIssue(int pId) throws ChangeIssueNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, EntityConstraintException; + ChangeIssue saveChangeIssueAffectedDocuments(String pWorkspaceId, int pId, DocumentIterationKey[] pAffectedDocuments) throws UserNotActiveException, UserNotFoundException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException, DocumentRevisionNotFoundException; + ChangeIssue saveChangeIssueAffectedParts(String pWorkspaceId, int pId, PartIterationKey[] pAffectedParts) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException; + ChangeIssue saveChangeIssueTags(String pWorkspaceId, int pId, String[] tagsLabel) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException; + ChangeIssue removeChangeIssueTag(String workspaceId, int pId, String tagName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException; + + ChangeRequest getChangeRequest(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException; + List<ChangeRequest> getChangeRequests(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + List<ChangeRequest> getRequestsWithReference(String workspaceId, String q, int maxResults) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + ChangeRequest createChangeRequest(String pWorkspaceId, String name, String description, int milestone, ChangeItem.Priority priority, String assignee, ChangeItem.Category category) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException; + ChangeRequest updateChangeRequest(int pId, String pWorkspaceId, String description, int milestoneId, ChangeItem.Priority priority, String assignee, ChangeItem.Category category) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException; + void deleteChangeRequest(String pWorkspaceId, int pId) throws ChangeRequestNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, EntityConstraintException; + ChangeRequest saveChangeRequestAffectedDocuments(String workspaceId, int requestId, DocumentIterationKey[] links) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException, DocumentRevisionNotFoundException; + ChangeRequest saveChangeRequestAffectedParts(String workspaceId, int requestId, PartIterationKey[] links) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException; + ChangeRequest saveChangeRequestAffectedIssues(String pWorkspaceId, int pRequestId, int[] pLinkId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException; + ChangeRequest saveChangeRequestTags(String pWorkspaceId, int pId, String[] tagsLabel) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException; + ChangeRequest removeChangeRequestTag(String workspaceId, int pId, String tagName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException; + + ChangeOrder getChangeOrder(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException; + List<ChangeOrder> getChangeOrders(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + ChangeOrder createChangeOrder(String pWorkspaceId, String name, String description, int milestone, ChangeItem.Priority priority, String assignee, ChangeItem.Category category) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException; + ChangeOrder updateChangeOrder(int pId, String pWorkspaceId, String description, int milestoneId, ChangeItem.Priority priority, String assignee, ChangeItem.Category category) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException; + void deleteChangeOrder(int pId) throws ChangeOrderNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException; + ChangeOrder saveChangeOrderAffectedDocuments(String workspaceId, int pOrderId, DocumentIterationKey[] links) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException, DocumentRevisionNotFoundException; + ChangeOrder saveChangeOrderAffectedParts(String workspaceId, int pOrderId, PartIterationKey[] links) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException; + ChangeOrder saveChangeOrderAffectedRequests(String pWorkspaceId, int pOrderId, int[] pLinkId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException; + ChangeOrder saveChangeOrderTags(String pWorkspaceId, int pId, String[] tagsLabel) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException; + ChangeOrder removeChangeOrderTag(String workspaceId, int pId, String tagName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException; + + Milestone getMilestone(String pWorkspaceId, int milestoneId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, MilestoneNotFoundException, AccessRightException; + Milestone getMilestoneByTitle(String pWorkspaceId, String pTitle) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, MilestoneNotFoundException, AccessRightException; + List<Milestone> getMilestones(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + Milestone createMilestone(String pWorkspaceId, String title, String description, Date dueDate) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, MilestoneAlreadyExistsException; + Milestone updateMilestone(int milestoneId, String pWorkspaceId, String title, String description, Date dueDate) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, MilestoneNotFoundException, AccessRightException; + void deleteMilestone(String pWorkspaceId, int milestoneId) throws MilestoneNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, EntityConstraintException; + List<ChangeRequest> getChangeRequestsByMilestone(String pWorkspaceId, int milestoneId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, MilestoneNotFoundException, AccessRightException; + List<ChangeOrder> getChangeOrdersByMilestone(String pWorkspaceId, int milestoneId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, MilestoneNotFoundException, AccessRightException; + int getNumberOfRequestByMilestone(String pWorkspaceId, int milestoneId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + int getNumberOfOrderByMilestone(String pWorkspaceId, int milestoneId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + void updateACLForChangeIssue(String pWorkspaceId, int pId, Map<String, String> pUserEntries, Map<String, String> pGroupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException; + void updateACLForChangeRequest(String pWorkspaceId, int pId, Map<String, String> pUserEntries, Map<String, String> pGroupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException; + void updateACLForChangeOrder(String pWorkspaceId, int pId, Map<String, String> pUserEntries, Map<String, String> pGroupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException; + void updateACLForMilestone(String pWorkspaceId, int pId, Map<String, String> pUserEntries, Map<String, String> pGroupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, MilestoneNotFoundException, AccessRightException; + + void removeACLFromChangeIssue(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException; + void removeACLFromChangeRequest(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException; + void removeACLFromChangeOrder(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException; + void removeACLFromMilestone(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, MilestoneNotFoundException, AccessRightException; + + boolean isChangeItemWritable(ChangeItem pChangeItem) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + boolean isMilestoneWritable(Milestone pMilestone) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IContextManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IContextManagerLocal.java new file mode 100644 index 0000000000..8df5930527 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IContextManagerLocal.java @@ -0,0 +1,31 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.services; + +/** + * + * @author Morgan Guimard + */ +public interface IContextManagerLocal { + boolean isCallerInRole(String role); + String getCallerPrincipalLogin(); + String getCallerPrincipalName(); +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IConverterManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IConverterManagerLocal.java new file mode 100644 index 0000000000..8ec04f658f --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IConverterManagerLocal.java @@ -0,0 +1,32 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.services; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.product.PartIterationKey; + +/** + * + * @author Florent Garin + */ +public interface IConverterManagerLocal { + void convertCADFileToOBJ(PartIterationKey pPartIPK, BinaryResource cadFile) throws Exception; +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IDataManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IDataManagerLocal.java new file mode 100644 index 0000000000..290c8a4d10 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IDataManagerLocal.java @@ -0,0 +1,45 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.services; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.FileNotFoundException; +import com.docdoku.core.exceptions.StorageException; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Date; +import java.util.List; + +public interface IDataManagerLocal { + InputStream getBinaryResourceInputStream(BinaryResource binaryResource) throws StorageException; + InputStream getBinarySubResourceInputStream(BinaryResource binaryResource, String subResourceVirtualPath) throws StorageException; + OutputStream getBinaryResourceOutputStream(BinaryResource binaryResource) throws StorageException; + OutputStream getBinarySubResourceOutputStream(BinaryResource binaryResource, String subResourceVirtualPath) throws StorageException; + boolean exists(BinaryResource binaryResource, String subResourceVirtualPath) throws StorageException; + void copyData(BinaryResource source, BinaryResource destination) throws StorageException; + void deleteData(BinaryResource binaryResource) throws StorageException; + void renameFile(BinaryResource binaryResource, String pNewName) throws StorageException, FileNotFoundException; + Date getLastModified(BinaryResource binaryResource, String subResourceVirtualPath) throws StorageException; + String getExternalStorageURI(BinaryResource binaryResource); + String getShortenExternalStorageURI(BinaryResource binaryResource); + void deleteWorkspaceFolder(String workspaceId, List<BinaryResource> binaryResourcesInWorkspace) throws StorageException; +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentBaselineManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentBaselineManagerLocal.java new file mode 100644 index 0000000000..05bcf378e4 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentBaselineManagerLocal.java @@ -0,0 +1,77 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.configuration.DocumentBaseline; +import com.docdoku.core.exceptions.*; + +import java.util.List; + +/** + * + * @author Taylor LABEJOF + * @version 2.0, 26/08/14 + * @since V2.0 + */ +public interface IDocumentBaselineManagerLocal { + /** + * Create a new {@link com.docdoku.core.configuration.DocumentBaseline} to snap the latest configuration of folder and documents + * @param workspaceId The id of the workspace to snap + * @param name The name of the new baseline + * @param description The description of the new baseline + * @return The new {@link com.docdoku.core.configuration.DocumentBaseline} + * @throws com.docdoku.core.exceptions.UserNotFoundException If no user is connected to this workspace + * @throws com.docdoku.core.exceptions.AccessRightException If you cann't access to this workspace + * @throws com.docdoku.core.exceptions.WorkspaceNotFoundException If the workspace cann't be found + * @throws com.docdoku.core.exceptions.FolderNotFoundException If a folder of the configuration cann't be find + * @throws com.docdoku.core.exceptions.UserNotActiveException If the connected user is disable + * @throws com.docdoku.core.exceptions.DocumentRevisionNotFoundException If a document revision of the configuration cann't be find + */ + DocumentBaseline createBaseline(String workspaceId, String name, String description) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, FolderNotFoundException, UserNotActiveException, DocumentRevisionNotFoundException; + /** + * Get all {@link com.docdoku.core.configuration.DocumentBaseline}s of a specific workspace + * @param workspaceId Id of the specific workspace + * @return The list of {@link com.docdoku.core.configuration.DocumentBaseline}s of the specif workspace + * @throws com.docdoku.core.exceptions.UserNotFoundException If no user is connected to this workspace + * @throws com.docdoku.core.exceptions.UserNotActiveException If the connected user is disable + * @throws com.docdoku.core.exceptions.WorkspaceNotFoundException If the workspace cann't be found + */ + List<DocumentBaseline> getBaselines(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + /** + * Delete a specific {@link com.docdoku.core.configuration.DocumentBaseline}. + * @param baselineId The id of the baseline to deleted + * @throws com.docdoku.core.exceptions.UserNotFoundException If no user is connected to this workspace + * @throws com.docdoku.core.exceptions.AccessRightException If you cann't access to this workspace + * @throws com.docdoku.core.exceptions.WorkspaceNotFoundException If the workspace cann't be found + * @throws com.docdoku.core.exceptions.BaselineNotFoundException If the baseline cann't be found + * @throws com.docdoku.core.exceptions.UserNotActiveException If the connected user is disable + */ + void deleteBaseline(int baselineId) throws BaselineNotFoundException, UserNotFoundException, AccessRightException, WorkspaceNotFoundException, UserNotActiveException; + /** + * Get a specific {@link com.docdoku.core.configuration.DocumentBaseline}. + * @param baselineId The id of the require baseline. + * @return The require {@link com.docdoku.core.configuration.DocumentBaseline}. + * @throws com.docdoku.core.exceptions.BaselineNotFoundException If the baseline cann't be found + * @throws com.docdoku.core.exceptions.UserNotFoundException If no user is connected to this workspace + * @throws com.docdoku.core.exceptions.UserNotActiveException If the connected user is disable + * @throws com.docdoku.core.exceptions.WorkspaceNotFoundException If the workspace cann't be found + */ + DocumentBaseline getBaseline(int baselineId) throws BaselineNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + } diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentConfigSpecManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentConfigSpecManagerLocal.java new file mode 100644 index 0000000000..356d9d8af3 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentConfigSpecManagerLocal.java @@ -0,0 +1,134 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.configuration.DocumentConfigSpec; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.meta.TagKey; +import com.docdoku.core.query.DocumentSearchQuery; + +/** + * + * @author Taylor LABEJOF + * @version 2.0, 26/08/14 + * @since V2.0 + */ +public interface IDocumentConfigSpecManagerLocal { + /** + * Get the {@link com.docdoku.core.configuration.DocumentConfigSpec} for a specific workspace + * @param workspaceId The specific workspace + * @return The LatestConfigSpec of the specific workspace + * @throws UserNotFoundException If no user is connected to this workspace + * @throws UserNotActiveException If the connected user is disable + * @throws WorkspaceNotFoundException If the workspace cann't be found + */ + DocumentConfigSpec getLatestConfigSpec(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + /** + * Get the {@link com.docdoku.core.configuration.DocumentConfigSpec} for a specific baseline + * @param baselineId The specific baseline + * @return The LatestConfigSpec of the specific workspace + * @throws UserNotFoundException If no user is connected to this workspace + * @throws UserNotActiveException If the connected user is disable + * @throws WorkspaceNotFoundException If the workspace cann't be found + * @throws BaselineNotFoundException If the baseline cann't be found + */ + DocumentConfigSpec getConfigSpecForBaseline(int baselineId) throws BaselineNotFoundException, WorkspaceNotFoundException, UserNotActiveException, UserNotFoundException; + + /** + * Get the list of folder filtered by a configuration specification + * @param workspaceId Workspace of the confspec + * @param cs The current confSpec + * @param completePath The complete path of the parent folder + * @return The first level of subfolder filtered by a confSpec + * @throws UserNotFoundException + * @throws UserNotActiveException + * @throws WorkspaceNotFoundException + */ + String[] getFilteredFolders(String workspaceId, DocumentConfigSpec cs, String completePath) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + /** + * Get the list of documents filtered by a configuration specification and a tag + * @param workspaceId Workspace of the confspec + * @param cs The current confSpec + * @param start Start with the start'th result + * @param pMaxResults Number of result max + * @return All documents with the tag filtered by a confSpec + * @throws UserNotFoundException + * @throws UserNotActiveException + * @throws WorkspaceNotFoundException + * @throws DocumentRevisionNotFoundException + */ + DocumentRevision[] getFilteredDocuments(String workspaceId, DocumentConfigSpec cs, int start, int pMaxResults) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException; + + /** + * Get the list of documents filtered by a configuration specification and a folder + * @param workspaceId Workspace of the confspec + * @param cs The current confSpec + * @param completePath The complete path of the folder + * @return All documents of the folder filtered by a confSpec + * @throws UserNotFoundException + * @throws UserNotActiveException + * @throws WorkspaceNotFoundException + */ + DocumentRevision[] getFilteredDocumentsByFolder(String workspaceId, DocumentConfigSpec cs, String completePath) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + /** + * Get the list of documents filtered by a configuration specification and a tag + * @param workspaceId Workspace of the confspec + * @param cs The current confSpec + * @param tagKey The key of a specific tag + * @throws UserNotFoundException + * @throws UserNotActiveException + * @throws WorkspaceNotFoundException + * @throws DocumentRevisionNotFoundException + * @return All documents with the tag filtered by a confSpec + */ + DocumentRevision[] getFilteredDocumentsByTag(String workspaceId, DocumentConfigSpec cs, TagKey tagKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException; + + /** + * Get the list of documents filtered by a configuration specification and a query + * @param workspaceId Workspace of the confspec + * @param cs The current confSpec + * @param pQuery The search query + * @throws UserNotFoundException + * @throws UserNotActiveException + * @throws WorkspaceNotFoundException + * @throws DocumentRevisionNotFoundException + * @throws ESServerException + * @return All documents with the tag filtered by a confSpec + */ + DocumentRevision[] searchFilteredDocuments(String workspaceId, DocumentConfigSpec cs, DocumentSearchQuery pQuery) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, ESServerException; + + /** + * Get a document revision filtered by a configuration specification + * @param documentRevisionKey The document revision wanted + * @param configSpec The current confSpec + * @throws AccessRightException + * @throws NotAllowedException + * @throws WorkspaceNotFoundException + * @throws UserNotFoundException + * @throws DocumentRevisionNotFoundException + * @throws UserNotActiveException + * @return The document revision without the iteration following the baselined document + */ + DocumentRevision getFilteredDocumentRevision(DocumentRevisionKey documentRevisionKey, DocumentConfigSpec configSpec) throws AccessRightException, NotAllowedException, WorkspaceNotFoundException, UserNotFoundException, DocumentRevisionNotFoundException, UserNotActiveException; +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentManagerLocal.java new file mode 100644 index 0000000000..460c02d331 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentManagerLocal.java @@ -0,0 +1,215 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.User; +import com.docdoku.core.document.*; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.log.DocumentLog; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.InstanceAttributeTemplate; +import com.docdoku.core.meta.TagKey; +import com.docdoku.core.query.DocumentSearchQuery; +import com.docdoku.core.security.ACLUserEntry; +import com.docdoku.core.security.ACLUserGroupEntry; +import com.docdoku.core.sharing.SharedDocument; +import com.docdoku.core.sharing.SharedEntityKey; +import com.docdoku.core.workflow.Task; + +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * + * @author Florent Garin + */ +public interface IDocumentManagerLocal { + + String generateId(String pWorkspaceId, String pDocMTemplateId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, DocumentMasterTemplateNotFoundException; + + DocumentRevision getDocumentRevision(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, DocumentRevisionNotFoundException, NotAllowedException, UserNotFoundException, UserNotActiveException, AccessRightException; + + DocumentRevision[] searchDocumentRevisions(DocumentSearchQuery pQuery) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, ESServerException; + + DocumentMasterTemplate getDocumentMasterTemplate(DocumentMasterTemplateKey pKey) throws WorkspaceNotFoundException, DocumentMasterTemplateNotFoundException, UserNotFoundException, UserNotActiveException; + + DocumentMasterTemplate[] getDocumentMasterTemplates(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + + DocumentRevision[] findDocumentRevisionsByFolder(String pCompletePath) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + + DocumentRevision[] findDocumentRevisionsByTag(TagKey pKey) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + + DocumentRevision[] getCheckedOutDocumentRevisions(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + + String[] getFolders(String pCompletePath) throws WorkspaceNotFoundException, FolderNotFoundException, UserNotFoundException, UserNotActiveException; + + String[] getTags(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + + Task[] getTasks(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + + BinaryResource saveFileInTemplate(DocumentMasterTemplateKey pDocMTemplateKey, String pName, long pSize) throws WorkspaceNotFoundException, NotAllowedException, DocumentMasterTemplateNotFoundException, FileAlreadyExistsException, UserNotFoundException, UserNotActiveException, CreationException, AccessRightException; + + BinaryResource saveFileInDocument(DocumentIterationKey pDocPK, String pName, long pSize) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, FileAlreadyExistsException, UserNotFoundException, UserNotActiveException, CreationException, AccessRightException; + + void setDocumentPublicShared(DocumentRevisionKey pDocRPK, boolean isPublicShared) throws AccessRightException, NotAllowedException, WorkspaceNotFoundException, UserNotFoundException, DocumentRevisionNotFoundException, UserNotActiveException; + + /** + * Returns the BinaryResource object given his Id. WARNING: You have to check access right before use it. + * + * @param fullName + * Id of the <a href="BinaryResource.html">BinaryResource</a> of which the + * data file will be returned + * + * @return + * The binary resource, a BinaryResource instance, that now needs to be created + * + * @throws UserNotFoundException + * @throws UserNotActiveException + * @throws WorkspaceNotFoundException + * @throws FileNotFoundException + * @throws NotAllowedException + * @throws AccessRightException + */ + BinaryResource getBinaryResource(String fullName) throws WorkspaceNotFoundException, NotAllowedException, FileNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException; + /** + * Returns the BinaryResource object given his Id. WARNING: You have to check access right before use it. + * + * @param fullName + * Id of the <a href="BinaryResource.html">BinaryResource</a> of which the + * data file will be returned + * + * @return + * The binary resource, a BinaryResource instance, that now needs to be created + * + * @throws UserNotFoundException + * @throws UserNotActiveException + * @throws WorkspaceNotFoundException + * @throws FileNotFoundException + */ + BinaryResource getTemplateBinaryResource(String fullName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, FileNotFoundException; + + DocumentRevision checkInDocument(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, AccessRightException, UserNotFoundException, UserNotActiveException, ESServerException; + + DocumentRevision checkOutDocument(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, AccessRightException, FileAlreadyExistsException, UserNotFoundException, UserNotActiveException, CreationException; + + DocumentRevision undoCheckOutDocument(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, DocumentRevisionNotFoundException, NotAllowedException, UserNotFoundException, UserNotActiveException, AccessRightException; + + void deleteDocumentRevision(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, AccessRightException, UserNotFoundException, UserNotActiveException, ESServerException, EntityConstraintException; + + DocumentRevision moveDocumentRevision(String pParentFolder, DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, DocumentRevisionNotFoundException, NotAllowedException, AccessRightException, FolderNotFoundException, UserNotFoundException, UserNotActiveException; + + Folder createFolder(String pParentFolder, String pFolder) throws WorkspaceNotFoundException, NotAllowedException, AccessRightException, FolderNotFoundException, FolderAlreadyExistsException, UserNotFoundException, CreationException; + + DocumentRevisionKey[] deleteFolder(String pCompletePath) throws WorkspaceNotFoundException, NotAllowedException, AccessRightException, UserNotFoundException, FolderNotFoundException, ESServerException, EntityConstraintException, UserNotActiveException, DocumentRevisionNotFoundException; + + DocumentRevisionKey[] deleteUserFolder(User user) throws WorkspaceNotFoundException, NotAllowedException, AccessRightException, UserNotFoundException, FolderNotFoundException, ESServerException, EntityConstraintException, UserNotActiveException, DocumentRevisionNotFoundException; + + DocumentRevisionKey[] moveFolder(String pCompletePath, String pDestParentFolder, String pDestFolder) throws WorkspaceNotFoundException, NotAllowedException, AccessRightException, UserNotFoundException, FolderNotFoundException, CreationException, FolderAlreadyExistsException; + + DocumentRevision updateDocument(DocumentIterationKey key, String revisionNote, List<InstanceAttribute> attributes, DocumentRevisionKey[] linkKeys, String[] documentLinkComments) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, AccessRightException, UserNotFoundException, UserNotActiveException; + + DocumentMasterTemplate updateDocumentMasterTemplate(DocumentMasterTemplateKey pKey, String pDocumentType, String pWorkflowModelId, String pMask, List<InstanceAttributeTemplate> pAttributeTemplates, String[] lovNames, boolean idGenerated, boolean attributesLocked) throws WorkspaceNotFoundException, AccessRightException, DocumentMasterTemplateNotFoundException, UserNotFoundException, WorkflowModelNotFoundException, UserNotActiveException, ListOfValuesNotFoundException, NotAllowedException; + + DocumentRevision[] createDocumentRevision(DocumentRevisionKey pOriginalDocRPK, String pTitle, String pDescription, String pWorkflowModelId, ACLUserEntry[] aclUserEntries, ACLUserGroupEntry[] aclUserGroupEntries, Map<String, Collection<String>> userRoleMapping, Map<String, Collection<String>> groupRoleMapping) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, NotAllowedException, DocumentRevisionAlreadyExistsException, CreationException, WorkflowModelNotFoundException, RoleNotFoundException, DocumentRevisionNotFoundException, FileAlreadyExistsException, UserGroupNotFoundException; + + DocumentRevision removeFileFromDocument(String pFullName) throws WorkspaceNotFoundException, DocumentRevisionNotFoundException, NotAllowedException, AccessRightException, FileNotFoundException, UserNotFoundException, UserNotActiveException; + + BinaryResource renameFileInDocument(String pFullName, String pNewName) throws WorkspaceNotFoundException, DocumentRevisionNotFoundException, NotAllowedException, AccessRightException, FileNotFoundException, UserNotFoundException, UserNotActiveException, FileAlreadyExistsException, CreationException, StorageException; + + DocumentMasterTemplate removeFileFromTemplate(String pFullName) throws WorkspaceNotFoundException, DocumentMasterTemplateNotFoundException, AccessRightException, FileNotFoundException, UserNotFoundException, UserNotActiveException, StorageException; + + BinaryResource renameFileInTemplate(String pFullName, String pNewName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, FileNotFoundException, AccessRightException, FileAlreadyExistsException, CreationException, StorageException; + + DocumentMasterTemplate createDocumentMasterTemplate(String pWorkspaceId, String pId, String pDocumentType, String pWorkflowModelId, String pMask, List<InstanceAttributeTemplate> pAttributeTemplates, String[] lovNames, boolean idGenerated, boolean attributesLocked) throws WorkspaceNotFoundException, AccessRightException, DocumentMasterTemplateAlreadyExistsException, UserNotFoundException, NotAllowedException, CreationException, WorkflowModelNotFoundException, ListOfValuesNotFoundException; + + void deleteDocumentMasterTemplate(DocumentMasterTemplateKey pKey) throws WorkspaceNotFoundException, AccessRightException, DocumentMasterTemplateNotFoundException, UserNotFoundException, UserNotActiveException; + + void deleteTag(TagKey pKey) throws WorkspaceNotFoundException, AccessRightException, TagNotFoundException, UserNotFoundException; + + void createTag(String pWorkspaceId, String pLabel) throws WorkspaceNotFoundException, AccessRightException, CreationException, TagAlreadyExistsException, UserNotFoundException, UserNotActiveException; + + DocumentRevision saveTags(DocumentRevisionKey pDocRPK, String[] pTags) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, AccessRightException, UserNotFoundException, UserNotActiveException, ESServerException; + + DocumentRevisionKey[] getIterationChangeEventSubscriptions(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + + DocumentRevisionKey[] getStateChangeEventSubscriptions(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + + void subscribeToIterationChangeEvent(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException; + + void subscribeToStateChangeEvent(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException; + + void unsubscribeToIterationChangeEvent(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException, DocumentRevisionNotFoundException; + + void unsubscribeToStateChangeEvent(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException, DocumentRevisionNotFoundException; + + boolean isUserStateChangeEventSubscribedForGivenDocument(String pWorkspaceId, DocumentRevision docR) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + boolean isUserIterationChangeEventSubscribedForGivenDocument(String pWorkspaceId, DocumentRevision docR) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + DocumentRevision[] getDocumentRevisionsWithReferenceOrTitle(String pWorkspaceId, String search, int maxResults) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + + int getTotalNumberOfDocuments(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException; + + long getDiskUsageForDocumentsInWorkspace(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException; + + long getDiskUsageForDocumentTemplatesInWorkspace(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException; + + DocumentRevision createDocumentMaster(String pParentFolder, String pDocMId, String pTitle, String pDescription, String pDocMTemplateId, String pWorkflowModelId, ACLUserEntry[] pACLUserEntries, ACLUserGroupEntry[] pACLUserGroupEntries, Map<String, Collection<String>> userRoleMapping, Map<String, Collection<String>> groupRoleMapping) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, NotAllowedException, FolderNotFoundException, DocumentMasterTemplateNotFoundException, FileAlreadyExistsException, CreationException, DocumentRevisionAlreadyExistsException, RoleNotFoundException, WorkflowModelNotFoundException, DocumentMasterAlreadyExistsException, UserGroupNotFoundException; + + DocumentRevision[] getAllCheckedOutDocumentRevisions(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException; + + SharedDocument createSharedDocument(DocumentRevisionKey pDocRPK, String pPassword, Date pExpireDate) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, UserNotActiveException, NotAllowedException; + + void deleteSharedDocument(SharedEntityKey sharedEntityKey) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, SharedEntityNotFoundException; + + DocumentIteration findDocumentIterationByBinaryResource(BinaryResource pBinaryResource) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + void updateDocumentACL(String pWorkspaceId, DocumentRevisionKey docKey, Map<String,String> userEntries, Map<String,String> userGroupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, AccessRightException, NotAllowedException; + void updateACLForDocumentMasterTemplate(String pWorkspaceId, String documentTemplateId, Map<String,String> userEntries, Map<String,String> userGroupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, NotAllowedException, DocumentMasterTemplateNotFoundException; + + void removeACLFromDocumentRevision(DocumentRevisionKey documentRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, AccessRightException; + void removeACLFromDocumentMasterTemplate(String pWorkspaceId,String documentTemplateId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, DocumentMasterTemplateNotFoundException; + + DocumentRevision[] getAllDocumentsInWorkspace(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + DocumentRevision[] getAllDocumentsInWorkspace(String workspaceId, int start, int pMaxResults) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + int getDocumentsInWorkspaceCount(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + DocumentRevision removeTag(DocumentRevisionKey pDocMPK, String pTag) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, DocumentRevisionNotFoundException, NotAllowedException, ESServerException; + + boolean canAccess(DocumentRevisionKey docRKey) throws DocumentRevisionNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + boolean canAccess(DocumentIterationKey docRKey) throws DocumentRevisionNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + boolean canUserAccess(User user, DocumentRevisionKey docRKey) throws DocumentRevisionNotFoundException; + boolean canUserAccess(User user, DocumentIterationKey docRKey) throws DocumentRevisionNotFoundException; + + List<DocumentIteration> getInverseDocumentsLink(DocumentRevisionKey docKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException; + + DocumentRevision[] getDocumentRevisionsWithAssignedTasksForGivenUser(String pWorkspaceId, String assignedUserLogin) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + DocumentRevision[] getDocumentRevisionsWithOpenedTasksForGivenUser(String pWorkspaceId, String assignedUserLogin) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + + void createDocumentLog(DocumentLog log); + + DocumentRevision releaseDocumentRevision(DocumentRevisionKey pRevisionKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, DocumentRevisionNotFoundException, AccessRightException, NotAllowedException; + DocumentRevision markDocumentRevisionAsObsolete(DocumentRevisionKey pRevisionKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, DocumentRevisionNotFoundException, AccessRightException, NotAllowedException; + +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentPostUploaderManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentPostUploaderManagerLocal.java new file mode 100644 index 0000000000..8d449745fe --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentPostUploaderManagerLocal.java @@ -0,0 +1,27 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.services; + +import com.docdoku.core.common.BinaryResource; + +public interface IDocumentPostUploaderManagerLocal { + void process(BinaryResource binaryResource); +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentResourceGetterManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentResourceGetterManagerLocal.java new file mode 100644 index 0000000000..f9f268f690 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentResourceGetterManagerLocal.java @@ -0,0 +1,39 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.services; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.ConvertedResourceException; +import com.docdoku.core.exceptions.UserNotActiveException; +import com.docdoku.core.exceptions.UserNotFoundException; +import com.docdoku.core.exceptions.WorkspaceNotFoundException; + +import java.io.InputStream; + +public interface IDocumentResourceGetterManagerLocal { + InputStream getDocumentConvertedResource(String outputFormat, BinaryResource binaryResource) + throws WorkspaceNotFoundException, UserNotActiveException, UserNotFoundException, ConvertedResourceException; + + InputStream getPartConvertedResource(String outputFormat, BinaryResource binaryResource) + throws WorkspaceNotFoundException, UserNotActiveException, UserNotFoundException, ConvertedResourceException; + + String getSubResourceVirtualPath(BinaryResource binaryResource, String subResourceUri); +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentWorkflowManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentWorkflowManagerLocal.java new file mode 100644 index 0000000000..dd733fd4ad --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IDocumentWorkflowManagerLocal.java @@ -0,0 +1,40 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.workflow.TaskKey; +import com.docdoku.core.workflow.Workflow; + +/** + * + * @author Taylor LABEJOF + * @version 2.0, 15/10/14 + * @since V2.0 + */ +public interface IDocumentWorkflowManagerLocal { + Workflow getCurrentWorkflow(DocumentRevisionKey documentRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, AccessRightException, WorkflowNotFoundException; + Workflow[] getAbortedWorkflow(DocumentRevisionKey documentRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, AccessRightException; + + DocumentRevision approveTaskOnDocument(String pWorkspaceId, TaskKey pTaskKey, String pComment, String pSignature) throws WorkspaceNotFoundException, TaskNotFoundException, NotAllowedException, UserNotFoundException, UserNotActiveException, WorkflowNotFoundException; + DocumentRevision rejectTaskOnDocument(String pWorkspaceId, TaskKey pTaskKey, String pComment, String pSignature) throws WorkspaceNotFoundException, TaskNotFoundException, NotAllowedException, UserNotFoundException, UserNotActiveException, WorkflowNotFoundException; +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IFileViewerManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IFileViewerManagerLocal.java new file mode 100644 index 0000000000..3bd0ca4d98 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IFileViewerManagerLocal.java @@ -0,0 +1,27 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.services; + +import com.docdoku.core.common.BinaryResource; + +public interface IFileViewerManagerLocal { + String getHtmlForViewer(BinaryResource file, String uuid); +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IGCMSenderLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IGCMSenderLocal.java new file mode 100644 index 0000000000..b95e33fcf1 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IGCMSenderLocal.java @@ -0,0 +1,33 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.services; + +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.gcm.GCMAccount; + +/** + * + * @author Morgan Guimard + */ +public interface IGCMSenderLocal { + public void sendStateNotification(GCMAccount[] pGCGcmAccounts, DocumentRevision pDocumentRevision); + void sendIterationNotification(GCMAccount[] pGCGcmAccounts, DocumentRevision pDocumentRevision); +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IImporterManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IImporterManagerLocal.java new file mode 100644 index 0000000000..2958ed32c5 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IImporterManagerLocal.java @@ -0,0 +1,40 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.product.ImportPreview; +import com.docdoku.core.product.ImportResult; + +import java.io.File; +import java.util.concurrent.Future; + +/** + * @author Elisabel Genereux + * @version 1.0.0 + * @since 11/02/16 + */ +public interface IImporterManagerLocal { + + Future<ImportResult> importIntoParts(String workspaceId, File file, String originalFileName, String revisionNote, boolean autoCheckout, boolean autoCheckin, boolean permissiveUpdate) throws Exception; + Future<ImportResult> importIntoPathData(String workspaceId, File file, String originalFileName, String revisionNote, boolean autoFreezeAfterUpdate, boolean permissiveUpdate) throws Exception; + ImportPreview dryRunImportIntoParts(String workspaceId, File file, String originalFileName, boolean autoCheckout, boolean autoCheckin, boolean permissiveUpdate) throws Exception; + + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/ILOVManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/ILOVManagerLocal.java new file mode 100644 index 0000000000..9f43f40677 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/ILOVManagerLocal.java @@ -0,0 +1,45 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.exceptions.*; +import com.docdoku.core.meta.ListOfValues; +import com.docdoku.core.meta.ListOfValuesKey; +import com.docdoku.core.meta.NameValuePair; + +import java.util.List; + +/** + * @author Julien Lebeau + */ +public interface ILOVManagerLocal { + + List<ListOfValues> findLOVFromWorkspace(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + ListOfValues findLov(ListOfValuesKey lovKey) throws ListOfValuesNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + void createLov(String workspaceId, String name, List<NameValuePair> nameValuePairList) throws ListOfValuesAlreadyExistsException, CreationException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException; + + void deleteLov(ListOfValuesKey lovKey) throws ListOfValuesNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, EntityConstraintException; + + ListOfValues updateLov(ListOfValuesKey lovKey, String name, String workspaceId, List<NameValuePair> nameValuePairList) throws ListOfValuesAlreadyExistsException, CreationException, ListOfValuesNotFoundException, UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException; + + boolean isLOVDeletable(ListOfValuesKey lovKey); +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IMailerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IMailerLocal.java new file mode 100644 index 0000000000..7ca615b8fc --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IMailerLocal.java @@ -0,0 +1,57 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.User; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.workflow.Task; + +import java.util.Collection; + +/** + * + * @author Florent Garin + */ +public interface IMailerLocal { + + void sendStateNotification(User[] pSubscribers, DocumentRevision pDocumentRevision); + + void sendIterationNotification(User[] pSubscribers, DocumentRevision pDocumentRevision); + + void sendApproval(Collection<Task> pRunningTasks, DocumentRevision pDocumentRevision); + + void sendPasswordRecovery(Account account, String passwordRRUuid); + + void sendApproval(Collection<Task> runningTasks, PartRevision partRevision); + + void sendWorkspaceDeletionNotification(Account admin, String workspaceId); + + void sendPartRevisionWorkflowRelaunchedNotification(PartRevision partRevision); + + void sendDocumentRevisionWorkflowRelaunchedNotification(DocumentRevision pDocumentRevision); + + void sendIndexerResult(Account account, String workspaceId, boolean hasSuccess, String pMessage); + + void sendCredential(Account account); + + void sendWorkspaceDeletionErrorNotification(Account admin, String workspaceId); +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IOrganizationManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IOrganizationManagerLocal.java new file mode 100644 index 0000000000..418dba91d1 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IOrganizationManagerLocal.java @@ -0,0 +1,40 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.services; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.Organization; +import com.docdoku.core.exceptions.*; + +/** + * + * @author Elisabel Généreux + */ +public interface IOrganizationManagerLocal { + + Organization getOrganizationOfAccount(String pLogin); + Organization createOrganization(String pName, String pDescription) throws OrganizationAlreadyExistsException, CreationException, NotAllowedException, AccountNotFoundException; + void deleteOrganization(String pName) throws OrganizationNotFoundException, AccountNotFoundException, AccessRightException; + void updateOrganization(Organization pOrganization) throws AccountNotFoundException, OrganizationNotFoundException, AccessRightException; + void addAccountInOrganization(String pOrganizationName, String pLogin) throws OrganizationNotFoundException, AccountNotFoundException, NotAllowedException, AccessRightException; + void removeAccountsFromOrganization(String pOrganizationName, String[] pLogins) throws OrganizationNotFoundException, AccessRightException, AccountNotFoundException; + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IPSFilterManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IPSFilterManagerLocal.java new file mode 100644 index 0000000000..ba9a122de5 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IPSFilterManagerLocal.java @@ -0,0 +1,35 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.services; + +import com.docdoku.core.configuration.PSFilter; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.ConfigurationItemKey; + +/** + * + * @author Morgan Guimard + */ +public interface IPSFilterManagerLocal { + PSFilter getBaselinePSFilter(int baselineId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException; + PSFilter getProductInstanceConfigSpec(ConfigurationItemKey ciKey, String serialNumber) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException; + PSFilter getPSFilter(ConfigurationItemKey ciKey, String filterType, boolean diverge) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, BaselineNotFoundException; +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IPartWorkflowManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IPartWorkflowManagerLocal.java new file mode 100644 index 0000000000..b900fa895d --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IPartWorkflowManagerLocal.java @@ -0,0 +1,43 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.product.PartRevisionKey; +import com.docdoku.core.workflow.TaskKey; +import com.docdoku.core.workflow.Workflow; + +/** + * + * @author Taylor LABEJOF + * @version 2.0, 15/10/14 + * @since V2.0 + */ +public interface IPartWorkflowManagerLocal { + Workflow getCurentWorkflow(PartRevisionKey documentRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException; + Workflow[] getAbortedWorkflow(PartRevisionKey documentRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException; + + PartRevision[] getPartRevisionsWithAssignedTasksForGivenUser(String pWorkspaceId, String assignedUserLogin) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + PartRevision[] getPartRevisionsWithOpenedTasksForGivenUser(String pWorkspaceId, String assignedUserLogin) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + + PartRevision approveTaskOnPart(String pWorkspaceId, TaskKey pTaskKey, String pComment, String pSignature) throws WorkspaceNotFoundException, TaskNotFoundException, NotAllowedException, UserNotFoundException, UserNotActiveException, WorkflowNotFoundException; + PartRevision rejectTaskOnPart(String pWorkspaceId, TaskKey pTaskKey, String pComment, String pSignature) throws WorkspaceNotFoundException, TaskNotFoundException, NotAllowedException, UserNotFoundException, UserNotActiveException, WorkflowNotFoundException; +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IProductBaselineManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IProductBaselineManagerLocal.java new file mode 100644 index 0000000000..578a101edf --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IProductBaselineManagerLocal.java @@ -0,0 +1,62 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.configuration.*; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.*; +import com.docdoku.core.security.ACL; + +import java.util.List; +import java.util.Map; + +/** + * + * @author Taylor LABEJOF + * @version 2.0, 26/09/14 + * @since V2.0 + */ +public interface IProductBaselineManagerLocal { + + ProductBaseline createBaseline(ConfigurationItemKey configurationItemKey, String name, ProductBaseline.BaselineType type, String description, List<PartIterationKey> partIterationKeys, List<String> substituteLinks, List<String> optionalUsageLinks) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, PartRevisionNotReleasedException, PartIterationNotFoundException, UserNotActiveException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException, CreationException, BaselineNotFoundException, PathToPathLinkAlreadyExistsException; + List<ProductBaseline> getAllBaselines(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + List<ProductBaseline> getBaselines(ConfigurationItemKey configurationItemKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + void deleteBaseline(String pWorkspaceId, int baselineId) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, BaselineNotFoundException, UserNotActiveException, EntityConstraintException; + ProductBaseline getBaseline(int baselineId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException; + ProductBaseline getBaselineById(int baselineId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + List<BaselinedPart> getBaselinedPartWithReference(int baselineId, String q, int maxResults) throws BaselineNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + List<PathChoice> getBaselineCreationPathChoices(ConfigurationItemKey ciKey, ProductBaseline.BaselineType type) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, PartMasterNotFoundException, NotAllowedException, EntityConstraintException; + List<PartIteration> getBaselineCreationVersionsChoices(ConfigurationItemKey ciKey) throws ConfigurationItemNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartMasterNotFoundException, NotAllowedException, EntityConstraintException; + + ProductConfiguration createProductConfiguration(ConfigurationItemKey ciKey, String name, String description, List<String> substituteLinks, List<String> optionalUsageLinks, Map<String,ACL.Permission> userEntries, Map<String,ACL.Permission> groupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, CreationException, AccessRightException; + List<ProductConfiguration> getAllProductConfigurations(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + List<ProductConfiguration> getAllProductConfigurationsByConfigurationItemId(ConfigurationItemKey ciKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException; + ProductConfiguration getProductConfiguration(ConfigurationItemKey ciKey, int productConfigurationId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductConfigurationNotFoundException, AccessRightException; + ProductConfiguration updateProductConfiguration(ConfigurationItemKey ciKey, int productConfigurationId, String name, String description,List<String> substituteLinks, List<String> optionalUsageLinks) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductConfigurationNotFoundException, AccessRightException; + void deleteProductConfiguration(ConfigurationItemKey ciKey, int productConfigurationId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductConfigurationNotFoundException, AccessRightException; + void updateACLForConfiguration(ConfigurationItemKey ciKey, int productConfigurationId, Map<String,String> userEntries, Map<String,String> groupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductConfigurationNotFoundException, AccessRightException; + void removeACLFromConfiguration(ConfigurationItemKey ciKey, int productConfigurationId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductConfigurationNotFoundException, AccessRightException; + List<PartRevision> getObsoletePartRevisionsInBaseline(String workspaceId, int baselineId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException; + + List<PathToPathLink> getPathToPathLinkFromSourceAndTarget(String workspaceId, String configurationItemId, int baselineId, String sourcePath, String targetPath) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException; + + List<String> getPathToPathLinkTypes(String workspaceId, String configurationItemId, int baselineId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException; + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IProductInstanceManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IProductInstanceManagerLocal.java new file mode 100644 index 0000000000..0b269e305c --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IProductInstanceManagerLocal.java @@ -0,0 +1,87 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.configuration.*; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.product.ConfigurationItemKey; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.product.PathToPathLink; +import com.docdoku.core.security.ACL; + +import java.util.List; +import java.util.Map; + +/** + * + * @author Taylor LABEJOF + * @version 2.0, 26/09/14 + * @since V2.0 + */ +public interface IProductInstanceManagerLocal { + List<ProductInstanceMaster> getProductInstanceMasters(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + List<ProductInstanceMaster> getProductInstanceMasters(ConfigurationItemKey configurationItemKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + ProductInstanceMaster getProductInstanceMaster(ProductInstanceMasterKey productInstanceMasterKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException; + List<ProductInstanceIteration> getProductInstanceIterations(ProductInstanceMasterKey productInstanceMasterKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException; + ProductInstanceIteration getProductInstanceIteration(ProductInstanceIterationKey productInstanceIterationKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceIterationNotFoundException, ProductInstanceMasterNotFoundException; + ProductInstanceMaster createProductInstance(String workspaceId, ConfigurationItemKey configurationItemKey, String serialNumber, int baselineId, Map<String, ACL.Permission> userEntries, Map<String, ACL.Permission> groupEntries, List<InstanceAttribute> attributes, DocumentRevisionKey[] links, String[] documentLinkComments) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, BaselineNotFoundException, CreationException, ProductInstanceAlreadyExistsException, NotAllowedException, EntityConstraintException, UserNotActiveException, PathToPathLinkAlreadyExistsException, PartMasterNotFoundException, ProductInstanceMasterNotFoundException; + void deleteProductInstance(String workspaceId, String configurationItemId, String serialNumber) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, UserNotActiveException, ProductInstanceMasterNotFoundException; + + void updateACLForProductInstanceMaster(String workspaceId, String configurationItemId, String serialNumber, Map<String, String> userEntries, Map<String, String> groupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException; + void removeACLFromProductInstanceMaster(String workspaceId, String configurationItemId, String serialNumber) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, ProductInstanceMasterNotFoundException; + + public BinaryResource saveFileInProductInstance(String workspaceId, ProductInstanceIterationKey pdtIterationKey, String fileName, int pSize) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, ProductInstanceMasterNotFoundException, AccessRightException, ProductInstanceIterationNotFoundException, FileAlreadyExistsException, CreationException; + public BinaryResource getBinaryResource(String fullName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, FileNotFoundException, NotAllowedException; + public BinaryResource renameFileInProductInstance(String pFullName, String pNewName, String serialNumber,String cId,int iteration) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, FileNotFoundException, ProductInstanceMasterNotFoundException, NotAllowedException, AccessRightException, FileAlreadyExistsException, CreationException, StorageException; + public ProductInstanceMaster removeFileFromProductInstanceIteration(String workspaceId, int iteration, String fullName,ProductInstanceMasterKey productInstanceMasterKey) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, UserNotActiveException, FileNotFoundException, ProductInstanceMasterNotFoundException; + + public ProductInstanceMaster updateProductInstance(String workspaceId,int iteration,String iterationNote, ConfigurationItemKey configurationItemKey, String serialNumber, int baselineId, List<InstanceAttribute> attributes, DocumentRevisionKey[] links, String[] documentLinkComments) throws ProductInstanceMasterNotFoundException, UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ProductInstanceIterationNotFoundException, UserNotActiveException, BaselineNotFoundException; + public ProductInstanceMaster rebaseProductInstance(String workspaceId, String serialNumber, ConfigurationItemKey configurationItemKey, int baselineId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException, BaselineNotFoundException, NotAllowedException, ConfigurationItemNotFoundException, PathToPathLinkAlreadyExistsException, PartMasterNotFoundException, CreationException, EntityConstraintException; + + public PathDataMaster addNewPathDataIteration(String workspaceId, String configurationItemId, String serialNumber, int pathDataId, List<InstanceAttribute> attributes, String note, DocumentRevisionKey[] links, String[] documentLinkComments) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, UserNotActiveException, NotAllowedException, PathDataAlreadyExistsException, FileAlreadyExistsException, CreationException, PathDataMasterNotFoundException; + public PathDataMaster updatePathData(String workspaceId, String configurationItemId, String serialNumber, int pathDataMasterId, int iteration, List<InstanceAttribute> attributes, String description, DocumentRevisionKey[] pLinkKeys, String[] documentLinkComments) throws UserNotActiveException, WorkspaceNotFoundException, UserNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException, NotAllowedException; + public void deletePathData(String workspaceId, String configurationItemId, String serialNumber, int pathDataId) throws UserNotActiveException, WorkspaceNotFoundException, UserNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException, NotAllowedException; + + public PathDataMaster getPathDataByPath(String workspaceId, String configurationItemId, String serialNumber, String pathAsString) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, ProductInstanceMasterNotFoundException; + boolean canWrite(String workspaceId, String configurationItemId, String serialNumber); + + public BinaryResource saveFileInPathData(String workspaceId, String configurationItemId, String serialNumber, int pathDataId,int iteration, String fileName, int pSize) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, AccessRightException, ProductInstanceMasterNotFoundException, FileAlreadyExistsException, CreationException; + public BinaryResource getPathDataBinaryResource(String fullName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, FileNotFoundException, AccessRightException; + public BinaryResource renameFileInPathData(String workspaceId, String configurationItemId, String serialNumber, int pathDataId,int iteration, String pFullName, String pNewName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, FileNotFoundException, ProductInstanceMasterNotFoundException, NotAllowedException, AccessRightException, FileAlreadyExistsException, CreationException; + public ProductInstanceMaster removeFileFromPathData(String workspaceId, String configurationItemId, String serialNumber, int pathDataId,int iteration, String fullName, ProductInstanceMaster productInstanceMaster) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, UserNotActiveException, NotAllowedException, FileNotFoundException; + + + BinaryResource saveFileInPathDataIteration(String workspaceId, String configurationItemId, String serialNumber, int pathDataId, int iteration, String fileName, int i) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, AccessRightException, ProductInstanceMasterNotFoundException, FileAlreadyExistsException, CreationException, PathDataMasterNotFoundException; + + PathDataMaster createPathDataMaster(String workspaceId, String configurationItemId, String serialNumber, String path, List<InstanceAttribute> attributes, String iterationNote) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException; + + PathToPathLink getPathToPathLink(String workspaceId, String configurationItemId, String serialNumber, int pathToPathLinkId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException, PathToPathLinkNotFoundException; + List<String> getPathToPathLinkTypes(String workspaceId, String configurationItemId, String serialNumber) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException; + List<PathToPathLink> getPathToPathLinks(String workspaceId, String configurationItemId, String serialNumber) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException; + + List<PathToPathLink> getPathToPathLinkFromSourceAndTarget(String workspaceId, String configurationItemId, String serialNumber, String sourcePath, String targetPath) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException; + List<PathToPathLink> getRootPathToPathLinks(String workspaceId, String configurationItemId, String serialNumber, String type) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException; + + List<ProductInstanceMaster> getProductInstanceMasters(PartRevision pPartRevision) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IProductManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IProductManagerLocal.java new file mode 100644 index 0000000000..0bc36da163 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IProductManagerLocal.java @@ -0,0 +1,316 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.services; + +import com.docdoku.core.change.ModificationNotification; +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.*; +import com.docdoku.core.document.DocumentIterationLink; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.InstanceAttributeDescriptor; +import com.docdoku.core.meta.InstanceAttributeTemplate; +import com.docdoku.core.product.*; +import com.docdoku.core.query.PartSearchQuery; +import com.docdoku.core.query.Query; +import com.docdoku.core.query.QueryResultRow; +import com.docdoku.core.security.ACLUserEntry; +import com.docdoku.core.security.ACLUserGroupEntry; +import com.docdoku.core.sharing.SharedEntityKey; +import com.docdoku.core.sharing.SharedPart; + +import java.util.*; + + +/** + * + * @author Florent Garin + */ +public interface IProductManagerLocal { + + public List<PartLink[]> findPartUsages(ConfigurationItemKey pKey, PSFilter filter, String search) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException, ConfigurationItemNotFoundException; + + ConfigurationItem createConfigurationItem(String pWorkspaceId, String pId, String pDescription, String pDesignItemNumber) throws UserNotFoundException, WorkspaceNotFoundException, AccessRightException, NotAllowedException, ConfigurationItemAlreadyExistsException, CreationException, PartMasterNotFoundException; + + PartMaster createPartMaster(String pWorkspaceId, String pNumber, String pName, boolean pStandardPart, String pWorkflowModelId, String pPartRevisionDescription, String templateId, ACLUserEntry[] userEntries, ACLUserGroupEntry[] userGroupEntries, Map<String, Collection<String>> userRoleMapping, Map<String, Collection<String>> groupRoleMapping) throws NotAllowedException, UserNotFoundException, WorkspaceNotFoundException, AccessRightException, WorkflowModelNotFoundException, PartMasterAlreadyExistsException, CreationException, PartMasterTemplateNotFoundException, FileAlreadyExistsException, RoleNotFoundException, UserGroupNotFoundException; + + PartRevision checkOutPart(PartRevisionKey pPartRPK) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, PartRevisionNotFoundException, NotAllowedException, FileAlreadyExistsException, CreationException, UserNotActiveException; + + PartRevision undoCheckOutPart(PartRevisionKey pPartRPK) throws NotAllowedException, PartRevisionNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException; + + PartRevision checkInPart(PartRevisionKey pPartRPK) throws PartRevisionNotFoundException, UserNotFoundException, WorkspaceNotFoundException, AccessRightException, NotAllowedException, ESServerException, UserNotActiveException, EntityConstraintException, PartMasterNotFoundException; + + BinaryResource saveNativeCADInPartIteration(PartIterationKey pPartIPK, String pName, long pSize) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, PartRevisionNotFoundException, FileAlreadyExistsException, CreationException; + + BinaryResource saveGeometryInPartIteration(PartIterationKey pPartIPK, String pName, int quality, long pSize, double[] box) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, PartRevisionNotFoundException, FileAlreadyExistsException, CreationException; + + PartRevision updatePartIteration(PartIterationKey pKey, java.lang.String pIterationNote, PartIteration.Source source, java.util.List<PartUsageLink> pUsageLinks, java.util.List<InstanceAttribute> pAttributes, java.util.List<InstanceAttributeTemplate> pAttributeTemplates, DocumentRevisionKey[] pLinkKeys, String[] documentLinkComments, String[] lovNames) throws UserNotFoundException, WorkspaceNotFoundException, AccessRightException, NotAllowedException, PartRevisionNotFoundException, PartMasterNotFoundException, EntityConstraintException, UserNotActiveException, ListOfValuesNotFoundException, PartUsageLinkNotFoundException; + + BinaryResource saveFileInPartIteration(PartIterationKey pPartIPK, String pName, String subType, long pSize) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, PartRevisionNotFoundException, FileAlreadyExistsException, CreationException; + + void removeFileInPartIteration(PartIterationKey pPartIPK, String pSubType, String pName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartIterationNotFoundException, FileNotFoundException; + + void setPublicSharedPart(PartRevisionKey pPartRPK, boolean isPublicShared) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException; + /** + * Returns the BinaryResource object given his Id. WARNING: You have to check access right before use it. + * + * @param fullName Id of the <a href="BinaryResource.html">BinaryResource</a> of which the + * data file will be returned + * @return The binary resource, a BinaryResource instance, that now needs to be created + * @throws UserNotFoundException + * @throws UserNotActiveException + * @throws WorkspaceNotFoundException + * @throws FileNotFoundException + * @throws NotAllowedException + */ + BinaryResource getBinaryResource(String fullName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, FileNotFoundException, NotAllowedException, AccessRightException; + + BinaryResource getTemplateBinaryResource(String pFullName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, FileNotFoundException; + + List<ConfigurationItem> getConfigurationItems(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + ConfigurationItem getConfigurationItem(ConfigurationItemKey ciKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException; + + List<Layer> getLayers(ConfigurationItemKey pKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + Layer getLayer(int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, LayerNotFoundException; + + Layer createLayer(ConfigurationItemKey pKey, String pName, String color) throws UserNotFoundException, WorkspaceNotFoundException, AccessRightException, ConfigurationItemNotFoundException; + + Layer updateLayer(ConfigurationItemKey pKey, int pId, String pName, String color) throws UserNotFoundException, WorkspaceNotFoundException, AccessRightException, ConfigurationItemNotFoundException, LayerNotFoundException, UserNotActiveException; + + Marker createMarker(int pLayerId, String pTitle, String pDescription, double pX, double pY, double pZ) throws LayerNotFoundException, UserNotFoundException, WorkspaceNotFoundException, AccessRightException; + + void deleteMarker(int pLayerId, int pMarkerId) throws WorkspaceNotFoundException, UserNotActiveException, LayerNotFoundException, UserNotFoundException, AccessRightException, MarkerNotFoundException; + + List<PartMaster> findPartMasters(String pWorkspaceId, String pPartNumber, String pPartName, int pMaxResults) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException; + + PartRevision getPartRevision(PartRevisionKey pPartRPK) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException; + PartIteration getPartIteration(PartIterationKey pPartIPK) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException, PartIterationNotFoundException, NotAllowedException; + PartMaster getPartMaster(PartMasterKey pPartMPK) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartMasterNotFoundException; + + List<PartUsageLink> getComponents(PartIterationKey pPartIPK) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartIterationNotFoundException, NotAllowedException; + + boolean partMasterExists(PartMasterKey partMasterKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + void deleteConfigurationItem(ConfigurationItemKey configurationItemKey) throws UserNotFoundException, WorkspaceNotFoundException, AccessRightException, NotAllowedException, UserNotActiveException, ConfigurationItemNotFoundException, LayerNotFoundException, EntityConstraintException; + + void deleteLayer(String workspaceId, int layerId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, LayerNotFoundException, AccessRightException; + + BinaryResource renameFileInPartIteration(String pSubType, String fullName, String newName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, FileNotFoundException, FileAlreadyExistsException, CreationException, StorageException; + + PartMasterTemplate createPartMasterTemplate(String pWorkspaceId, String pId, String pPartType, String pWorkflowModelId, String pMask, List<InstanceAttributeTemplate> pAttributeTemplates, String[] lovNames, List<InstanceAttributeTemplate> pAttributeInstanceTemplates, String[] instanceLovNames, boolean idGenerated, boolean attributesLocked) throws WorkspaceNotFoundException, AccessRightException, PartMasterTemplateAlreadyExistsException, UserNotFoundException, NotAllowedException, CreationException, WorkflowModelNotFoundException, ListOfValuesNotFoundException; + + BinaryResource saveFileInTemplate(PartMasterTemplateKey pPartMTemplateKey, String pName, long pSize) throws WorkspaceNotFoundException, NotAllowedException, PartMasterTemplateNotFoundException, FileAlreadyExistsException, UserNotFoundException, UserNotActiveException, CreationException, AccessRightException; + + String generateId(String pWorkspaceId, String pPartMTemplateId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, PartMasterTemplateNotFoundException; + + PartMasterTemplate[] getPartMasterTemplates(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + + PartMasterTemplate getPartMasterTemplate(PartMasterTemplateKey pKey) throws WorkspaceNotFoundException, PartMasterTemplateNotFoundException, UserNotFoundException, UserNotActiveException; + + PartMasterTemplate updatePartMasterTemplate(PartMasterTemplateKey pKey, String pPartType, String pWorkflowModelId, String pMask, List<InstanceAttributeTemplate> pAttributeTemplates, String[] lovNames, List<InstanceAttributeTemplate> pAttributeInstanceTemplates, String[] instanceLovNames, boolean idGenerated, boolean attributesLocked) throws WorkspaceNotFoundException, AccessRightException, PartMasterTemplateNotFoundException, UserNotFoundException, WorkflowModelNotFoundException, UserNotActiveException, ListOfValuesNotFoundException, NotAllowedException; + + void deletePartMasterTemplate(PartMasterTemplateKey pKey) throws WorkspaceNotFoundException, AccessRightException, PartMasterTemplateNotFoundException, UserNotFoundException, UserNotActiveException; + + PartMasterTemplate removeFileFromTemplate(String pFullName) throws WorkspaceNotFoundException, PartMasterTemplateNotFoundException, AccessRightException, FileNotFoundException, UserNotFoundException, UserNotActiveException; + + BinaryResource renameFileInTemplate(String fileFullName, String newName) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, FileNotFoundException, UserNotActiveException, FileAlreadyExistsException, CreationException, StorageException, NotAllowedException; + + List<PartMaster> getPartMasters(String pWorkspaceId, int start, int pMaxResults) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, UserNotActiveException; + + int getTotalNumberOfParts(String pWorkspaceId) throws AccessRightException, WorkspaceNotFoundException, AccountNotFoundException, UserNotFoundException, UserNotActiveException; + + long getDiskUsageForPartsInWorkspace(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException; + + long getDiskUsageForPartTemplatesInWorkspace(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException; + + PartRevision[] getCheckedOutPartRevisions(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException, UserNotFoundException, UserNotActiveException; + + PartRevision[] getAllCheckedOutPartRevisions(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException; + + SharedPart createSharedPart(PartRevisionKey pPartRevisionKey, String pPassword, Date pExpireDate) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, PartRevisionNotFoundException, UserNotActiveException; + + void deleteSharedPart(SharedEntityKey pSharedEntityKey) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, SharedEntityNotFoundException; + + void updatePartRevisionACL(String workspaceId, PartRevisionKey revisionKey, Map<String, String> userEntries, Map<String, String> groupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException, DocumentRevisionNotFoundException; + + List<PartRevision> getPartRevisions(String pWorkspaceId, int start, int pMaxResults) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, UserNotActiveException; + + int getPartsInWorkspaceCount(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + + void deletePartRevision(PartRevisionKey partRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, EntityConstraintException, ESServerException, AccessRightException; + + int getNumberOfIteration(PartRevisionKey partRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException; + + PartRevision createPartRevision(PartRevisionKey revisionKey, String pDescription, String pWorkflowModelId, ACLUserEntry[] pUserEntries, ACLUserGroupEntry[] pUserGroupEntries, Map<String, Collection<String>> userRoleMapping, Map<String, Collection<String>> groupRoleMapping) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, PartRevisionNotFoundException, NotAllowedException, FileAlreadyExistsException, CreationException, RoleNotFoundException, WorkflowModelNotFoundException, PartRevisionAlreadyExistsException, UserGroupNotFoundException; + + void removeACLFromPartRevision(PartRevisionKey revisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException; + + List<PartRevision> searchPartRevisions(PartSearchQuery partSearchQuery) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ESServerException; + + List<ProductBaseline> findBaselinesWherePartRevisionHasIterations(PartRevisionKey partRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException; + + + PartMaster findPartMasterByCADFileName(String workspaceId, String cadFileName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + PartRevision[] getPartRevisionsWithReferenceOrName(String pWorkspaceId, String reference, int maxResults) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + + PartRevision releasePartRevision(PartRevisionKey pRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException, NotAllowedException; + + PartRevision markPartRevisionAsObsolete(PartRevisionKey pRevisionKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException, NotAllowedException; + + PartRevision getLastReleasePartRevision(ConfigurationItemKey ciKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, AccessRightException, PartRevisionNotFoundException; + + public User checkPartRevisionReadAccess(PartRevisionKey partRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException; + + boolean canAccess(PartRevisionKey partRKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException; + + boolean canAccess(PartIterationKey partRKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, PartIterationNotFoundException; + + /** + * Check if a user can access to a PartRevision + * [WARN] Don't check if the user exist and if he can access to the part workspace + * + * @param user The specific user + * @param partRKey The key of the specif part + * @return TRUE if he can access, False otherwise + */ + boolean canUserAccess(User user, PartRevisionKey partRKey) throws PartRevisionNotFoundException; + + /** + * Check if a user can access to a PartIteration + * [WARN] Don't check if the user exist and if he can access to the part iteration workspace + * + * @param user The specific user + * @param partIKey The key of the specif part iteration + * @return TRUE if he can access, False otherwise + */ + boolean canUserAccess(User user, PartIterationKey partIKey) throws PartRevisionNotFoundException, PartIterationNotFoundException; + + boolean canWrite(PartRevisionKey partRKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException; + + Conversion getConversion(PartIterationKey partIterationKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException, PartIterationNotFoundException ; + + Conversion createConversion(PartIterationKey partIterationKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException, PartIterationNotFoundException, CreationException; + + void removeConversion(PartIterationKey partIterationKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException, PartIterationNotFoundException; + + void endConversion(PartIterationKey partIterationKey, boolean succeed) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException, PartIterationNotFoundException; + + Import createImport(String workspaceId, String fileName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, CreationException; + List<Import> getImports(String workspaceId, String filename) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + Import getImport(String workspaceId, String id) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException; + void endImport(String workspaceId, String id, ImportResult importResult) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException; + void removeImport(String workspaceId, String id) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException; + + void updateACLForPartMasterTemplate(String workspaceId, String templateId, Map<String, String> userEntries, Map<String, String> groupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, PartMasterTemplateNotFoundException; + + void removeACLFromPartMasterTemplate(String workspaceId, String templateId) throws PartMasterNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartMasterTemplateNotFoundException, AccessRightException; + + + PartRevision saveTags(PartRevisionKey revisionKey, String[] strings) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException; + + PartRevision removeTag(PartRevisionKey partRevisionKey, String tagName) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException; + + PartRevision[] findPartRevisionsByTag(String workspaceId, String tagId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + List<ModificationNotification> getModificationNotifications(PartIterationKey pPartIPK) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException; + + void createModificationNotifications(PartIteration modifiedPartIteration) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException; + + void removeModificationNotificationsOnIteration(PartIterationKey pPartIPK); + + void removeModificationNotificationsOnRevision(PartRevisionKey pPartRPK); + + List<PartIteration> getUsedByAsComponent(PartRevisionKey pPartRPK) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException; + + List<PartIteration> getUsedByAsSubstitute(PartRevisionKey pPartRPK) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException; + + void checkCyclicAssemblyForPartIteration(PartIteration partIteration) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException; + + Component filterProductStructure(ConfigurationItemKey ciKey, PSFilter filter, List<PartLink> path, Integer depth) throws ConfigurationItemNotFoundException, WorkspaceNotFoundException, NotAllowedException, UserNotFoundException, UserNotActiveException, PartUsageLinkNotFoundException, AccessRightException, PartMasterNotFoundException, EntityConstraintException; + + Component filterProductStructureOnLinkType(ConfigurationItemKey ciKey, PSFilter filter, String configSpecType, String path, String linkType) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException, ProductInstanceMasterNotFoundException, BaselineNotFoundException; + + Set<PartRevision> getWritablePartRevisionsFromPath(ConfigurationItemKey configurationItemKey, String path) throws EntityConstraintException, PartMasterNotFoundException, NotAllowedException, UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException; + + PartLink getRootPartUsageLink(ConfigurationItemKey pKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException; + + PSFilter getLatestCheckedInPSFilter(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + void updateModificationNotification(String pWorkspaceId, int pModificationNotificationId, String pAcknowledgementComment) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, PartRevisionNotFoundException; + + List<PartLink> decodePath(ConfigurationItemKey ciKey, String path) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartUsageLinkNotFoundException, ConfigurationItemNotFoundException; + + List<PartRevision> searchPartRevisions(String workspaceId, Query query) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + List<Query> getQueries(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + Query getQuery(String workspaceId, int queryId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + void createQuery(String workspaceId, Query query) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, QueryAlreadyExistsException, CreationException; + + void deleteQuery(String workspaceId, int queryId) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException; + + List<InstanceAttributeDescriptor> getPartIterationsInstanceAttributesInWorkspace(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + List<InstanceAttributeDescriptor> getPathDataInstanceAttributesInWorkspace(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + List<PartIteration> getInversePartsLink(DocumentRevisionKey docKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, PartIterationNotFoundException, PartRevisionNotFoundException; + + Set<ProductInstanceMaster> getInverseProductInstancesLink(DocumentRevisionKey docKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, PartIterationNotFoundException, PartRevisionNotFoundException; + + Set<PathDataMaster> getInversePathDataLink(DocumentRevisionKey docKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, PartIterationNotFoundException, PartRevisionNotFoundException; + + List<QueryResultRow> filterProductBreakdownStructure(String workspaceId, Query query) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException, ProductInstanceMasterNotFoundException, ConfigurationItemNotFoundException, NotAllowedException, PartMasterNotFoundException, EntityConstraintException; + + Query loadQuery(String workspaceId, int queryId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + Map<String, Set<BinaryResource>> getBinariesInTree(Integer baselineId, String workspaceId, ConfigurationItemKey configurationItemKey, PSFilter psFilter, boolean exportNativeCADFiles, boolean exportDocumentLinks) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException; + + ProductBaseline loadProductBaselineForProductInstanceMaster(ConfigurationItemKey ciKey, String serialNumber) throws ProductInstanceMasterNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + List<BinaryResource> getBinaryResourceFromBaseline(int baselineId); + + void deletePathToPathLink(String workspaceId, String configurationItemId, int pathToPathLinkId) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, PathToPathLinkNotFoundException; + + PathToPathLink createPathToPathLink(String workspaceId, String configurationItemId, String type, String sourcePath, String targetPath, String description) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, PathToPathLinkAlreadyExistsException, CreationException, PathToPathCyclicException, PartUsageLinkNotFoundException, UserNotActiveException, NotAllowedException; + + PathToPathLink updatePathToPathLink(String workspaceId, String configurationItemId, int pathToPathLinkId, String description) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, PathToPathLinkAlreadyExistsException, CreationException, PathToPathCyclicException, PartUsageLinkNotFoundException, UserNotActiveException, NotAllowedException, PathToPathLinkNotFoundException; + + List<String> getPathToPathLinkTypes(String workspaceId, String configurationItemId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException; + + List<PathToPathLink> getPathToPathLinkFromSourceAndTarget(String workspaceId, String configurationItemId, String sourcePath, String targetPath) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException; + + ProductInstanceMaster findProductByPathMaster(String workspaceId, PathDataMaster pathDataMaster) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + public PartMaster getPartMasterFromPath(String workspaceId, String configurationItemId, String partPath) throws ConfigurationItemNotFoundException, UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartUsageLinkNotFoundException; + + boolean hasModificationNotification(ConfigurationItemKey key) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException; + + List<DocumentIterationLink> getDocumentLinksAsDocumentIterations(String workspaceId, String configurationItemId, String configSpec, PartIterationKey partIterationKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException, PartIterationNotFoundException, ProductInstanceMasterNotFoundException; + + PartIteration findPartIterationByBinaryResource(BinaryResource binaryResource) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IShareManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IShareManagerLocal.java new file mode 100644 index 0000000000..08c3433e04 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IShareManagerLocal.java @@ -0,0 +1,35 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.exceptions.SharedEntityNotFoundException; +import com.docdoku.core.sharing.SharedEntity; + +/** + * + * @author Morgan Guimard + */ +public interface IShareManagerLocal { + + SharedEntity findSharedEntityForGivenUUID(String pUuid) throws SharedEntityNotFoundException; + + public void deleteSharedEntityIfExpired(SharedEntity pSharedEntity); + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/ITaskManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/ITaskManagerLocal.java new file mode 100644 index 0000000000..d7b8e61fdb --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/ITaskManagerLocal.java @@ -0,0 +1,36 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.exceptions.*; +import com.docdoku.core.workflow.TaskKey; +import com.docdoku.core.workflow.TaskWrapper; + +/** + * + * @author Morgan Guimard + */ +public interface ITaskManagerLocal { + TaskWrapper[] getAssignedTasksForGivenUser(String workspaceId, String userLogin) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + TaskWrapper[] getInProgressTasksForGivenUser(String workspaceId, String userLogin) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + TaskWrapper getTask(String workspaceId, TaskKey taskKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, TaskNotFoundException, AccessRightException; + + void processTask(String workspaceId, TaskKey taskKey, String action, String comment, String signature) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, TaskNotFoundException, NotAllowedException, WorkflowNotFoundException, AccessRightException; +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IUserManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IUserManagerLocal.java new file mode 100644 index 0000000000..b8ac63fb91 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IUserManagerLocal.java @@ -0,0 +1,107 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.services; + +import com.docdoku.core.common.*; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.security.PasswordRecoveryRequest; +import com.docdoku.core.security.WorkspaceUserGroupMembership; +import com.docdoku.core.security.WorkspaceUserMembership; + + +/** + * + * @author Florent Garin + */ +public interface IUserManagerLocal{ + + void recoverPassword(String pPasswdRRUuid, String pPassword) throws PasswordRecoveryRequestNotFoundException; + PasswordRecoveryRequest createPasswordRecoveryRequest(Account account); + + Workspace getWorkspace(String pWorkspaceId) throws WorkspaceNotFoundException, AccountNotFoundException; + Workspace[] getWorkspacesWhereCallerIsActive(); + Workspace createWorkspace(String pID, Account pAdmin, String pDescription, boolean pFolderLocked) throws FolderAlreadyExistsException, UserAlreadyExistsException, WorkspaceAlreadyExistsException, CreationException, ESIndexNamingException, NotAllowedException; + void updateWorkspace(Workspace pWorkspace) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException; + Workspace updateWorkspace(String workspaceId, String description, boolean isFolderLocked) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException; + + User whoAmI(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + void addUserInWorkspace(String pWorkspaceId, String pLogin) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, UserAlreadyExistsException, FolderAlreadyExistsException, CreationException; + Workspace removeUser(String pWorkspaceId, String login) throws UserNotFoundException, NotAllowedException, AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, FolderNotFoundException, ESServerException, EntityConstraintException, UserNotActiveException, DocumentRevisionNotFoundException; + Workspace[] getAdministratedWorkspaces() throws AccountNotFoundException; + + UserGroup getUserGroup(UserGroupKey pKey) throws WorkspaceNotFoundException, UserGroupNotFoundException, UserNotFoundException, UserNotActiveException, AccountNotFoundException; + UserGroup[] getUserGroups(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccountNotFoundException; + + UserGroup removeUserFromGroup(UserGroupKey pGroupKey, String logins) throws AccessRightException, UserGroupNotFoundException, AccountNotFoundException, WorkspaceNotFoundException; + + UserGroup createUserGroup(String pId, Workspace pWorkspace) throws UserGroupAlreadyExistsException, AccessRightException, AccountNotFoundException, CreationException; + + void passivateUserGroup(String pWorkspaceId, String groupId) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException; + + void removeUsers(String pWorkspaceId, String[] pLogins) throws UserNotFoundException, NotAllowedException, AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, FolderNotFoundException, ESServerException, EntityConstraintException, UserNotActiveException, DocumentRevisionNotFoundException; + + void passivateUser(String pWorkspaceId, String login) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException; + + void removeUserGroups(String pWorkspaceId, String[] pIds) throws UserGroupNotFoundException, AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, EntityConstraintException; + void addUserInGroup(UserGroupKey pGroupKey, String pLogin) throws AccessRightException, UserGroupNotFoundException, AccountNotFoundException, WorkspaceNotFoundException, UserAlreadyExistsException, FolderAlreadyExistsException, CreationException; + void removeUserFromGroup(UserGroupKey pGroupKey, String[] pLogins) throws AccessRightException, UserGroupNotFoundException, AccountNotFoundException, WorkspaceNotFoundException; + + boolean hasWorkspaceWriteAccess(User user, String pWorkspaceId) throws WorkspaceNotFoundException; + + boolean hasCommonWorkspace(String user1, String user2); + + void grantUserAccess(String pWorkspaceId, String[] pLogins, boolean pReadOnly) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException; + + WorkspaceUserMembership grantUserAccess(String pWorkspaceId, String login, boolean pReadOnly) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException; + + WorkspaceUserGroupMembership grantGroupAccess(String pWorkspaceId, String groupId, boolean pReadOnly) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, UserGroupNotFoundException; + + void grantGroupAccess(String pWorkspaceId, String[] pGroupIds, boolean pReadOnly) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, UserGroupNotFoundException; + + void activateUsers(String pWorkspaceId, String[] pLogins) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException; + void passivateUsers(String pWorkspaceId, String[] pLogins) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException; + + void activateUserGroups(String pWorkspaceId, String[] pGroupIds) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException; + + void activateUser(String pWorkspaceId, String login) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException; + + void activateUserGroup(String pWorkspaceId, String groupId) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException; + + void passivateUserGroups(String pWorkspaceId, String[] pGroupIds) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException; + + WorkspaceUserMembership getWorkspaceSpecificUserMemberships(String workspaceId) throws AccountNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + WorkspaceUserMembership[] getWorkspaceUserMemberships(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccountNotFoundException; + WorkspaceUserGroupMembership[] getWorkspaceSpecificUserGroupMemberships(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, UserGroupNotFoundException; + WorkspaceUserGroupMembership[] getWorkspaceUserGroupMemberships(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccountNotFoundException; + + Account checkAdmin(String pWorkspaceId) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException; + Account checkAdmin(Workspace pWorkspace) throws AccessRightException, AccountNotFoundException; + User checkWorkspaceReadAccess(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + User checkWorkspaceWriteAccess(String pWorkspaceId) throws UserNotFoundException, WorkspaceNotFoundException, AccessRightException; + + UserGroup[] getUserGroupsForUser(UserKey userKey) throws UserNotFoundException; + + User[] getReachableUsers() throws AccountNotFoundException; + + User[] getUsers(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException, UserNotFoundException, UserNotActiveException; + + UserGroup createUserGroup(String pId, String workspaceId) throws UserGroupAlreadyExistsException, AccessRightException, AccountNotFoundException, CreationException, WorkspaceNotFoundException; +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IWorkflowManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IWorkflowManagerLocal.java new file mode 100644 index 0000000000..2322e27f00 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IWorkflowManagerLocal.java @@ -0,0 +1,65 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.services; + +import com.docdoku.core.exceptions.*; +import com.docdoku.core.workflow.*; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * + * @author Florent Garin + */ +public interface IWorkflowManagerLocal { + + void deleteWorkflowModel(WorkflowModelKey pKey) throws WorkspaceNotFoundException, AccessRightException, WorkflowModelNotFoundException, UserNotFoundException, UserNotActiveException, EntityConstraintException; + WorkflowModel getWorkflowModel(WorkflowModelKey pKey) throws WorkspaceNotFoundException, WorkflowModelNotFoundException, UserNotFoundException, UserNotActiveException; + WorkflowModel[] getWorkflowModels(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + WorkflowModel createWorkflowModel(String pWorkspaceId, String pId, String pFinalLifeCycleState, ActivityModel[] pActivityModels) throws WorkspaceNotFoundException, AccessRightException, WorkflowModelAlreadyExistsException, UserNotFoundException, CreationException, NotAllowedException; + WorkflowModel updateWorkflowModel(WorkflowModelKey workflowModelKey, String pFinalLifeCycleState, ActivityModel[] pActivityModels) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, WorkflowModelNotFoundException, NotAllowedException, WorkflowModelAlreadyExistsException, CreationException; + + Role[] getRoles(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + Role[] getRolesInUse(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException; + Role createRole(String roleName, String workspaceId, List<String> userLogins, List<String> userGroupIds) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException, RoleAlreadyExistsException, CreationException, UserGroupNotFoundException; + Role updateRole(RoleKey roleKey, List<String> userLogins, List<String> userGroupIds) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException, RoleNotFoundException, UserGroupNotFoundException; + void deleteRole(RoleKey roleKey) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException, RoleNotFoundException, EntityConstraintException; + + void removeACLFromWorkflow(String pWorkspaceId, String workflowModelId) throws WorkflowNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, WorkflowModelNotFoundException, AccessRightException; + WorkflowModel updateACLForWorkflow(String pWorkspaceId, String workflowModelId, Map<String, String> userEntries, Map<String, String> groupEntries) throws WorkflowNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, WorkflowModelNotFoundException, AccessRightException; + + WorkspaceWorkflow instantiateWorkflow(String workspaceId, String id, String workflowModelId, Map<String, Collection<String>> userRoleMapping, Map<String, Collection<String>> groupRoleMapping) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, RoleNotFoundException, WorkflowModelNotFoundException, NotAllowedException, UserGroupNotFoundException; + + Workflow getWorkflow(String workspaceId, int workflowId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException; + + WorkspaceWorkflow getWorkspaceWorkflow(String workspaceId, String workspaceWorkflowId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, WorkflowNotFoundException; + + WorkspaceWorkflow[] getWorkspaceWorkflowList(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException; + + Workflow[] getWorkflowAbortedWorkflows(String workspaceId, int workflowId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException; + + void approveTaskOnWorkspaceWorkflow(String workspaceId, TaskKey taskKey, String comment, String signature) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, TaskNotFoundException, AccessRightException, WorkflowNotFoundException, NotAllowedException; + + void rejectTaskOnWorkspaceWorkflow(String workspaceId, TaskKey taskKey, String comment, String signature) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, TaskNotFoundException, AccessRightException, WorkflowNotFoundException, NotAllowedException; + + void deleteWorkspaceWorkflow(String workspaceId, String workspaceWorkflowId) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException; +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/services/IWorkspaceManagerLocal.java b/docdoku-common/src/main/java/com/docdoku/core/services/IWorkspaceManagerLocal.java new file mode 100644 index 0000000000..e35833f827 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/services/IWorkspaceManagerLocal.java @@ -0,0 +1,37 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.services; + +import com.docdoku.core.common.Workspace; +import com.docdoku.core.exceptions.AccountNotFoundException; +import com.docdoku.core.exceptions.WorkspaceNotFoundException; + +/** + * + * @author Morgan Guimard + */ +public interface IWorkspaceManagerLocal { + long getDiskUsageInWorkspace(String workspaceId) throws AccountNotFoundException; + void deleteWorkspace(String workspaceId); + void synchronizeIndexer(String workspaceId); + + Workspace changeAdmin(String workspaceId, String login) throws WorkspaceNotFoundException, AccountNotFoundException; +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/sharing/SharedDocument.java b/docdoku-common/src/main/java/com/docdoku/core/sharing/SharedDocument.java new file mode 100644 index 0000000000..b8585d2609 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/sharing/SharedDocument.java @@ -0,0 +1,82 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.sharing; + + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.document.DocumentRevision; + +import javax.persistence.*; +import java.util.Date; + +/** + * SharedDocument permits the creation of permanent link to document for users that do not have an account. + * + * @author Morgan Guimard + */ + +@Table(name="SHAREDDOCUMENT") +@Entity +@NamedQueries({ + @NamedQuery(name="SharedDocument.deleteSharesForGivenDocument", query="DELETE FROM SharedDocument sd WHERE sd.documentRevision = :pDocR"), +}) +public class SharedDocument extends SharedEntity{ + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "DOCUMENTMASTER_ID", referencedColumnName = "DOCUMENTMASTER_ID"), + @JoinColumn(name = "DOCUMENTREVISION_VERSION", referencedColumnName = "VERSION"), + @JoinColumn(name = "ENTITY_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private DocumentRevision documentRevision; + + public SharedDocument(){ + } + + public SharedDocument(Workspace workspace, User author, Date expireDate, String password, DocumentRevision documentRevision) { + super(workspace, author, expireDate, password); + this.documentRevision = documentRevision; + } + + public SharedDocument(Workspace workspace, User author, DocumentRevision documentRevision) { + super(workspace, author); + this.documentRevision = documentRevision; + } + + public SharedDocument(Workspace workspace, User author, Date expireDate, DocumentRevision documentRevision) { + super(workspace, author, expireDate); + this.documentRevision = documentRevision; + } + + public SharedDocument(Workspace workspace, User author, String password, DocumentRevision documentRevision) { + super(workspace, author, password); + this.documentRevision = documentRevision; + } + + public DocumentRevision getDocumentRevision() { + return documentRevision; + } + + public void setDocumentRevision(DocumentRevision documentRevision) { + this.documentRevision = documentRevision; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/sharing/SharedEntity.java b/docdoku-common/src/main/java/com/docdoku/core/sharing/SharedEntity.java new file mode 100644 index 0000000000..7df1598a6b --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/sharing/SharedEntity.java @@ -0,0 +1,192 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.sharing; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlSeeAlso; +import java.io.Serializable; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Date; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Abstract class for all classes that allow the definition of permanent link to business objects + * like documents or parts. + * + * @author Morgan Guimard + */ + +@Table(name="SHAREDENTITY") +@XmlSeeAlso({SharedDocument.class,SharedPart.class}) +@Inheritance() +@Entity +@javax.persistence.IdClass(SharedEntityKey.class) +@NamedQueries({ + @NamedQuery(name="SharedEntity.findSharedEntityForGivenUuid", query="SELECT se FROM SharedEntity se WHERE se.uuid = :pUuid") +}) +public abstract class SharedEntity implements Serializable { + + @Id + private String uuid; + + @Id + @ManyToOne(optional = false, fetch = FetchType.EAGER) + private Workspace workspace; + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "AUTHOR_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "AUTHOR_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User author; + + @Temporal(TemporalType.TIMESTAMP) + private Date creationDate; + + @Temporal(TemporalType.TIMESTAMP) + private Date expireDate; + + private String password; + + public SharedEntity() { + } + + + public SharedEntity(Workspace workspace, User author, Date expireDate, String password) { + this.workspace = workspace; + this.uuid = UUID.randomUUID().toString(); + this.author = author; + this.creationDate = new Date(); + this.expireDate = (expireDate!=null) ? (Date) expireDate.clone() : null; + if(password != null){ + try{ + this.password = md5Sum(password); + }catch(NoSuchAlgorithmException pEx){ + Logger.getLogger(SharedEntity.class.getName()).log(Level.FINEST, null, pEx); + } + } + } + + public SharedEntity(Workspace workspace, User author) { + this(workspace,author,null,null); + } + public SharedEntity(Workspace workspace, User author, Date expireDate) { + this(workspace,author,expireDate,null); + } + public SharedEntity(Workspace workspace, User author, String password) { + this(workspace,author,null,password); + } + + public String getUuid() { + return uuid; + } + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public Date getExpireDate() { + return (expireDate!=null) ? (Date) expireDate.clone() : null; + } + public void setExpireDate(Date expireDate) { + this.expireDate = (expireDate!=null) ? (Date) expireDate.clone() : null; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public User getAuthor() { + return author; + } + public void setAuthor(User author) { + this.author = author; + } + + public Date getCreationDate() { + return (creationDate!=null) ? (Date) creationDate.clone() : null; + } + public void setCreationDate(Date creationDate) { + this.creationDate = (creationDate!=null) ? (Date) creationDate.clone() : null; + } + + public Workspace getWorkspace() { + return workspace; + } + public void setWorkspace(Workspace workspace) { + this.workspace = workspace; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SharedEntity that = (SharedEntity) o; + + if (expireDate != null ? !expireDate.equals(that.expireDate) : that.expireDate != null) { + return false; + } + if (password != null ? !password.equals(that.password) : that.password != null) { + return false; + } + if (uuid != null ? !uuid.equals(that.uuid) : that.uuid != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = uuid != null ? uuid.hashCode() : 0; + result = 31 * result + (expireDate != null ? expireDate.hashCode() : 0); + result = 31 * result + (password != null ? password.hashCode() : 0); + return result; + } + + private static String md5Sum(String pText) throws NoSuchAlgorithmException { + byte[] digest = MessageDigest.getInstance("MD5").digest(pText.getBytes()); + StringBuilder hexString = new StringBuilder(); + for (byte aDigest : digest) { + String hex = Integer.toHexString(0xFF & aDigest); + if (hex.length() == 1) { + hexString.append("0").append(hex); + } else { + hexString.append(hex); + } + } + return hexString.toString(); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/sharing/SharedEntityKey.java b/docdoku-common/src/main/java/com/docdoku/core/sharing/SharedEntityKey.java new file mode 100644 index 0000000000..4a00e02653 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/sharing/SharedEntityKey.java @@ -0,0 +1,88 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.sharing; + +import java.io.Serializable; + +/** + * Identity class of {@link SharedEntity} objects. + * + * @author Morgan Guimard + */ + +public class SharedEntityKey implements Serializable{ + + private String workspace; + private String uuid; + + public SharedEntityKey() { + } + + public SharedEntityKey(String workspace, String uuid) { + this.workspace = workspace; + this.uuid = uuid; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + + public String getWorkspace() { + return workspace; + } + + public void setWorkspace(String pWorkspace) { + workspace = pWorkspace; + } + + @Override + public String toString() { + return workspace + "-" + uuid; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SharedEntityKey that = (SharedEntityKey) o; + + return !(uuid != null ? !uuid.equals(that.uuid) : that.uuid != null) && + !(workspace != null ? !workspace.equals(that.workspace) : that.workspace != null); + + } + + @Override + public int hashCode() { + int result = workspace != null ? workspace.hashCode() : 0; + result = 31 * result + (uuid != null ? uuid.hashCode() : 0); + return result; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/sharing/SharedPart.java b/docdoku-common/src/main/java/com/docdoku/core/sharing/SharedPart.java new file mode 100644 index 0000000000..f0fe043018 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/sharing/SharedPart.java @@ -0,0 +1,82 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.sharing; + + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.product.PartRevision; + +import javax.persistence.*; +import java.util.Date; + +/** + * SharedPart permits the creation of permanent link to part for users that do not have an account. + * + * @author Morgan Guimard + */ + +@Table(name="SHAREDPART") +@Entity +@NamedQueries({ + @NamedQuery(name="SharedPart.deleteSharesForGivenPart", query="DELETE FROM SharedPart sp WHERE sp.partRevision = :pPartR") +}) +public class SharedPart extends SharedEntity{ + + @ManyToOne(optional=false, fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="PARTMASTER_PARTNUMBER", referencedColumnName="PARTMASTER_PARTNUMBER"), + @JoinColumn(name="PARTREVISION_VERSION", referencedColumnName="VERSION"), + @JoinColumn(name="ENTITY_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private PartRevision partRevision; + + public SharedPart(){ + } + + public SharedPart(Workspace workspace, User author, Date expireDate, String password, PartRevision partRevision) { + super(workspace, author, expireDate, password); + this.partRevision = partRevision; + } + + public SharedPart(Workspace workspace, User author, PartRevision partRevision) { + super(workspace, author); + this.partRevision = partRevision; + } + + public SharedPart(Workspace workspace, User author, Date expireDate, PartRevision partRevision) { + super(workspace, author, expireDate); + this.partRevision = partRevision; + } + + public SharedPart(Workspace workspace, User author, String password, PartRevision partRevision) { + super(workspace, author, password); + this.partRevision = partRevision; + } + + public PartRevision getPartRevision() { + return partRevision; + } + + public void setPartRevision(PartRevision partRevision) { + this.partRevision = partRevision; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/util/FileIO.java b/docdoku-common/src/main/java/com/docdoku/core/util/FileIO.java new file mode 100644 index 0000000000..0aba81c97d --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/util/FileIO.java @@ -0,0 +1,228 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.util; + +import java.io.*; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +/** + * + * @author Florent Garin + */ +public class FileIO { + + private static final int CHUNK_SIZE = 1024 * 8; + private static final int BUFFER_CAPACITY = 1024 * 16; + + + private static final List<String> DOC_EXTENSIONS = Arrays.asList("odt", "html", "sxw", "swf", "sxc", "doc", "docx", "xls", "xlsx", "rtf", "txt", "ppt", "pptx", "odp", "wpd", "tsv", "sxi", "csv", "pdf"); + private static final List<String> AV_EXTENSIONS = Arrays.asList("mp3", "mpg", "flv", "mp4", "aac", "mov"); + private static final List<String> IMAGE_EXTENSIONS = Arrays.asList("jpg", "png", "gif", "psd", "jpeg", "psp", "tif"); + private static final List<String> ARCHIVE_EXTENSIONS = Arrays.asList("zip"); + + private static final Logger LOGGER = Logger.getLogger(FileIO.class.getName()); + + private FileIO() { + } + + public static void rmDir(File pDir) { + if (pDir.isDirectory()) { + File[] files = pDir.listFiles(); + if(files!=null){ + for (File subFile : files) { + if (subFile.isDirectory()) { + rmDir(subFile); + } else { + subFile.delete(); + } + } + pDir.delete(); + } + } + } + + public static void copyFile(File pIn, File pOut) throws IOException { + pOut.getParentFile().mkdirs(); + pOut.createNewFile(); + try(InputStream in = new BufferedInputStream(new FileInputStream(pIn), BUFFER_CAPACITY); + OutputStream out = new BufferedOutputStream(new FileOutputStream(pOut), BUFFER_CAPACITY)) { + FileIO.copyBufferedStream(in, out); + } + } + + public static void copyBufferedStream(InputStream in, OutputStream out) throws IOException { + byte[] data = new byte[CHUNK_SIZE]; + int length; + while ((length = in.read(data)) != -1) { + out.write(data, 0, length); + } + } + + public static String getExtension(String fileName) { + String ext = null; + int i = fileName.lastIndexOf('.'); + if (i > 0 && i < fileName.length() - 1) { + ext = fileName.substring(i + 1).toLowerCase(); + } + return ext; + } + + public static String getExtension(File file) { + return getExtension(file.getName()); + } + + public static String getFileNameWithoutExtension(File file) { + return getFileNameWithoutExtension(file.getName()); + } + + public static String getFileNameWithoutExtension(String fileName) { + int index = fileName.lastIndexOf("."); + if(index!=-1) { + return fileName.substring(0, index); + } else { + return fileName; + } + } + + public static File urlToFile(URL url) { + File f; + try { + f = new File(url.toURI()); + } catch (Exception ex) { + f = new File(url.getPath()); + } + return f; + } + + public static String encodeURL(String url) throws UnsupportedEncodingException { + StringBuilder encodedURLBuf = new StringBuilder(); + + String[] parts = url.split("/"); + encodedURLBuf.append(URLEncoder.encode(parts[0], "UTF-8")); + + for (int i = 1; i < parts.length; i++) { + encodedURLBuf.append("/").append(URLEncoder.encode(parts[i], "UTF-8")); + } + return encodedURLBuf.toString(); + } + + public static String getLinkEncoded(String link, String enc) throws UnsupportedEncodingException { + StringBuilder codeUriBuild = new StringBuilder(); + + String[] tabFolder = link.split("/"); + codeUriBuild.append(URLEncoder.encode(tabFolder[0], enc)); + + for (int i = 1; i < tabFolder.length; i++) { + codeUriBuild.append("/").append(URLEncoder.encode(tabFolder[i], enc)); + } + return codeUriBuild.toString(); + } + + public static boolean isAVFile(String fileName){ + String ext=getExtension(fileName); + return AV_EXTENSIONS.contains(ext); + } + + public static boolean isDocFile(String fileName){ + String ext=getExtension(fileName); + return DOC_EXTENSIONS.contains(ext); + } + + public static boolean isImageFile(String fileName){ + String ext=getExtension(fileName); + return IMAGE_EXTENSIONS.contains(ext); + } + + public static boolean isArchiveFile(String fileName){ + String ext=getExtension(fileName); + return ARCHIVE_EXTENSIONS.contains(ext); + } + + public static void unzipArchive(File archive, File outputDir) { + try { + ZipFile zipfile = new ZipFile(archive); + for (Enumeration e = zipfile.entries(); e.hasMoreElements(); ) { + ZipEntry entry = (ZipEntry) e.nextElement(); + unzipEntry(zipfile, entry, outputDir); + } + } catch (Exception e) { + LOGGER.log(Level.FINEST,null,e); + } + } + + private static void unzipEntry(ZipFile zipfile, ZipEntry entry, File outputDir) throws IOException { + if (entry.isDirectory()) { + new File(outputDir, entry.getName()).mkdirs(); + return; + } + + File outputFile = new File(outputDir, entry.getName()); + if (!outputFile.getParentFile().exists()){ + outputFile.getParentFile().mkdirs(); + } + try(BufferedInputStream in = new BufferedInputStream(zipfile.getInputStream(entry), BUFFER_CAPACITY); + BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outputFile), BUFFER_CAPACITY)){ + copyBufferedStream(in, out); + } + } + + public static boolean existsInArchive(File archiveFile, String fileName) throws IOException { + boolean exists = false; + ZipFile zipfile = null; + try{ + zipfile = new ZipFile(archiveFile); + exists = zipfile.getEntry(fileName) != null; + }finally { + try{ + if(zipfile != null){ + zipfile.close(); + } + }catch (IOException e){ + LOGGER.log(Level.FINEST,null,e); + } + } + return exists; + } + + public static boolean existsInArchive(InputStream archiveInputStream, String fileName) { + ZipEntry zipEntry; + try(ZipInputStream zipInputStream = new ZipInputStream(archiveInputStream)) { + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + if (zipEntry.getName().equals(fileName)) { + zipInputStream.close(); + return true; + } + } + } catch (IOException e) { + LOGGER.log(Level.INFO, null, e); + } + return false; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/util/NamingConvention.java b/docdoku-common/src/main/java/com/docdoku/core/util/NamingConvention.java new file mode 100644 index 0000000000..c82c3bc033 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/util/NamingConvention.java @@ -0,0 +1,86 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.util; + +/** + * + * @author Florent Garin + */ +public class NamingConvention { + + private static final char[] FORBIDDEN_CHARS = { + '$','&','+',',','/',':',';','=','?','@','"', '<', '>', '#','%','{','}','|','\\','^','~','[',']',' ', '*','`' + }; + + private static final char[] FORBIDDEN_CHARS_MASK = { + '$','&','+',',','/',':',';','=','?','@','"', '<', '>','%','{','}','|','\\','^','~','[',']',' ','`' + }; + + private static final char[] FORBIDDEN_CHARS_FILE = { + '/', '\\', ':', '*', '?','"', '<', '>', '|', '~', '#', + '^', '%', '{', '}','&','$','+',',', ';', '@', '\'', '`','=', '[', ']' + }; + + private static final String[] FORBIDDEN_NAMES = {"",".."}; + + private NamingConvention() { + } + + private static boolean forbidden(char pChar, char[] forbiddenChars) { + for (char forbiddenChar : forbiddenChars) { + if (pChar == forbiddenChar) { + return true; + } + } + return false; + } + + private static boolean correct(String pShortName, char[] forbiddenChars) { + if (pShortName == null) { + return false; + } + + for (String forbiddenName : FORBIDDEN_NAMES) { + if (pShortName.equals(forbiddenName)) { + return false; + } + } + + for (int i = 0; i < pShortName.length(); i++) { + if (forbidden(pShortName.charAt(i), forbiddenChars)) { + return false; + } + } + return true; + } + + public static boolean correct(String pShortName) { + return correct(pShortName, FORBIDDEN_CHARS); + } + + + public static boolean correctNameFile(String pShortName) { + return correct(pShortName, FORBIDDEN_CHARS_FILE); + } + public static boolean correctNameMask(String mask) { + return correct(mask, FORBIDDEN_CHARS_MASK); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/util/Tools.java b/docdoku-common/src/main/java/com/docdoku/core/util/Tools.java new file mode 100644 index 0000000000..c22084b660 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/util/Tools.java @@ -0,0 +1,280 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.util; + +import com.docdoku.core.product.PartLink; +import com.docdoku.core.product.PartLinkList; +import com.docdoku.core.workflow.ActivityModel; +import com.docdoku.core.workflow.TaskModel; +import com.docdoku.core.workflow.WorkflowModel; + +import javax.swing.text.MaskFormatter; +import java.text.Normalizer; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/** + * + * @author Florent Garin + */ +public class Tools { + private static final Logger LOGGER = Logger.getLogger(Tools.class.getName()); + + private Tools() { + } + + public static WorkflowModel resetParentReferences(WorkflowModel pWf) { + for (ActivityModel activity : pWf.getActivityModels()) { + activity.setWorkflowModel(pWf); + resetParentReferences(activity); + } + + return pWf; + } + + private static ActivityModel resetParentReferences(ActivityModel pActivity) { + for (TaskModel task : pActivity.getTaskModels()) { + task.setActivityModel(pActivity); + } + + return pActivity; + } + + public static String unAccent(String s) { + String temp = Normalizer.normalize(s, Normalizer.Form.NFD); + Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); + return pattern.matcher(temp).replaceAll("").replaceAll("\\p{javaSpaceChar}", "_"); + } + + public static String increaseId(String id, String mask) throws ParseException { + LOGGER.info("#### Tools.increaseId id = " + id + " , mask = " + mask); + MaskFormatter formatter = new MaskFormatter(mask); + formatter.setValueContainsLiteralCharacters(false); + String value = formatter.stringToValue(id).toString(); + StringBuilder newValue = new StringBuilder(); + boolean increase = true; + for (int i = value.length() - 1; i >= 0; i--) { + char c = value.charAt(i); + switch (c) { + case '9': + newValue.append((increase) ? '0' : '9'); + break; + + case '8': + newValue.append((increase) ? '9' : '8'); + increase = false; + break; + + case '7': + newValue.append((increase) ? '8' : '7'); + increase = false; + break; + + case '6': + newValue.append((increase) ? '7' : '6'); + increase = false; + break; + + case '5': + newValue.append((increase) ? '6' : '5'); + increase = false; + break; + + case '4': + newValue.append((increase) ? '5' : '4'); + increase = false; + break; + + case '3': + newValue.append((increase) ? '4' : '3'); + increase = false; + break; + + case '2': + newValue.append((increase) ? '3' : '2'); + increase = false; + break; + + case '1': + newValue.append((increase) ? '2' : '1'); + increase = false; + break; + + case '0': + newValue.append((increase) ? '1' : '0'); + increase = false; + break; + + default: + newValue.append(c); + break; + } + } + return formatter.valueToString(newValue.reverse().toString()); + } + + public static boolean validateMask(String mask, String str){ + + // '*' goes for any alpha-numeric char, '#' for numbers only + if(mask == null || mask.length() == 0){ + return true; + } + + // Not same length + if(mask.length() != str.length()){ + return false; + } + + Pattern alphaNum = Pattern.compile("[a-zA-Z0-9]"); + + for (int i = 0; i < mask.length(); i++) { + + if('*' == mask.charAt(i) && !alphaNum.matcher(str.charAt(i)+"").find()){ + return false; + } + if ('#' == mask.charAt(i) && !Character.isDigit(str.charAt(i))){ + return false; + } + } + + return true; + + } + + public static String convertMask(String inputMask) { + StringBuilder maskBuilder = new StringBuilder(); + for (int i = 0; i < inputMask.length(); i++) { + char currentChar = inputMask.charAt(i); + switch (currentChar) { + case '#': + case '*': + maskBuilder.append(currentChar); + break; + + case '\'': + if (i + 1 < inputMask.length()) { + char nextChar = inputMask.charAt(i + 1); + switch (nextChar) { + case '#': + case '*': + maskBuilder.append(currentChar); + break; + case '\'': + maskBuilder.append(currentChar); + maskBuilder.append(nextChar); + i++; + break; + } + } + break; + + case 'U': + case 'L': + case 'A': + case '?': + case 'H': + maskBuilder.append('\''); + maskBuilder.append(currentChar); + break; + + default: + maskBuilder.append(currentChar); + break; + } + + } + return maskBuilder.toString(); + } + + public static String getPathAsString(List<PartLink> path) { + List<String> ids = new ArrayList<>(); + for (PartLink link : path) { + ids.add(link.getFullId()); + } + return String.join("-", ids); // java 8 + } + + public static String getPathInstanceAsString(List<PartLink> path, List<Integer> instancesIds) { + + if(path.size() != instancesIds.size()){ + throw new IllegalArgumentException("Path and instances must be same sized"); + } + + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < path.size(); i++){ + sb.append(path.get(i).getFullId()); + sb.append("-"); + sb.append(instancesIds.get(i)); + sb.append("-"); + } + + String s = sb.toString(); + return s.substring(0, s.length() - 1); + } + + public static String getPartLinksAsHumanString(Map<String, List<PartLinkList>> links){ + return getPartLinkAsString(links, " -> "); + } + + public static String getPartLinksAsExcelString(Map<String, List<PartLinkList>> links){ + return getPartLinkAsString(links, " - "); + } + + private static String getPartLinkAsString(Map<String, List<PartLinkList>> links, String joinWith) { + List<String> componentNumbers = new ArrayList<>(); + List<String> pathStrings = new ArrayList<>(); + List<String> typeStrings = new ArrayList<>(); + + for (String type : links.keySet()) { + List<PartLinkList> paths = links.get(type); + + for (PartLinkList path : paths) { + + for (PartLink partLink : path.getPath()) { + String linkAsString = partLink.getComponent().getName() + " < " + partLink.getComponent().getNumber() + " > "; + + if (partLink.getReferenceDescription() != null && !partLink.getReferenceDescription().isEmpty()) { + linkAsString += " ( " + partLink.getReferenceDescription() + " )"; + } + componentNumbers.add(linkAsString); + } + + + String join = String.join(joinWith,componentNumbers); + pathStrings.add(type + ": " + join); + componentNumbers.clear(); + } + + String typeLines = String.join("\n", pathStrings); + typeStrings.add(typeLines); + pathStrings.clear(); + } + + String fullString = String.join("\n", typeStrings); + typeStrings.clear(); + + return fullString; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/Activity.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/Activity.java new file mode 100644 index 0000000000..262720825d --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/Activity.java @@ -0,0 +1,195 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlSeeAlso; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +/** + * A base class which represents a group of {@link Task} + * linked to a step of a {@link Workflow}. + * It's the responsibility of the concrete implementation to decide how + * the workflow will progress to the next step and thus launch the execution + * of the next Activity. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="ACTIVITY") +@javax.persistence.IdClass(com.docdoku.core.workflow.ActivityKey.class) +@XmlSeeAlso({SequentialActivity.class, ParallelActivity.class}) +@Inheritance() +@Entity +public abstract class Activity implements Serializable, Cloneable { + + @Id + protected int step; + + @Id + @ManyToOne(optional=false, fetch=FetchType.EAGER) + @JoinColumn(name="WORKFLOW_ID", referencedColumnName="ID") + protected Workflow workflow; + + @OneToMany(mappedBy="activity", cascade=CascadeType.ALL, fetch=FetchType.EAGER) + @OrderBy(value="num") + protected List<Task> tasks=new LinkedList<>(); + protected String lifeCycleState; + @ManyToOne(optional = true,fetch=FetchType.EAGER) + @JoinTable ( + name="ACTIVITY_RELAUNCH", + joinColumns={ + @JoinColumn(name="ACTIVITY_STEP", referencedColumnName="STEP"), + @JoinColumn(name="ACTIVITY_WORKFLOW_ID", referencedColumnName="WORKFLOW_ID") + }, + inverseJoinColumns={ + @JoinColumn(name="RELAUNCH_STEP", referencedColumnName = "STEP"), + @JoinColumn(name="RELAUNCH_WORKFLOW_ID", referencedColumnName = "WORKFLOW_ID") + } + ) + private Activity relaunchActivity; + + public Activity(){ + + } + public Activity(int pStep, String pLifeCycleState){ + step=pStep; + lifeCycleState=pLifeCycleState; + } + + public ActivityKey getKey() { + return new ActivityKey(getWorkflowId(), step); + } + + public int getStep(){ + return step; + } + public void setStep(int pStep){ + step=pStep; + } + + public List<Task> getTasks() { + return tasks; + } + public void setTasks(List<Task> tasks) { + this.tasks = tasks; + } + + public String getLifeCycleState(){ + return lifeCycleState; + } + public void setLifeCycleState(String pLifeCycleState){ + lifeCycleState=pLifeCycleState; + } + + @XmlTransient + public Workflow getWorkflow() { + return workflow; + } + public void setWorkflow(Workflow workflow) { + this.workflow = workflow; + } + public int getWorkflowId() { + return workflow==null ? 0 : workflow.getId(); + } + + @XmlTransient + public Activity getRelaunchActivity() { + return relaunchActivity; + } + public void setRelaunchActivity(Activity relaunchActivity) { + this.relaunchActivity = relaunchActivity; + } + + public abstract Collection<Task> getOpenTasks(); + + public abstract boolean isComplete(); + public abstract boolean isStopped(); + public boolean isInProgress() { + if (!isComplete() && !isStopped()) { + for (Task task : tasks) { + if(task.isInProgress()){ + return true; + } + } + } + return false; + } + public boolean isToDo(){ + for(Task task : tasks){ + if(task.isNotToBeDone()){ + return false; + } + } + return true; + } + + public abstract void relaunch(); + + /** + * perform a deep clone operation + */ + @Override + public Activity clone() { + try { + Activity clone = (Activity) super.clone(); + //perform a deep copy + List<Task> clonedTasks = new LinkedList<>(); + for (Task task : tasks) { + Task clonedTask=task.clone(); + clonedTask.setActivity(clone); + clonedTasks.add(clonedTask); + } + clone.tasks = clonedTasks; + return clone; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Activity activity = (Activity) o; + + return step == activity.step + && (workflow != null ? workflow.equals(activity.workflow) : activity.workflow == null); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + (workflow==null?0:workflow.hashCode()); + hash = 31 * hash + step; + return hash; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/ActivityKey.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/ActivityKey.java new file mode 100644 index 0000000000..534fa88119 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/ActivityKey.java @@ -0,0 +1,81 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.workflow; + +import java.io.Serializable; + +/** + * + * @author Florent Garin + */ +public class ActivityKey implements Serializable { + + private int workflow; + private int step; + + public ActivityKey() { + } + + public ActivityKey(int pWorkflowId, int pStep) { + workflow = pWorkflowId; + step = pStep; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workflow; + hash = 31 * hash + step; + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof ActivityKey)) { + return false; + } + ActivityKey key = (ActivityKey) pObj; + return key.workflow == workflow && key.step == step; + } + + @Override + public String toString() { + return workflow + "-" + step; + } + + public int getWorkflowId() { + return workflow; + } + + public void setWorkflowId(int workflowId) { + this.workflow = workflowId; + } + + public int getStep() { + return step; + } + + public void setStep(int pStep) { + step = pStep; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/ActivityModel.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/ActivityModel.java new file mode 100644 index 0000000000..95a938d580 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/ActivityModel.java @@ -0,0 +1,200 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlSeeAlso; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Defines common attributes and behaviors for activities model. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="ACTIVITYMODEL") +@XmlSeeAlso({SequentialActivityModel.class, ParallelActivityModel.class}) +@Inheritance() +@Entity +public abstract class ActivityModel implements Serializable, Cloneable { + + @javax.persistence.Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @ManyToOne(optional=false, fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="WORKFLOWMODEL_ID", referencedColumnName="ID"), + @JoinColumn(name="WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + protected WorkflowModel workflowModel; + + @OneToMany(mappedBy = "activityModel", cascade=CascadeType.ALL, fetch=FetchType.EAGER) + @OrderBy("num") + protected List<TaskModel> taskModels=new LinkedList<>(); + + protected int step; + + @ManyToOne(optional = true,fetch=FetchType.EAGER) + @JoinTable ( + name="ACTIVITYMODEL_RELAUNCH", + joinColumns={ + @JoinColumn(name="ACTIVITYMODEL_ID", referencedColumnName="ID") + }, + inverseJoinColumns={ + @JoinColumn(name="RELAUNCHACTIVITYMODEL_ID", referencedColumnName="ID") + } + ) + private ActivityModel relaunchActivity; + + protected String lifeCycleState; + + public ActivityModel(){ + + } + + public ActivityModel(WorkflowModel pWorkflowModel, int pStep, List<TaskModel> pTaskModels, String pLifeCycleState){ + setWorkflowModel(pWorkflowModel); + taskModels=pTaskModels; + step=pStep; + lifeCycleState=pLifeCycleState; + } + + + public void setWorkflowModel(WorkflowModel pWorkflowModel){ + workflowModel=pWorkflowModel; + } + + public int getStep(){ + return step; + } + + public void setStep(int pStep){ + step=pStep; + } + + public String getLifeCycleState(){ + return lifeCycleState; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public void setTaskModels(List<TaskModel> taskModels) { + this.taskModels = taskModels; + } + + + @XmlTransient + public WorkflowModel getWorkflowModel() { + return workflowModel; + } + + public void setLifeCycleState(String pLifeCycleState){ + lifeCycleState=pLifeCycleState; + } + + public void addTaskModel(TaskModel pTaskModel) { + taskModels.add(pTaskModel); + int index = taskModels.size()-1; + pTaskModel.setNum(index); + } + + public TaskModel removeTaskModel(int pOrder) { + TaskModel taskModel = taskModels.remove(pOrder); + for(int i=pOrder;i<taskModels.size();i++){ + taskModels.get(i).setNum(i); + } + return taskModel; + } + + public void removeTaskModel(TaskModel pTaskModel) { + int index = taskModels.indexOf(pTaskModel); + removeTaskModel(index); + } + + public List<TaskModel> getTaskModels() { + return taskModels; + } + + @XmlTransient + public ActivityModel getRelaunchActivity() { + return relaunchActivity; + } + + public void setRelaunchActivity(ActivityModel relaunchActivity) { + this.relaunchActivity = relaunchActivity; + } + + /** + * perform a deep clone operation + */ + @Override + public ActivityModel clone() { + ActivityModel clone; + try { + clone = (ActivityModel) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + //perform a deep copy + List<TaskModel> clonedTaskModels = new LinkedList<>(); + for (TaskModel taskModel : taskModels) { + TaskModel clonedTaskModel=taskModel.clone(); + clonedTaskModel.setActivityModel(clone); + clonedTaskModels.add(clonedTaskModel); + } + clone.taskModels = clonedTaskModels; + return clone; + } + + public abstract Activity createActivity(Map<Role,Collection<User>> roleUserMap, Map<Role,Collection<UserGroup>> roleGroupMap); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ActivityModel that = (ActivityModel) o; + + return id == that.id; + + } + + @Override + public int hashCode() { + return id; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/ParallelActivity.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/ParallelActivity.java new file mode 100644 index 0000000000..a38cc731f6 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/ParallelActivity.java @@ -0,0 +1,113 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import javax.persistence.Entity; +import javax.persistence.Table; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * ParallelActivity is a kind of activity where + * all its tasks start at the same time as the activity itself. + * Thus, there is no order between the executions of tasks. + * The <code>tasksToComplete</code> attribute specifies the number of tasks that + * should be completed so the workflow can progress to the next step. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="PARALLELACTIVITY") +@Entity +public class ParallelActivity extends Activity { + + private int tasksToComplete; + + public ParallelActivity() { + + } + + public ParallelActivity(int pStep, String pLifeCycleState, int pTasksToComplete) { + super(pStep, pLifeCycleState); + tasksToComplete=pTasksToComplete; + } + + @Override + public boolean isStopped() { + return tasks.size() - numberOfRejected() < tasksToComplete; + } + + private int numberOfApproved(){ + int approved=0; + for(Task task:tasks){ + if(task.isApproved() || task.isNotToBeDone()) { + approved++; + } + } + return approved; + } + + private int numberOfRejected(){ + int rejected=0; + for(Task task:tasks){ + if(task.isRejected()) { + rejected++; + } + } + return rejected; + } + + @Override + public Collection<Task> getOpenTasks() { + Set<Task> runningTasks = new HashSet<>(); + if (!isComplete() && !isStopped()) { + for (Task task : tasks) { + if(task.isInProgress() || task.isNotStarted()) { + runningTasks.add(task); + } + } + } + return runningTasks; + } + + public void setTasksToComplete(int tasksToComplete) { + this.tasksToComplete = tasksToComplete; + } + + + public int getTasksToComplete() { + return tasksToComplete; + } + + @Override + public boolean isComplete() { + return numberOfApproved() >= tasksToComplete; + } + + @Override + public void relaunch(){ + for(Task t : tasks){ + t.start(); + } + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/ParallelActivityModel.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/ParallelActivityModel.java new file mode 100644 index 0000000000..7dd9a20d1a --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/ParallelActivityModel.java @@ -0,0 +1,102 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; + +import javax.persistence.Entity; +import javax.persistence.Table; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * This class is the model used to create instances + * of {@link ParallelActivity} attached to workflows. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="PARALLELACTIVITYMODEL") +@Entity +public class ParallelActivityModel extends ActivityModel { + + + private int tasksToComplete; + + public ParallelActivityModel() { + } + + public ParallelActivityModel(WorkflowModel pWorkflowModel, int pStep, List<TaskModel> pTaskModels, String pLifeCycleState, int pTasksToComplete) { + super(pWorkflowModel, pStep, pTaskModels, pLifeCycleState); + tasksToComplete=pTasksToComplete; + } + + public ParallelActivityModel(WorkflowModel pWorkflowModel, String pLifeCycleState, int pTasksToComplete) { + this(pWorkflowModel, 0, new LinkedList<>(), pLifeCycleState,pTasksToComplete); + } + + + public int getTasksToComplete() { + return tasksToComplete; + } + + public void setTasksToComplete(int pTasksToComplete) { + if (pTasksToComplete < this.getTaskModels().size()) { + tasksToComplete = pTasksToComplete; + } else { + tasksToComplete = this.getTaskModels().size(); + } + } + + @Override + public void removeTaskModel(TaskModel pTaskModel) { + super.removeTaskModel(pTaskModel); + if (tasksToComplete > taskModels.size()) { + tasksToComplete--; + } + } + + @Override + public Activity createActivity(Map<Role,Collection<User>> roleUserMap, Map<Role,Collection<UserGroup>> roleGroupMap) { + Activity activity = new ParallelActivity(step, lifeCycleState, tasksToComplete); + List<Task> tasks = activity.getTasks(); + for(TaskModel model:taskModels){ + Task task = model.createTask(roleUserMap, roleGroupMap); + task.setActivity(activity); + tasks.add(task); + } + return activity; + } + + @Override + public String toString() { + return taskModels + " (" + getTasksToComplete() + "/" + taskModels.size() + ")"; + } + + @Override + public ParallelActivityModel clone() { + return (ParallelActivityModel) super.clone(); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/Role.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/Role.java new file mode 100644 index 0000000000..87212b0f26 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/Role.java @@ -0,0 +1,162 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; +import com.docdoku.core.common.Workspace; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * This class is the model used to create roles in a workspace + * + * @author Morgan Guimard + */ +@Table(name="ROLE") +@IdClass(RoleKey.class) +@javax.persistence.Entity +@NamedQueries({ + @NamedQuery(name="Role.findByWorkspace", query="SELECT r FROM Role r WHERE r.workspace.id = :workspaceId"), + @NamedQuery(name="Role.findRolesWhereUserIsAssigned", query="SELECT r FROM Role r WHERE :user member of r.defaultAssignedUsers"), + @NamedQuery(name="Role.findRolesWhereGroupIsAssigned", query="SELECT r FROM Role r WHERE :userGroup member of r.defaultAssignedUsers") +}) +public class Role implements Serializable { + + @Column(length = 100) + @Id + private String name = ""; + + @Id + @ManyToOne(optional = false, fetch = FetchType.EAGER) + private Workspace workspace; + + @ManyToMany(fetch=FetchType.EAGER) + @JoinTable(name="ROLE_USER", + inverseJoinColumns={ + @JoinColumn(name="USER_LOGIN", referencedColumnName="LOGIN"), + @JoinColumn(name="USER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }, + joinColumns={ + @JoinColumn(name="ROLE_NAME", referencedColumnName="NAME"), + @JoinColumn(name="ROLE_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID"), + }) + private Set<User> defaultAssignedUsers=new HashSet<>(); + + @ManyToMany(fetch=FetchType.EAGER) + @JoinTable(name="ROLE_USERGROUP", + inverseJoinColumns={ + @JoinColumn(name="USERGROUP_ID", referencedColumnName="ID"), + @JoinColumn(name="USERGROUP_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }, + joinColumns={ + @JoinColumn(name="ROLE_NAME", referencedColumnName="NAME"), + @JoinColumn(name="ROLE_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private Set<UserGroup> defaultAssignedGroups=new HashSet<>(); + + public Role(){ + } + + public Role(String name, Workspace workspace){ + this.name = name; + this.workspace = workspace; + } + + public Role(String name, Workspace workspace, Set<User> defaultAssignedUsers, Set<UserGroup> defaultAssignedGroups){ + this.name = name; + this.workspace = workspace; + this.defaultAssignedUsers = defaultAssignedUsers; + this.defaultAssignedGroups = defaultAssignedGroups; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Workspace getWorkspace() { + return workspace; + } + + public void setWorkspace(Workspace workspace) { + this.workspace = workspace; + } + + public Set<User> getDefaultAssignedUsers() { + return defaultAssignedUsers; + } + + public void setDefaultAssignedUsers(Set<User> defaultAssignedUsers) { + this.defaultAssignedUsers = defaultAssignedUsers; + } + + public Set<UserGroup> getDefaultAssignedGroups() { + return defaultAssignedGroups; + } + + public void setDefaultAssignedGroups(Set<UserGroup> defaultAssignedGroups) { + this.defaultAssignedGroups = defaultAssignedGroups; + } + + public void addUser(User user){ + this.defaultAssignedUsers.add(user); + } + + public void removeUser(User user){ + this.defaultAssignedUsers.remove(user); + } + + public void addUserGroup(UserGroup userGroup){ + this.defaultAssignedGroups.add(userGroup); + } + + public void removeUserGroup(UserGroup userGroup){ + this.defaultAssignedGroups.remove(userGroup); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Role role = (Role) o; + + if (name != null ? !name.equals(role.name) : role.name != null) return false; + return !(workspace != null ? !workspace.equals(role.workspace) : role.workspace != null); + + } + + @Override + public int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + (workspace != null ? workspace.hashCode() : 0); + return result; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/RoleKey.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/RoleKey.java new file mode 100644 index 0000000000..8c0a4c94e3 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/RoleKey.java @@ -0,0 +1,81 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import java.io.Serializable; + +/** + * + * @author Morgan Guimard + */ +public class RoleKey implements Serializable { + + + private String workspace; + private String name; + + public RoleKey() { + } + + public RoleKey(String pWorkspaceId, String pName) { + workspace = pWorkspaceId; + name = pName; + } + + public String getWorkspace() { + return workspace; + } + + public void setWorkspace(String workspace) { + this.workspace = workspace; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + RoleKey roleKey = (RoleKey) o; + + return name.equals(roleKey.name) && workspace.equals(roleKey.workspace); + + } + + @Override + public int hashCode() { + int result = workspace.hashCode(); + result = 31 * result + name.hashCode(); + return result; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/SequentialActivity.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/SequentialActivity.java new file mode 100644 index 0000000000..ae4ccc1e49 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/SequentialActivity.java @@ -0,0 +1,89 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import javax.persistence.Entity; +import javax.persistence.Table; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * SequentialActivity is an activity where + * all tasks are launched subsequently in a specific order. + * For the workflow to proceed to the next step, all tasks of + * SequentialActivity should have been completed. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="SEQUENTIALACTIVITY") +@Entity +public class SequentialActivity extends Activity { + public SequentialActivity() { + + } + + public SequentialActivity(int pStep, String pLifeCycleState) { + super(pStep, pLifeCycleState); + } + + @Override + public boolean isStopped() { + for(Task task:tasks) + if(task.isRejected()) { + return true; + } + + return false; + } + + @Override + public Collection<Task> getOpenTasks() { + List<Task> runningTasks = new ArrayList<>(); + if (!isComplete() && !isStopped()) { + for(Task task:tasks){ + if (task.isInProgress() || task.isNotStarted()) { + runningTasks.add(task); + break; + } + } + } + return runningTasks; + } + + @Override + public boolean isComplete() { + for(Task task:tasks) { + if (!(task.isApproved() || task.isNotToBeDone())) { + return false; + } + } + + return true; + } + + @Override + public void relaunch(){ + tasks.get(0).start(); + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/SequentialActivityModel.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/SequentialActivityModel.java new file mode 100644 index 0000000000..d2c2722a25 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/SequentialActivityModel.java @@ -0,0 +1,99 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; + +import javax.persistence.Entity; +import javax.persistence.Table; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * This class is the model used to create instances + * of {@link SequentialActivity} attached to workflows. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="SEQUENTIALACTIVITYMODEL") +@Entity +public class SequentialActivityModel extends ActivityModel { + + + + public SequentialActivityModel() { + } + + public SequentialActivityModel(WorkflowModel pWorkflowModel, int pStep, List<TaskModel> pTaskModels, String pLifeCycleState) { + super(pWorkflowModel, pStep, pTaskModels, pLifeCycleState); + } + + public SequentialActivityModel(WorkflowModel pWorkflowModel, String pLifeCycleState) { + this(pWorkflowModel, 0, new LinkedList<>(), pLifeCycleState); + } + + @Override + public Activity createActivity(Map<Role,Collection<User>> roleUserMap, Map<Role,Collection<UserGroup>> roleGroupMap) { + Activity activity = new SequentialActivity(step, lifeCycleState); + List<Task> tasks = activity.getTasks(); + for(TaskModel model:taskModels){ + Task task = model.createTask(roleUserMap, roleGroupMap); + task.setActivity(activity); + tasks.add(task); + } + return activity; + } + + @Override + public String toString() { + return taskModels.toString(); + } + + public void moveUpTaskModel(int pSelectedIndex) { + if (pSelectedIndex > 0) { + TaskModel taskModel = taskModels.remove(pSelectedIndex); + int newIndex=pSelectedIndex-1; + taskModels.get(newIndex).setNum(pSelectedIndex); + taskModels.add(newIndex, taskModel); + taskModel.setNum(newIndex); + } + } + + public void moveDownTaskModel(int pSelectedIndex) { + if (pSelectedIndex < taskModels.size() - 1) { + TaskModel taskModel = taskModels.remove(pSelectedIndex); + int newIndex=pSelectedIndex+1; + taskModels.get(pSelectedIndex).setNum(pSelectedIndex); + taskModels.add(newIndex, taskModel); + taskModel.setNum(newIndex); + } + } + + @Override + public SequentialActivityModel clone() { + return (SequentialActivityModel) super.clone(); + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/Task.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/Task.java new file mode 100644 index 0000000000..21e2bca03f --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/Task.java @@ -0,0 +1,377 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.*; + +/** + * Task is the smallest unit of work in a workflow. + * + * @author Florent Garin + * @version 2.5, 27/06/16 + * @since V1.0 + */ +@Table(name = "TASK") +@javax.persistence.IdClass(com.docdoku.core.workflow.TaskKey.class) +@Entity +@NamedQueries ({ + @NamedQuery(name="Task.findInProgressTasks", query="SELECT DISTINCT t FROM Task t LEFT JOIN t.assignedUsers au LEFT JOIN t.assignedGroups ag LEFT JOIN ag.users agu WHERE ((au.login = :login AND au.workspaceId = :workspaceId) OR (agu.login = :login AND agu.workspaceId = :workspaceId)) AND t.status = com.docdoku.core.workflow.Task.Status.IN_PROGRESS"), + @NamedQuery(name="Task.findAssignedTasks", query="SELECT DISTINCT t FROM Task t LEFT JOIN t.assignedUsers au LEFT JOIN t.assignedGroups ag LEFT JOIN ag.users agu WHERE ((au.login = :login AND au.workspaceId = :workspaceId) OR (agu.login = :login AND agu.workspaceId = :workspaceId))") +}) +public class Task implements Serializable, Cloneable { + @Id + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "ACTIVITY_STEP", referencedColumnName = "STEP"), + @JoinColumn(name = "WORKFLOW_ID", referencedColumnName = "WORKFLOW_ID") + }) + private Activity activity; + + @Id + private int num; + + private String title; + + @Lob + private String instructions; + + private int duration = 1; + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date startDate; + + /** + * The user who effectively performed the task, which hence is in + * the {@link Status#APPROVED} or {@link Status#REJECTED} state. + */ + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name = "WORKER_LOGIN", referencedColumnName = "LOGIN"), + @JoinColumn(name = "WORKER_WORKSPACE_ID", referencedColumnName = "WORKSPACE_ID") + }) + private User worker; + + @ManyToMany + @JoinTable(name="TASK_USER", + inverseJoinColumns={ + @JoinColumn(name="USER_LOGIN", referencedColumnName="LOGIN"), + @JoinColumn(name="USER_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }, + joinColumns={ + @JoinColumn(name="TASK_NUM", referencedColumnName="NUM"), + @JoinColumn(name="ACTIVITY_STEP", referencedColumnName="ACTIVITY_STEP"), + @JoinColumn(name="WORKFLOW_ID", referencedColumnName="WORKFLOW_ID") + }) + private Set<User> assignedUsers=new HashSet<>(); + + + @ManyToMany + @JoinTable(name="TASK_USERGROUP", + inverseJoinColumns={ + @JoinColumn(name="USERGROUP_ID", referencedColumnName="ID"), + @JoinColumn(name="USERGROUP_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }, + joinColumns={ + @JoinColumn(name="TASK_NUM", referencedColumnName="NUM"), + @JoinColumn(name="ACTIVITY_STEP", referencedColumnName="ACTIVITY_STEP"), + @JoinColumn(name="WORKFLOW_ID", referencedColumnName="WORKFLOW_ID") + }) + private Set<UserGroup> assignedGroups=new HashSet<>(); + + private int targetIteration; + private String closureComment; + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date closureDate; + + @Lob + private String signature; + + private Status status = Status.NOT_STARTED; + + /** + * Enumeration for the possible status of a task. + * + * {@link Status#NOT_STARTED} indicates that the task is not started yet. + * + * {@link Status#IN_PROGRESS} indicates that the task has been started and is currently in progress. + * + * {@link Status#APPROVED} indicates that the task has been closed and its output status is approved. + * + * {@link Status#REJECTED} indicates that the task has been closed and its output status is rejected. + * + * {@link Status#NOT_TO_BE_DONE} indicates that the task has been marked as not to be done which means + * that the task will never be started. This status is notably used when a workflow has been relaunched + * to identify the tasks which are considered definitely done and thus not to be done again. + */ + public enum Status { + NOT_STARTED, IN_PROGRESS, APPROVED, REJECTED, NOT_TO_BE_DONE + } + + + public Task() { + } + public Task(int pNum, String pTitle, String pInstructions, Collection<User> assignedU, Collection<UserGroup> assignedG) { + num = pNum; + title = pTitle; + if(assignedU!=null)assignedUsers.addAll(assignedU); + if(assignedG!=null)assignedGroups.addAll(assignedG); + instructions = pInstructions; + } + + public TaskKey getKey() { + return new TaskKey(new ActivityKey(getWorkflowId(), getActivityStep()), num); + } + + public int getNum() { + return num; + } + public void setNum(int num) { + this.num = num; + } + + public int getWorkflowId() { + return activity == null ? 0 : activity.getWorkflowId(); + } + + @XmlTransient + public Activity getActivity() { + return activity; + } + public void setActivity(Activity activity) { + this.activity = activity; + } + + public int getActivityStep() { + return activity == null ? 0 : activity.getStep(); + } + + public Task.Status getStatus() { + return status; + } + public void setStatus(Task.Status status) { + this.status = status; + } + + public String getTitle() { + return title; + } + public void setTitle(String title) { + this.title = title; + } + + public String getInstructions() { + return instructions; + } + public void setInstructions(String instructions) { + this.instructions = instructions; + } + + public User getWorker() { + return worker; + } + public void setWorker(User worker) { + this.worker = worker; + } + + public Date getStartDate() { + return (startDate!=null) ? (Date) startDate.clone(): null; + } + public void setStartDate(Date startDate) { + this.startDate = (startDate!=null) ? (Date) startDate.clone(): null; + } + + public int getDuration() { + return duration; + } + public void setDuration(int duration) { + this.duration = duration; + } + + public Date getClosureDate() { + return (closureDate!=null) ? (Date) closureDate.clone() : null; + } + public void setClosureDate(Date closureDate) { + this.closureDate = (closureDate!=null) ? (Date) closureDate.clone() : null; + } + + public String getClosureComment() { + return closureComment; + } + public void setClosureComment(String closureComment) { + this.closureComment = closureComment; + } + + public int getTargetIteration() { + return targetIteration; + } + public void setTargetIteration(int targetIteration) { + this.targetIteration = targetIteration; + } + + public String getSignature() { + return signature; + } + public void setSignature(String signature) { + this.signature = signature; + } + + public void reject(User pWorker, String pComment, int pTargetIteration) { + reject(pWorker, pComment, pTargetIteration, null); + } + public void reject(User pWorker, String pComment, int pTargetIteration, String pSignature) { + submit(pWorker, pComment,pTargetIteration,pSignature); + status = Status.REJECTED; + } + + public void approve(User pWorker, String pComment, int pTargetIteration) { + approve(pWorker, pComment, pTargetIteration, null); + } + public void approve(User pWorker, String pComment, int pTargetIteration, String pSignature) { + submit(pWorker, pComment,pTargetIteration,pSignature); + status = Status.APPROVED; + } + + private void submit(User pWorker, String pComment, int pTargetIteration, String pSignature){ + worker=pWorker; + closureDate = new Date(); + closureComment = pComment; + signature = pSignature; + targetIteration = pTargetIteration; + } + + public void start() { + if (isNotStarted()) { + startDate = new Date(); + status = Status.IN_PROGRESS; + } + } + public void stop() { + if (isInProgress()) { + status = Status.NOT_STARTED; + } + } + public void reset(Task.Status status) { + setStatus(status); + setSignature(null); + setClosureComment(null); + setClosureDate(null); + setStartDate(null); + } + + public boolean hasPotentialWorker(){ + return !assignedUsers.isEmpty() || !assignedGroups.isEmpty(); + } + + public boolean isPotentialWorker(User user){ + return assignedUsers.contains(user) || assignedGroups.stream().anyMatch(g->g.isMember(user)); + } + + public Set<User> getAssignedUsers() { + return assignedUsers; + } + + public void setAssignedUsers(Set<User> assignedUsers) { + this.assignedUsers = assignedUsers; + } + + public Set<UserGroup> getAssignedGroups() { + return assignedGroups; + } + + public void setAssignedGroups(Set<UserGroup> assignedGroups) { + this.assignedGroups = assignedGroups; + } + + public boolean isNotStarted() { + //because of bug #6277781 + return status.ordinal() == Status.NOT_STARTED.ordinal(); + } + public boolean isRejected() { + //because of bug #6277781 + return status.ordinal() == Status.REJECTED.ordinal(); + } + public boolean isApproved() { + //because of bug #6277781 + return status.ordinal() == Status.APPROVED.ordinal(); + } + public boolean isInProgress() { + //because of bug #6277781 + return status.ordinal() == Status.IN_PROGRESS.ordinal(); + } + public boolean isNotToBeDone() { + //because of bug #6277781 + return status.ordinal() == Status.NOT_TO_BE_DONE.ordinal(); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + (activity == null ? 0 : activity.hashCode()); + hash = 31 * hash + num; + return hash; + } + + @Override + public boolean equals(Object o) { + if (this == o){ + return true; + } + if (o == null || getClass() != o.getClass()){ + return false; + } + + Task task = (Task) o; + + return num == task.num && (activity != null ? activity.equals(task.activity) : task.activity == null); + } + + @Override + public String toString() { + return title; + } + + /** + * perform a deep clone operation + */ + @Override + public Task clone() { + try { + Task clone = (Task) super.clone(); + + if (startDate != null) { + clone.startDate = (Date) startDate.clone(); + } + if (closureDate != null) { + clone.closureDate = (Date) closureDate.clone(); + } + return clone; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/TaskKey.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/TaskKey.java new file mode 100644 index 0000000000..f55ca88b2c --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/TaskKey.java @@ -0,0 +1,84 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import java.io.Serializable; + +/** + * + * @author Florent Garin + */ +public class TaskKey implements Serializable { + + + private ActivityKey activity; + private int num; + + public TaskKey() { + } + + public TaskKey(ActivityKey pActivityKey, int pNum) { + activity=pActivityKey; + num=pNum; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + activity.hashCode(); + hash = 31 * hash + num; + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof TaskKey)) { + return false; + } + TaskKey key = (TaskKey) pObj; + return key.activity.equals(activity) && key.num==num; + } + + @Override + public String toString() { + return activity.toString() + "-" + num; + } + + public ActivityKey getActivity() { + return activity; + } + + public void setActivity(ActivityKey activity) { + this.activity = activity; + } + + public int getNum() { + return num; + } + + + public void setNum(int num) { + this.num = num; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/TaskModel.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/TaskModel.java new file mode 100644 index 0000000000..02dbdfe3f7 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/TaskModel.java @@ -0,0 +1,187 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; + +import javax.persistence.*; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.Collection; +import java.util.Map; + +/** + * This is the model for creating instances of {@link Task} + * that belong to instances of {@link Activity} themselves + * attached to instances of {@link Workflow}. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="TASKMODEL") +@javax.persistence.IdClass(com.docdoku.core.workflow.TaskModelKey.class) +@NamedQueries({ + @NamedQuery(name="Role.findRolesInUseByRoleName", query="SELECT t FROM TaskModel t WHERE t.role.name = :roleName AND t.role.workspace = :workspace"), + @NamedQuery(name="Role.findRolesInUse", query="SELECT t.role FROM TaskModel t WHERE t.role.workspace.id = :workspaceId") +}) +@Entity +public class TaskModel implements Serializable, Cloneable { + + @Id + @ManyToOne(optional=false, fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="ACTIVITYMODEL_ID", referencedColumnName="ID") + }) + private ActivityModel activityModel; + + @Id + private int num; + + @Lob + private String instructions; + private String title; + private int duration; + + @ManyToOne(fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="ROLE_NAME", referencedColumnName="NAME"), + @JoinColumn(name="ROLE_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private Role role; + + + public TaskModel(ActivityModel pActivityModel, int pNum, String pTitle, String pInstructions, Role pRole) { + setActivityModel(pActivityModel); + num=pNum; + title=pTitle; + role=pRole; + instructions=pInstructions; + } + + public TaskModel(ActivityModel pActivityModel, String pTitle, String pInstructions, Role pRole) { + this(pActivityModel, 0,pTitle,pInstructions,pRole); + } + public TaskModel() { + + } + + public Task createTask(Map<Role,Collection<User>> roleUserMap, Map<Role,Collection<UserGroup>> roleGroupMap) { + Collection<User> assignedUsers = roleUserMap.get(role); + Collection<UserGroup> assignedGroups = roleGroupMap.get(role); + return new Task(num, title,instructions,assignedUsers,assignedGroups); + } + + public String getTitle() { + return title; + } + + public int getDuration() { + return duration; + } + + public void setDuration(int duration) { + this.duration = duration; + } + + + public String getInstructions() { + return instructions; + } + + public Role getRole() { + return role; + } + + public void setRole(Role role) { + this.role = role; + } + + public void setTitle(String pTitle) { + title=pTitle; + } + + public void setInstructions(String pInstructions) { + instructions = pInstructions; + } + + @XmlTransient + public ActivityModel getActivityModel() { + return activityModel; + } + + + public void setActivityModel(ActivityModel activityModel) { + this.activityModel = activityModel; + } + + public int getNum() { + return num; + } + + public void setNum(int num) { + this.num = num; + } + + public int getActivityModelId(){ + return activityModel==null?0:activityModel.getId(); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + getActivityModelId(); + hash = 31 * hash + num; + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof TaskModel)) { + return false; + } + TaskModel model = (TaskModel) pObj; + return model.getActivityModelId()==getActivityModelId() && + model.num==num; + } + + @Override + public String toString() { + return getActivityModelId() + "-" + num; + } + + + @Override + public TaskModel clone() { + TaskModel clone; + try { + clone = (TaskModel) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + return clone; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/TaskModelKey.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/TaskModelKey.java new file mode 100644 index 0000000000..4955a4a6c5 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/TaskModelKey.java @@ -0,0 +1,82 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import java.io.Serializable; + +/** + * + * @author Florent Garin + */ +public class TaskModelKey implements Serializable { + + private int activityModel; + private int num; + + public TaskModelKey() { + } + + public TaskModelKey(int pActivityModelId, int pNum) { + activityModel =pActivityModelId; + num=pNum; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + activityModel; + hash = 31 * hash + num; + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof TaskModelKey)) { + return false; + } + TaskModelKey key = (TaskModelKey) pObj; + return key.activityModel == activityModel && + key.num==num; + } + + @Override + public String toString() { + return activityModel + "-" + num; + } + + public int getNum() { + return num; + } + public void setNum(int num) { + this.num = num; + } + + public int getActivityModel() { + return activityModel; + } + + public void setActivityModel(int activityModel) { + this.activityModel = activityModel; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/TaskWrapper.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/TaskWrapper.java new file mode 100644 index 0000000000..8524c3860b --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/TaskWrapper.java @@ -0,0 +1,90 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import java.io.Serializable; + +/** + * @author Morgan Guimard + */ + +public class TaskWrapper implements Serializable{ + + private Task task; + + private String workspaceId; + private String holderType; + private String holderReference; + private String holderVersion; + + + public TaskWrapper(Task task, String workspaceId) { + this.task = task; + this.workspaceId = workspaceId; + } + + public TaskWrapper(Task task, String workspaceId, String holderType, String holderReference, String holderVersion) { + this(task,workspaceId); + this.holderType = holderType; + this.holderReference = holderReference; + this.holderVersion = holderVersion; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public Task getTask() { + return task; + } + + public void setTask(Task task) { + this.task = task; + } + + public String getHolderType() { + return holderType; + } + + public void setHolderType(String holderType) { + this.holderType = holderType; + } + + public String getHolderReference() { + return holderReference; + } + + public void setHolderReference(String holderReference) { + this.holderReference = holderReference; + } + + public String getHolderVersion() { + return holderVersion; + } + + public void setHolderVersion(String holderVersion) { + this.holderVersion = holderVersion; + } +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/Workflow.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/Workflow.java new file mode 100644 index 0000000000..c1f53e8622 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/Workflow.java @@ -0,0 +1,189 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ +package com.docdoku.core.workflow; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.*; + +/** + * Workflows organize tasks around documents, parts (or other objects) + * on which they are applied. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="WORKFLOW") +@javax.persistence.Entity +public class Workflow implements Serializable { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private int id; + + @OneToMany(mappedBy = "workflow", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @OrderBy("step ASC") + private List<Activity> activities = new LinkedList<>(); + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date abortedDate; + + private String finalLifeCycleState; + + public Workflow() { + } + public Workflow(String pFinalLifeCycleState) { + finalLifeCycleState = pFinalLifeCycleState; + } + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + public List<Activity> getActivities() { + return activities; + } + public void setActivities(List<Activity> activities) { + this.activities = activities; + for(Activity activity : activities){ + activity.setWorkflow(this); + } + } + + public Activity getActivity(int pIndex) { + return activities.get(pIndex); + } + public Activity getCurrentActivity() { + if (getCurrentStep() < activities.size()) { + return getActivity(getCurrentStep()); + } else { + return null; + } + } + + public int getCurrentStep() { + int i = 0; + for (Activity activity : activities) { + if (activity.isComplete()) { + i++; + } else { + break; + } + } + return i; + } + + public String getFinalLifeCycleState() { + return finalLifeCycleState; + } + public void setFinalLifeCycleState(String finalLifeCycleState) { + this.finalLifeCycleState = finalLifeCycleState; + } + + public Date getAbortedDate() { + return (abortedDate!=null) ? (Date) abortedDate.clone() : null; + } + public void setAbortedDate(Date abortedDate) { + this.abortedDate = (abortedDate!=null) ? (Date) abortedDate.clone() : null; + } + + public Collection<Task> getRunningTasks() { + + Activity current = getCurrentActivity(); + if (current != null) { + return current.getOpenTasks(); + } else { + return new ArrayList<>(); + } + } + + public Collection<Task> getTasks(){ + Collection<Task> tasks = new ArrayList<>(); + for(Activity activity:activities){ + tasks.addAll(activity.getTasks()); + } + return tasks; + } + + public int numberOfSteps() { + return activities.size(); + } + + public List<String> getLifeCycle() { + List<String> lc = new LinkedList<>(); + for (Activity activity : activities) { + lc.add(activity.getLifeCycleState()); + } + + return lc; + } + public String getLifeCycleState() { + Activity current = getCurrentActivity(); + return current == null ? finalLifeCycleState : current.getLifeCycleState(); + } + + public void abort() { + for (Activity activity : activities) { + for(Task task : activity.getTasks()){ + task.stop(); + } + } + this.setAbortedDate(new Date()); + } + public void relaunch(int relaunchActivityStep) { + for(Activity a :activities){ + if(a.getStep() < relaunchActivityStep){ + for(Task t : a.getTasks()){ + t.reset(Task.Status.NOT_TO_BE_DONE); + } + } + if(a.getStep() >= relaunchActivityStep){ + for(Task t : a.getTasks()){ + t.reset(Task.Status.NOT_STARTED); + } + } + } + + Activity currentActivity = activities.get(relaunchActivityStep); + currentActivity.relaunch(); + + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Workflow)) { + return false; + } + Workflow workflow = (Workflow) obj; + return workflow.id == id; + } + + @Override + public int hashCode() { + return id; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/WorkflowModel.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/WorkflowModel.java new file mode 100644 index 0000000000..df9fb9936e --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/WorkflowModel.java @@ -0,0 +1,289 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.security.ACL; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.*; + +/** + * This class is the model used to create instances of + * {@link Workflow} attached to documents, parts or any objects. + * + * @author Florent Garin + * @version 1.0, 02/06/08 + * @since V1.0 + */ +@Table(name="WORKFLOWMODEL") +@javax.persistence.IdClass(com.docdoku.core.workflow.WorkflowModelKey.class) +@javax.persistence.Entity +public class WorkflowModel implements Serializable, Cloneable { + + @Column(length=100) + @javax.persistence.Id + private String id=""; + + @javax.persistence.Column(name = "WORKSPACE_ID", length=100, nullable = false, insertable = false, updatable = false) + @javax.persistence.Id + private String workspaceId=""; + + + + @OneToMany(mappedBy = "workflowModel", cascade=CascadeType.ALL, fetch=FetchType.EAGER) + @OrderBy("step ASC") + private List<ActivityModel> activityModels=new LinkedList<>(); + + private String finalLifeCycleState; + + + @ManyToOne(fetch=FetchType.EAGER) + @JoinColumns({ + @JoinColumn(name="AUTHOR_LOGIN", referencedColumnName="LOGIN"), + @JoinColumn(name="AUTHOR_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private User author; + + @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP) + private Date creationDate; + + @javax.persistence.ManyToOne(optional=false, fetch=FetchType.EAGER) + private Workspace workspace; + + @OneToOne(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER) + private ACL acl; + + + public WorkflowModel() { + + } + + public WorkflowModel(Workspace pWorkspace, String pId, User pAuthor, String pFinalLifeCycleState) { + this(pWorkspace,pId,pAuthor,pFinalLifeCycleState,new LinkedList<>()); + } + + public WorkflowModel(Workspace pWorkspace, String pId, User pAuthor, String pFinalLifeCycleState, ActivityModel[] pActivityModels) { + this(pWorkspace, pId, pAuthor, pFinalLifeCycleState,new LinkedList<>(Arrays.asList(pActivityModels))); + } + public WorkflowModel(Workspace pWorkspace, String pId, User pAuthor, String pFinalLifeCycleState, List<ActivityModel> pActivityModels) { + id=pId; + setWorkspace(pWorkspace); + author = pAuthor; + finalLifeCycleState=pFinalLifeCycleState; + activityModels = pActivityModels; + } + + + public void addActivityModel(int pStep, ActivityModel pActivity) { + activityModels.add(pStep, pActivity); + for(int i=pStep;i<activityModels.size();i++){ + activityModels.get(i).setStep(i); + } + } + + public int numberOfSteps(){ + return activityModels.size(); + } + + public ActivityModel removeActivityModel(int pStep) { + ActivityModel activityModel =activityModels.remove(pStep); + for(int i=pStep;i<activityModels.size();i++){ + activityModels.get(i).setStep(i); + } + return activityModel; + } + + public List<ActivityModel> getActivityModels() { + return activityModels; + } + + public void setActivityModels(List<ActivityModel> activityModels) { + this.activityModels=activityModels; + } + + + public ActivityModel setActivityModel(int pStep, ActivityModel pActivity) { + pActivity.setStep(pStep); + return activityModels.set(pStep, pActivity); + } + + public ACL getAcl() { + return acl; + } + + public void setAcl(ACL acl) { + this.acl = acl; + } + + public Workflow createWorkflow(Map<Role,Collection<User>> roleUserMap, Map<Role,Collection<UserGroup>> roleGroupMap) { + Workflow workflow = new Workflow(finalLifeCycleState); + List<Activity> activities = workflow.getActivities(); + for(ActivityModel model:activityModels){ + Activity activity = model.createActivity(roleUserMap, roleGroupMap); + activity.setWorkflow(workflow); + if(model.getRelaunchActivity()!=null){ + activity.setRelaunchActivity(activities.get(model.getRelaunchActivity().getStep())); + } + activities.add(activity); + } + return workflow; + } + + public void setId(String id) { + this.id = id; + } + + + public String lifeCycleStateOfStep(int pStep) { + if(pStep == activityModels.size()) { + return finalLifeCycleState; + } + return activityModels.get(pStep).getLifeCycleState(); + } + + public ActivityModel getActivityModel(int pIndex) { + return activityModels.get(pIndex); + } + + public String getFinalLifeCycleState() { + return finalLifeCycleState; + } + public void setFinalLifeCycleState(String pFinalLifeCycleState) { + finalLifeCycleState = pFinalLifeCycleState; + } + + public void setAuthor(User pAuthor) { + author = pAuthor; + } + + public User getAuthor() { + return author; + } + + public void setCreationDate(Date pCreationDate) { + creationDate = pCreationDate; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setWorkspace(Workspace pWorkspace){ + workspace=pWorkspace; + workspaceId=workspace.getId(); + } + + public Workspace getWorkspace(){ + return workspace; + } + + public String getWorkspaceId(){ + return workspaceId; + } + + public WorkflowModelKey getKey() { + return new WorkflowModelKey(workspaceId, id); + } + + public List<String> getLifeCycle(){ + List<String> lc=new LinkedList<>(); + for(ActivityModel activityModel:activityModels) { + lc.add(activityModel.getLifeCycleState()); + } + return lc; + } + + public String getId(){ + return id; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof WorkflowModel)) { + return false; + } + WorkflowModel workflow = (WorkflowModel) pObj; + return workflow.id.equals(id) && workflow.workspaceId.equals(workspaceId); + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + id.hashCode(); + return hash; + } + + @Override + public String toString() { + return id; + } + + /** + * perform a deep clone operation + */ + @Override + public WorkflowModel clone() { + WorkflowModel clone; + try { + clone = (WorkflowModel) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + + Map<ActivityModel, ActivityModel> originalCopyMap=new IdentityHashMap<>(); + //perform a deep copy + List<ActivityModel> clonedActivityModels = new LinkedList<>(); + for (ActivityModel activityModel : activityModels) { + ActivityModel clonedActivityModel=activityModel.clone(); + originalCopyMap.put(activityModel, clonedActivityModel); + clonedActivityModel.setWorkflowModel(clone); + + //set relaunchActivity to cloned reference + ActivityModel relaunchAM = activityModel.getRelaunchActivity(); + if(relaunchAM!=null){ + ActivityModel clonedRelaunchAM = originalCopyMap.get(relaunchAM); + clonedActivityModel.setRelaunchActivity(clonedRelaunchAM); + } + + clonedActivityModels.add(clonedActivityModel); + } + clone.activityModels = clonedActivityModels; + + if(creationDate!=null) { + clone.creationDate = (Date) creationDate.clone(); + } + + if(acl!=null) { + clone.acl = acl.clone(); + } + return clone; + } + + +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/WorkflowModelKey.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/WorkflowModelKey.java new file mode 100644 index 0000000000..aafd8c9b0d --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/WorkflowModelKey.java @@ -0,0 +1,84 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import java.io.Serializable; + +/** + * Identity class of {@link WorkflowModel} objects. + * + * @author Florent Garin + */ +public class WorkflowModelKey implements Serializable { + + private String workspaceId; + private String id; + + public WorkflowModelKey() { + } + + public WorkflowModelKey(String pWorkspaceId, String pId) { + workspaceId=pWorkspaceId; + id=pId; + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + id.hashCode(); + return hash; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof WorkflowModelKey)) { + return false; + } + WorkflowModelKey key = (WorkflowModelKey) pObj; + return key.id.equals(id) && key.workspaceId.equals(workspaceId); + } + + @Override + public String toString() { + return workspaceId + "-" + id; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String pWorkspaceId) { + workspaceId = pWorkspaceId; + } + + public String getId() { + return id; + } + + public void setId(String pId) { + id = pId; + } + +} diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/WorkspaceWorkflow.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/WorkspaceWorkflow.java new file mode 100644 index 0000000000..d6c0024ee0 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/WorkspaceWorkflow.java @@ -0,0 +1,125 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import com.docdoku.core.common.Workspace; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * {@link Workflow}s are intended to be attached to a business object like + * a document or a part for instance. + * However sometimes we just need to start a workflow not linked to a particular document or part. + * This especially happens when we want to use a workflow to manage an object living in an external + * system. + * Hence this class wraps the necessary data for managing a running workflow virtually attached to + * an external object like the ordered list of aborted workflows. Moreover for convenience access, + * instances of this class have a string based id, unique in the context of a specific workspace. + * + * + * @version 2.5, 27/06/16 + * @since V2.5 + * + * @author Morgan Guimard + */ +@Table(name="WORKSPACE_WORKFLOW") +@IdClass(WorkspaceWorkflowKey.class) +@javax.persistence.Entity +public class WorkspaceWorkflow implements Serializable { + + @Column(name="ID", length=100) + @Id + private String id=""; + + @Id + @ManyToOne(optional=false, fetch=FetchType.EAGER) + private Workspace workspace; + + @OneToOne(orphanRemoval=true, cascade= CascadeType.ALL, fetch=FetchType.EAGER) + private Workflow workflow; + + + @OrderBy("abortedDate") + @OneToMany(orphanRemoval=true, cascade= CascadeType.ALL, fetch= FetchType.EAGER) + @JoinTable(name="WORKSPACE_ABORTED_WORKFLOW", + inverseJoinColumns={ + @JoinColumn(name="WORKFLOW_ID", referencedColumnName="ID") + }, + joinColumns={ + @JoinColumn(name="WORKSPACE_WORKFLOW_ID", referencedColumnName="ID"), + @JoinColumn(name="WORKSPACE_WORKFLOW_WORKSPACE_ID", referencedColumnName="WORKSPACE_ID") + }) + private List<Workflow> abortedWorkflows = new ArrayList<>(); + + public WorkspaceWorkflow(){ + } + + public WorkspaceWorkflow(Workspace workspace, String id, Workflow workflow) { + setWorkspace(workspace); + this.id = id; + this.workflow = workflow; + } + + + public Workspace getWorkspace() { + return workspace; + } + + public void setWorkspace(Workspace workspace) { + this.workspace = workspace; + } + + public String getWorkspaceId() { + return workspace == null ? "" : workspace.getId(); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Workflow getWorkflow() { + return workflow; + } + + public void setWorkflow(Workflow workflow) { + this.workflow = workflow; + } + + public List<Workflow> getAbortedWorkflows() { + return abortedWorkflows; + } + + public void setAbortedWorkflows(List<Workflow> abortedWorkflows) { + this.abortedWorkflows = abortedWorkflows; + } + + public void addAbortedWorkflows(Workflow abortedWorkflow) { + this.abortedWorkflows.add(abortedWorkflow); + } + +} \ No newline at end of file diff --git a/docdoku-common/src/main/java/com/docdoku/core/workflow/WorkspaceWorkflowKey.java b/docdoku-common/src/main/java/com/docdoku/core/workflow/WorkspaceWorkflowKey.java new file mode 100644 index 0000000000..3c82291b80 --- /dev/null +++ b/docdoku-common/src/main/java/com/docdoku/core/workflow/WorkspaceWorkflowKey.java @@ -0,0 +1,116 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.workflow; + +import java.io.Serializable; + +/** + * Identity class of {@link WorkspaceWorkflow} objects. + * + * @author Florent Garin + */ +public class WorkspaceWorkflowKey implements Serializable, Comparable<WorkspaceWorkflowKey>, Cloneable { + + private String workspace; + private String id; + + + public WorkspaceWorkflowKey() { + } + + public WorkspaceWorkflowKey(String pWorkspaceId, String pId) { + workspace = pWorkspaceId; + id = pId; + } + + + public String getWorkspace() { + return workspace; + } + + public void setWorkspace(String pWorkspaceId) { + workspace = pWorkspaceId; + } + + public String getId() { + return id; + } + + public void setId(String pId) { + id = pId; + } + + + @Override + public String toString() { + return workspace + "-" + id; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + WorkspaceWorkflowKey that = (WorkspaceWorkflowKey) o; + + if (!id.equals(that.id)) { + return false; + } + if (!workspace.equals(that.workspace)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = workspace.hashCode(); + result = 31 * result + id.hashCode(); + return result; + } + + + public int compareTo(WorkspaceWorkflowKey pKey) { + int wksComp = workspace.compareTo(pKey.workspace); + if (wksComp != 0) { + return wksComp; + } else { + return id.compareTo(pKey.id); + } + } + + @Override + public WorkspaceWorkflowKey clone() { + WorkspaceWorkflowKey clone; + try { + clone = (WorkspaceWorkflowKey) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + return clone; + } +} \ No newline at end of file diff --git a/docdoku-common/src/main/resources/META-INF/LICENSE.txt b/docdoku-common/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 0000000000..2def0e8831 --- /dev/null +++ b/docdoku-common/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>. \ No newline at end of file diff --git a/docdoku-common/src/main/resources/com/docdoku/core/i18n/LocalStrings.properties b/docdoku-common/src/main/resources/com/docdoku/core/i18n/LocalStrings.properties new file mode 100644 index 0000000000..d2b0a04b2b --- /dev/null +++ b/docdoku-common/src/main/resources/com/docdoku/core/i18n/LocalStrings.properties @@ -0,0 +1,179 @@ +AccessRightException=You, {0}, have not sufficient rights to perform this operation +AccountAlreadyExistsException=The account "{0}" already exists +AccountNotFoundException=The account "{0}" has not been found +BaselinedFolderAlreadyExistsException=The folder "{0}" already exists in the baseline +BaselineNotFoundException=The baseline "{0}" has not been found +ProductConfigurationNotFoundException=The configuration "{0}" has not been found +BaselineWarningException=The baseline creation meet some trouble +BaselineWarningException1=The checked out and invisible parts was ignored in the baseline creation +ChangeIssueNotFoundException=The change issue "{0}" has not been found +ChangeOrderNotFoundException=The change order "{0}" has not been found +ChangeRequestNotFoundException=The change request "{0}" has not been found +ConfigurationItemAlreadyExistsException=The configuration item "{0}" already exists +ConfigurationItemNotFoundException=The configuration item "{0}" or its sub-elements have not been found +PartRevisionNotReleasedException=The part "{0}" has not been released +ConvertedResourceException=The resource can not be converted. +CreationException=Error creating the object which may not be unique or its attributes may not be correct +DocumentMasterAlreadyExistsException=The document "{0}" already exists +DocumentMasterTemplateAlreadyExistsException=The document template "{0}" already exists +MaskCreationException=The mask contains forbidden character +DocumentMasterTemplateNotFoundException=The document template "{0}" or its sub-elements have not been found +DocumentRevisionAlreadyExistsException=The document "{0}" already exists +DocumentRevisionNotFoundException=The document "{0}" version "{1}" or its sub-elements have not been found +DocumentIterationNotFoundException=The document "{0}" version "{1}" iteration "{2}" or its sub-elements have not been found +EntityConstraintException1=You cannot delete a root part of a product +EntityConstraintException2=You cannot delete a part used as component in an assembly +EntityConstraintException3=You cannot delete a role which has a usage in a workflow model +EntityConstraintException4=You cannot delete a product which has baselines +EntityConstraintException5=You cannot delete a part which is baselined +EntityConstraintException6=You cannot delete a document which is baselined +EntityConstraintException7=You cannot delete a document affected to a change item +EntityConstraintException8=You cannot delete a milestone affected to change orders +EntityConstraintException9=You cannot delete a milestone affected to change requests +EntityConstraintException10=You cannot delete a request linked to change orders +EntityConstraintException11=You cannot delete a group which has ACL on documents or parts +EntityConstraintException12=You cannot create an assembly which contains circular references +EntityConstraintException13=You cannot delete a product which has instances +EntityConstraintException14=You cannot delete a list of values wich is used in a document template +EntityConstraintException15=You cannot delete a list of values wich is used in a part template +EntityConstraintException16=You cannot delete a baseline used by product instances +EntityConstraintException17=You cannot delete a document linked to other documents +EntityConstraintException18=You cannot delete a document used in parts +EntityConstraintException19=You cannot delete a document used in product instance +EntityConstraintException20=You cannot delete a document used in path data +EntityConstraintException21=You cannot delete a part affected to a change item +EntityConstraintException22=You cannot delete a part used as substitute in an assembly +EntityConstraintException23=You cannot delete a product which has configurations +EntityConstraintException24=You cannot delete a workflow involved in a document template +EntityConstraintException25=You cannot delete a workflow involved in a part template +EntityConstraintException26=You cannot delete an issue linked to change requests +ES_DeleteError1=Delete index can''t be done. The ElasticSearch server doesn''t seem to respond. +ES_DeleteError2=Multiple deleting index can''t be done. The ElasticSearch server doesn''t seem to respond. +ES_DeleteError3=Multiple deleting index meet some problem: +ES_IndexCreationError1=Index can''t be create. The following index name already exist: +ES_IndexCreationError2=Index can''t be create. The following index name is invalid: +ES_IndexError1=Indexing can''t be done. The ElasticSearch server doesn''t seem to respond. +ES_IndexError2=Multiple indexing can''t be done. The ElasticSearch server doesn''t seem to respond. +ES_IndexError3=Multiple indexing meet some problem: +ES_IndexError4=Nothing to index +ES_MailError1=Application can''t send a ElasticSearch response mail +ES_SearchError1=Search can''t be done. The ElasticSearch server doesn''t seem to respond. +ES_SearchError2=Search can''t be done. The ElasticSearch server don''t recognize the index. Indexing the workspace should fix the issue +FileAlreadyExistsException=The file "{0}" already exists +FileNotFoundException=The file "{0}" has not been found +FolderAlreadyExistsException=The folder "{0}" already exists +FolderNotFoundException=The folder "{0}" has not been found +GCMAccountAlreadyExistsException=The GCM account "{0}" already exists +GCMAccountNotFoundException=The GCM account "{0}" has not been found +IndexAlreadyExistsException=This index name already exists +IndexerServerException=The indexer server doesn''t respond +MissingIndexException=No Index present. Please index the workspace before searching +IndexNamingException=This index name isn''t allowed +LayerNotFoundException=The layer "{0}" has not been found +LOVNameEmptyException="The name is empty" +LOVPossibleValueException="The list of possible value is empty" +MarkerNotFoundException=The marker "{0}" has not been found +MilestoneAlreadyExistsException=The milestone {0} already exists +MilestoneNotFoundException=The milestone {0} can not be found +NotAllowedException1=You cannot create the baseline. A part has not any iteration checked in +NotAllowedException2=You cannot create a workflow without any activity +NotAllowedException3=One or more activity(ies) of this workflow is invalid. (no reference or any task inside) +NotAllowedException4=You cannot add or update a file to an item which is not yours +NotAllowedException5=The document is private and you are not the owner +NotAllowedException6=You cannot move a document which is not yours +NotAllowedException7=The folder structure has been frozen only the workspace manager is allowed to create or remove folders +NotAllowedException8=The user cannot be deleted because he''s an author or he''s involved in a workflow +NotAllowedException9=The supplied name "{0}" is empty or contains a forbidden character. (Only alphanumeric characters, _ and - are allowed) +NotAllowedException10=Before marking a task as done or rejecting it on a document you must have downloaded and opened it at least one time +NotAllowedException11=You cannot create an organization as you are a member of another one +NotAllowedException12=You cannot add an account to an organization if he is already a member of another one +NotAllowedException13=One or more task(s) of this workflow is invalid. (Any role defined) +NotAllowedException14=You are not the task owner +NotAllowedException15=The task is not currently running +NotAllowedException16=You cannot mark a task as done or reject it on a document which is checked out +NotAllowedException17=You cannot mark a task as done or reject it on a part which is checked out +NotAllowedException18=You cannot access to a protected resource out of its permalink. +NotAllowedException19=You cannot undo check out an item that you didn''t check out +NotAllowedException20=You cannot check in an item that you didn''t check out +NotAllowedException21=You cannot delete a system folder or one which is not yours +NotAllowedException22=You cannot delete a document which is not yours +NotAllowedException23=You cannot move a folder from one workspace to another +NotAllowedException24=You cannot remove a file to a document which is not yours +NotAllowedException25=You don''t have modification right on the item "{0}" +NotAllowedException26=The document must be checked in to create a new version +NotAllowedException27=The document must have at least one iteration +NotAllowedException28=You cannot create a anonymous workflow. +NotAllowedException29=You cannot rename a file which is not yours +NotAllowedException30=You cannot subscribe to a document which is not yours +NotAllowedException33=Parent folder is private and you are not the owner +NotAllowedException34=You cannot retrieve a file which is not yours +NotAllowedException35=You cannot rename a file which is not yours +NotAllowedException36=Only released parts can be marked as obsolete +NotAllowedException37=The item is already checked out +NotAllowedException38=An obsolete part cannot be released again +NotAllowedException40=The part must be checked in to create a new version +NotAllowedException41=The part must have at least one iteration +NotAllowedException42=The document reference does not match the defined mask +NotAllowedException43=The part number does not match the defined mask +NotAllowedException44=You cannot modify documents attributes for a document created with fixed attributes template +NotAllowedException45=You cannot modify part attributes for a part created with fixed attributes template +NotAllowedException46=The part must be checked in before it can be released +NotAllowedException47=You cannot check out an item which is released or obsolete +NotAllowedException48=You cannot create the baseline. A part has many versions choices +NotAllowedException49=You cannot create the baseline. A part has no iteration available according to the selected type. You have to checkin/release it. +NotAllowedException50=You cannot create the baseline. A link has no path choices +NotAllowedException51=You cannot create the baseline. A link has many path choices +NotAllowedException52=You cannot access data not owned by given configuration +NotAllowedException53=You cannot rebase a product instance with a baseline that is not created from the same product +NotAllowedException54=You cannot create a link without a type +NotAllowedException55=You can only update deliverable data in the last iteration +NotAllowedException56=You cannot use the workflow without specifying a worker for each tasks +NotAllowedException57=You cannot create a link between the same path. +NotAllowedException58=You cannot create a link between a component and one of its substitutes +NotAllowedException59=The provided attributes are not valid +NotAllowedException60=You cannot modify a path to path link not owned by the given product +NotAllowedException61=You cannot create a baseline without a name +NotAllowedException62=You need an organization to proceed +NotAllowedException63=The document must be checked in before it can be released +NotAllowedException64=An obsolete document cannot be released again +NotAllowedException65=Only released documents can be marked as obsolete +OrganizationAlreadyExistsException=The organization "{0}" already exists +OrganizationNotFoundException=The organization "{0}" has not been found +PartIterationNotFoundException=The part "{0}" revision "{1}" iteration "{2}" or its sub-elements have not been found +PartMasterAlreadyExistsException=The part "{0}" already exists +PartMasterNotFoundException=The part "{0}" or its sub-elements have not been found +PartMasterTemplateAlreadyExistsException=The part template "{0}" already exists +PartMasterTemplateNotFoundException=The part template "{0}" or its sub-elements have not been found +PartRevisionAlreadyExistsException=The part "{0}" already exists +PartRevisionNotFoundException=The part "{0}" revision "{1}" or its sub-elements have not been found +PartUsageLinkNotFoundException=The usage link "{0}" has not been found +PasswordRecoveryRequestNotFoundException=The id "{0}" doesn''t correspond to a correct password recovery request +ProductInstanceAlreadyExistsException=The product deliverable already exist +ProductInstanceMasterNotFoundException=The product deliverable has not been found +ProductInstanceIterationNotFoundException=The product deliverable iteration has not been found +RoleAlreadyExistsException=The role "{0}" already exists +QueryAlreadyExistsException=The query "{0}" already exists +RoleNotFoundException=The role "{0}" has not been found +SharedEntityNotFoundException=The shared entity "{0}" has not been found +TagAlreadyExistsException=The tag "{0}" already exists +TagNotFoundException=The tag "{0}" has not been found +TaskNotFoundException=The task "{0}" has not been found +UserAlreadyExistsException=The user "{0}" already exists +PathToPathLinkAlreadyExistsException=The link "{0}" already exists +UserGroupAlreadyExistsException=The group "{0}" already exists +UserGroupNotFoundException=The group "{0}" has not been found +UserNotActiveException=The user "{0}" is not currently active +UserNotFoundException=The user "{0}" has not been found +PathToPathLinkNotFoundException=The link "{0}" has not been found +WorkflowModelAlreadyExistsException=The workflow model "{0}" already exists +WorkflowModelNotFoundException=The workflow model "{0}" or its sub-elements have not been found +WorkflowNotFoundException=The workflow "{0}" or its sub-elements have not been found +WorkflowNameEmptyException=The workflow name cannot be empty +WorkspaceAlreadyExistsException=The workspace "{0}" already exists +WorkspaceNotFoundException=The workspace "{0}" or its sub-elements have not been found +PathDataAlreadyExistsException=The data already exists for given configuration +PathDataMasterNotFoundException=The path data "{0}" has not been found +PathToPathCyclicException=You cannot create a cyclic link +ListOfValuesNotFoundException=The list of values {0} have not been found +WrongInputException=One or more inputs are wrong +NoImporterAvailable=No importer available diff --git a/docdoku-common/src/main/resources/com/docdoku/core/i18n/LocalStrings_en.properties b/docdoku-common/src/main/resources/com/docdoku/core/i18n/LocalStrings_en.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docdoku-common/src/main/resources/com/docdoku/core/i18n/LocalStrings_fr.properties b/docdoku-common/src/main/resources/com/docdoku/core/i18n/LocalStrings_fr.properties new file mode 100644 index 0000000000..914e237d4d --- /dev/null +++ b/docdoku-common/src/main/resources/com/docdoku/core/i18n/LocalStrings_fr.properties @@ -0,0 +1,179 @@ +AccessRightException=Vous, {0}, n''avez pas les droits suffisants pour faire cette op\u00e9ration +AccountAlreadyExistsException=Le compte "{0}" existe d\u00e9j\u00e0 +AccountNotFoundException=Le compte "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +BaselinedFolderAlreadyExistsException=Le r\u00e9pertoire "{0}" existe d\u00e9j\u00e0 dans cette baseline +BaselineNotFoundException=La baseline "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9e +ProductConfigurationNotFoundException=La configuration "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9e +BaselineWarningException=La baseline "{0}" a rencontr\u00e9e quelques problemes +BaselineWarningException1=Les articles r\u00e9serv\u00e9s ou invisibles ont \u00e9t\u00e9s ignor\u00e9s lors de la cr\u00e9ation de la baseline +ChangeIssueNotFoundException=Le rapport de probl\u00e8me "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +ChangeOrderNotFoundException=L''ordre de modification "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +ChangeRequestNotFoundException=La demande de modification "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9e +ConfigurationItemAlreadyExistsException=Le produit configurable "{0}" existe d\u00e9j\u00e0 +ConfigurationItemNotFoundException=Le produit configurable "{0}" ou ses \u00e9l\u00e9ments n''ont pas \u00e9t\u00e9 trouv\u00e9s +PartRevisionNotReleasedException=L''article "{0}" n''a pas \u00e9t\u00e9 finalis\u00e9 +ConvertedResourceException=La resource n''a pu \u00eatre convertie +CreationException=Erreur \u00e0 la cr\u00e9ation de l''objet dont les attributs sont incorrects ou ne garantissent pas l''unicit\u00e9 +DocumentMasterAlreadyExistsException=Le document "{0}" existe d\u00e9j\u00e0 +DocumentMasterTemplateAlreadyExistsException=Le mod\u00e8le de document "{0}" existe d\u00e9j\u00e0 +MaskCreationException=Le masque contient des carat\u00e8res interdits +DocumentMasterTemplateNotFoundException=Le mod\u00e8le de document "{0}" ou ses \u00e9l\u00e9ments n''ont pas \u00e9t\u00e9 trouv\u00e9s +DocumentRevisionAlreadyExistsException=Le document "{0}" existe d\u00e9j\u00e0 +DocumentRevisionNotFoundException=Le document "{0}" version "{1}" ou ses \u00e9l\u00e9ments n''ont pas \u00e9t\u00e9 trouv\u00e9s +DocumentIterationNotFoundException=Le document "{0}" version "{1}" it\u00e9ration "{2}" ou ses \u00e9l\u00e9ments n''ont pas \u00e9t\u00e9 trouv\u00e9s +EntityConstraintException1=Vous ne pouvez pas supprimer un article de t\u00eate d''un produit +EntityConstraintException2=Vous ne pouvez pas supprimer un article utilis\u00e9 comme composant dans un assemblage +EntityConstraintException3=Vous ne pouvez pas supprimer un role contenu dans un processus m\u00e9tier +EntityConstraintException4=Vous ne pouvez pas supprimer un produit qui a des baselines +EntityConstraintException5=Vous ne pouvez pas supprimer un article qui est dans une baseline +EntityConstraintException6=Vous ne pouvez pas supprimer un document qui est dans une baseline +EntityConstraintException7=Vous ne pouvez pas supprimer un document li\u00e9 \u00e0 un changement +EntityConstraintException8=Vous ne pouvez pas supprimer un jalon li\u00e9 \u00e0 un ordre +EntityConstraintException9=Vous ne pouvez pas supprimer un jalon li\u00e9 \u00e0 une demande +EntityConstraintException10=Vous ne pouvez pas supprimer une demande li\u00e9e \u00e0 un ordre +EntityConstraintException11=Vous ne pouvez pas supprimer un groupe qui est li\u00e9 \u00e0 des droits sur des documents ou des articles +EntityConstraintException12=Vous ne pouvez pas cr\u00e9er un assemblage contenant des r\u00e9f\u00e9rences circulaires +EntityConstraintException13=Vous ne pouvez pas supprimer un produit qui a des instances +EntityConstraintException14=Vous ne pouvez pas supprimer une liste de valeurs qui est utilis\u00e9e dans un mod\u00e8le de document +EntityConstraintException15=Vous ne pouvez pas supprimer une liste de valeurs qui est utilis\u00e9e dans un mod\u00e8le d''article +EntityConstraintException16=Vous ne pouvez pas supprimer une baseline utilis\u00e9e dans un exemplaire +EntityConstraintException17=Vous ne pouvez pas supprimer un document rattach\u00e9 \u00E0 d''autres documents +EntityConstraintException18=Vous ne pouvez pas supprimer un document utilis\u00e9 dans des articles +EntityConstraintException19=Vous ne pouvez pas supprimer un document utilis\u00e9 dans des exemplaires +EntityConstraintException20=Vous ne pouvez pas supprimer un document utilis\u00e9 dans des donn\u00e9es d''exemplaires +EntityConstraintException21=Vous ne pouvez pas supprimer un article li\u00e9 \u00e0 un changement +EntityConstraintException22=Vous ne pouvez pas supprimer un article utilis\u00e9 comme variante dans un assemblage +EntityConstraintException23=Vous ne pouvez pas supprimer un produit qui a des configurations +EntityConstraintException24=Vous ne pouvez pas supprimer un processus m\u00e9tier qui est utilis\u00e9 dans un mod\u00e8le de document +EntityConstraintException25=Vous ne pouvez pas supprimer un processus m\u00e9tier qui est utilis\u00e9 dans un mod\u00e8le d''article +EntityConstraintException26=Vous ne pouvez pas supprimer une anomalie li\u00e9e \u00e0 une demande +ES_DeleteError1=La suppression de l''index n''a pu \u00eatre effectu\u00e9e. Le serveur ElasticSearch ne semble pas r\u00e9pondre +ES_DeleteError2=La suppression de plusieurs index n''a pu \u00eatre effectu\u00e9e. Le serveur ElasticSearch ne semble pas r\u00e9pondre +ES_DeleteError3=La suppression d''index a rencontr\u00e9 les probl\u00e8mes suivants \: +ES_IndexCreationError1=L''index n''a pu \u00eatre cr\u00e9\u00e9. Les index suivants existent \: +ES_IndexCreationError2=L''index n''a pu \u00eatre cr\u00e9\u00e9. Les noms suivants sont invalides \: +ES_IndexError1=L''indexation n''a pu \u00eatre effectu\u00e9e. Le serveur ElasticSearch ne semble pas r\u00e9pondre +ES_IndexError2=L''indexation multiple n''a pu \u00eatre effectu\u00e9e. Le serveur ElasticSearch ne semble pas r\u00e9pondre +ES_IndexError3=L''indexation multiple a rencontr\u00e9 les probl\u00e8mes suivants \: +ES_IndexError4=Rien \u00e0 indexer +ES_MailError1=L''application ne peut pas envoyer le mail de r\u00e9sultat d''indexation +ES_SearchError1=La recherche ne peut \u00eatre effectu\u00e9ee. Le serveur ElasticSearch ne semble pas r\u00e9pondre +ES_SearchError2=La recherche ne peut \u00eatre effectu\u00e9ee. Le serveur ElasticSearch ne reconnait pas l''index. Une indexation du workspace est n\u00e9cessaire +FileAlreadyExistsException=Le fichier "{0}" existe d\u00e9j\u00e0 +FileNotFoundException=Le fichier "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +FolderAlreadyExistsException=Le r\u00e9pertoire "{0}" existe d\u00e9j\u00e0 +FolderNotFoundException=Le r\u00e9pertoire "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +GCMAccountAlreadyExistsException=Le compte GCM "{0}" existe d\u00e9j\u00e0 +GCMAccountNotFoundException=Le compte GCM "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +IndexAlreadyExistsException=Ce nom d''index est d\u00e9j\u00e0 utilis\u00e9 +IndexerServerException=Le serveur d''indexation ne r\u00e9pond pas +MissingIndexException=Aucun Index pr\u00e9sent. Veuillez indexer le workspace avant de lancer une recherche +IndexNamingException=Ce nom d''index n''est pas autoris\u00e9 +LayerNotFoundException=La couche "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9e +LOVNameEmptyException="Le nom est vide" +LOVPossibleValueException="La liste des valeurs possibles est vide" +MarkerNotFoundException=Le marqueur "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9e +MilestoneAlreadyExistsException=Le jalon {0} existe d\u00e9j\u00e0 +MilestoneNotFoundException=Le jalon {0} n''a pas \u00e9t\u00e9 trouv\u00e9e +NotAllowedException1=Vous ne pouvez pas figer une configuration. Un article n''a aucune it\u00e9ration non r\u00e9serv\u00e9e +NotAllowedException2=Vous ne pouvez pas cr\u00e9er un worflow sans aucune activit\u00e9 +NotAllowedException3=Une ou plusieurs activit\u00e9(s) de ce processus m\u00e9tier est(sont) invalide(s). (R\u00e9f\u00e9rence manquante ou ne contient aucune t\u00e2che) +NotAllowedException4=Vous ne pouvez pas ajouter ou modifier un fichier d''un objet qui ne vous appartient pas +NotAllowedException5=Le document est priv\u00e9 et vous n''en \u00eates pas le propri\u00e9taire +NotAllowedException6=Vous ne pouvez pas d\u00e9placer un document qui ne vous appartient pas +NotAllowedException7=la structure des r\u00e9pertoires a \u00e9t\u00e9 gel\u00e9e, seul le gestionnaire de l''espace de travail est autoris\u00e9 \u00e0 cr\u00e9er ou supprimer des r\u00e9pertoires +NotAllowedException8=L''utilisateur ne peut pas \u00eatre supprim\u00e9 car c''est un auteur ou il est impliqu\u00e9 dans un processus m\u00e9tier +NotAllowedException9=Le nom "{0}" est vide ou contient des caract\u00e8res interdits. (Seuls les caract\u00e8res alphanum\u00e9riques, _ et - sont autoris\u00e9s) +NotAllowedException10=Avant de marquer une t\u00e2che comme effectu\u00e9e ou de la rejeter sur un document vous devez l''avoir t\u00e9l\u00e9charg\u00e9 et ouvert au moins une fois +NotAllowedException11=Vous ne pouvez pas cr\u00e9er une organisation car vous \u00eates d\u00e9j\u00e0 membre d''une autre organisation +NotAllowedException12=Vous ne pouvez pas ajouter un compte \u00e0 une organisation s''il est d\u00e9j\u00e0 membre d''une autre organisation +NotAllowedException13=Une ou plusieurs t\u00e2ches de ce processus m\u00e9tier est(sont) invalide(s). (Aucun role d\u00e9fini) +NotAllowedException14=Vous n''\u00eates pas le propri\u00e9taire de la t\u00e2che +NotAllowedException15=La t\u00e2che n''est pas en cours +NotAllowedException16=Vous ne pouvez pas marquer une t\u00e2che comme effectu\u00e9e ou la rejeter sur un document r\u00e9serv\u00e9 +NotAllowedException17=Vous ne pouvez pas marquer une t\u00e2che comme effectu\u00e9e ou la rejeter sur un article r\u00e9serv\u00e9 +NotAllowedException18=Vous ne pouvez pas acc\u00e9der \u00e0 une ressource prot\u00e9g\u00e9e hors de son lien permanent +NotAllowedException19=Vous ne pouvez pas annuler la r\u00e9servation d''un objet que vous n''avez pas r\u00e9serv\u00e9 +NotAllowedException20=Vous ne pouvez pas lib\u00e9rer un objet que vous n''avez pas r\u00e9serv\u00e9 +NotAllowedException21=Vous ne pouvez pas supprimer un r\u00e9pertoire syst\u00e8me ou qui ne vous appartient pas +NotAllowedException22=Vous ne pouvez pas supprimer un document qui ne vous appartient pas +NotAllowedException23=Vous ne pouvez pas d\u00e9placer un dossier d''un espace de travail \u00e0 un autre +NotAllowedException24=Vous ne pouvez pas supprimer un fichier d''un document qui ne vous appartient pas +NotAllowedException25=Vous n''avez pas les droits en modification sur l''objet "{0}" +NotAllowedException26=Le document doit \u00eatre lib\u00e9r\u00e9 pour cr\u00e9er une nouvelle version +NotAllowedException27=Le document doit avoir au moins une iteration +NotAllowedException28=Vous ne pouvez pas cr\u00e9er de processus m\u00e9tier anonyme +NotAllowedException29=Vous ne pouvez pas renommer un fichier qui ne vous appartient pas +NotAllowedException30=Vous ne pouvez pas souscrire \u00e0 un document qui ne vous appartient pas +NotAllowedException33=Le r\u00e9pertoire parent est priv\u00e9 et vous n''en \u00eates pas le propri\u00e9taire +NotAllowedException34=Vous ne pouvez pas r\u00e9cup\u00e9rer un fichier qui ne vous appartient pas +NotAllowedException35=Vous ne pouvez pas renommer un fichier qui ne vous appartient pas +NotAllowedException36=Seuls les articles finalis\u00e9s peuvent \u00eatre marqu\u00e9s comme obsol\u00e8tes +NotAllowedException37=L''objet est d\u00e9j\u00e0 r\u00e9serv\u00e9 +NotAllowedException38=Un article obsol\u00e8te ne peut plus \u00eatre finalis\u00e9 +NotAllowedException40=L''article doit \u00eatre lib\u00e9r\u00e9 pour cr\u00e9er une nouvelle version +NotAllowedException41=L''article doit avoir au moins une iteration +NotAllowedException42=La r\u00e9f\u00e9rence du document doit correspondre au masque d\u00e9fini +NotAllowedException43=La r\u00e9f\u00e9rence de l''article doit correspondre au masque d\u00e9fini +NotAllowedException44=Vous ne pouvez pas modifier les attributs d''un document cr\u00e9\u00e9 avec un mod\u00e8le dont les attributs sont fig\u00e9s +NotAllowedException45=Vous ne pouvez pas modifier les attributs d''un article cr\u00e9\u00e9 avec un mod\u00e8le dont les attributs sont fig\u00e9s +NotAllowedException46=L''article doit \u00eatre lib\u00e9r\u00e9 pour le finaliser +NotAllowedException47=Vous ne pouvez pas r\u00e9server un object qui est finalis\u00e9 ou obsol\u00e8te +NotAllowedException48=Vous ne pouvez pas figer une configuration. Un article peut \u00eatre utilis\u00e9 en plusieurs versions. +NotAllowedException49=Vous ne pouvez pas figer une configuration. Un des articles n''a aucune it\u00e9ration disponible correspondant au type s\u00e9lectionn\u00e9. Il faut le lib\u00e9rer/finaliser. +NotAllowedException50=Vous ne pouvez pas figer une configuration. Un lien n''a aucun choix d''usage +NotAllowedException51=Vous ne pouvez pas figer une configuration. Un lien a plusieurs choix d''usage +NotAllowedException52=Vous ne pouvez pas acc\u00e9der aux donn\u00e9es n''appartenant pas \u00e0 la configuration donn\u00e9e +NotAllowedException53=Vous ne pouvez pas rebaser un exemplaire avec une baseline d''un produit diff\u00e9rent. +NotAllowedException54=Vous ne pouvez pas cr\u00e9er un lien sans type +NotAllowedException55=Vous ne pouvez modifier des donn\u00e9es d''exemplaires que sur la derni\u00e8re it\u00e9ration +NotAllowedException57=Vous ne pouvez pas cr\u00e9er un lien entre le même chemin +NotAllowedException58=Vous ne pouvez pas cr\u00e9er un lien entre un composant et une de ses variantes +NotAllowedException56=Vous ne pouvez pas utiliser le processus m\u00e9tier sans assigner un utilisateur pour chaque t\u00e2che +NotAllowedException59=Les attributs envoy\u00e9s ne sont pas valides +NotAllowedException60=Vous ne pouvez pas modifier un lien qui n''est pas port\u00e9 par le produit donn\u00e9 +NotAllowedException61=Vous ne pouvez pas figer une configuration sans lui donner un nom +NotAllowedException62=Il vous faut une organisation pour effectuer cette op\u00e9ration +NotAllowedException63=Le document doit \u00eatre lib\u00e9r\u00e9 pour le finaliser +NotAllowedException64=Un document obsol\u00e8te ne peut plus \u00eatre finalis\u00e9 +NotAllowedException65=Seuls les documents finalis\u00e9s peuvent \u00eatre marqu\u00e9s comme obsol\u00e8tes +OrganizationAlreadyExistsException=L''organisation "{0}" existe d\u00e9j\u00e0 +OrganizationNotFoundException=L''organisation "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +PartIterationNotFoundException=La pi\u00e8ce "{0}" revision "{1}" iteration "{2}" ou ses \u00e9l\u00e9ments n''ont pas \u00e9t\u00e9 trouv\u00e9s +PartMasterAlreadyExistsException=La pi\u00e8ce "{0}" existe d\u00e9j\u00e0 +PartMasterNotFoundException=La pi\u00e8ce "{0}" ou ses \u00e9l\u00e9ments n''ont pas \u00e9t\u00e9 trouv\u00e9s +PartMasterTemplateAlreadyExistsException=Le mod\u00e8le d''article "{0}" existe d\u00e9j\u00e0 +PartMasterTemplateNotFoundException=Le mod\u00e8le d''article "{0}" ou ses \u00e9l\u00e9ments n''ont pas \u00e9t\u00e9 trouv\u00e9s +PartRevisionAlreadyExistsException=La pi\u00e8ce "{0}" existe d\u00e9j\u00e0 +PartRevisionNotFoundException=La pi\u00e8ce "{0}" revision "{1}" ou ses \u00e9l\u00e9ments n''ont pas \u00e9t\u00e9 trouv\u00e9s +PartUsageLinkNotFoundException=Le lien d''usage "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +PasswordRecoveryRequestNotFoundException=L''identifiant "{0}" ne correspond \u00e0 aucune demande de r\u00e9cup\u00e9ration de mot de passe +ProductInstanceAlreadyExistsException=L''exemplaire du produit existe d\u00e9j\u00e0 +ProductInstanceMasterNotFoundException=L''exemplaire du produit n''a pas \u00e9t\u00e9 trouv\u00e9 +ProductInstanceIterationNotFoundException=L''it\u00e9ration de l''exemplaire du produit n''a pas \u00e9t\u00e9 trouv\u00e9 +RoleAlreadyExistsException=Le role "{0}" existe d\u00e9j\u00e0 +QueryAlreadyExistsException=La requ\u00eate "{0}" existe d\u00e9j\u00e0 +RoleNotFoundException=Le role "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +SharedEntityNotFoundException=L''\u00e9l\u00e9ment partag\u00e9 "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +TagAlreadyExistsException=Le libell\u00e9 "{0}" existe d\u00e9j\u00e0 +TagNotFoundException=Le libell\u00e9 "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +TaskNotFoundException=La t\u00e2che "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +UserAlreadyExistsException=L''utilisateur "{0}" existe d\u00e9j\u00e0 +PathToPathLinkAlreadyExistsException=Le lien "{0}" existe d\u00e9j\u00e0 +UserGroupAlreadyExistsException=Le groupe "{0}" existe d\u00e9j\u00e0 +UserGroupNotFoundException=Le groupe "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +UserNotActiveException=L''utilisateur "{0}" n''est pas actuellement actif +UserNotFoundException=L''utilisateur "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +PathToPathLinkNotFoundException=Le lien "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9 +WorkflowModelAlreadyExistsException=Le mod\u00e8le de processus m\u00e9tier "{0}" existe d\u00e9j\u00e0 +WorkflowModelNotFoundException=Le mod\u00e8le de processus m\u00e9tier "{0}" ou ses \u00e9l\u00e9ments n''ont pas \u00e9t\u00e9 trouv\u00e9s +WorkflowNameEmptyException=Le nom du processus m\u00e9tier ne peut pas \u00eatre vide +WorkflowNotFoundException=Le processus m\u00e9tier "{0}" ou ses \u00e9l\u00e9ments n''ont pas \u00e9t\u00e9 trouv\u00e9s +WorkspaceAlreadyExistsException=L''espace de travail "{0}" existe d\u00e9j\u00e0 +WorkspaceNotFoundException=L''espace de travail "{0}" ou ses \u00e9l\u00e9ments n''ont pas \u00e9t\u00e9 trouv\u00e9s +PathDataAlreadyExistsException=La donn\u00e9e existe d\u00e9j\u00e0 pour la configuration donn\u00e9e +PathDataMasterNotFoundException=La donn\u00e9e "{0}" n''a pas \u00e9t\u00e9 trouv\u00e9s +PathToPathCyclicException=Il est interdit de cr\u00e9er des liens cycliques +ListOfValuesNotFoundException=La liste de valeurs {0} n''a pas \u00e9t\u00e9 trouv\u00e9e +WrongInputException=Une ou plusieurs valeurs saisie(s) est (sont) incorrecte(s) +NoImporterAvailable=Pas d'importeur disponible \ No newline at end of file diff --git a/docdoku-common/src/test/java/com/docdoku/core/product/PartRevisionTest.java b/docdoku-common/src/test/java/com/docdoku/core/product/PartRevisionTest.java new file mode 100644 index 0000000000..1f90254e10 --- /dev/null +++ b/docdoku-common/src/test/java/com/docdoku/core/product/PartRevisionTest.java @@ -0,0 +1,73 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.docdoku.core.product; + +import com.docdoku.core.common.User; +import junit.framework.TestCase; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author kelto on 13/01/16. + */ +@RunWith(MockitoJUnitRunner.class) +public class PartRevisionTest extends TestCase { + + private PartRevision partRevision; + private User user; + private List<PartIteration> partIterations; + + @Before + public void setup() { + partRevision = new PartRevision(); + user = Mockito.spy(new User()); + partIterations = new ArrayList<>(); + partRevision.setAuthor(user); + partRevision.setPartIterations(partIterations); + } + @Test + public void testGetLastAccessibleIteration() throws Exception { + PartIteration partIteration = Mockito.mock(PartIteration.class); + partIterations.add(partIteration); + + //No checkedOut user, if any iteration present, should send back the last one. + Assert.assertTrue(partRevision.getLastAccessibleIteration(new User()) != null); + + partRevision.setCheckOutUser(user); + + //The only iteration has been checked-out, should return null since it's not the same user + Assert.assertTrue(partRevision.getLastAccessibleIteration(new User()) == null); + + //The user who checked-out the part can access it + Assert.assertTrue(partRevision.getLastAccessibleIteration(user) != null); + + partIterations.add(Mockito.mock(PartIteration.class)); + //Any other user should have access to the previous iteration + Assert.assertTrue(partRevision.getLastAccessibleIteration(new User()) == partIteration); + } +} \ No newline at end of file diff --git a/docdoku-dplm/.gitignore b/docdoku-dplm/.gitignore new file mode 100644 index 0000000000..ece8252ddf --- /dev/null +++ b/docdoku-dplm/.gitignore @@ -0,0 +1,4 @@ +tmp +ui/dist +ui/tmp +ui/app/docdoku-cli-jar-with-dependencies.jar \ No newline at end of file diff --git a/docdoku-dplm/Info.plist b/docdoku-dplm/Info.plist new file mode 100644 index 0000000000..c3ce9be39b --- /dev/null +++ b/docdoku-dplm/Info.plist @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleDisplayName</key> + <string>DPLM</string> + <key>CFBundleDocumentTypes</key> + <array> + <dict> + <key>CFBundleTypeIconFile</key> + <string>nw.icns</string> + <key>CFBundleTypeName</key> + <string>DPLM</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + <key>LSHandlerRank</key> + <string>Owner</string> + <key>LSItemContentTypes</key> + <array> + <string>com.intel.nw.app</string> + </array> + </dict> + <dict> + <key>CFBundleTypeName</key> + <string>Folder</string> + <key>CFBundleTypeOSTypes</key> + <array> + <string>fold</string> + </array> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + <key>LSHandlerRank</key> + <string>None</string> + </dict> + </array> + <key>CFBundleExecutable</key> + <string>node-webkit</string> + <key>CFBundleIconFile</key> + <string>nw.icns</string> + <key>CFBundleIdentifier</key> + <string>com.intel.nw</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>DPLM</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>29.0.1547.31</string> + <key>CFBundleVersion</key> + <string>1547.31</string> + <key>LSFileQuarantineEnabled</key> + <true/> + <key>LSMinimumSystemVersion</key> + <string>10.6.0</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> + <key>NSSupportsAutomaticGraphicsSwitching</key> + <true/> + <key>SCMRevision</key> + <string>213023</string> + <key>UTExportedTypeDeclarations</key> + <array> + <dict> + <key>UTTypeConformsTo</key> + <array> + <string>com.pkware.zip-archive</string> + </array> + <key>UTTypeDescription</key> + <string>node-webkit App</string> + <key>UTTypeIconFile</key> + <string>nw.icns</string> + <key>UTTypeIdentifier</key> + <string>com.intel.nw.app</string> + <key>UTTypeReferenceURL</key> + <string>https://github.com/rogerwang/node-webkit/wiki/How-to-package-and-distribute-your-apps</string> + <key>UTTypeTagSpecification</key> + <dict> + <key>com.apple.ostype</key> + <string>DPLM</string> + <key>public.filename-extension</key> + <array> + <string>nw</string> + </array> + <key>public.mime-type</key> + <string>application/x-node-webkit-app</string> + </dict> + </dict> + </array> +</dict> +</plist> \ No newline at end of file diff --git a/docdoku-dplm/build-cli.sh b/docdoku-dplm/build-cli.sh new file mode 100755 index 0000000000..95b61e65c1 --- /dev/null +++ b/docdoku-dplm/build-cli.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +SOURCES=${BASE_DIR}/ui +CLI_DIR=${BASE_DIR}/../docdoku-cli + +echo "Building docdoku-cli ... "; +cd ${CLI_DIR} +mvn clean install +cp ${CLI_DIR}/target/docdoku-cli-jar-with-dependencies.jar ${SOURCES}/app/ +echo "... done" diff --git a/docdoku-dplm/build-dplm.sh b/docdoku-dplm/build-dplm.sh new file mode 100755 index 0000000000..f0aaee495c --- /dev/null +++ b/docdoku-dplm/build-dplm.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +SOURCES=${BASE_DIR}/ui + +echo "Building docdoku-dplm ... "; +cd ${SOURCES} +grunt build; +[[ $? -eq 0 ]] || { echo "Grunt build failed"; exit 1; } +echo "... done" diff --git a/docdoku-dplm/dev-guide.md b/docdoku-dplm/dev-guide.md new file mode 100644 index 0000000000..887c0ecec3 --- /dev/null +++ b/docdoku-dplm/dev-guide.md @@ -0,0 +1,41 @@ +# DPLM dev guide + +## Download NodeWebkit + +Download the appropriate version for your local environment 32/64 bits: + +* http://dl.node-webkit.org/v0.11.1/node-webkit-v0.11.1-linux-ia32.tar.gz +* http://dl.node-webkit.org/v0.11.1/node-webkit-v0.11.1-linux-x64.tar.gz +* http://dl.node-webkit.org/v0.11.1/node-webkit-v0.11.1-osx-ia32.zip +* http://dl.node-webkit.org/v0.11.1/node-webkit-v0.11.1-osx-x64.zip +* http://dl.node-webkit.org/v0.11.1/node-webkit-v0.11.1-win-ia32.zip +* http://dl.node-webkit.org/v0.11.1/node-webkit-v0.11.1-win-x64.zip + +Unzip the archive, `nw` executable is the command to launch a node webkit shell + +## Run the app in dev mode + +Install dependencies + + $ npm install + +To activate the debug tools in node webkit, edit `ui/app/package.json`, set toolbar value to `true` + +Then run : + + $ ./build-cli.sh + $ /path/to/nw ui/app + +Add `nw` to your $PATH if wanted + + $ nw ui/app + +## Build the app + +Builds all platforms releases + + $ ./release.sh + +## Known issues + +* https://github.com/docdoku/docdoku-plm/issues/940 \ No newline at end of file diff --git a/docdoku-dplm/download.sh b/docdoku-dplm/download.sh new file mode 100755 index 0000000000..0771b23083 --- /dev/null +++ b/docdoku-dplm/download.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +TMP_DIR=${BASE_DIR}/tmp + +echo "Downloading node webkit if needed ..." + +[[ -f ${TMP_DIR}/node-webkit-v0.11.1-linux-ia32.tar.gz ]] || wget http://dl.node-webkit.org/v0.11.1/node-webkit-v0.11.1-linux-ia32.tar.gz -P ${TMP_DIR} +[[ -f ${TMP_DIR}/node-webkit-v0.11.1-linux-x64.tar.gz ]] || wget http://dl.node-webkit.org/v0.11.1/node-webkit-v0.11.1-linux-x64.tar.gz -P ${TMP_DIR} +[[ -f ${TMP_DIR}/node-webkit-v0.11.1-osx-ia32.zip ]] || wget http://dl.node-webkit.org/v0.11.1/node-webkit-v0.11.1-osx-ia32.zip -P ${TMP_DIR} +[[ -f ${TMP_DIR}/node-webkit-v0.11.1-osx-x64.zip ]] || wget http://dl.node-webkit.org/v0.11.1/node-webkit-v0.11.1-osx-x64.zip -P ${TMP_DIR} +[[ -f ${TMP_DIR}/node-webkit-v0.11.1-win-ia32.zip ]] || wget http://dl.node-webkit.org/v0.11.1/node-webkit-v0.11.1-win-ia32.zip -P ${TMP_DIR} +[[ -f ${TMP_DIR}/node-webkit-v0.11.1-win-x64.zip ]] || wget http://dl.node-webkit.org/v0.11.1/node-webkit-v0.11.1-win-x64.zip -P ${TMP_DIR} + +echo "... done\n Building docdoku-cli ..." \ No newline at end of file diff --git a/docdoku-dplm/linux-all-platforms.sh b/docdoku-dplm/linux-all-platforms.sh new file mode 100755 index 0000000000..b7ef4fb6eb --- /dev/null +++ b/docdoku-dplm/linux-all-platforms.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +TMP_DIR=${BASE_DIR}/tmp +OUT_DIR=${BASE_DIR}/target + +cd ${TMP_DIR}; + +mkdir -p linux-ia32 && rm -rf linux-ia32/*; +mkdir -p linux-x64 && rm -rf linux-x64/*; + +[[ -d node-webkit-v0.11.1-linux-ia32 ]] || tar -xzvf node-webkit-v0.11.1-linux-ia32.tar.gz +[[ -d node-webkit-v0.11.1-linux-x64 ]] || tar -xzvf node-webkit-v0.11.1-linux-x64.tar.gz + +echo "Building linux 32bit app ..."; + +/bin/cat ${TMP_DIR}/node-webkit-v0.11.1-linux-ia32/nw ${TMP_DIR}/app.nw > ${TMP_DIR}/linux-ia32/dplm +chmod +x ${TMP_DIR}/linux-ia32/dplm +cp ${TMP_DIR}/node-webkit-v0.11.1-linux-ia32/nw.pak ${TMP_DIR}/linux-ia32 +cp ${TMP_DIR}/node-webkit-v0.11.1-linux-ia32/*.dat ${TMP_DIR}/linux-ia32 +cd ${TMP_DIR}/linux-ia32; +zip dplm-linux-32.zip *; +mv dplm-linux-32.zip ${OUT_DIR}; + +echo "... done"; + +echo "Building linux 64bit app ..."; + +/bin/cat ${TMP_DIR}/node-webkit-v0.11.1-linux-x64/nw ${TMP_DIR}/app.nw > ${TMP_DIR}/linux-x64/dplm +chmod +x ${TMP_DIR}/linux-x64/dplm +cp ${TMP_DIR}/node-webkit-v0.11.1-linux-x64/nw.pak ${TMP_DIR}/linux-x64 +cp ${TMP_DIR}/node-webkit-v0.11.1-linux-x64/*.dat ${TMP_DIR}/linux-x64 +cd ${TMP_DIR}/linux-x64; +zip dplm-linux-64.zip *; +mv dplm-linux-64.zip ${OUT_DIR}; + +echo "... done" + diff --git a/docdoku-dplm/nw.icns b/docdoku-dplm/nw.icns new file mode 100644 index 0000000000..e989b7bfa0 Binary files /dev/null and b/docdoku-dplm/nw.icns differ diff --git a/docdoku-dplm/osx-all-platforms.sh b/docdoku-dplm/osx-all-platforms.sh new file mode 100755 index 0000000000..4b7a76b9fe --- /dev/null +++ b/docdoku-dplm/osx-all-platforms.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +TMP_DIR=${BASE_DIR}/tmp +SOURCE=${BASE_DIR}/ui/dist +OUT_DIR=${BASE_DIR}/target + +cd ${TMP_DIR}; + +mkdir -p osx-ia32 && rm -rf osx-ia32/*; +mkdir -p osx-x64 && rm -rf osx-x64/*; + +[[ -d node-webkit-v0.11.1-osx-ia32 ]] || unzip node-webkit-v0.11.1-osx-ia32.zip +[[ -d node-webkit-v0.11.1-osx-x64 ]] || unzip node-webkit-v0.11.1-osx-x64.zip + +echo "Building osx 32 bits app ..." + +cp -R ${TMP_DIR}/node-webkit-v0.11.1-osx-ia32/node-webkit.app ${TMP_DIR}/osx-ia32 +mv ${TMP_DIR}/osx-ia32/node-webkit.app ${TMP_DIR}/osx-ia32/dplm.app +mkdir ${TMP_DIR}/osx-ia32/dplm.app/Contents/Resources/app.nw +cp -R ${SOURCE}/* ${TMP_DIR}/osx-ia32/dplm.app/Contents/Resources/app.nw +cp ${BASE_DIR}/nw.icns ${TMP_DIR}/osx-ia32/dplm.app/Contents/Resources +chmod -R 0775 ${TMP_DIR}/osx-ia32/dplm.app +cd ${TMP_DIR}/osx-ia32 +zip -r dplm-osx-32.zip dplm.app +mv dplm-osx-32.zip ${OUT_DIR}; + +echo "... done" + +echo "Building osx 64 bits app ..." + +cp -R ${TMP_DIR}/node-webkit-v0.11.1-osx-x64/node-webkit.app ${TMP_DIR}/osx-x64 +mv ${TMP_DIR}/osx-x64/node-webkit.app ${TMP_DIR}/osx-x64/dplm.app +mkdir ${TMP_DIR}/osx-x64/dplm.app/Contents/Resources/app.nw +cp -R ${SOURCE}/* ${TMP_DIR}/osx-x64/dplm.app/Contents/Resources/app.nw +cp ${BASE_DIR}/nw.icns ${TMP_DIR}/osx-x64/dplm.app/Contents/Resources +chmod -R 0775 ${TMP_DIR}/osx-x64/dplm.app +cd ${TMP_DIR}/osx-x64 +zip -r dplm-osx-64.zip dplm.app +mv dplm-osx-64.zip ${OUT_DIR}; + +echo "... done" diff --git a/docdoku-dplm/pom.xml b/docdoku-dplm/pom.xml new file mode 100644 index 0000000000..d50e106e3a --- /dev/null +++ b/docdoku-dplm/pom.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.docdoku</groupId> + <artifactId>docdoku-plm</artifactId> + <version>2.5-SNAPSHOT</version> + </parent> + <artifactId>docdoku-dplm</artifactId> + <name>docdoku-dplm UI Desktop Vault Client</name> + <profiles> + <profile> + <id>env-ci</id> + <build> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>1.3.2</version> + <executions> + <execution> + <id>build-dplm</id> + <phase>install</phase> + <goals> + <goal>exec</goal> + </goals> + <configuration> + <workingDirectory>ui</workingDirectory> + <executable>npm</executable> + <arguments> + <argument>install</argument> + </arguments> + </configuration> + </execution> + <execution> + <id>release-dplm</id> + <phase>install</phase> + <goals> + <goal>exec</goal> + </goals> + <configuration> + <executable>bash</executable> + <arguments> + <argument>release.sh</argument> + </arguments> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> \ No newline at end of file diff --git a/docdoku-dplm/release.sh b/docdoku-dplm/release.sh new file mode 100755 index 0000000000..3374a0f16c --- /dev/null +++ b/docdoku-dplm/release.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# +# DPLM Gui Releaser +# +# Author : Morgan Guimard +# Date : Wed Oct 2 2013 +# + +BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +mkdir -p tmp; +mkdir -p target; + +bash build-cli.sh; +[[ $? -eq 0 ]] || { echo "Build cli failed"; exit $?; } +bash build-dplm.sh; +[[ $? -eq 0 ]] || { echo "Build dplm failed"; exit $?; } +bash download.sh; +[[ $? -eq 0 ]] || { echo "Download nw failed"; exit $?; } +bash zip-ui.sh; +[[ $? -eq 0 ]] || { echo "Zipping app failed"; exit $?; } +bash linux-all-platforms.sh; +[[ $? -eq 0 ]] || { echo "Linux build failed"; exit $?; } +bash windows-all-platforms.sh; +[[ $? -eq 0 ]] || { echo "Windows build failed"; exit $?; } +bash osx-all-platforms.sh; +[[ $? -eq 0 ]] || { echo "OSX build failed"; exit $?; } + diff --git a/docdoku-dplm/run-dplm-client.sh b/docdoku-dplm/run-dplm-client.sh new file mode 100755 index 0000000000..bf5faf4217 --- /dev/null +++ b/docdoku-dplm/run-dplm-client.sh @@ -0,0 +1,2 @@ +#!/bin/sh +nw ui/app diff --git a/docdoku-dplm/ui/.bowerrc b/docdoku-dplm/ui/.bowerrc new file mode 100644 index 0000000000..8c58c8efc9 --- /dev/null +++ b/docdoku-dplm/ui/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "app/bower_components" +} \ No newline at end of file diff --git a/docdoku-dplm/ui/.jshintrc b/docdoku-dplm/ui/.jshintrc new file mode 100644 index 0000000000..32b8c6a026 --- /dev/null +++ b/docdoku-dplm/ui/.jshintrc @@ -0,0 +1,10 @@ +{ + "esnext": false, + "moz": true, + "boss": true, + "node": true, + "validthis": true, + "globals": { + "angular": true + } +} diff --git a/docdoku-dplm/ui/Gruntfile.js b/docdoku-dplm/ui/Gruntfile.js new file mode 100644 index 0000000000..7369a544ea --- /dev/null +++ b/docdoku-dplm/ui/Gruntfile.js @@ -0,0 +1,176 @@ +module.exports = function(grunt){ + + 'use strict'; + + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-html2js'); + grunt.loadNpmTasks('grunt-ng-annotate'); + grunt.loadNpmTasks('grunt-usemin'); + grunt.loadNpmTasks('grunt-contrib-cssmin'); + grunt.loadNpmTasks('grunt-text-replace'); + + grunt.initConfig ({ + + jshint: { + all: ['Gruntfile.js', 'app/js/**/*.js'], + options: { + ignores:[ + 'app/js/components/3d/utils/*.js', + 'app/js/components/3d/loaders/*.js', + 'app/js/components/3d/controls/*.js' + ], + strict:false, + globals: { + angular: true, + localStorage:true + } + } + }, + + clean: { + dist: ['tmp', 'dist'] + }, + + usemin: { + html: 'dist/index.html' + }, + + cssmin: { + dist: { + files: { + 'dist/app.css': ['dist/app.css'] + } + } + }, + + html2js: { + dist: { + src: [ 'app/js/**/*.html' ], + dest: 'tmp/templates.js', + options:{ + base:'', + rename:function (moduleName) { + return moduleName.replace('app/js', 'js'); + } + }, + module:'dplm.templates' + } + }, + + copy:{ + dist: { + files: [{ + expand: true, + cwd: 'app/', + src: [ + 'index.html', + 'package.json', + 'node_modules/**', + 'docdoku-cli-jar-with-dependencies.jar', + 'img/**' + ], + dest: 'dist/' + },{ + expand: true, + flatten: true, + cwd: 'app/', + src: [ + 'bower_components/threejs/build/three.js', + 'bower_components/angular-aria/angular-aria.js', + 'bower_components/hammerjs/hammer.js', + 'bower_components/angular-animate/angular-animate.js', + 'bower_components/angular-material/angular-material.js', + 'bower_components/angular-route/angular-route.js', + 'bower_components/angular-translate/angular-translate.js', + 'bower_components/angular-uuid4/angular-uuid4.js' + ], + dest: 'tmp/libs/' + },{ + expand: true, + flatten: true, + cwd: 'app/', + src: [ + 'bower_components/fontawesome/fonts/**', + ], + dest: 'dist/fonts' + },{ + expand: true, + flatten: true, + cwd: 'app/', + src: [ + 'css/fonts/**', + ], + dest: 'dist/fonts' + }] + } + }, + + concat: { + js: { + src: ['app/bower_components/angular/angular.js','tmp/libs/*.js', 'app/js/**/*.js', 'tmp/templates.js'], + dest: 'dist/app.js' + }, + css:{ + src:[ + 'app/css/*.css', + 'app/bower_components/angular-material/themes/blue-theme.css', + 'app/bower_components/fontawesome/css/font-awesome.css' + ], + dest: 'dist/app.css' + } + }, + + uglify: { + dist: { + files: { + 'dist/app.js': [ 'dist/app.js' ] + }, + options: { + mangle: true + } + } + }, + + ngAnnotate:{ + dist:{ + files:{ + 'dist/app.js':['dist/app.js'] + } + }, + options: { + singleQuotes: true + } + }, + + replace: { + dist: { + src: ['dist/app.css'], + dest: 'dist/app.css', + replacements: [{ + from: '../fonts', + to: 'fonts' + }] + } + } + + }); + + grunt.registerTask('build',[ + 'jshint', + 'clean:dist', + 'copy:dist', + 'html2js:dist', + 'concat:js', + 'concat:css', + 'ngAnnotate:dist', + 'uglify:dist', + 'replace:dist', + 'cssmin:dist', + 'usemin' + ]); + +}; \ No newline at end of file diff --git a/docdoku-dplm/ui/app/css/angular-material.mod.css b/docdoku-dplm/ui/app/css/angular-material.mod.css new file mode 100644 index 0000000000..547c7e0ecc --- /dev/null +++ b/docdoku-dplm/ui/app/css/angular-material.mod.css @@ -0,0 +1,3443 @@ +/*! + * Angular Material Design + * https://github.com/angular/material + * @license MIT + * v0.5.0 + */ +*, *:before, *:after { + box-sizing: border-box; } + +:focus { + outline: none; } + +html, body { + height: 100%; + color: rgba(0, 0, 0, 0.73); + background: #ffffff; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-touch-callout: none; + -webkit-text-size-adjust: 100%; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; } + html p, body p { + line-height: 1.846; } + html h3, body h3 { + display: block; + -webkit-margin-before: 1em; + -webkit-margin-after: 1em; + -webkit-margin-start: 0px; + -webkit-margin-end: 0px; + font-size: 1.17em; + font-weight: bold; } + +button, select, html, textarea, input { + font-family: 'RobotoDraft', 'Helvetica Neue', Helvetica, Arial; } + +body { + margin: 0; + padding: 0; + outline: none; } + +.inset { + padding: 10px; } + +button { + font-family: 'RobotoDraft', 'Helvetica Neue', Helvetica, Arial; } + +a { + background: transparent; + outline: none; } + +h1 { + font-size: 2em; + margin: 0.67em 0; } + +h2 { + font-size: 1.5em; + margin: 0.83em 0; } + +h3 { + font-size: 1.17em; + margin: 1em 0; } + +h4 { + font-size: 1em; + margin: 1.33em 0; } + +h5 { + font-size: 0.83em; + margin: 1.67em 0; } + +h6 { + font-size: 0.75em; + margin: 2.33em 0; } + +select, button, textarea, input { + margin: 0; + font-size: 100%; + font-family: inherit; + vertical-align: baseline; } + +input[type="reset"], input[type="submit"], html input[type="button"], button { + cursor: pointer; + -webkit-appearance: button; } + input[type="reset"][disabled], input[type="submit"][disabled], html input[type="button"][disabled], button[disabled] { + cursor: default; } + +textarea { + vertical-align: top; + overflow: auto; } + +input[type="radio"], input[type="checkbox"] { + padding: 0; + box-sizing: border-box; } +input[type="search"] { + -webkit-appearance: textfield; + box-sizing: content-box; + -webkit-box-sizing: content-box; } + input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; } + +@font-face { + font-family: 'RobotoDraft'; + font-style: normal; + font-weight: 100; + src: local('RobotoDraft Thin'), local('RobotoDraft-Thin'), url(fonts/hope9NW9iJ5hh8P5PM_EAyeJLMOzE6CCkidNEpZOseY.woff) format('woff'); } + +@font-face { + font-family: 'RobotoDraft'; + font-style: normal; + font-weight: 300; + src: local('RobotoDraft Light'), local('RobotoDraft-Light'), url(fonts/u0_CMoUf3y3-4Ss4ci-VwR_xHqYgAV9Bl_ZQbYUxnQU.woff) format('woff'); } + +@font-face { + font-family: 'RobotoDraft'; + font-style: normal; + font-weight: 400; + src: local('RobotoDraft'), local('RobotoDraft-Regular'), url(fonts/0xES5Sl_v6oyT7dAKuoni4bN6UDyHWBl620a-IRfuBk.woff) format('woff'); } + +@font-face { + font-family: 'RobotoDraft'; + font-style: normal; + font-weight: 500; + src: local('RobotoDraft Medium'), local('RobotoDraft-Medium'), url(fonts/u0_CMoUf3y3-4Ss4ci-VwSqHEX2q--o2so14pIEl08w.woff) format('woff'); } + +@font-face { + font-family: 'RobotoDraft'; + font-style: normal; + font-weight: 700; + src: local('RobotoDraft Bold'), local('RobotoDraft-Bold'), url(fonts/u0_CMoUf3y3-4Ss4ci-VwTqR_3kx9_hJXbbyU8S6IN0.woff) format('woff'); } + +@font-face { + font-family: 'RobotoDraft'; + font-style: normal; + font-weight: 900; + src: local('RobotoDraft Black'), local('RobotoDraft-Black'), url(fonts/u0_CMoUf3y3-4Ss4ci-VwUExzZ44ka2Lr5i-x5aWr0E.woff) format('woff'); } + +@font-face { + font-family: 'RobotoDraft'; + font-style: italic; + font-weight: 400; + src: local('RobotoDraft Italic'), local('RobotoDraft-Italic'), url(fonts/er-TIW55l9KWsTS1x9bTftkZXW4sYc4BjuAIFc1SXII.woff) format('woff'); } + +@font-face { + font-family: 'RobotoDraft'; + font-style: italic; + font-weight: 700; + src: local('RobotoDraft Bold Italic'), local('RobotoDraft-BoldItalic'), url(fonts/5SAvdU0uYYlH8OURAykt5wRV2F9RPTaqyJ4QibDfkzM.woff) format('woff'); } + +.md-shadow { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + border-radius: inherit; + pointer-events: none; } + +.md-shadow-bottom-z-1, md-button.md-raised:not([disabled]), md-button.md-fab { + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); } + +.md-shadow-bottom-z-2, md-button.md-raised.focus:not([disabled]), md-button.md-raised:not([disabled]):hover, md-button.md-fab.focus:not([disabled]), md-button.md-fab:not([disabled]):hover { + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.4); } + +.md-shadow-animated.md-shadow { + transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1); } + +[layout] { + box-sizing: border-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; } + +[layout=vertical] { + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; } + +[layout-padding], [layout][layout-padding] > [flex] { + padding: 8px; } + +[layout-padding] + [layout-padding] { + margin-top: -8px; + padding-top: 0; } + +[layout-fill] { + margin: 0; + min-height: 100%; + width: 100%; } + +[layout-align="center"], [layout-align="center center"], [layout-align="center start"], [layout-align="center end"] { + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; } + +[layout-align="end"], [layout-align="end center"], [layout-align="end start"], [layout-align="end end"] { + -webkit-justify-content: flex-end; + -ms-flex-pack: end; + justify-content: flex-end; } + +[layout-align="space-around"], [layout-align="space-around center"], [layout-align="space-around start"], [layout-align="space-around end"] { + -webkit-justify-content: space-around; + -ms-flex-pack: distribute; + justify-content: space-around; } + +[layout-align="space-between"], [layout-align="space-between center"], [layout-align="space-between start"], [layout-align="space-between end"] { + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; } + +[layout-align="center center"], [layout-align="start center"], [layout-align="end center"], [layout-align="space-between center"], [layout-align="space-around center"] { + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; } + +[layout-align="center start"], [layout-align="start start"], [layout-align="end start"], [layout-align="space-between start"], [layout-align="space-around start"] { + -webkit-align-items: flex-start; + -ms-flex-align: start; + align-items: flex-start; } + +[layout-align="center end"], [layout-align="start end"], [layout-align="end end"], [layout-align="space-between end"], [layout-align="space-around end"] { + -webkit-align-items: flex-end; + -ms-flex-align: end; + align-items: flex-end; } + +[block] { + display: block; } + +[inline-block] { + display: inline-block; } + +[hide] { + display: none; } + +[show] { + display: inherit; } + +[flex] { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; } + +[flex="5"] { + -webkit-flex: 0 0 5%; + -ms-flex: 0 0 5%; + flex: 0 0 5%; + max-width: 5%; } + +[flex="10"] { + -webkit-flex: 0 0 10%; + -ms-flex: 0 0 10%; + flex: 0 0 10%; + max-width: 10%; } + +[flex="15"] { + -webkit-flex: 0 0 15%; + -ms-flex: 0 0 15%; + flex: 0 0 15%; + max-width: 15%; } + +[flex="20"] { + -webkit-flex: 0 0 20%; + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; } + +[flex="25"] { + -webkit-flex: 0 0 25%; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; } + +[flex="30"] { + -webkit-flex: 0 0 30%; + -ms-flex: 0 0 30%; + flex: 0 0 30%; + max-width: 30%; } + +[flex="35"] { + -webkit-flex: 0 0 35%; + -ms-flex: 0 0 35%; + flex: 0 0 35%; + max-width: 35%; } + +[flex="40"] { + -webkit-flex: 0 0 40%; + -ms-flex: 0 0 40%; + flex: 0 0 40%; + max-width: 40%; } + +[flex="45"] { + -webkit-flex: 0 0 45%; + -ms-flex: 0 0 45%; + flex: 0 0 45%; + max-width: 45%; } + +[flex="50"] { + -webkit-flex: 0 0 50%; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; } + +[flex="55"] { + -webkit-flex: 0 0 55%; + -ms-flex: 0 0 55%; + flex: 0 0 55%; + max-width: 55%; } + +[flex="60"] { + -webkit-flex: 0 0 60%; + -ms-flex: 0 0 60%; + flex: 0 0 60%; + max-width: 60%; } + +[flex="65"] { + -webkit-flex: 0 0 65%; + -ms-flex: 0 0 65%; + flex: 0 0 65%; + max-width: 65%; } + +[flex="70"] { + -webkit-flex: 0 0 70%; + -ms-flex: 0 0 70%; + flex: 0 0 70%; + max-width: 70%; } + +[flex="75"] { + -webkit-flex: 0 0 75%; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; } + +[flex="80"] { + -webkit-flex: 0 0 80%; + -ms-flex: 0 0 80%; + flex: 0 0 80%; + max-width: 80%; } + +[flex="85"] { + -webkit-flex: 0 0 85%; + -ms-flex: 0 0 85%; + flex: 0 0 85%; + max-width: 85%; } + +[flex="90"] { + -webkit-flex: 0 0 90%; + -ms-flex: 0 0 90%; + flex: 0 0 90%; + max-width: 90%; } + +[flex="95"] { + -webkit-flex: 0 0 95%; + -ms-flex: 0 0 95%; + flex: 0 0 95%; + max-width: 95%; } + +[flex="33"], [flex="34"] { + -webkit-flex: 0 0 33.33%; + -ms-flex: 0 0 33.33%; + flex: 0 0 33.33%; + max-width: 33.33%; } + +[flex="66"], [flex="67"] { + -webkit-flex: 0 0 66.66%; + -ms-flex: 0 0 66.66%; + flex: 0 0 66.66%; + max-width: 66.66%; } + +[offset="5"] { + margin-left: 5%; } + +[offset="10"] { + margin-left: 10%; } + +[offset="15"] { + margin-left: 15%; } + +[offset="20"] { + margin-left: 20%; } + +[offset="25"] { + margin-left: 25%; } + +[offset="30"] { + margin-left: 30%; } + +[offset="35"] { + margin-left: 35%; } + +[offset="40"] { + margin-left: 40%; } + +[offset="45"] { + margin-left: 45%; } + +[offset="50"] { + margin-left: 50%; } + +[offset="55"] { + margin-left: 55%; } + +[offset="60"] { + margin-left: 60%; } + +[offset="65"] { + margin-left: 65%; } + +[offset="70"] { + margin-left: 70%; } + +[offset="75"] { + margin-left: 75%; } + +[offset="80"] { + margin-left: 80%; } + +[offset="85"] { + margin-left: 85%; } + +[offset="90"] { + margin-left: 90%; } + +[offset="95"] { + margin-left: 95%; } + +[offset="33"], [offset="34"] { + margin-left: 33.33%; } + +[offset="66"], [offset="67"] { + margin-left: 66.66%; } + +[layout-order="1"] { + -webkit-order: 1; + -ms-flex-order: 1; + order: 1; } + +[layout-order="2"] { + -webkit-order: 2; + -ms-flex-order: 2; + order: 2; } + +[layout-order="3"] { + -webkit-order: 3; + -ms-flex-order: 3; + order: 3; } + +[layout-order="4"] { + -webkit-order: 4; + -ms-flex-order: 4; + order: 4; } + +[layout-order="5"] { + -webkit-order: 5; + -ms-flex-order: 5; + order: 5; } + +[layout-order="6"] { + -webkit-order: 6; + -ms-flex-order: 6; + order: 6; } + +@media (min-width: 600px) { + [flex-sm] { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; } + + [flex-sm="5"] { + -webkit-flex: 0 0 5%; + -ms-flex: 0 0 5%; + flex: 0 0 5%; + max-width: 5%; } + + [flex-sm="10"] { + -webkit-flex: 0 0 10%; + -ms-flex: 0 0 10%; + flex: 0 0 10%; + max-width: 10%; } + + [flex-sm="15"] { + -webkit-flex: 0 0 15%; + -ms-flex: 0 0 15%; + flex: 0 0 15%; + max-width: 15%; } + + [flex-sm="20"] { + -webkit-flex: 0 0 20%; + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; } + + [flex-sm="25"] { + -webkit-flex: 0 0 25%; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; } + + [flex-sm="30"] { + -webkit-flex: 0 0 30%; + -ms-flex: 0 0 30%; + flex: 0 0 30%; + max-width: 30%; } + + [flex-sm="35"] { + -webkit-flex: 0 0 35%; + -ms-flex: 0 0 35%; + flex: 0 0 35%; + max-width: 35%; } + + [flex-sm="40"] { + -webkit-flex: 0 0 40%; + -ms-flex: 0 0 40%; + flex: 0 0 40%; + max-width: 40%; } + + [flex-sm="45"] { + -webkit-flex: 0 0 45%; + -ms-flex: 0 0 45%; + flex: 0 0 45%; + max-width: 45%; } + + [flex-sm="50"] { + -webkit-flex: 0 0 50%; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; } + + [flex-sm="55"] { + -webkit-flex: 0 0 55%; + -ms-flex: 0 0 55%; + flex: 0 0 55%; + max-width: 55%; } + + [flex-sm="60"] { + -webkit-flex: 0 0 60%; + -ms-flex: 0 0 60%; + flex: 0 0 60%; + max-width: 60%; } + + [flex-sm="65"] { + -webkit-flex: 0 0 65%; + -ms-flex: 0 0 65%; + flex: 0 0 65%; + max-width: 65%; } + + [flex-sm="70"] { + -webkit-flex: 0 0 70%; + -ms-flex: 0 0 70%; + flex: 0 0 70%; + max-width: 70%; } + + [flex-sm="75"] { + -webkit-flex: 0 0 75%; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; } + + [flex-sm="80"] { + -webkit-flex: 0 0 80%; + -ms-flex: 0 0 80%; + flex: 0 0 80%; + max-width: 80%; } + + [flex-sm="85"] { + -webkit-flex: 0 0 85%; + -ms-flex: 0 0 85%; + flex: 0 0 85%; + max-width: 85%; } + + [flex-sm="90"] { + -webkit-flex: 0 0 90%; + -ms-flex: 0 0 90%; + flex: 0 0 90%; + max-width: 90%; } + + [flex-sm="95"] { + -webkit-flex: 0 0 95%; + -ms-flex: 0 0 95%; + flex: 0 0 95%; + max-width: 95%; } + + [flex-sm="33"], [flex-sm="34"] { + -webkit-flex: 0 0 33.33%; + -ms-flex: 0 0 33.33%; + flex: 0 0 33.33%; + max-width: 33.33%; } + + [flex-sm="66"], [flex-sm="67"] { + -webkit-flex: 0 0 66.66%; + -ms-flex: 0 0 66.66%; + flex: 0 0 66.66%; + max-width: 66.66%; } + + [offset-sm="5"] { + margin-left: 5%; } + + [offset-sm="10"] { + margin-left: 10%; } + + [offset-sm="15"] { + margin-left: 15%; } + + [offset-sm="20"] { + margin-left: 20%; } + + [offset-sm="25"] { + margin-left: 25%; } + + [offset-sm="30"] { + margin-left: 30%; } + + [offset-sm="35"] { + margin-left: 35%; } + + [offset-sm="40"] { + margin-left: 40%; } + + [offset-sm="45"] { + margin-left: 45%; } + + [offset-sm="50"] { + margin-left: 50%; } + + [offset-sm="55"] { + margin-left: 55%; } + + [offset-sm="60"] { + margin-left: 60%; } + + [offset-sm="65"] { + margin-left: 65%; } + + [offset-sm="70"] { + margin-left: 70%; } + + [offset-sm="75"] { + margin-left: 75%; } + + [offset-sm="80"] { + margin-left: 80%; } + + [offset-sm="85"] { + margin-left: 85%; } + + [offset-sm="90"] { + margin-left: 90%; } + + [offset-sm="95"] { + margin-left: 95%; } + + [offset-sm="33"], [offset-sm="34"] { + margin-left: 33.33%; } + + [offset-sm="66"], [offset-sm="67"] { + margin-left: 66.66%; } + + [layout-order-sm="1"] { + -webkit-order: 1; + -ms-flex-order: 1; + order: 1; } + + [layout-order-sm="2"] { + -webkit-order: 2; + -ms-flex-order: 2; + order: 2; } + + [layout-order-sm="3"] { + -webkit-order: 3; + -ms-flex-order: 3; + order: 3; } + + [layout-order-sm="4"] { + -webkit-order: 4; + -ms-flex-order: 4; + order: 4; } + + [layout-order-sm="5"] { + -webkit-order: 5; + -ms-flex-order: 5; + order: 5; } + + [layout-order-sm="6"] { + -webkit-order: 6; + -ms-flex-order: 6; + order: 6; } + + [layout-sm] { + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; } + + [layout-sm=vertical] { + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; } + + [block-sm] { + display: block; } + + [inline-block-sm] { + display: inline-block; } + + [show-sm] { + display: inherit; } + + [hide-sm] { + display: none; } + } + +@media (min-width: 960px) { + [flex-md] { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; } + + [flex-md="5"] { + -webkit-flex: 0 0 5%; + -ms-flex: 0 0 5%; + flex: 0 0 5%; + max-width: 5%; } + + [flex-md="10"] { + -webkit-flex: 0 0 10%; + -ms-flex: 0 0 10%; + flex: 0 0 10%; + max-width: 10%; } + + [flex-md="15"] { + -webkit-flex: 0 0 15%; + -ms-flex: 0 0 15%; + flex: 0 0 15%; + max-width: 15%; } + + [flex-md="20"] { + -webkit-flex: 0 0 20%; + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; } + + [flex-md="25"] { + -webkit-flex: 0 0 25%; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; } + + [flex-md="30"] { + -webkit-flex: 0 0 30%; + -ms-flex: 0 0 30%; + flex: 0 0 30%; + max-width: 30%; } + + [flex-md="35"] { + -webkit-flex: 0 0 35%; + -ms-flex: 0 0 35%; + flex: 0 0 35%; + max-width: 35%; } + + [flex-md="40"] { + -webkit-flex: 0 0 40%; + -ms-flex: 0 0 40%; + flex: 0 0 40%; + max-width: 40%; } + + [flex-md="45"] { + -webkit-flex: 0 0 45%; + -ms-flex: 0 0 45%; + flex: 0 0 45%; + max-width: 45%; } + + [flex-md="50"] { + -webkit-flex: 0 0 50%; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; } + + [flex-md="55"] { + -webkit-flex: 0 0 55%; + -ms-flex: 0 0 55%; + flex: 0 0 55%; + max-width: 55%; } + + [flex-md="60"] { + -webkit-flex: 0 0 60%; + -ms-flex: 0 0 60%; + flex: 0 0 60%; + max-width: 60%; } + + [flex-md="65"] { + -webkit-flex: 0 0 65%; + -ms-flex: 0 0 65%; + flex: 0 0 65%; + max-width: 65%; } + + [flex-md="70"] { + -webkit-flex: 0 0 70%; + -ms-flex: 0 0 70%; + flex: 0 0 70%; + max-width: 70%; } + + [flex-md="75"] { + -webkit-flex: 0 0 75%; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; } + + [flex-md="80"] { + -webkit-flex: 0 0 80%; + -ms-flex: 0 0 80%; + flex: 0 0 80%; + max-width: 80%; } + + [flex-md="85"] { + -webkit-flex: 0 0 85%; + -ms-flex: 0 0 85%; + flex: 0 0 85%; + max-width: 85%; } + + [flex-md="90"] { + -webkit-flex: 0 0 90%; + -ms-flex: 0 0 90%; + flex: 0 0 90%; + max-width: 90%; } + + [flex-md="95"] { + -webkit-flex: 0 0 95%; + -ms-flex: 0 0 95%; + flex: 0 0 95%; + max-width: 95%; } + + [flex-md="33"], [flex-md="34"] { + -webkit-flex: 0 0 33.33%; + -ms-flex: 0 0 33.33%; + flex: 0 0 33.33%; + max-width: 33.33%; } + + [flex-md="66"], [flex-md="67"] { + -webkit-flex: 0 0 66.66%; + -ms-flex: 0 0 66.66%; + flex: 0 0 66.66%; + max-width: 66.66%; } + + [offset-md="5"] { + margin-left: 5%; } + + [offset-md="10"] { + margin-left: 10%; } + + [offset-md="15"] { + margin-left: 15%; } + + [offset-md="20"] { + margin-left: 20%; } + + [offset-md="25"] { + margin-left: 25%; } + + [offset-md="30"] { + margin-left: 30%; } + + [offset-md="35"] { + margin-left: 35%; } + + [offset-md="40"] { + margin-left: 40%; } + + [offset-md="45"] { + margin-left: 45%; } + + [offset-md="50"] { + margin-left: 50%; } + + [offset-md="55"] { + margin-left: 55%; } + + [offset-md="60"] { + margin-left: 60%; } + + [offset-md="65"] { + margin-left: 65%; } + + [offset-md="70"] { + margin-left: 70%; } + + [offset-md="75"] { + margin-left: 75%; } + + [offset-md="80"] { + margin-left: 80%; } + + [offset-md="85"] { + margin-left: 85%; } + + [offset-md="90"] { + margin-left: 90%; } + + [offset-md="95"] { + margin-left: 95%; } + + [offset-md="33"], [offset-md="34"] { + margin-left: 33.33%; } + + [offset-md="66"], [offset-md="67"] { + margin-left: 66.66%; } + + [layout-order-md="1"] { + -webkit-order: 1; + -ms-flex-order: 1; + order: 1; } + + [layout-order-md="2"] { + -webkit-order: 2; + -ms-flex-order: 2; + order: 2; } + + [layout-order-md="3"] { + -webkit-order: 3; + -ms-flex-order: 3; + order: 3; } + + [layout-order-md="4"] { + -webkit-order: 4; + -ms-flex-order: 4; + order: 4; } + + [layout-order-md="5"] { + -webkit-order: 5; + -ms-flex-order: 5; + order: 5; } + + [layout-order-md="6"] { + -webkit-order: 6; + -ms-flex-order: 6; + order: 6; } + + [layout-md] { + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; } + + [layout-md=vertical] { + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; } + + [block-md] { + display: block; } + + [inline-block-md] { + display: inline-block; } + + [show-md] { + display: inherit; } + + [hide-md] { + display: none; } + } + +@media (min-width: 1200px) { + [flex-lg] { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; } + + [flex-lg="5"] { + -webkit-flex: 0 0 5%; + -ms-flex: 0 0 5%; + flex: 0 0 5%; + max-width: 5%; } + + [flex-lg="10"] { + -webkit-flex: 0 0 10%; + -ms-flex: 0 0 10%; + flex: 0 0 10%; + max-width: 10%; } + + [flex-lg="15"] { + -webkit-flex: 0 0 15%; + -ms-flex: 0 0 15%; + flex: 0 0 15%; + max-width: 15%; } + + [flex-lg="20"] { + -webkit-flex: 0 0 20%; + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; } + + [flex-lg="25"] { + -webkit-flex: 0 0 25%; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; } + + [flex-lg="30"] { + -webkit-flex: 0 0 30%; + -ms-flex: 0 0 30%; + flex: 0 0 30%; + max-width: 30%; } + + [flex-lg="35"] { + -webkit-flex: 0 0 35%; + -ms-flex: 0 0 35%; + flex: 0 0 35%; + max-width: 35%; } + + [flex-lg="40"] { + -webkit-flex: 0 0 40%; + -ms-flex: 0 0 40%; + flex: 0 0 40%; + max-width: 40%; } + + [flex-lg="45"] { + -webkit-flex: 0 0 45%; + -ms-flex: 0 0 45%; + flex: 0 0 45%; + max-width: 45%; } + + [flex-lg="50"] { + -webkit-flex: 0 0 50%; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; } + + [flex-lg="55"] { + -webkit-flex: 0 0 55%; + -ms-flex: 0 0 55%; + flex: 0 0 55%; + max-width: 55%; } + + [flex-lg="60"] { + -webkit-flex: 0 0 60%; + -ms-flex: 0 0 60%; + flex: 0 0 60%; + max-width: 60%; } + + [flex-lg="65"] { + -webkit-flex: 0 0 65%; + -ms-flex: 0 0 65%; + flex: 0 0 65%; + max-width: 65%; } + + [flex-lg="70"] { + -webkit-flex: 0 0 70%; + -ms-flex: 0 0 70%; + flex: 0 0 70%; + max-width: 70%; } + + [flex-lg="75"] { + -webkit-flex: 0 0 75%; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; } + + [flex-lg="80"] { + -webkit-flex: 0 0 80%; + -ms-flex: 0 0 80%; + flex: 0 0 80%; + max-width: 80%; } + + [flex-lg="85"] { + -webkit-flex: 0 0 85%; + -ms-flex: 0 0 85%; + flex: 0 0 85%; + max-width: 85%; } + + [flex-lg="90"] { + -webkit-flex: 0 0 90%; + -ms-flex: 0 0 90%; + flex: 0 0 90%; + max-width: 90%; } + + [flex-lg="95"] { + -webkit-flex: 0 0 95%; + -ms-flex: 0 0 95%; + flex: 0 0 95%; + max-width: 95%; } + + [flex-lg="33"], [flex-lg="34"] { + -webkit-flex: 0 0 33.33%; + -ms-flex: 0 0 33.33%; + flex: 0 0 33.33%; + max-width: 33.33%; } + + [flex-lg="66"], [flex-lg="67"] { + -webkit-flex: 0 0 66.66%; + -ms-flex: 0 0 66.66%; + flex: 0 0 66.66%; + max-width: 66.66%; } + + [offset-lg="5"] { + margin-left: 5%; } + + [offset-lg="10"] { + margin-left: 10%; } + + [offset-lg="15"] { + margin-left: 15%; } + + [offset-lg="20"] { + margin-left: 20%; } + + [offset-lg="25"] { + margin-left: 25%; } + + [offset-lg="30"] { + margin-left: 30%; } + + [offset-lg="35"] { + margin-left: 35%; } + + [offset-lg="40"] { + margin-left: 40%; } + + [offset-lg="45"] { + margin-left: 45%; } + + [offset-lg="50"] { + margin-left: 50%; } + + [offset-lg="55"] { + margin-left: 55%; } + + [offset-lg="60"] { + margin-left: 60%; } + + [offset-lg="65"] { + margin-left: 65%; } + + [offset-lg="70"] { + margin-left: 70%; } + + [offset-lg="75"] { + margin-left: 75%; } + + [offset-lg="80"] { + margin-left: 80%; } + + [offset-lg="85"] { + margin-left: 85%; } + + [offset-lg="90"] { + margin-left: 90%; } + + [offset-lg="95"] { + margin-left: 95%; } + + [offset-lg="33"], [offset-lg="34"] { + margin-left: 33.33%; } + + [offset-lg="66"], [offset-lg="67"] { + margin-left: 66.66%; } + + [layout-order-lg="1"] { + -webkit-order: 1; + -ms-flex-order: 1; + order: 1; } + + [layout-order-lg="2"] { + -webkit-order: 2; + -ms-flex-order: 2; + order: 2; } + + [layout-order-lg="3"] { + -webkit-order: 3; + -ms-flex-order: 3; + order: 3; } + + [layout-order-lg="4"] { + -webkit-order: 4; + -ms-flex-order: 4; + order: 4; } + + [layout-order-lg="5"] { + -webkit-order: 5; + -ms-flex-order: 5; + order: 5; } + + [layout-order-lg="6"] { + -webkit-order: 6; + -ms-flex-order: 6; + order: 6; } + + [layout-lg] { + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; } + + [layout-lg=vertical] { + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; } + + [block-lg] { + display: block; } + + [inline-block-lg] { + display: inline-block; } + + [show-lg] { + display: inherit; } + + [hide-lg] { + display: none; } + } + +md-backdrop { + z-index: 7; + background-color: rgba(0, 0, 0, 0); + position: fixed; + left: 0; + top: 0; + right: 0; + bottom: 0; + transition: all 0.2s ease-out; } + md-backdrop.ng-enter { + transition-delay: 0.1s; } + md-backdrop.ng-enter, md-backdrop.ng-leave.ng-leave-active { + opacity: 0; } + md-backdrop.ng-leave, md-backdrop.ng-enter.ng-enter-active { + opacity: 1; } + +@-webkit-keyframes inkRippleButton { + 0% { + -webkit-transform: scale(0); + transform: scale(0); + opacity: 0.15; } + + 50% { + -webkit-transform: scale(0.75); + transform: scale(0.75); + opacity: 0.15; } + + 100% { + -webkit-transform: scale(2); + transform: scale(2); + opacity: 0; } } + +@keyframes inkRippleButton { + 0% { + -webkit-transform: scale(0); + transform: scale(0); + opacity: 0.15; } + + 50% { + -webkit-transform: scale(0.75); + transform: scale(0.75); + opacity: 0.15; } + + 100% { + -webkit-transform: scale(2); + transform: scale(2); + opacity: 0; } } + +@-webkit-keyframes inkRippleCheckbox { + 0% { + -webkit-transform: scale(0); + transform: scale(0); + opacity: 0.4; } + + 50% { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 0.4; } + + 100% { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 0; } } + +@keyframes inkRippleCheckbox { + 0% { + -webkit-transform: scale(0); + transform: scale(0); + opacity: 0.4; } + + 50% { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 0.4; } + + 100% { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 0; } } + +/* + * A container inside of a rippling element (eg a button), + * which contains all of the individual ripples + */ +.md-ripple-container { + pointer-events: none; + position: absolute; + overflow: hidden; + left: 0; + top: 0; + width: 100%; + height: 100%; } + +.md-ripple { + position: absolute; } + +md-bottom-sheet { + position: fixed; + left: 0; + right: 0; + bottom: 0; + padding: 8px 16px 88px 16px; + z-index: 8; + border-top: 1px solid; + -webkit-transform: translate3d(0, 80px, 0); + transform: translate3d(0, 80px, 0); + transition: 0.2s linear; + transition-property: -webkit-transform; + transition-property: transform; } + md-bottom-sheet.md-has-header { + padding-top: 0; } + md-bottom-sheet.ng-enter { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); } + md-bottom-sheet.ng-enter-active { + opacity: 1; + display: block; + -webkit-transform: translate3d(0, 80px, 0) !important; + transform: translate3d(0, 80px, 0) !important; } + md-bottom-sheet.ng-leave-active { + -webkit-transform: translate3d(0, 100%, 0) !important; + transform: translate3d(0, 100%, 0) !important; } + md-bottom-sheet .md-subheader { + background-color: transparent; + font-family: 'RobotoDraft', 'Helvetica Neue', Helvetica, Arial; + line-height: 56px; + padding: 0; + white-space: nowrap; } + md-bottom-sheet md-inline-icon { + display: inline-block; + height: 24px; + width: 24px; + fill: #444; } + md-bottom-sheet md-item { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + outline: none; } + md-bottom-sheet md-item:hover { + cursor: pointer; } + md-bottom-sheet.md-list md-item { + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + height: 48px; } + md-bottom-sheet.md-list md-item div.md-icon-container { + display: inline-block; + height: 24px; + margin-right: 32px; } + md-bottom-sheet.md-grid { + padding-left: 24px; + padding-right: 24px; + padding-top: 0; } + md-bottom-sheet.md-grid md-list { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + transition: all 0.5s; } + md-bottom-sheet.md-grid md-item { + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + transition: all 0.5s; + height: 80px; + margin-top: 8px; + margin-bottom: 8px; + /* Mixin for how many grid items to show per row */ } + @media screen and (max-width: 600px) { + md-bottom-sheet.md-grid md-item { + -webkit-flex: 1 1 33.33333%; + -ms-flex: 1 1 33.33333%; + flex: 1 1 33.33333%; + max-width: 33.33333%; } + md-bottom-sheet.md-grid md-item:nth-of-type(3n+1) { + -webkit-align-items: flex-start; + -ms-flex-align: start; + align-items: flex-start; } + md-bottom-sheet.md-grid md-item:nth-of-type(3n) { + -webkit-align-items: flex-end; + -ms-flex-align: end; + align-items: flex-end; } } + @media screen and (min-width: 600px) and (max-width: 960px) { + md-bottom-sheet.md-grid md-item { + -webkit-flex: 1 1 25%; + -ms-flex: 1 1 25%; + flex: 1 1 25%; + max-width: 25%; } } + @media screen and (min-width: 960px) and (max-width: 1200px) { + md-bottom-sheet.md-grid md-item { + -webkit-flex: 1 1 16.66667%; + -ms-flex: 1 1 16.66667%; + flex: 1 1 16.66667%; + max-width: 16.66667%; } } + @media screen and (min-width: 1200px) { + md-bottom-sheet.md-grid md-item { + -webkit-flex: 1 1 14.28571%; + -ms-flex: 1 1 14.28571%; + flex: 1 1 14.28571%; + max-width: 14.28571%; } } + md-bottom-sheet.md-grid md-item .md-item-content { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + width: 48px; } + md-bottom-sheet.md-grid md-item .md-icon-container { + display: inline-block; + box-sizing: border-box; + height: 48px; + width: 48px; + margin: 8px 0; } + md-bottom-sheet.md-grid md-item p.md-grid-text { + font-weight: 300; + line-height: 16px; + font-size: 13px; + margin: 0; + white-space: nowrap; + width: 48px; + text-align: center; } + +/** + * Position a FAB button. + */ +md-button { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + position: relative; + display: inline-block; + outline: none; + border: 0; + padding: 0; + margin: 0; + text-align: center; + text-transform: uppercase; + font-weight: inherit; + font-style: inherit; + font-variant: inherit; + font-size: inherit; + font-family: inherit; + line-height: inherit; + cursor: pointer; + overflow: hidden; } + md-button .md-button-inner { + display: block; + left: 0; + top: 0; + width: 100%; + height: auto; /*MOD:100%*/ + background: transparent; + border: none; + color: inherit; + outline: none; + text-transform: inherit; + font-weight: inherit; + font-style: inherit; + font-variant: inherit; + font-size: inherit; + font-family: inherit; + line-height: inherit; + white-space: nowrap; + padding: 6px; } + md-button a { + text-decoration: none; } + md-button[href] { + pointer-events: none; } + md-button[href] .md-button-inner { + pointer-events: auto; } + md-button.md-cornered { + border-radius: 0; } + md-button.md-icon { + padding: 0; + background: none; } + md-button.md-raised { + transition: 0.2s linear; + transition-property: box-shadow, -webkit-transform, background-color; + transition-property: box-shadow, transform, background-color; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); } + md-button.md-fab { + z-index: 2; + width: 56px; + height: 56px; + border-radius: 50%; + border-radius: 50%; + overflow: hidden; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + transition: 0.2s linear; + transition-property: -webkit-transform, box-shadow; + transition-property: transform, box-shadow; } + md-button.md-fab.md-fab-bottom-right { + top: auto; + right: 28px; + bottom: 28px; + left: auto; + position: absolute; } + md-button.md-fab.md-fab-bottom-left { + top: auto; + right: auto; + bottom: 28px; + left: 28px; + position: absolute; } + md-button.md-fab.md-fab-top-right { + top: 28px; + right: 28px; + bottom: auto; + left: auto; + position: absolute; } + md-button.md-fab.md-fab-top-left { + top: 28px; + right: auto; + bottom: auto; + left: 28px; + position: absolute; } + md-button:not([disabled]).md-raised.focus, md-button:not([disabled]).md-raised:hover, md-button:not([disabled]).md-fab.focus, md-button:not([disabled]).md-fab:hover { + -webkit-transform: translate3d(0, -1px, 0); + transform: translate3d(0, -1px, 0); } + +.md-toast-open-top md-button.md-fab-top-left, .md-toast-open-top md-button.md-fab-top-right { + -webkit-transform: translate3d(0, 32px, 0); + transform: translate3d(0, 32px, 0); } + .md-toast-open-top md-button.md-fab-top-left.focus, .md-toast-open-top md-button.md-fab-top-left:hover:not([disabled]), .md-toast-open-top md-button.md-fab-top-right.focus, .md-toast-open-top md-button.md-fab-top-right:hover:not([disabled]) { + -webkit-transform: translate3d(0, 31px, 0); + transform: translate3d(0, 31px, 0); } + +.md-toast-open-bottom md-button.md-fab-bottom-left, .md-toast-open-bottom md-button.md-fab-bottom-right { + -webkit-transform: translate3d(0, -32px, 0); + transform: translate3d(0, -32px, 0); } + .md-toast-open-bottom md-button.md-fab-bottom-left.focus, .md-toast-open-bottom md-button.md-fab-bottom-left:hover, .md-toast-open-bottom md-button.md-fab-bottom-right.focus, .md-toast-open-bottom md-button.md-fab-bottom-right:hover { + -webkit-transform: translate3d(0, -33px, 0); + transform: translate3d(0, -33px, 0); } + +.md-button-group { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + width: 100%; } + +.md-button-group > .md-button { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + display: block; + overflow: hidden; + width: 0; + border-width: 1px 0px 1px 1px; + border-radius: 0; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; } + .md-button-group > .md-button:first-child { + border-radius: 2px 0px 0px 2px; } + .md-button-group > .md-button:last-child { + border-right-width: 1px; + border-radius: 0px 2px 2px 0px; } + +md-card { + display: block; + box-sizing: border-box; + box-shadow: 0px 2px 5px 0 rgba(0, 0, 0, 0.26); + margin: 8px; + padding: 8px; } + md-card .md-card-image { + display: block; + width: 100%; } + +md-checkbox { + display: block; + margin: 15px; + white-space: nowrap; + cursor: pointer; + outline: none; } + md-checkbox .md-container { + position: relative; + top: 4px; + display: inline-block; + width: 18px; + height: 18px; } + md-checkbox .md-container .md-ripple-container { + position: absolute; + display: block; + width: 54px; + height: 54px; + left: -18px; + top: -18px; } + md-checkbox .md-icon { + transition: 240ms; + position: absolute; + top: 0; + left: 0; + width: 18px; + height: 18px; + border: 2px solid; + border-radius: 2px; } + md-checkbox.md-checked .md-icon { + border: none; } + md-checkbox[disabled] { + cursor: no-drop; } + md-checkbox:focus .md-label { + border-color: black; } + md-checkbox.md-checked .md-icon:after { + -webkit-transform: rotate(45deg); + transform: rotate(45deg); + position: absolute; + left: 6px; + top: 2px; + display: table; + width: 6px; + height: 12px; + border: 2px solid; + border-top: 0; + border-left: 0; + content: ' '; } + md-checkbox .md-label { + border: 1px dotted transparent; + position: relative; + display: inline-block; + margin-left: 10px; + vertical-align: middle; + white-space: normal; + pointer-events: none; } + +md-content { + display: block; + position: relative; + overflow: auto; + -webkit-overflow-scrolling: touch; } + md-content[scroll-y] { + overflow-y: auto; + overflow-x: hidden; } + md-content[scroll-x] { + overflow-x: auto; + overflow-y: hidden; } + md-content.md-padding { + padding: 8px; } + +@media (min-width: 600px) { + md-content.md-padding { + padding: 16px; } + } + +.md-dialog-container { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + position: fixed; + left: 0; + top: 0; + right: 0; + bottom: 0; + z-index: 10; } + .md-dialog-container.md-active { + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + -webkit-transform: translate3d(0, 0, 0) scale(1); + transform: translate3d(0, 0, 0) scale(1); + opacity: 1; } + .md-dialog-container.ng-leave.ng-leave-active { + -webkit-transform: translate3d(0, 100%, 0) scale(0.5); + transform: translate3d(0, 100%, 0) scale(0.5); + opacity: 0; } + +md-dialog { + min-width: 240px; + max-width: 80%; + max-height: 80%; + margin: auto; + position: relative; + box-shadow: 0px 27px 24px 0 rgba(0, 0, 0, 0.2); + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; } + md-dialog md-content { + -webkit-order: 1; + -ms-flex-order: 1; + order: 1; + padding: 24px; + overflow: auto; + -webkit-overflow-scrolling: touch; } + md-dialog md-content *:first-child { + margin-top: 0px; } + md-dialog .md-actions { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-order: 2; + -ms-flex-order: 2; + order: 2; + box-sizing: border-box; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + -webkit-justify-content: flex-end; + -ms-flex-pack: end; + justify-content: flex-end; + padding: 16px 16px; + min-height: 40px; } + md-dialog .md-actions > * { + margin-left: 8px; } + md-dialog.md-content-overflow .md-actions { + border-top: 1px solid; } + +md-divider { + display: block; + border-top: 1px solid; + margin: 0; } + md-divider[inset] { + margin-left: 80px; } + +md-icon { + margin: auto; + padding: 0; + display: inline-block; + margin-top: 5px; + background-repeat: no-repeat no-repeat; + pointer-events: none; } + +svg, object { + fill: currentColor; + color: currentColor; } + +md-list { + padding: 8px 0px 8px 0px; } + +md-item-content { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + box-sizing: border-box; + position: relative; + padding: 0px 0px 0px 0px; } + +/** + * The left tile for a list item. + */ +.md-tile-left { + min-width: 56px; + margin-right: -16px; } + +/** + * The center content tile for a list item. + */ +.md-tile-content { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + padding: 16px; + text-overflow: ellipsis; } + .md-tile-content h3 { + margin: 0 0 3px 0; + font-weight: 400; + font-size: 1.1em; } + .md-tile-content h4 { + margin: 0 0 3px 0; + font-weight: 400; + font-size: 0.9em; } + .md-tile-content p { + margin: 0 0 3px 0; + font-size: 0.75em; } + +/** + * The right tile for a list item. + */ +.md-tile-right { + padding-right: 0px; } + +md-progress-circular { + display: block; + width: 54px; + height: 54px; + border-radius: 50%; + padding: 3px; } + md-progress-circular .md-wrapper1, md-progress-circular .md-wrapper2 { + width: 48px; + height: 48px; + position: absolute; + border-radius: 50%; } + md-progress-circular .md-circle .md-mask, md-progress-circular .md-circle .md-fill, md-progress-circular .md-circle .md-shadow { + width: 48px; + height: 48px; + position: absolute; + border-radius: 50%; } + md-progress-circular .md-circle .md-mask, md-progress-circular .md-circle .md-fill { + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + transition: -webkit-transform 0.3s; + transition: transform 0.3s; } + md-progress-circular .md-circle .md-mask { + clip: rect(0px, 48px, 48px, 24px); } + md-progress-circular .md-circle .md-mask .md-fill { + clip: rect(0px, 24px, 48px, 0px); } + md-progress-circular .md-inset { + width: 36px; + height: 36px; + position: absolute; + margin-left: 6px; + margin-top: 6px; + border-radius: 50%; } + md-progress-circular[mode=indeterminate] .md-wrapper1, md-progress-circular[mode=indeterminate] .md-wrapper2 { + -webkit-transform-origin: 50% 50%; + transform-origin: 50% 50%; } + md-progress-circular[mode=indeterminate] .md-wrapper1 { + -webkit-animation: indeterminate_rotate1 3s infinite linear; + animation: indeterminate_rotate1 3s infinite linear; } + md-progress-circular[mode=indeterminate] .md-wrapper2 { + -webkit-animation: indeterminate_rotate2 1.5s infinite linear; + animation: indeterminate_rotate2 1.5s infinite linear; } + md-progress-circular[mode=indeterminate] .md-fill, md-progress-circular[mode=indeterminate] .md-mask.md-full { + -webkit-animation: indeterminate_size_fill 1.5s infinite linear; + animation: indeterminate_size_fill 1.5s infinite linear; } + md-progress-circular[mode=indeterminate] .md-fill.md-fix { + -webkit-animation: indeterminate_size_fix 1.5s infinite linear; + animation: indeterminate_size_fix 1.5s infinite linear; } + +@-webkit-keyframes indeterminate_rotate1 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +@keyframes indeterminate_rotate1 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +@-webkit-keyframes indeterminate_rotate2 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + + 70% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +@keyframes indeterminate_rotate2 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + + 70% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +@-webkit-keyframes indeterminate_size_fill { + 0% { + -webkit-transform: rotate(5deg); + transform: rotate(5deg); } + + 10% { + -webkit-transform: rotate(5deg); + transform: rotate(5deg); } + + 50% { + -webkit-transform: rotate(135deg); + transform: rotate(135deg); } + + 70% { + -webkit-transform: rotate(135deg); + transform: rotate(135deg); } + + 100% { + -webkit-transform: rotate(5deg); + transform: rotate(5deg); } } + +@keyframes indeterminate_size_fill { + 0% { + -webkit-transform: rotate(5deg); + transform: rotate(5deg); } + + 10% { + -webkit-transform: rotate(5deg); + transform: rotate(5deg); } + + 50% { + -webkit-transform: rotate(135deg); + transform: rotate(135deg); } + + 70% { + -webkit-transform: rotate(135deg); + transform: rotate(135deg); } + + 100% { + -webkit-transform: rotate(5deg); + transform: rotate(5deg); } } + +@-webkit-keyframes indeterminate_size_fix { + 0% { + -webkit-transform: rotate(10deg); + transform: rotate(10deg); } + + 10% { + -webkit-transform: rotate(10deg); + transform: rotate(10deg); } + + 50% { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } + + 70% { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } + + 100% { + -webkit-transform: rotate(10deg); + transform: rotate(10deg); } } + +@keyframes indeterminate_size_fix { + 0% { + -webkit-transform: rotate(10deg); + transform: rotate(10deg); } + + 10% { + -webkit-transform: rotate(10deg); + transform: rotate(10deg); } + + 50% { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } + + 70% { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } + + 100% { + -webkit-transform: rotate(10deg); + transform: rotate(10deg); } } + +md-progress-linear { + display: block; + width: 100%; + height: 5px; } + md-progress-linear .md-container { + overflow: hidden; + position: relative; + height: 5px; + top: 5px; + -webkit-transform: translate(0, 5px) scale(1, 0); + transform: translate(0, 5px) scale(1, 0); + transition: all 0.3s linear; } + md-progress-linear .md-container.md-ready { + -webkit-transform: translate(0, 0) scale(1, 1); + transform: translate(0, 0) scale(1, 1); } + md-progress-linear .md-bar { + height: 5px; + position: absolute; + width: 100%; } + md-progress-linear .md-bar1, md-progress-linear .md-bar2 { + transition: all 0.2s linear; } + md-progress-linear[mode=determinate] .md-bar1 { + display: none; } + md-progress-linear[mode=indeterminate] .md-bar1 { + -webkit-animation: indeterminate1 4s infinite linear; + animation: indeterminate1 4s infinite linear; } + md-progress-linear[mode=indeterminate] .md-bar2 { + -webkit-animation: indeterminate2 4s infinite linear; + animation: indeterminate2 4s infinite linear; } + md-progress-linear[mode=buffer] .md-container { + background-color: transparent !important; } + md-progress-linear[mode=buffer] .md-dashed:before { + content: ""; + display: block; + height: 5px; + width: 100%; + margin-top: 0px; + position: absolute; + background-color: transparent; + background-size: 10px 10px !important; + background-position: 0px -23px; + -webkit-animation: buffer 3s infinite linear; + animation: buffer 3s infinite linear; } + md-progress-linear[mode=query] .md-bar2 { + -webkit-animation: query 0.8s infinite cubic-bezier(0.39, 0.575, 0.565, 1); + animation: query 0.8s infinite cubic-bezier(0.39, 0.575, 0.565, 1); } + +@-webkit-keyframes indeterminate1 { + 0% { + -webkit-transform: translateX(-25%) scale(0.5, 1); + transform: translateX(-25%) scale(0.5, 1); } + + 10% { + -webkit-transform: translateX(25%) scale(0.5, 1); + transform: translateX(25%) scale(0.5, 1); } + + 19.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 20% { + -webkit-transform: translateX(-37.5%) scale(0.25, 1); + transform: translateX(-37.5%) scale(0.25, 1); } + + 30% { + -webkit-transform: translateX(37.5%) scale(0.25, 1); + transform: translateX(37.5%) scale(0.25, 1); } + + 34.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 36.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 37% { + -webkit-transform: translateX(-37.5%) scale(0.25, 1); + transform: translateX(-37.5%) scale(0.25, 1); } + + 47% { + -webkit-transform: translateX(20%) scale(0.25, 1); + transform: translateX(20%) scale(0.25, 1); } + + 52% { + -webkit-transform: translateX(35%) scale(0.05, 1); + transform: translateX(35%) scale(0.05, 1); } + + 55% { + -webkit-transform: translateX(35%) scale(0.1, 1); + transform: translateX(35%) scale(0.1, 1); } + + 58% { + -webkit-transform: translateX(50%) scale(0.1, 1); + transform: translateX(50%) scale(0.1, 1); } + + 61.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 69.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 70% { + -webkit-transform: translateX(-37.5%) scale(0.25, 1); + transform: translateX(-37.5%) scale(0.25, 1); } + + 80% { + -webkit-transform: translateX(20%) scale(0.25, 1); + transform: translateX(20%) scale(0.25, 1); } + + 85% { + -webkit-transform: translateX(35%) scale(0.05, 1); + transform: translateX(35%) scale(0.05, 1); } + + 88% { + -webkit-transform: translateX(35%) scale(0.1, 1); + transform: translateX(35%) scale(0.1, 1); } + + 91% { + -webkit-transform: translateX(50%) scale(0.1, 1); + transform: translateX(50%) scale(0.1, 1); } + + 92.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 93% { + -webkit-transform: translateX(-50%) scale(0, 1); + transform: translateX(-50%) scale(0, 1); } + + 100% { + -webkit-transform: translateX(-25%) scale(0.5, 1); + transform: translateX(-25%) scale(0.5, 1); } } + +@keyframes indeterminate1 { + 0% { + -webkit-transform: translateX(-25%) scale(0.5, 1); + transform: translateX(-25%) scale(0.5, 1); } + + 10% { + -webkit-transform: translateX(25%) scale(0.5, 1); + transform: translateX(25%) scale(0.5, 1); } + + 19.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 20% { + -webkit-transform: translateX(-37.5%) scale(0.25, 1); + transform: translateX(-37.5%) scale(0.25, 1); } + + 30% { + -webkit-transform: translateX(37.5%) scale(0.25, 1); + transform: translateX(37.5%) scale(0.25, 1); } + + 34.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 36.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 37% { + -webkit-transform: translateX(-37.5%) scale(0.25, 1); + transform: translateX(-37.5%) scale(0.25, 1); } + + 47% { + -webkit-transform: translateX(20%) scale(0.25, 1); + transform: translateX(20%) scale(0.25, 1); } + + 52% { + -webkit-transform: translateX(35%) scale(0.05, 1); + transform: translateX(35%) scale(0.05, 1); } + + 55% { + -webkit-transform: translateX(35%) scale(0.1, 1); + transform: translateX(35%) scale(0.1, 1); } + + 58% { + -webkit-transform: translateX(50%) scale(0.1, 1); + transform: translateX(50%) scale(0.1, 1); } + + 61.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 69.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 70% { + -webkit-transform: translateX(-37.5%) scale(0.25, 1); + transform: translateX(-37.5%) scale(0.25, 1); } + + 80% { + -webkit-transform: translateX(20%) scale(0.25, 1); + transform: translateX(20%) scale(0.25, 1); } + + 85% { + -webkit-transform: translateX(35%) scale(0.05, 1); + transform: translateX(35%) scale(0.05, 1); } + + 88% { + -webkit-transform: translateX(35%) scale(0.1, 1); + transform: translateX(35%) scale(0.1, 1); } + + 91% { + -webkit-transform: translateX(50%) scale(0.1, 1); + transform: translateX(50%) scale(0.1, 1); } + + 92.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 93% { + -webkit-transform: translateX(-50%) scale(0, 1); + transform: translateX(-50%) scale(0, 1); } + + 100% { + -webkit-transform: translateX(-25%) scale(0.5, 1); + transform: translateX(-25%) scale(0.5, 1); } } + +@-webkit-keyframes indeterminate2 { + 0% { + -webkit-transform: translateX(-50%) scale(0, 1); + transform: translateX(-50%) scale(0, 1); } + + 25.99% { + -webkit-transform: translateX(-50%) scale(0, 1); + transform: translateX(-50%) scale(0, 1); } + + 28% { + -webkit-transform: translateX(-37.5%) scale(0.25, 1); + transform: translateX(-37.5%) scale(0.25, 1); } + + 38% { + -webkit-transform: translateX(37.5%) scale(0.25, 1); + transform: translateX(37.5%) scale(0.25, 1); } + + 42.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 46.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 49.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 50% { + -webkit-transform: translateX(-50%) scale(0, 1); + transform: translateX(-50%) scale(0, 1); } + + 60% { + -webkit-transform: translateX(-25%) scale(0.5, 1); + transform: translateX(-25%) scale(0.5, 1); } + + 70% { + -webkit-transform: translateX(25%) scale(0.5, 1); + transform: translateX(25%) scale(0.5, 1); } + + 79.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } } + +@keyframes indeterminate2 { + 0% { + -webkit-transform: translateX(-50%) scale(0, 1); + transform: translateX(-50%) scale(0, 1); } + + 25.99% { + -webkit-transform: translateX(-50%) scale(0, 1); + transform: translateX(-50%) scale(0, 1); } + + 28% { + -webkit-transform: translateX(-37.5%) scale(0.25, 1); + transform: translateX(-37.5%) scale(0.25, 1); } + + 38% { + -webkit-transform: translateX(37.5%) scale(0.25, 1); + transform: translateX(37.5%) scale(0.25, 1); } + + 42.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 46.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 49.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } + + 50% { + -webkit-transform: translateX(-50%) scale(0, 1); + transform: translateX(-50%) scale(0, 1); } + + 60% { + -webkit-transform: translateX(-25%) scale(0.5, 1); + transform: translateX(-25%) scale(0.5, 1); } + + 70% { + -webkit-transform: translateX(25%) scale(0.5, 1); + transform: translateX(25%) scale(0.5, 1); } + + 79.99% { + -webkit-transform: translateX(50%) scale(0, 1); + transform: translateX(50%) scale(0, 1); } } + +@-webkit-keyframes query { + 0% { + opacity: 1; + -webkit-transform: translateX(35%) scale(0.3, 1); + transform: translateX(35%) scale(0.3, 1); } + + 100% { + opacity: 0; + -webkit-transform: translateX(-50%) scale(0, 1); + transform: translateX(-50%) scale(0, 1); } } + +@keyframes query { + 0% { + opacity: 1; + -webkit-transform: translateX(35%) scale(0.3, 1); + transform: translateX(35%) scale(0.3, 1); } + + 100% { + opacity: 0; + -webkit-transform: translateX(-50%) scale(0, 1); + transform: translateX(-50%) scale(0, 1); } } + +@-webkit-keyframes buffer { + 0% { + opacity: 1; + background-position: 0px -23px; } + + 50% { + opacity: 0; } + + 100% { + opacity: 1; + background-position: -200px -23px; } } + +@keyframes buffer { + 0% { + opacity: 1; + background-position: 0px -23px; } + + 50% { + opacity: 0; } + + 100% { + opacity: 1; + background-position: -200px -23px; } } + +md-radio-button, .md-switch-thumb { + display: block; + margin: 15px; + white-space: nowrap; + cursor: pointer; } + md-radio-button input, .md-switch-thumb input { + display: none; } + md-radio-button .md-container, .md-switch-thumb .md-container { + position: relative; + top: 4px; + display: inline-block; + width: 16px; + height: 16px; + cursor: pointer; } + md-radio-button .md-container .md-ripple-container, .md-switch-thumb .md-container .md-ripple-container { + position: absolute; + display: block; + width: 48px; + height: 48px; + left: -16px; + top: -16px; } + md-radio-button .md-off, .md-switch-thumb .md-off { + position: absolute; + top: 0px; + left: 0px; + width: 16px; + height: 16px; + border: solid 2px; + border-radius: 50%; + transition: border-color ease 0.28s; } + md-radio-button .md-on, .md-switch-thumb .md-on { + position: absolute; + top: 0; + left: 0; + width: 16px; + height: 16px; + border-radius: 50%; + transition: -webkit-transform ease 0.28s; + transition: transform ease 0.28s; + -webkit-transform: scale(0); + transform: scale(0); } + md-radio-button.md-checked .md-on, .md-switch-thumb.md-checked .md-on { + -webkit-transform: scale(0.55); + transform: scale(0.55); } + md-radio-button .md-label, .md-switch-thumb .md-label { + position: relative; + display: inline-block; + margin-left: 10px; + vertical-align: middle; + white-space: normal; + pointer-events: none; + width: auto; } + md-radio-button .circle, .md-switch-thumb .circle { + border-radius: 50%; } + +md-radio-group { + border: 1px dotted transparent; + display: block; + outline: none; } + +md-sidenav { + position: absolute; + width: 304px; + bottom: 0; + z-index: 8; + background-color: white; + overflow: auto; } + md-sidenav.md-closed { + display: none; } + md-sidenav.md-closed-add, md-sidenav.md-closed-remove { + display: block; + /* this is required as of 1.3x to properly + apply all styling in a show/hide animation */ + transition: 0s all; } + md-sidenav.md-closed-add.md-closed-add-active, md-sidenav.md-closed-remove.md-closed-remove-active { + transition: -webkit-transform 0.3s ease-in-out; + transition: transform 0.3s ease-in-out; } + md-sidenav.md-locked-open, md-sidenav.md-locked-open.md-closed, md-sidenav.md-locked-open.md-closed.md-sidenav-left, md-sidenav.md-locked-open.md-closed, md-sidenav.md-locked-open.md-closed.md-sidenav-right { + position: static; + display: block; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); } + +.md-sidenav-backdrop.locked-open { + display: none; } + +.md-sidenav-left, md-sidenav { + left: 0; + top: 0; + -webkit-transform: translate3d(0%, 0, 0); + transform: translate3d(0%, 0, 0); } + .md-sidenav-left.md-closed, md-sidenav.md-closed { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); } + +.md-sidenav-right { + left: 100%; + top: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); } + .md-sidenav-right.md-closed { + -webkit-transform: translate3d(0%, 0, 0); + transform: translate3d(0%, 0, 0); } + +@-webkit-keyframes sliderFocusThumb { + 0% { + opacity: 0; + -webkit-transform: scale(0); + transform: scale(0); } + + 50% { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 1; } + + 100% { + opacity: 0; } } + +@keyframes sliderFocusThumb { + 0% { + opacity: 0; + -webkit-transform: scale(0); + transform: scale(0); } + + 50% { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 1; } + + 100% { + opacity: 0; } } + +md-slider { + height: 48px; + position: relative; + display: block; + margin-left: 4px; + margin-right: 4px; + /** + * Track + */ + /** + * Slider thumb + */ + /* The sign that's focused in discrete mode */ + /** + * The border/background that comes in when focused in non-discrete mode + */ + /* Don't animate left/right while panning */ } + md-slider .md-track-container { + width: 100%; + position: absolute; + top: 23px; + height: 2px; } + md-slider .md-track { + position: absolute; + left: 0; + right: 0; + height: 100%; } + md-slider .md-track-fill { + transition: width 0.05s linear; } + md-slider .md-track-ticks { + position: absolute; + left: 0; + right: 0; + height: 100%; } + md-slider .md-thumb-container { + position: absolute; + left: 0; + top: 0; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + transition: -webkit-transform 0.1s linear; + transition: transform 0.1s linear; } + md-slider .md-thumb { + z-index: 1; + position: absolute; + left: -19px; + top: 5px; + width: 38px; + height: 38px; + border-radius: 38px; + -webkit-transform: scale(0.5); + transform: scale(0.5); + transition: all 0.1s linear; } + md-slider .md-thumb:after { + content: ''; + position: absolute; + left: 3px; + top: 3px; + width: 32px; + height: 32px; + border-radius: 32px; + border: 3px solid; } + md-slider .md-sign { + /* Center the children (slider-thumb-text) */ + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + position: absolute; + left: -14px; + top: -20px; + width: 28px; + height: 28px; + border-radius: 28px; + -webkit-transform: scale(0.4) translate3d(0, 70px, 0); + transform: scale(0.4) translate3d(0, 70px, 0); + transition: all 0.2s ease-in-out; + /* The arrow pointing down under the sign */ } + md-slider .md-sign:after { + position: absolute; + content: ''; + left: 0px; + border-radius: 16px; + top: 19px; + border-left: 14px solid transparent; + border-right: 14px solid transparent; + border-top: 16px solid; + opacity: 0; + -webkit-transform: translate3d(0, -8px, 0); + transform: translate3d(0, -8px, 0); + transition: all 0.2s ease-in-out; } + md-slider .md-sign .md-thumb-text { + z-index: 1; + font-size: 12px; + font-weight: bold; } + md-slider .md-focus-thumb { + position: absolute; + left: -24px; + top: 0px; + width: 48px; + height: 48px; + border-radius: 48px; + display: none; + opacity: 0; + background-color: #C0C0C0; + -webkit-animation: sliderFocusThumb 0.4s linear; + animation: sliderFocusThumb 0.4s linear; } + md-slider .md-focus-ring { + position: absolute; + left: -24px; + top: 0px; + width: 48px; + height: 48px; + border-radius: 48px; + border: 2px solid #D6D6D6; + background-color: transparent; + -webkit-transform: scale(0); + transform: scale(0); + transition: all 0.2s linear; } + md-slider .md-disabled-thumb { + position: absolute; + left: -22px; + top: 2px; + width: 44px; + height: 44px; + border-radius: 44px; + -webkit-transform: scale(0.35); + transform: scale(0.35); + border: 6px solid; + display: none; } + md-slider.md-min .md-thumb:after { + background-color: white; } + md-slider.md-min .md-sign { + opacity: 0; } + md-slider:focus { + outline: none; } + md-slider.panning .md-thumb-container, md-slider.panning .md-track-fill { + transition: none; } + md-slider:not([discrete]) { + /* Hide the sign and ticks in non-discrete mode */ } + md-slider:not([discrete]) .md-track-ticks, md-slider:not([discrete]) .md-sign { + display: none; } + md-slider:not([discrete]):not([disabled]):hover .md-thumb { + -webkit-transform: scale(0.6); + transform: scale(0.6); } + md-slider:not([discrete]):not([disabled]):focus .md-focus-thumb, md-slider:not([discrete]):not([disabled]).active .md-focus-thumb { + display: block; } + md-slider:not([discrete]):not([disabled]):focus .md-focus-ring, md-slider:not([discrete]):not([disabled]).active .md-focus-ring { + -webkit-transform: scale(1); + transform: scale(1); } + md-slider:not([discrete]):not([disabled]):focus .md-thumb, md-slider:not([discrete]):not([disabled]).active .md-thumb { + -webkit-transform: scale(0.85); + transform: scale(0.85); } + md-slider[discrete] { + /* Hide the focus thumb in discrete mode */ } + md-slider[discrete] .md-focus-thumb, md-slider[discrete] .md-focus-ring { + display: none; } + md-slider[discrete]:not([disabled]):focus .md-sign, md-slider[discrete]:not([disabled]):focus .md-sign:after, md-slider[discrete]:not([disabled]).active .md-sign, md-slider[discrete]:not([disabled]).active .md-sign:after { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0) scale(1); + transform: translate3d(0, 0, 0) scale(1); } + md-slider[disabled] .md-track-fill { + display: none; } + md-slider[disabled] .md-sign { + display: none; } + md-slider[disabled] .md-thumb { + -webkit-transform: scale(0.35); + transform: scale(0.35); } + md-slider[disabled] .md-disabled-thumb { + display: block; } + +.md-sticky-clone { + z-index: 1; + top: 0; + left: 0; + right: 0; + position: absolute !important; + -webkit-transform: translate3d(-9999px, -9999px, 0); + transform: translate3d(-9999px, -9999px, 0); } + .md-sticky-clone[sticky-state="active"] { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); } + .md-sticky-clone[sticky-state="active"]:not(.md-sticky-no-effect):after { + -webkit-animation: subheaderStickyHoverIn 0.3s ease-out both; + animation: subheaderStickyHoverIn 0.3s ease-out both; } + +@-webkit-keyframes subheaderStickyHoverIn { + 0% { + box-shadow: 0 0 0 0 transparent; } + + 100% { + box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.16); } } + +@keyframes subheaderStickyHoverIn { + 0% { + box-shadow: 0 0 0 0 transparent; } + + 100% { + box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.16); } } + +@-webkit-keyframes subheaderStickyHoverOut { + 0% { + box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.16); } + + 100% { + box-shadow: 0 0 0 0 transparent; } } + +@keyframes subheaderStickyHoverOut { + 0% { + box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.16); } + + 100% { + box-shadow: 0 0 0 0 transparent; } } + +.md-subheader { + display: block; + font-size: 0.9em; + font-weight: 400; + line-height: 1em; + padding: 16px 0px 16px 16px; + margin: 0 0 0 0; + margin-right: 16px; + position: relative; } + .md-subheader:not(.md-sticky-no-effect) { + transition: 0.2s ease-out margin; } + .md-subheader:not(.md-sticky-no-effect):after { + position: absolute; + left: 0; + bottom: 0; + top: 0; + right: -16px; + content: ''; } + .md-subheader:not(.md-sticky-no-effect)[sticky-state="active"] { + margin-top: -2px; } + .md-subheader:not(.md-sticky-no-effect):not(.md-sticky-clone)[sticky-prev-state="active"]:after { + -webkit-animation: subheaderStickyHoverOut 0.3s ease-out both; + animation: subheaderStickyHoverOut 0.3s ease-out both; } + +md-switch { + display: block; + position: relative; + height: 24px; + margin: 8px; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + /* used also in _radio-button.scss */ } + md-switch .md-switch-bar { + position: absolute; + left: 16px; + top: 12px; + width: 32px; + height: 1px; + pointer-events: none; } + md-switch .md-switch-thumb { + position: absolute; + margin: 0; + left: 0; + top: 0; + outline: none; } + md-switch .md-switch-thumb .md-container { + position: absolute; + transition: -webkit-transform 0.2s linear; + transition: transform 0.2s linear; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); } + md-switch .md-switch-thumb.md-checked .md-container { + -webkit-transform: translate3d(48px, 0, 0); + transform: translate3d(48px, 0, 0); } + md-switch .md-switch-thumb .md-label { + margin-left: 72px; } + +md-tabs { + display: block; + width: 100%; + font-weight: 500; } + +.md-header { + width: 100%; + height: 48px; + box-sizing: border-box; + position: relative; } + +.md-paginator { + z-index: 1; + margin-right: -2px; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + width: 32px; + min-height: 100%; + cursor: pointer; + background-repeat: no-repeat; + background-position: center center; + position: absolute; + /* TODO Once we have a better way to inline svg images, change this + to use svgs correctly */ } + .md-paginator.md-prev { + left: 0; } + .md-paginator.md-next { + right: 0; } + .md-paginator.md-prev { + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE3LjEuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPiA8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPiA8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjQgMjQiIHhtbDpzcGFjZT0icHJlc2VydmUiPiA8ZyBpZD0iSGVhZGVyIj4gPGc+IDxyZWN0IHg9Ii02MTgiIHk9Ii0xMjA4IiBmaWxsPSJub25lIiB3aWR0aD0iMTQwMCIgaGVpZ2h0PSIzNjAwIi8+IDwvZz4gPC9nPiA8ZyBpZD0iTGFiZWwiPiA8L2c+IDxnIGlkPSJJY29uIj4gPGc+IDxwb2x5Z29uIHBvaW50cz0iMTUuNCw3LjQgMTQsNiA4LDEyIDE0LDE4IDE1LjQsMTYuNiAxMC44LDEyIAkJIiBzdHlsZT0iZmlsbDp3aGl0ZTsiLz4gPHJlY3QgZmlsbD0ibm9uZSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ii8+IDwvZz4gPC9nPiA8ZyBpZD0iR3JpZCIgZGlzcGxheT0ibm9uZSI+IDxnIGRpc3BsYXk9ImlubGluZSI+IDwvZz4gPC9nPiA8L3N2Zz4NCg=='); } + .md-paginator.md-next { + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE3LjEuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPiA8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPiA8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjQgMjQiIHhtbDpzcGFjZT0icHJlc2VydmUiPiA8ZyBpZD0iSGVhZGVyIj4gPGc+IDxyZWN0IHg9Ii02MTgiIHk9Ii0xMzM2IiBmaWxsPSJub25lIiB3aWR0aD0iMTQwMCIgaGVpZ2h0PSIzNjAwIi8+IDwvZz4gPC9nPiA8ZyBpZD0iTGFiZWwiPiA8L2c+IDxnIGlkPSJJY29uIj4gPGc+IDxwb2x5Z29uIHBvaW50cz0iMTAsNiA4LjYsNy40IDEzLjIsMTIgOC42LDE2LjYgMTAsMTggMTYsMTIgCQkiIHN0eWxlPSJmaWxsOndoaXRlOyIvPiA8cmVjdCBmaWxsPSJub25lIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiLz4gPC9nPiA8L2c+IDxnIGlkPSJHcmlkIiBkaXNwbGF5PSJub25lIj4gPGcgZGlzcGxheT0iaW5saW5lIj4gPC9nPiA8L2c+IDwvc3ZnPg0K'); } + +/* If `center` justified, change to left-justify if paginating */ +md-tabs[center] .md-header:not(.md-paginating) .md-header-items { + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; } + +.md-paginating .md-header-items-container { + left: 32px; + right: 32px; } + +.md-header-items-container { + overflow: hidden; + position: absolute; + left: 0; + right: 0; + height: 100%; + white-space: nowrap; + font-size: 16px; + font-weight: 500; + text-transform: uppercase; + margin: auto; } + .md-header-items-container .md-header-items { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + box-sizing: border-box; + transition: -webkit-transform 0.2s linear; + transition: transform 0.2s linear; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + width: 100%; + height: 100%; } + +.md-tabs-content { + overflow: hidden; + width: 100%; } + .md-tabs-content .md-tab-content { + height: 100%; } + +md-tabs-ink-bar { + position: absolute; + left: 0; + bottom: 0; + box-sizing: border-box; + transition: all 0.2s linear; + height: 2px; + margin-top: -2px; } + +md-tab { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + box-sizing: border-box; + position: relative; + z-index: 0; + overflow: hidden; + height: 100%; + text-align: center; + cursor: pointer; + min-width: 96px; + border: 1px dotted transparent; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + md-tab[disabled] { + pointer-events: none; + cursor: default; } + md-tab:focus { + outline: none; } + md-tab md-tab-label { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + z-index: 100; + opacity: 1; + overflow: hidden; + text-overflow: ellipsis; } + +md-input-group label, .md-input-group label { + display: block; + font-size: 0.75em; } +md-input-group textarea, md-input-group input[type="text"], md-input-group input[type="password"], md-input-group input[type="datetime"], md-input-group input[type="datetime-local"], md-input-group input[type="date"], md-input-group input[type="month"], md-input-group input[type="time"], md-input-group input[type="week"], md-input-group input[type="number"], md-input-group input[type="email"], md-input-group input[type="url"], md-input-group input[type="search"], md-input-group input[type="tel"], md-input-group input[type="color"], .md-input-group textarea, .md-input-group input[type="text"], .md-input-group input[type="password"], .md-input-group input[type="datetime"], .md-input-group input[type="datetime-local"], .md-input-group input[type="date"], .md-input-group input[type="month"], .md-input-group input[type="time"], .md-input-group input[type="week"], .md-input-group input[type="number"], .md-input-group input[type="email"], .md-input-group input[type="url"], .md-input-group input[type="search"], .md-input-group input[type="tel"], .md-input-group input[type="color"] { + display: block; + border-width: 0 0 1px 0; + padding-top: 2px; + line-height: 26px; + padding-bottom: 1px; } + md-input-group textarea:focus, md-input-group input[type="text"]:focus, md-input-group input[type="password"]:focus, md-input-group input[type="datetime"]:focus, md-input-group input[type="datetime-local"]:focus, md-input-group input[type="date"]:focus, md-input-group input[type="month"]:focus, md-input-group input[type="time"]:focus, md-input-group input[type="week"]:focus, md-input-group input[type="number"]:focus, md-input-group input[type="email"]:focus, md-input-group input[type="url"]:focus, md-input-group input[type="search"]:focus, md-input-group input[type="tel"]:focus, md-input-group input[type="color"]:focus, .md-input-group textarea:focus, .md-input-group input[type="text"]:focus, .md-input-group input[type="password"]:focus, .md-input-group input[type="datetime"]:focus, .md-input-group input[type="datetime-local"]:focus, .md-input-group input[type="date"]:focus, .md-input-group input[type="month"]:focus, .md-input-group input[type="time"]:focus, .md-input-group input[type="week"]:focus, .md-input-group input[type="number"]:focus, .md-input-group input[type="email"]:focus, .md-input-group input[type="url"]:focus, .md-input-group input[type="search"]:focus, .md-input-group input[type="tel"]:focus, .md-input-group input[type="color"]:focus { + outline: 0; } +md-input-group input, md-input-group textarea, .md-input-group input, .md-input-group textarea { + background: none; } + +md-input-group, .md-input-group { + padding-bottom: 2px; + margin: 10px 0 8px 0; + position: relative; + display: block; } + md-input-group label, .md-input-group label { + font-size: 1em; + z-index: 1; + pointer-events: none; + -webkit-font-smoothing: antialiased; } + md-input-group label:hover, .md-input-group label:hover { + cursor: text; } + md-input-group label, .md-input-group label { + -webkit-transform: translate3d(0, 22px, 0); + transform: translate3d(0, 22px, 0); + -webkit-transform-origin: left center; + transform-origin: left center; + transition: all ease-out 150ms; + transition: all ease-out 150ms; } + md-input-group input, md-input-group textarea, .md-input-group input, .md-input-group textarea { + border-bottom-width: 1px; + transition: all ease-out 150ms; } + md-input-group.md-input-focused label, .md-input-group.md-input-focused label { + -webkit-transform: translate3d(0, 4px, 0) scale(0.75); + transform: translate3d(0, 4px, 0) scale(0.75); } + md-input-group.md-input-focused input, md-input-group.md-input-focused textarea, .md-input-group.md-input-focused input, .md-input-group.md-input-focused textarea { + border-bottom-width: 2px; } + md-input-group.md-input-focused input, .md-input-group.md-input-focused input { + padding-bottom: 0px; } + md-input-group.md-input-has-value label, .md-input-group.md-input-has-value label { + -webkit-transform: translate3d(0, 4px, 0) scale(0.75); + transform: translate3d(0, 4px, 0) scale(0.75); } + md-input-group.md-input-has-value:not(.md-input-focused) label, .md-input-group.md-input-has-value:not(.md-input-focused) label { + -webkit-transform: translate3d(0, 4px, 0) scale(0.75); + transform: translate3d(0, 4px, 0) scale(0.75); } + md-input-group[disabled] input, md-input-group[disabled] textarea, .md-input-group[disabled] input, .md-input-group[disabled] textarea { + border-bottom-width: 0px; } + md-input-group[disabled] input, md-input-group[disabled] textarea, .md-input-group[disabled] input, .md-input-group[disabled] textarea { + background-size: 3px 1px; + background-position: 0 bottom; + background-size: 2px 1px; + background-repeat: repeat-x; + pointer-events: none; } + md-input-group[disabled] label, .md-input-group[disabled] label { + -webkit-transform: translate3d(0, 4px, 0) scale(0.75); + transform: translate3d(0, 4px, 0) scale(0.75); } + md-input-group[disabled] *:not(.md-input-has-value) label, .md-input-group[disabled] *:not(.md-input-has-value) label { + -webkit-transform: translate3d(0, 22px, 0); + transform: translate3d(0, 22px, 0); + -webkit-transform-origin: left center; + transform-origin: left center; + transition: all ease-out 150ms; } + +md-toast { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + box-sizing: border-box; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + min-height: 48px; + padding-left: 24px; + padding-right: 24px; + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); + border-radius: 2px; + font-size: 14px; + cursor: default; + position: fixed; + max-width: 879px; + max-height: 40px; + height: 24px; + z-index: 9; + opacity: 1; + -webkit-transform: translate3d(0, 0, 0) rotateZ(0deg); + transform: translate3d(0, 0, 0) rotateZ(0deg); + transition: 0.2s linear; + transition-property: -webkit-transform, opacity; + transition-property: transform, opacity; + /* Transition differently when swiping */ } + md-toast.md-capsule { + border-radius: 24px; } + md-toast.md-swipeleft, md-toast.md-swiperight, md-toast.md-swipeup, md-toast.md-swipedown { + transition: 0.15s ease-out; } + md-toast.ng-enter { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + opacity: 0; } + md-toast.ng-enter.md-top { + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); } + md-toast.ng-enter.ng-enter-active { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; } + md-toast.ng-leave.ng-leave-active { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); } + md-toast.ng-leave.ng-leave-active.md-top { + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); } + md-toast.ng-leave.ng-leave-active.md-swipeleft { + -webkit-transform: translate3d(-100%, 0%, 0); + transform: translate3d(-100%, 0%, 0); } + md-toast.ng-leave.ng-leave-active.md-swiperight { + -webkit-transform: translate3d(100%, 0%, 0); + transform: translate3d(100%, 0%, 0); } + md-toast .md-action { + line-height: 19px; + padding-left: 24px; + cursor: pointer; + text-transform: uppercase; + float: right; } + +@media (max-width: 600px) { + md-toast { + left: 0; + right: 0; + width: 100%; + max-width: 100%; + min-width: 0; + border-radius: 0; + bottom: 0; } + md-toast.md-top { + bottom: auto; + top: 0; } + } + +@media (min-width: 600px) { + md-toast { + min-width: 288px; + /* + * When the toast doesn't take up the whole screen, + * make it rotate when the user swipes it away + */ } + md-toast.md-bottom { + bottom: 8px; } + md-toast.md-left { + left: 8px; } + md-toast.md-right { + right: 8px; } + md-toast.md-top { + top: 8px; } + md-toast.ng-leave.ng-leave-active.md-swipeleft { + -webkit-transform: translate3d(-100%, 25%, 0) rotateZ(-15deg); + transform: translate3d(-100%, 25%, 0) rotateZ(-15deg); } + md-toast.ng-leave.ng-leave-active.md-swiperight { + -webkit-transform: translate3d(100%, 25%, 0) rotateZ(15deg); + transform: translate3d(100%, 25%, 0) rotateZ(15deg); } + md-toast.ng-leave.ng-leave-active.md-top.md-swipeleft { + -webkit-transform: translate3d(-100%, 0, 0) rotateZ(-15deg); + transform: translate3d(-100%, 0, 0) rotateZ(-15deg); } + md-toast.ng-leave.ng-leave-active.md-top.md-swiperight { + -webkit-transform: translate3d(100%, 0, 0) rotateZ(15deg); + transform: translate3d(100%, 0, 0) rotateZ(15deg); } + } + +md-toolbar { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + position: relative; + z-index: 2; + font-size: 1.3em; + min-height: 64px; + width: 100%; } + md-toolbar.md-tall { + height: 192px; } + md-toolbar.md-medium-tall { + height: 88px; } + md-toolbar.md-medium-tall .md-toolbar-tools { + height: 48px; } + md-toolbar .md-indent { + margin-left: 64px; } + +.md-toolbar-tools { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + width: 100%; + height: 64px; + font-size: inherit; + font-weight: normal; + padding: 0 10px; + margin: 0; } + .md-toolbar-tools > * { + font-size: inherit; + margin: 0 8px; } + .md-toolbar-tools h2, .md-toolbar-tools h3 { + font-weight: normal; } + .md-toolbar-tools a { + color: inherit; + text-decoration: none; } + .md-toolbar-tools md-button { + font-size: 16px; } + +@-webkit-keyframes tooltipBackgroundShow { + 0% { + -webkit-transform: scale(0.2); + transform: scale(0.2); + opacity: 0.25; } + + 50% { + opacity: 1; } + + 100% { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 1; } } + +@keyframes tooltipBackgroundShow { + 0% { + -webkit-transform: scale(0.2); + transform: scale(0.2); + opacity: 0.25; } + + 50% { + opacity: 1; } + + 100% { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 1; } } + +@-webkit-keyframes tooltipBackgroundHide { + 0% { + opacity: 1; } + + 100% { + opacity: 0; } } + +@keyframes tooltipBackgroundHide { + 0% { + opacity: 1; } + + 100% { + opacity: 0; } } + +md-tooltip { + position: absolute; + font-size: 14px; + z-index: 6; + overflow: hidden; + pointer-events: none; + border-radius: 4px; + /** + * Depending on the tooltip's size as a multiple of 32 (set by JS), + * change the background's animation duration. + * The larger the tooltip, the less time the background should take to ripple outwards. + */ } + md-tooltip[md-direction="bottom"] { + -webkit-transform: translate3d(0, -30%, 0); + transform: translate3d(0, -30%, 0); + margin-top: 8px; } + md-tooltip[md-direction="top"] { + -webkit-transform: translate3d(0, 30%, 0); + transform: translate3d(0, 30%, 0); + margin-bottom: 8px; } + md-tooltip .md-background { + position: absolute; + left: 50%; + width: 256px; + height: 256px; + margin-left: -128px; + margin-top: -128px; + border-radius: 256px; + opacity: 0.25; + -webkit-transform: scale(0.2); + transform: scale(0.2); } + md-tooltip .md-content { + max-width: 240px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 8px; + background: transparent; + opacity: 0.3; + transition: inherit; } + md-tooltip.md-show, md-tooltip.md-hide { + transition: 0.2s ease-out; + transition-property: -webkit-transform, opacity; + transition-property: transform, opacity; } + md-tooltip.md-show { + pointer-events: auto; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); } + md-tooltip.md-show .md-background { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 1; + -webkit-animation: tooltipBackgroundShow linear; + animation: tooltipBackgroundShow linear; } + md-tooltip.md-show .md-content { + opacity: 0.99; } + md-tooltip.md-hide .md-background { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 0; + -webkit-animation: tooltipBackgroundHide 0.2s linear; + animation: tooltipBackgroundHide 0.2s linear; } + md-tooltip[width-32="1"].md-show .md-background { + -webkit-animation-duration: 900ms; + animation-duration: 900ms; } + md-tooltip[width-32="2"].md-show .md-background { + -webkit-animation-duration: 800ms; + animation-duration: 800ms; } + md-tooltip[width-32="3"].md-show .md-background { + -webkit-animation-duration: 700ms; + animation-duration: 700ms; } + md-tooltip[width-32="4"].md-show .md-background { + -webkit-animation-duration: 600ms; + animation-duration: 600ms; } + md-tooltip[width-32="5"].md-show .md-background { + -webkit-animation-duration: 500ms; + animation-duration: 500ms; } + md-tooltip[width-32="6"].md-show .md-background { + -webkit-animation-duration: 400ms; + animation-duration: 400ms; } + md-tooltip[width-32="7"].md-show .md-background { + -webkit-animation-duration: 300ms; + animation-duration: 300ms; } + md-tooltip[width-32="8"].md-show .md-background { + -webkit-animation-duration: 200ms; + animation-duration: 200ms; } + +.md-whiteframe-z1 { + box-shadow: 0px 2px 5px 0 rgba(0, 0, 0, 0.26); } + +.md-whiteframe-z2 { + box-shadow: 0px 8px 17px rgba(0, 0, 0, 0.2); } + +.md-whiteframe-z3 { + box-shadow: 0px 17px 50px rgba(0, 0, 0, 0.19); } + +.md-whiteframe-z4 { + box-shadow: 0px 16px 28px 0 rgba(0, 0, 0, 0.22); } + +.md-whiteframe-z5 { + box-shadow: 0px 27px 24px 0 rgba(0, 0, 0, 0.2); } + +md-backdrop.md-opaque.md-default-theme { + background-color: rgba(0, 0, 0, 0.3); } + +md-bottom-sheet.md-default-theme { + background-color: #fafafa; + border-top-color: #bdbdbd; } + md-bottom-sheet.md-default-theme.md-list md-item { + color: rgba(0, 0, 0, 0.54); } + md-bottom-sheet.md-default-theme .md-subheader { + background-color: #fafafa; } + md-bottom-sheet.md-default-theme .md-subheader { + color: rgba(0, 0, 0, 0.54); } + +md-button.md-default-theme { + border-radius: 3px; } + md-button.md-default-theme[disabled] { + background-color: rgba(158, 158, 158, 0.145) !important; + color: #9e9e9e !important; + fill: #9e9e9e !important; + cursor: auto; } + md-button.md-default-theme:not([disabled]):hover, md-button.md-default-theme:not([disabled]).focus { + background-color: rgba(158, 158, 158, 0.2); } + md-button.md-default-theme.md-fab { + border-radius: 50%; } + md-button.md-default-theme.md-primary { + color: #29b6f6; + fill: #29b6f6; } + md-button.md-default-theme.md-warn { + color: #e84e40; + fill: #e84e40; } + md-button.md-default-theme.md-raised, md-button.md-default-theme.md-fab { + background-color: rgba(158, 158, 158, 0.185); } + md-button.md-default-theme.md-raised:not([disabled]):hover, md-button.md-default-theme.md-raised:not([disabled]).focus, md-button.md-default-theme.md-fab:not([disabled]):hover, md-button.md-default-theme.md-fab:not([disabled]).focus { + background-color: rgba(158, 158, 158, 0.3); } + md-button.md-default-theme.md-raised.md-primary, md-button.md-default-theme.md-fab.md-primary { + color: white; + background-color: #03a9f4; } + md-button.md-default-theme.md-raised.md-primary:not([disabled]):hover, md-button.md-default-theme.md-raised.md-primary:not([disabled]).focus, md-button.md-default-theme.md-fab.md-primary:not([disabled]):hover, md-button.md-default-theme.md-fab.md-primary:not([disabled]).focus { + background-color: #039be5; } + md-button.md-default-theme.md-raised.md-warn, md-button.md-default-theme.md-fab.md-warn { + color: white; + background-color: #e51c23; } + md-button.md-default-theme.md-raised.md-warn:not([disabled]):hover, md-button.md-default-theme.md-raised.md-warn:not([disabled]).focus, md-button.md-default-theme.md-fab.md-warn:not([disabled]):hover, md-button.md-default-theme.md-fab.md-warn:not([disabled]).focus { + background-color: #d01716; } + +md-card.md-default-theme { + border-radius: 2px; } + md-card.md-default-theme .md-card-image { + border-radius: 2px 2px 0 0; } + +md-checkbox.md-default-theme .md-ripple { + color: #0a8f08; } +md-checkbox.md-default-theme.md-checked .md-ripple { + color: #757575; } +md-checkbox.md-default-theme .md-icon { + border-color: rgba(0, 0, 0, 0.54); } +md-checkbox.md-default-theme.md-checked .md-icon { + background-color: rgba(43, 175, 43, 0.87); } +md-checkbox.md-default-theme.md-checked .md-icon:after { + border-color: #eeeeee; } +md-checkbox.md-default-theme[disabled] .md-icon { + border-color: rgba(0, 0, 0, 0.26); } +md-checkbox.md-default-theme[disabled].md-checked .md-icon { + background-color: rgba(0, 0, 0, 0.26); } + +md-content.md-default-theme { + background-color: #ffffff; } + +md-dialog.md-default-theme { + border-radius: 4px; + background-color: #ffffff; } + md-dialog.md-default-theme.md-content-overflow .md-actions { + border-top-color: rgba(0, 0, 0, 0.12); } + +md-divider.md-default-theme { + border-top-color: rgba(0, 0, 0, 0.12); } + +md-progress-circular.md-default-theme { + background-color: transparent; } + md-progress-circular.md-default-theme .md-circle .md-mask .md-fill { + background-color: #03a9f4; } + md-progress-circular.md-default-theme .md-inset { + background-color: #ffffff; } + +md-progress-linear.md-default-theme .md-container { + background-color: #b3e5fc; } +md-progress-linear.md-default-theme .md-bar { + background-color: #03a9f4; } +md-progress-linear.md-default-theme[mode=buffer] .md-dashed:before { + background: radial-gradient(#b3e5fc 0%, #b3e5fc 16%, transparent 42%); } +md-progress-linear.md-default-theme[mode=buffer] .md-bar1 { + background-color: #b3e5fc; } + +md-radio-button.md-default-theme .md-container .md-ripple, md-switch.md-default-theme .md-switch-thumb .md-container .md-ripple { + color: #0a8f08; } +md-radio-button.md-default-theme .md-off, md-switch.md-default-theme .md-switch-thumb .md-off { + border-color: rgba(0, 0, 0, 0.54); } +md-radio-button.md-default-theme .md-on, md-switch.md-default-theme .md-switch-thumb .md-on { + background-color: rgba(43, 175, 43, 0.87); } +md-radio-button.md-default-theme.md-checked .md-off, md-switch.md-default-theme .md-switch-thumb.md-checked .md-off { + border-color: rgba(43, 175, 43, 0.87); } +md-radio-button.md-default-theme.md-checked .md-ink-ripple, md-switch.md-default-theme .md-switch-thumb.md-checked .md-ink-ripple { + color: rgba(43, 175, 43, 0.87); } + +md-radio-group.md-default-theme:focus { + border-color: rgba(0, 0, 0, 0.73); } + +md-slider.md-default-theme .md-track { + background-color: #c8c8c8; } +md-slider.md-default-theme .md-track-fill { + background-color: #03a9f4; } +md-slider.md-default-theme .md-thumb:after { + border-color: #03a9f4; + background-color: #03a9f4; } +md-slider.md-default-theme .md-sign { + background-color: #03a9f4; } + md-slider.md-default-theme .md-sign:after { + border-top-color: #03a9f4; } +md-slider.md-default-theme .md-thumb-text { + color: white; } +md-slider.md-default-theme .md-focus-thumb { + background-color: rgba(0, 0, 0, 0.54); } +md-slider.md-default-theme .md-focus-ring { + border-color: rgba(0, 0, 0, 0.12); } +md-slider.md-default-theme .md-disabled-thumb { + border-color: #ffffff; } +md-slider.md-default-theme.md-min .md-thumb:after { + background-color: #ffffff; } +md-slider.md-default-theme[disabled] .md-thumb:after { + border-color: #bdbdbd; } +md-slider.md-default-theme[disabled]:not(.md-min) .md-thumb:after { + background-color: #bdbdbd; } + +.md-subheader.md-default-theme { + color: rgba(0, 0, 0, 0.54); + background-color: #ffffff; } + .md-subheader.md-default-theme.md-primary { + color: #03a9f4; } + +md-switch.md-default-theme .md-switch-bar { + background-color: rgba(0, 0, 0, 0.54); } +md-switch.md-default-theme .md-switch-thumb:focus .md-label { + border: 1px dotted black; } + +md-tabs.md-default-theme .md-header { + background-color: #03a9f4; } +md-tabs.md-default-theme md-tabs-ink-bar { + background: #ffff85; } +md-tabs.md-default-theme md-tab { + color: #b3e5fc; } + md-tabs.md-default-theme md-tab.active { + color: white; } + md-tabs.md-default-theme md-tab[disabled] { + color: rgba(0, 0, 0, 0.12); } + md-tabs.md-default-theme md-tab:focus { + border-color: rgba(0, 0, 0, 0.73); } + md-tabs.md-default-theme md-tab .md-ripple-container { + color: #ffff85; } + +md-input-group.md-default-theme input, md-input-group.md-default-theme textarea { + text-shadow: none; } + md-input-group.md-default-theme input:-ms-input-placeholder, md-input-group.md-default-theme textarea:-ms-input-placeholder { + color: rgba(0, 0, 0, 0.26); } + md-input-group.md-default-theme input::-webkit-input-placeholder, md-input-group.md-default-theme textarea::-webkit-input-placeholder { + color: rgba(0, 0, 0, 0.26); } +md-input-group.md-default-theme label { + text-shadow: none; + color: rgba(0, 0, 0, 0.26); } +md-input-group.md-default-theme input, md-input-group.md-default-theme textarea { + color: rgba(0, 0, 0, 0.73); + border-color: rgba(0, 0, 0, 0.12); } +md-input-group.md-default-theme.md-input-focused input, md-input-group.md-default-theme.md-input-focused textarea { + border-color: #03a9f4; } +md-input-group.md-default-theme.md-input-focused label { + color: #03a9f4; } +md-input-group.md-default-theme.md-input-has-value:not(.md-input-focused) label { + color: rgba(0, 0, 0, 0.372); } +md-input-group.md-default-theme[disabled] input, md-input-group.md-default-theme[disabled] textarea { + border-bottom-color: rgba(0, 0, 0, 0.12); + color: rgba(0, 0, 0, 0.26); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.19) 0%, rgba(0, 0, 0, 0.19) 50%, rgba(0, 0, 0, 0) 0%); } + +md-toast.md-default-theme { + background-color: #323232; + color: white; } + md-toast.md-default-theme md-button { + color: white; } + md-toast.md-default-theme .md-action { + color: #40c4ff; } + +md-toolbar.md-default-theme { + background-color: #03a9f4; + color: white; } + +md-tooltip.md-default-theme { + color: #ffffff; } + md-tooltip.md-default-theme .md-background { + background-color: rgba(0, 0, 0, 0.52); } diff --git a/docdoku-dplm/ui/app/css/animations.css b/docdoku-dplm/ui/app/css/animations.css new file mode 100644 index 0000000000..7de51b290e --- /dev/null +++ b/docdoku-dplm/ui/app/css/animations.css @@ -0,0 +1,21 @@ +.slide-down { + display: block; + border-top:1px solid #eee; + transform:scale(1,1); +} + +.slide-down.ng-enter, .slide-down.ng-leave { + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; +} + +.slide-down.ng-enter, +.slide-down.ng-leave.ng-leave-active { + opacity:0; + transform:scale(1,0); +} + +.slide-down.ng-leave, +.slide-down.ng-enter.ng-enter-active { + opacity:1; + transform:scale(1,1); +} diff --git a/docdoku-dplm/ui/app/css/buttons.css b/docdoku-dplm/ui/app/css/buttons.css new file mode 100644 index 0000000000..ed589a938f --- /dev/null +++ b/docdoku-dplm/ui/app/css/buttons.css @@ -0,0 +1,3 @@ +md-button.md-margin{ + margin: 2px; +} \ No newline at end of file diff --git a/docdoku-dplm/ui/app/css/droppables.css b/docdoku-dplm/ui/app/css/droppables.css new file mode 100644 index 0000000000..f70781f331 --- /dev/null +++ b/docdoku-dplm/ui/app/css/droppables.css @@ -0,0 +1,7 @@ +.dropzone{ + transition:background-color 0.2s linear; + background-color: #F5F5F5; +} +.dropzone-selected{ + background-color: #D9D9D9; +} \ No newline at end of file diff --git a/docdoku-dplm/ui/app/css/fixes.css b/docdoku-dplm/ui/app/css/fixes.css new file mode 100644 index 0000000000..89fb8cd923 --- /dev/null +++ b/docdoku-dplm/ui/app/css/fixes.css @@ -0,0 +1,6 @@ +md-tooltip{ + z-index: 10; +} +md-backdrop:not(.md-sidenav-backdrop){ + z-index: 10; +} \ No newline at end of file diff --git a/docdoku-dplm/ui/app/css/folders.css b/docdoku-dplm/ui/app/css/folders.css new file mode 100644 index 0000000000..2ed39fcd85 --- /dev/null +++ b/docdoku-dplm/ui/app/css/folders.css @@ -0,0 +1,9 @@ +.path{} +.path.selected{ + color:greenyellow; +} + +.sub-folder{ + color:#999; + font-size: 90%; +} \ No newline at end of file diff --git a/docdoku-dplm/ui/app/css/fonts/0xES5Sl_v6oyT7dAKuoni4bN6UDyHWBl620a-IRfuBk.woff b/docdoku-dplm/ui/app/css/fonts/0xES5Sl_v6oyT7dAKuoni4bN6UDyHWBl620a-IRfuBk.woff new file mode 100644 index 0000000000..3566a16ef4 Binary files /dev/null and b/docdoku-dplm/ui/app/css/fonts/0xES5Sl_v6oyT7dAKuoni4bN6UDyHWBl620a-IRfuBk.woff differ diff --git a/docdoku-dplm/ui/app/css/fonts/5SAvdU0uYYlH8OURAykt5wRV2F9RPTaqyJ4QibDfkzM.woff b/docdoku-dplm/ui/app/css/fonts/5SAvdU0uYYlH8OURAykt5wRV2F9RPTaqyJ4QibDfkzM.woff new file mode 100644 index 0000000000..f7ec420ceb Binary files /dev/null and b/docdoku-dplm/ui/app/css/fonts/5SAvdU0uYYlH8OURAykt5wRV2F9RPTaqyJ4QibDfkzM.woff differ diff --git a/docdoku-dplm/ui/app/css/fonts/er-TIW55l9KWsTS1x9bTftkZXW4sYc4BjuAIFc1SXII.woff b/docdoku-dplm/ui/app/css/fonts/er-TIW55l9KWsTS1x9bTftkZXW4sYc4BjuAIFc1SXII.woff new file mode 100644 index 0000000000..ebfde73d7f Binary files /dev/null and b/docdoku-dplm/ui/app/css/fonts/er-TIW55l9KWsTS1x9bTftkZXW4sYc4BjuAIFc1SXII.woff differ diff --git a/docdoku-dplm/ui/app/css/fonts/hope9NW9iJ5hh8P5PM_EAyeJLMOzE6CCkidNEpZOseY.woff b/docdoku-dplm/ui/app/css/fonts/hope9NW9iJ5hh8P5PM_EAyeJLMOzE6CCkidNEpZOseY.woff new file mode 100644 index 0000000000..28b6650b98 Binary files /dev/null and b/docdoku-dplm/ui/app/css/fonts/hope9NW9iJ5hh8P5PM_EAyeJLMOzE6CCkidNEpZOseY.woff differ diff --git a/docdoku-dplm/ui/app/css/fonts/u0_CMoUf3y3-4Ss4ci-VwR_xHqYgAV9Bl_ZQbYUxnQU.woff b/docdoku-dplm/ui/app/css/fonts/u0_CMoUf3y3-4Ss4ci-VwR_xHqYgAV9Bl_ZQbYUxnQU.woff new file mode 100644 index 0000000000..b0f1fbe883 Binary files /dev/null and b/docdoku-dplm/ui/app/css/fonts/u0_CMoUf3y3-4Ss4ci-VwR_xHqYgAV9Bl_ZQbYUxnQU.woff differ diff --git a/docdoku-dplm/ui/app/css/fonts/u0_CMoUf3y3-4Ss4ci-VwSqHEX2q--o2so14pIEl08w.woff b/docdoku-dplm/ui/app/css/fonts/u0_CMoUf3y3-4Ss4ci-VwSqHEX2q--o2so14pIEl08w.woff new file mode 100644 index 0000000000..327427fcae Binary files /dev/null and b/docdoku-dplm/ui/app/css/fonts/u0_CMoUf3y3-4Ss4ci-VwSqHEX2q--o2so14pIEl08w.woff differ diff --git a/docdoku-dplm/ui/app/css/fonts/u0_CMoUf3y3-4Ss4ci-VwTqR_3kx9_hJXbbyU8S6IN0.woff b/docdoku-dplm/ui/app/css/fonts/u0_CMoUf3y3-4Ss4ci-VwTqR_3kx9_hJXbbyU8S6IN0.woff new file mode 100644 index 0000000000..e59ed9bec7 Binary files /dev/null and b/docdoku-dplm/ui/app/css/fonts/u0_CMoUf3y3-4Ss4ci-VwTqR_3kx9_hJXbbyU8S6IN0.woff differ diff --git a/docdoku-dplm/ui/app/css/fonts/u0_CMoUf3y3-4Ss4ci-VwUExzZ44ka2Lr5i-x5aWr0E.woff b/docdoku-dplm/ui/app/css/fonts/u0_CMoUf3y3-4Ss4ci-VwUExzZ44ka2Lr5i-x5aWr0E.woff new file mode 100644 index 0000000000..6e8eb01528 Binary files /dev/null and b/docdoku-dplm/ui/app/css/fonts/u0_CMoUf3y3-4Ss4ci-VwUExzZ44ka2Lr5i-x5aWr0E.woff differ diff --git a/docdoku-dplm/ui/app/css/fullscreen-wrapper.css b/docdoku-dplm/ui/app/css/fullscreen-wrapper.css new file mode 100644 index 0000000000..e680897f00 --- /dev/null +++ b/docdoku-dplm/ui/app/css/fullscreen-wrapper.css @@ -0,0 +1,14 @@ +.fullscreen-wrapper{ + position: absolute; + top: 0; + left: 0; + right: 0px; + bottom: 0; + overflow: auto; +} +.fullscreen-wrapper.offset-toolbar{ + top: 64px; +} +.fullscreen-wrapper.offset-2-toolbar{ + top: 154px; +} diff --git a/docdoku-dplm/ui/app/css/general.css b/docdoku-dplm/ui/app/css/general.css new file mode 100644 index 0000000000..224efe9b0f --- /dev/null +++ b/docdoku-dplm/ui/app/css/general.css @@ -0,0 +1,12 @@ +#brand-icon{ + width: 36px; + height:36px; +} + +.right{ + float:right; +} + +h3{ + width: 100%; +} \ No newline at end of file diff --git a/docdoku-dplm/ui/app/css/inputs.css b/docdoku-dplm/ui/app/css/inputs.css new file mode 100644 index 0000000000..0d3fffd23b --- /dev/null +++ b/docdoku-dplm/ui/app/css/inputs.css @@ -0,0 +1,67 @@ +.hide-input [type=file] { + display: none; +} +.file-input [type=file] { + opacity: 0; + cursor: pointer; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; +} + +.menu-file-input { + float: right; +} +.menu-file-input-wrapper{ + position: relative; +} +.file-input-container{ + position: absolute; + right: 0; + top: 0; +} +.file-input-container md-button.file-input { + float: right; + display: inline-block; + cursor: pointer; + font-size:12px; +} +.file-input-container md-button.file-input *{ + cursor: pointer; +} +select{ + background: transparent; + border: none; + color: inherit; +} + +.row md-input-group, +.row md-checkbox, +.row md-radio-button, +.row md-button, +.row select, +.row label { + font-size: inherit; + margin-right: 8px; + display: inline-block; +} +.row .label-margin{ + padding: 0 6px; + margin: 15px 15px 15px 0; +} +.cursor{ + cursor: pointer; +} + +md-radio-group{ + border:none; +} + +h3{ + width: 100%; +} +.right{ + float:right; +} \ No newline at end of file diff --git a/docdoku-dplm/ui/app/css/lists.css b/docdoku-dplm/ui/app/css/lists.css new file mode 100644 index 0000000000..f398b21ea6 --- /dev/null +++ b/docdoku-dplm/ui/app/css/lists.css @@ -0,0 +1,101 @@ +.md-tile-content h3, .md-tile-content h4{ + cursor: pointer; +} +.md-tile-content.small{ + padding: 6px; +} +.md-tile-left{ + margin-right: 0; +} +.md-tile-left.md-tile-top{ + margin-top: 20px; +} +.md-tile-left.small{ + min-width:32px; +} + +.opened{ + background: #ECECEC; + border-radius: 4px; +} +document-actions, +part-actions, +file-unknown-actions, +file-document-actions, +file-part-actions{ + padding-top: 18px; +} + +.file{ + width: 100%; +} +.clickable{ + cursor: pointer; +} +small.subtitle{ + color: #757575; +} +.subtitle{ + color: #02587F; +} +md-sidenav .subtitle{ + color: rgba(0, 0, 0, 0.729412); + font-weight: normal; +} + +.subtitle.selected{ + font-weight: bold; + color: #02587F; +} + +[ng-controller="PartController"] h3{ + overflow: hidden; + text-overflow: ellipsis; +} + +md-sidenav h3:first-of-type{ + margin-top: 0; +} + +.menu-list.hover{ + border: 1px dashed green; +} +.menu-list{ + border: 1px dashed white; + max-height: 150px; + transition: all 1s linear; +} +.menu-list.expanded{ + max-height: initial; +} +.list-toggle{ + transition: all 0.5s linear; + text-align: center; + cursor: pointer; + width: 100%; + color: #999; +} +.list-toggle:hover{ + color: #555; +} +.list-toggle.expanded{ + color:blue; +} + +.empty-list{ + width: 100%; + text-align: center; + padding: 50px 0; +} + +ul.file-list{ + margin: 12px; + padding: 0; + font-size: small; + color: #757575; +} +ul.file-list li{ + margin: 0; + padding: 0; + list-style: none; +} \ No newline at end of file diff --git a/docdoku-dplm/ui/app/css/model-viewer.css b/docdoku-dplm/ui/app/css/model-viewer.css new file mode 100644 index 0000000000..5b808ec13d --- /dev/null +++ b/docdoku-dplm/ui/app/css/model-viewer.css @@ -0,0 +1,7 @@ +[model-viewer]{ + margin: 8px; +} +[model-viewer] canvas{ + /*border-radius: 4px; + box-shadow:0 2px 5px 0 rgba(0, 0, 0, 0.26);*/ +} \ No newline at end of file diff --git a/docdoku-dplm/ui/app/css/output.css b/docdoku-dplm/ui/app/css/output.css new file mode 100644 index 0000000000..4b21fe128f --- /dev/null +++ b/docdoku-dplm/ui/app/css/output.css @@ -0,0 +1,14 @@ +.output{ + margin-top: 12px; +} + +.output span{ + display: block; + font-size: smaller; +} +.output-info{ + color: #02587F; +} +.output-error{ + color: #e84e40; +} \ No newline at end of file diff --git a/docdoku-dplm/ui/app/css/scrollbars.css b/docdoku-dplm/ui/app/css/scrollbars.css new file mode 100644 index 0000000000..bb04d54261 --- /dev/null +++ b/docdoku-dplm/ui/app/css/scrollbars.css @@ -0,0 +1,22 @@ +::-webkit-scrollbar { + width: 4px; + height: 4px; +} +::-webkit-scrollbar-button { + width: 8px; + height:5px; +} +::-webkit-scrollbar-track { + background:#eee; + border: none; + box-shadow: 0px 0px 3px #dfdfdf inset; + border-radius:10px; +} +::-webkit-scrollbar-thumb { + background:#999; + border: thin solid gray; + border-radius:10px; +} +::-webkit-scrollbar-thumb:hover { + background:#7d7d7d; +} diff --git a/docdoku-dplm/ui/app/css/states.css b/docdoku-dplm/ui/app/css/states.css new file mode 100644 index 0000000000..66482d23c0 --- /dev/null +++ b/docdoku-dplm/ui/app/css/states.css @@ -0,0 +1,13 @@ +i.file-sync{ + color:#2E7D32; +} +i.file-modified{ + color:#FFC107; +} +i.file-notSync{ + color:#999; +} + +i.favorite{ + color:#FFC107; +} \ No newline at end of file diff --git a/docdoku-dplm/ui/app/css/tabs.css b/docdoku-dplm/ui/app/css/tabs.css new file mode 100644 index 0000000000..887fd2cd55 --- /dev/null +++ b/docdoku-dplm/ui/app/css/tabs.css @@ -0,0 +1,28 @@ +.tabs{ + width:100%; + height: 42px; +} + +.tabs.default{ + background: #03a9f4; +} +.tabs.blue{ + background: #5677fc; +} +.tabs > div{ + text-align: center; +} +.tabs.default > div.selected{ + border-bottom:2px solid #ffff85; +} +.tabs.blue > div.selected{ + border-bottom:2px solid #6bff85; +} +.tabs > div > a { + display: block; + width: 100%; + height: 100%; + text-decoration: none; + color: white; + line-height: 36px; +} \ No newline at end of file diff --git a/docdoku-dplm/ui/app/img/icon.png b/docdoku-dplm/ui/app/img/icon.png new file mode 100644 index 0000000000..ee4245542e Binary files /dev/null and b/docdoku-dplm/ui/app/img/icon.png differ diff --git a/docdoku-dplm/ui/app/index.html b/docdoku-dplm/ui/app/index.html new file mode 100644 index 0000000000..6cc83936f5 --- /dev/null +++ b/docdoku-dplm/ui/app/index.html @@ -0,0 +1,95 @@ +<html ng-app="dplm" ng-controller="AppCtrl" > +<head> + <title ng-bind="title"> + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/app.js b/docdoku-dplm/ui/app/js/app.js new file mode 100644 index 0000000000..bc0624bfc9 --- /dev/null +++ b/docdoku-dplm/ui/app/js/app.js @@ -0,0 +1,89 @@ +(function(){ + + 'use strict'; + + process.on('uncaughtException', function (e) { + console.log(e); + }); + + angular.module('dplm', [ + + // Dependencies + 'ngMaterial', + 'ngAnimate', + 'ngRoute', + 'pascalprecht.translate', + 'uuid4', + 'ngDragDrop', + 'ngAnimate', + 'ngAria', + + // Templates + 'dplm.templates', + + // Routes + 'dplm.home', + 'dplm.settings', + 'dplm.workspace', + + 'dplm.folder', + // Components + 'dplm.services.cli', + 'dplm.services.configuration', + 'dplm.services.translations', + 'dplm.services.notification', + 'dplm.services.folders', + 'dplm.services.workspaces', + 'dplm.services.confirm', + 'dplm.services.prompt', + 'dplm.services.output', + + 'dplm.services.3d', + 'dplm.directives.filechange', + 'dplm.directives.scrollend', + 'dplm.directives.filedrop', + 'dplm.filters.fileshortname', + 'dplm.filters.timeago', + 'dplm.filters.last', + 'dplm.filters.join', + 'dplm.filters.humanreadablesize', + + 'dplm.contextmenu', + 'dplm.menu' + + ]) + + .config(function ($routeProvider) { + $routeProvider.otherwise('/'); + }) + + .controller('AppCtrl', function ($scope, $location, $mdSidenav, $filter, NotificationService, ConfigurationService, CliService, WorkspaceService, FolderService) { + + $scope.title = 'DocDoku DPLM'; + + $scope.openMenu = function () { + $mdSidenav('menu').open(); + }; + + $scope.configuration = ConfigurationService.configuration; + $scope.workspaces = WorkspaceService.workspaces; + $scope.folders = FolderService.folders; + + $scope.addFolder = function ($event, files) { + if (files && files.length === 1) { + FolderService.add(files[0].path); + } + }; + + $scope.isSelectedWorkspace = function(workspace){ + var currentParts = $location.path().split('/'); + return currentParts[1] === 'workspace' && workspace === currentParts[2]; + }; + + $scope.isSelectedFolder = function(folderUuid) { + var currentParts = $location.path().split('/'); + return currentParts[1] === 'folder' && folderUuid === currentParts[2]; + }; + }); + +})(); \ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/components/3d/controls/OrbitControls.js b/docdoku-dplm/ui/app/js/components/3d/controls/OrbitControls.js new file mode 100644 index 0000000000..fc5ca09245 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/3d/controls/OrbitControls.js @@ -0,0 +1,640 @@ +/** + * @author qiao / https://github.com/qiao + * @author mrdoob / http://mrdoob.com + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + * @author erich666 / http://erichaines.com + */ +/*global THREE, console */ + +// This set of controls performs orbiting, dollying (zooming), and panning. It maintains +// the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is +// supported. +// +// Orbit - left mouse / touch: one finger move +// Zoom - middle mouse, or mousewheel / touch: two finger spread or squish +// Pan - right mouse, or arrow keys / touch: three finter swipe +// +// This is a drop-in replacement for (most) TrackballControls used in examples. +// That is, include this js file and wherever you see: +// controls = new THREE.TrackballControls( camera ); +// controls.target.z = 150; +// Simple substitute "OrbitControls" and the control should work as-is. + +THREE.OrbitControls = function ( object, domElement ) { + + this.object = object; + this.domElement = ( domElement !== undefined ) ? domElement : document; + + // API + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the control orbits around + // and where it pans with respect to. + this.target = new THREE.Vector3(); + + // center is old, deprecated; use "target" instead + this.center = this.target; + + // This option actually enables dollying in and out; left as "zoom" for + // backwards compatibility + this.noZoom = false; + this.zoomSpeed = 1.0; + + // Limits to how far you can dolly in and out + this.minDistance = 0; + this.maxDistance = Infinity; + + // Set to true to disable this control + this.noRotate = false; + this.rotateSpeed = 1.0; + + // Set to true to disable this control + this.noPan = false; + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // Set to true to disable use of the keys + this.noKeys = false; + + // The four arrow keys + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; + + //////////// + // internals + + var scope = this; + + var EPS = 0.000001; + + var rotateStart = new THREE.Vector2(); + var rotateEnd = new THREE.Vector2(); + var rotateDelta = new THREE.Vector2(); + + var panStart = new THREE.Vector2(); + var panEnd = new THREE.Vector2(); + var panDelta = new THREE.Vector2(); + var panOffset = new THREE.Vector3(); + + var offset = new THREE.Vector3(); + + var dollyStart = new THREE.Vector2(); + var dollyEnd = new THREE.Vector2(); + var dollyDelta = new THREE.Vector2(); + + var phiDelta = 0; + var thetaDelta = 0; + var scale = 1; + var pan = new THREE.Vector3(); + + var lastPosition = new THREE.Vector3(); + + var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; + + var state = STATE.NONE; + + // for reset + + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + + // so camera.up is the orbit axis + + var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); + var quatInverse = quat.clone().inverse(); + + // events + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start'}; + var endEvent = { type: 'end'}; + + this.rotateLeft = function ( angle ) { + + if ( angle === undefined ) { + + angle = getAutoRotationAngle(); + + } + + thetaDelta -= angle; + + }; + + this.rotateUp = function ( angle ) { + + if ( angle === undefined ) { + + angle = getAutoRotationAngle(); + + } + + phiDelta -= angle; + + }; + + // pass in distance in world space to move left + this.panLeft = function ( distance ) { + + var te = this.object.matrix.elements; + + // get X column of matrix + panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); + panOffset.multiplyScalar( - distance ); + + pan.add( panOffset ); + + }; + + // pass in distance in world space to move up + this.panUp = function ( distance ) { + + var te = this.object.matrix.elements; + + // get Y column of matrix + panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); + panOffset.multiplyScalar( distance ); + + pan.add( panOffset ); + + }; + + // pass in x,y of change desired in pixel space, + // right and down are positive + this.pan = function ( deltaX, deltaY ) { + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + if ( scope.object.fov !== undefined ) { + + // perspective + var position = scope.object.position; + var offset = position.clone().sub( scope.target ); + var targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we actually don't use screenWidth, since perspective camera is fixed to screen height + scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); + scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); + + } else if ( scope.object.top !== undefined ) { + + // orthographic + scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); + scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); + + } else { + + // camera neither orthographic or perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + + } + + }; + + this.dollyIn = function ( dollyScale ) { + + if ( dollyScale === undefined ) { + + dollyScale = getZoomScale(); + + } + + scale /= dollyScale; + + }; + + this.dollyOut = function ( dollyScale ) { + + if ( dollyScale === undefined ) { + + dollyScale = getZoomScale(); + + } + + scale *= dollyScale; + + }; + + this.update = function () { + + var position = this.object.position; + + offset.copy( position ).sub( this.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + + var theta = Math.atan2( offset.x, offset.z ); + + // angle from y-axis + + var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); + + if ( this.autoRotate ) { + + this.rotateLeft( getAutoRotationAngle() ); + + } + + theta += thetaDelta; + phi += phiDelta; + + // restrict phi to be between desired limits + phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); + + // restrict phi to be betwee EPS and PI-EPS + phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); + + var radius = offset.length() * scale; + + // restrict radius to be between desired limits + radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); + + // move target to panned location + this.target.add( pan ); + + offset.x = radius * Math.sin( phi ) * Math.sin( theta ); + offset.y = radius * Math.cos( phi ); + offset.z = radius * Math.sin( phi ) * Math.cos( theta ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( this.target ).add( offset ); + + this.object.lookAt( this.target ); + + thetaDelta = 0; + phiDelta = 0; + scale = 1; + pan.set( 0, 0, 0 ); + + if ( lastPosition.distanceToSquared( this.object.position ) > EPS ) { + + this.dispatchEvent( changeEvent ); + + lastPosition.copy( this.object.position ); + + } + + }; + + + this.reset = function () { + + state = STATE.NONE; + + this.target.copy( this.target0 ); + this.object.position.copy( this.position0 ); + + this.update(); + + }; + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function onMouseDown( event ) { + + if ( scope.enabled === false ) return; + event.preventDefault(); + + if ( event.button === 0 ) { + if ( scope.noRotate === true ) return; + + state = STATE.ROTATE; + + rotateStart.set( event.clientX, event.clientY ); + + } else if ( event.button === 1 ) { + if ( scope.noZoom === true ) return; + + state = STATE.DOLLY; + + dollyStart.set( event.clientX, event.clientY ); + + } else if ( event.button === 2 ) { + if ( scope.noPan === true ) return; + + state = STATE.PAN; + + panStart.set( event.clientX, event.clientY ); + + } + + scope.domElement.addEventListener( 'mousemove', onMouseMove, false ); + scope.domElement.addEventListener( 'mouseup', onMouseUp, false ); + scope.dispatchEvent( startEvent ); + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + if ( state === STATE.ROTATE ) { + + if ( scope.noRotate === true ) return; + + rotateEnd.set( event.clientX, event.clientY ); + rotateDelta.subVectors( rotateEnd, rotateStart ); + + // rotating across whole screen goes 360 degrees around + scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); + + // rotating up and down along whole screen attempts to go 360, but limited to 180 + scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); + + rotateStart.copy( rotateEnd ); + + } else if ( state === STATE.DOLLY ) { + + if ( scope.noZoom === true ) return; + + dollyEnd.set( event.clientX, event.clientY ); + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + scope.dollyIn(); + + } else { + + scope.dollyOut(); + + } + + dollyStart.copy( dollyEnd ); + + } else if ( state === STATE.PAN ) { + + if ( scope.noPan === true ) return; + + panEnd.set( event.clientX, event.clientY ); + panDelta.subVectors( panEnd, panStart ); + + scope.pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + scope.update(); + + } + + function onMouseUp( /* event */ ) { + + if ( scope.enabled === false ) return; + + scope.domElement.removeEventListener( 'mousemove', onMouseMove, false ); + scope.domElement.removeEventListener( 'mouseup', onMouseUp, false ); + scope.dispatchEvent( endEvent ); + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.noZoom === true ) return; + + event.preventDefault(); + event.stopPropagation(); + + var delta = 0; + + if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 + + delta = event.wheelDelta; + + } else if ( event.detail !== undefined ) { // Firefox + + delta = - event.detail; + + } + + if ( delta > 0 ) { + + scope.dollyOut(); + + } else { + + scope.dollyIn(); + + } + + scope.update(); + scope.dispatchEvent( startEvent ); + scope.dispatchEvent( endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return; + + switch ( event.keyCode ) { + + case scope.keys.UP: + scope.pan( 0, scope.keyPanSpeed ); + scope.update(); + break; + + case scope.keys.BOTTOM: + scope.pan( 0, - scope.keyPanSpeed ); + scope.update(); + break; + + case scope.keys.LEFT: + scope.pan( scope.keyPanSpeed, 0 ); + scope.update(); + break; + + case scope.keys.RIGHT: + scope.pan( - scope.keyPanSpeed, 0 ); + scope.update(); + break; + + } + + } + + function touchstart( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + + if ( scope.noRotate === true ) return; + + state = STATE.TOUCH_ROTATE; + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + case 2: // two-fingered touch: dolly + + if ( scope.noZoom === true ) return; + + state = STATE.TOUCH_DOLLY; + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + var distance = Math.sqrt( dx * dx + dy * dy ); + dollyStart.set( 0, distance ); + break; + + case 3: // three-fingered touch: pan + + if ( scope.noPan === true ) return; + + state = STATE.TOUCH_PAN; + + panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + default: + + state = STATE.NONE; + + } + + scope.dispatchEvent( startEvent ); + + } + + function touchmove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + + if ( scope.noRotate === true ) return; + if ( state !== STATE.TOUCH_ROTATE ) return; + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + rotateDelta.subVectors( rotateEnd, rotateStart ); + + // rotating across whole screen goes 360 degrees around + scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); + // rotating up and down along whole screen attempts to go 360, but limited to 180 + scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + break; + + case 2: // two-fingered touch: dolly + + if ( scope.noZoom === true ) return; + if ( state !== STATE.TOUCH_DOLLY ) return; + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + scope.dollyOut(); + + } else { + + scope.dollyIn(); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + break; + + case 3: // three-fingered touch: pan + + if ( scope.noPan === true ) return; + if ( state !== STATE.TOUCH_PAN ) return; + + panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + panDelta.subVectors( panEnd, panStart ); + + scope.pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + break; + + default: + + state = STATE.NONE; + + } + + } + + function touchend( /* event */ ) { + + if ( scope.enabled === false ) return; + + scope.dispatchEvent( endEvent ); + state = STATE.NONE; + + } + + this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); + this.domElement.addEventListener( 'mousedown', onMouseDown, false ); + this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); + this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox + + this.domElement.addEventListener( 'touchstart', touchstart, false ); + this.domElement.addEventListener( 'touchend', touchend, false ); + this.domElement.addEventListener( 'touchmove', touchmove, false ); + + window.addEventListener( 'keydown', onKeyDown, false ); + + // force an update at start + this.update(); + +}; + +THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); diff --git a/docdoku-dplm/ui/app/js/components/3d/controls/TrackballControls.js b/docdoku-dplm/ui/app/js/components/3d/controls/TrackballControls.js new file mode 100644 index 0000000000..2c1bfaa5a5 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/3d/controls/TrackballControls.js @@ -0,0 +1,599 @@ +/** + * @author Eberhard Graether / http://egraether.com/ + * @author Mark Lundin / http://mark-lundin.com + */ + +THREE.TrackballControls = function ( object, domElement ) { + + var _this = this; + var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 }; + + this.object = object; + this.domElement = ( domElement !== undefined ) ? domElement : document; + + // API + + this.enabled = true; + + this.screen = { left: 0, top: 0, width: 0, height: 0 }; + + this.rotateSpeed = 1.0; + this.zoomSpeed = 1.2; + this.panSpeed = 0.3; + + this.noRotate = false; + this.noZoom = false; + this.noPan = false; + this.noRoll = false; + + this.staticMoving = false; + this.dynamicDampingFactor = 0.2; + + this.minDistance = 0; + this.maxDistance = Infinity; + + this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; + + // internals + + this.target = new THREE.Vector3(); + + var EPS = 0.000001; + + var lastPosition = new THREE.Vector3(); + + var _state = STATE.NONE, + _prevState = STATE.NONE, + + _eye = new THREE.Vector3(), + + _rotateStart = new THREE.Vector3(), + _rotateEnd = new THREE.Vector3(), + + _zoomStart = new THREE.Vector2(), + _zoomEnd = new THREE.Vector2(), + + _touchZoomDistanceStart = 0, + _touchZoomDistanceEnd = 0, + + _panStart = new THREE.Vector2(), + _panEnd = new THREE.Vector2(); + + // for reset + + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.up0 = this.object.up.clone(); + + // events + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start'}; + var endEvent = { type: 'end'}; + + + // methods + + this.handleResize = function () { + + if ( this.domElement === document ) { + + this.screen.left = 0; + this.screen.top = 0; + this.screen.width = window.innerWidth; + this.screen.height = window.innerHeight; + + } else { + + var box = this.domElement.getBoundingClientRect(); + // adjustments come from similar code in the jquery offset() function + var d = this.domElement.ownerDocument.documentElement; + this.screen.left = box.left + window.pageXOffset - d.clientLeft; + this.screen.top = box.top + window.pageYOffset - d.clientTop; + this.screen.width = box.width; + this.screen.height = box.height; + + } + + }; + + this.handleEvent = function ( event ) { + + if ( typeof this[ event.type ] == 'function' ) { + + this[ event.type ]( event ); + + } + + }; + + this.getMouseOnScreen = function ( pageX, pageY, vector ) { + + return vector.set( + ( pageX - _this.screen.left ) / _this.screen.width, + ( pageY - _this.screen.top ) / _this.screen.height + ); + + }; + + this.getMouseProjectionOnBall = (function(){ + + var objectUp = new THREE.Vector3(), + mouseOnBall = new THREE.Vector3(); + + + return function ( pageX, pageY, projection ) { + + mouseOnBall.set( + ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / (_this.screen.width*.5), + ( _this.screen.height * 0.5 + _this.screen.top - pageY ) / (_this.screen.height*.5), + 0.0 + ); + + var length = mouseOnBall.length(); + + if ( _this.noRoll ) { + + if ( length < Math.SQRT1_2 ) { + + mouseOnBall.z = Math.sqrt( 1.0 - length*length ); + + } else { + + mouseOnBall.z = .5 / length; + + } + + } else if ( length > 1.0 ) { + + mouseOnBall.normalize(); + + } else { + + mouseOnBall.z = Math.sqrt( 1.0 - length * length ); + + } + + _eye.copy( _this.object.position ).sub( _this.target ); + + projection.copy( _this.object.up ).setLength( mouseOnBall.y ) + projection.add( objectUp.copy( _this.object.up ).cross( _eye ).setLength( mouseOnBall.x ) ); + projection.add( _eye.setLength( mouseOnBall.z ) ); + + return projection; + } + + }()); + + this.rotateCamera = (function(){ + + var axis = new THREE.Vector3(), + quaternion = new THREE.Quaternion(); + + + return function () { + + var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() ); + + if ( angle ) { + + axis.crossVectors( _rotateStart, _rotateEnd ).normalize(); + + angle *= _this.rotateSpeed; + + quaternion.setFromAxisAngle( axis, -angle ); + + _eye.applyQuaternion( quaternion ); + _this.object.up.applyQuaternion( quaternion ); + + _rotateEnd.applyQuaternion( quaternion ); + + if ( _this.staticMoving ) { + + _rotateStart.copy( _rotateEnd ); + + } else { + + quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) ); + _rotateStart.applyQuaternion( quaternion ); + + } + + } + } + + }()); + + this.zoomCamera = function () { + + if ( _state === STATE.TOUCH_ZOOM ) { + + var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; + _touchZoomDistanceStart = _touchZoomDistanceEnd; + _eye.multiplyScalar( factor ); + + } else { + + var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed; + + if ( factor !== 1.0 && factor > 0.0 ) { + + _eye.multiplyScalar( factor ); + + if ( _this.staticMoving ) { + + _zoomStart.copy( _zoomEnd ); + + } else { + + _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; + + } + + } + + } + + }; + + this.panCamera = (function(){ + + var mouseChange = new THREE.Vector2(), + objectUp = new THREE.Vector3(), + pan = new THREE.Vector3(); + + return function () { + + mouseChange.copy( _panEnd ).sub( _panStart ); + + if ( mouseChange.lengthSq() ) { + + mouseChange.multiplyScalar( _eye.length() * _this.panSpeed ); + + pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x ); + pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) ); + + _this.object.position.add( pan ); + _this.target.add( pan ); + + if ( _this.staticMoving ) { + + _panStart.copy( _panEnd ); + + } else { + + _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) ); + + } + + } + } + + }()); + + this.checkDistances = function () { + + if ( !_this.noZoom || !_this.noPan ) { + + if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) { + + _this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) ); + + } + + if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) { + + _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) ); + + } + + } + + }; + + this.update = function () { + + _eye.subVectors( _this.object.position, _this.target ); + + if ( !_this.noRotate ) { + + _this.rotateCamera(); + + } + + if ( !_this.noZoom ) { + + _this.zoomCamera(); + + } + + if ( !_this.noPan ) { + + _this.panCamera(); + + } + + _this.object.position.addVectors( _this.target, _eye ); + + _this.checkDistances(); + + _this.object.lookAt( _this.target ); + + if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) { + + _this.dispatchEvent( changeEvent ); + + lastPosition.copy( _this.object.position ); + + } + + }; + + this.reset = function () { + + _state = STATE.NONE; + _prevState = STATE.NONE; + + _this.target.copy( _this.target0 ); + _this.object.position.copy( _this.position0 ); + _this.object.up.copy( _this.up0 ); + + _eye.subVectors( _this.object.position, _this.target ); + + _this.object.lookAt( _this.target ); + + _this.dispatchEvent( changeEvent ); + + lastPosition.copy( _this.object.position ); + + }; + + // listeners + + function keydown( event ) { + + if ( _this.enabled === false ) return; + + window.removeEventListener( 'keydown', keydown ); + + _prevState = _state; + + if ( _state !== STATE.NONE ) { + + return; + + } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) { + + _state = STATE.ROTATE; + + } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) { + + _state = STATE.ZOOM; + + } else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) { + + _state = STATE.PAN; + + } + + } + + function keyup( event ) { + + if ( _this.enabled === false ) return; + + _state = _prevState; + + window.addEventListener( 'keydown', keydown, false ); + + } + + function mousedown( event ) { + + if ( _this.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + if ( _state === STATE.NONE ) { + + _state = event.button; + + } + + if ( _state === STATE.ROTATE && !_this.noRotate ) { + + _this.getMouseProjectionOnBall( event.pageX, event.pageY, _rotateStart ); + _rotateEnd.copy(_rotateStart) + + } else if ( _state === STATE.ZOOM && !_this.noZoom ) { + + _this.getMouseOnScreen( event.pageX, event.pageY, _zoomStart ); + _zoomEnd.copy(_zoomStart); + + } else if ( _state === STATE.PAN && !_this.noPan ) { + + _this.getMouseOnScreen( event.pageX, event.pageY, _panStart ); + _panEnd.copy(_panStart) + + } + + document.addEventListener( 'mousemove', mousemove, false ); + document.addEventListener( 'mouseup', mouseup, false ); + _this.dispatchEvent( startEvent ); + + + } + + function mousemove( event ) { + + if ( _this.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + if ( _state === STATE.ROTATE && !_this.noRotate ) { + + _this.getMouseProjectionOnBall( event.pageX, event.pageY, _rotateEnd ); + + } else if ( _state === STATE.ZOOM && !_this.noZoom ) { + + _this.getMouseOnScreen( event.pageX, event.pageY, _zoomEnd ); + + } else if ( _state === STATE.PAN && !_this.noPan ) { + + _this.getMouseOnScreen( event.pageX, event.pageY, _panEnd ); + + } + + } + + function mouseup( event ) { + + if ( _this.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + _state = STATE.NONE; + + document.removeEventListener( 'mousemove', mousemove ); + document.removeEventListener( 'mouseup', mouseup ); + _this.dispatchEvent( endEvent ); + + } + + function mousewheel( event ) { + + if ( _this.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + var delta = 0; + + if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9 + + delta = event.wheelDelta / 40; + + } else if ( event.detail ) { // Firefox + + delta = - event.detail / 3; + + } + + _zoomStart.y += delta * 0.01; + _this.dispatchEvent( startEvent ); + _this.dispatchEvent( endEvent ); + + } + + function touchstart( event ) { + + if ( _this.enabled === false ) return; + + switch ( event.touches.length ) { + + case 1: + _state = STATE.TOUCH_ROTATE; + _rotateEnd.copy( _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _rotateStart )); + break; + + case 2: + _state = STATE.TOUCH_ZOOM; + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); + break; + + case 3: + _state = STATE.TOUCH_PAN; + _panEnd.copy( _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _panStart )); + break; + + default: + _state = STATE.NONE; + + } + _this.dispatchEvent( startEvent ); + + + } + + function touchmove( event ) { + + if ( _this.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + switch ( event.touches.length ) { + + case 1: + _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _rotateEnd ); + break; + + case 2: + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ) + break; + + case 3: + _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _panEnd ); + break; + + default: + _state = STATE.NONE; + + } + + } + + function touchend( event ) { + + if ( _this.enabled === false ) return; + + switch ( event.touches.length ) { + + case 1: + _rotateStart.copy( _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _rotateEnd )); + break; + + case 2: + _touchZoomDistanceStart = _touchZoomDistanceEnd = 0; + break; + + case 3: + _panStart.copy( _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _panEnd )); + break; + + } + + _state = STATE.NONE; + _this.dispatchEvent( endEvent ); + + } + + this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); + + this.domElement.addEventListener( 'mousedown', mousedown, false ); + + this.domElement.addEventListener( 'mousewheel', mousewheel, false ); + this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox + + this.domElement.addEventListener( 'touchstart', touchstart, false ); + this.domElement.addEventListener( 'touchend', touchend, false ); + this.domElement.addEventListener( 'touchmove', touchmove, false ); + + window.addEventListener( 'keydown', keydown, false ); + window.addEventListener( 'keyup', keyup, false ); + + this.handleResize(); + + // force an update at start + this.update(); + +}; + +THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype ); diff --git a/docdoku-dplm/ui/app/js/components/3d/loaders/BinaryLoader.js b/docdoku-dplm/ui/app/js/components/3d/loaders/BinaryLoader.js new file mode 100644 index 0000000000..59d606ea0e --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/3d/loaders/BinaryLoader.js @@ -0,0 +1,750 @@ +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.BinaryLoader = function ( showStatus ) { + + THREE.Loader.call( this, showStatus ); + +}; + +THREE.BinaryLoader.prototype = Object.create( THREE.Loader.prototype ); + +// Load models generated by slim OBJ converter with BINARY option (converter_obj_three_slim.py -t binary) +// - binary models consist of two files: JS and BIN +// - parameters +// - url (required) +// - callback (required) +// - texturePath (optional: if not specified, textures will be assumed to be in the same folder as JS model file) +// - binaryPath (optional: if not specified, binary file will be assumed to be in the same folder as JS model file) + +THREE.BinaryLoader.prototype.load = function ( url, callback, texturePath, binaryPath ) { + + // todo: unify load API to for easier SceneLoader use + + texturePath = texturePath || this.extractUrlBase( url ); + binaryPath = binaryPath || this.extractUrlBase( url ); + + var callbackProgress = this.showProgress ? THREE.Loader.prototype.updateProgress : undefined; + + this.onLoadStart(); + + // #1 load JS part via web worker + + this.loadAjaxJSON( this, url, callback, texturePath, binaryPath, callbackProgress ); + +}; + +THREE.BinaryLoader.prototype.loadAjaxJSON = function ( context, url, callback, texturePath, binaryPath, callbackProgress ) { + + var xhr = new XMLHttpRequest(); + + texturePath = texturePath && ( typeof texturePath === "string" ) ? texturePath : this.extractUrlBase( url ); + binaryPath = binaryPath && ( typeof binaryPath === "string" ) ? binaryPath : this.extractUrlBase( url ); + + xhr.onreadystatechange = function () { + + if ( xhr.readyState == 4 ) { + + if ( xhr.status == 200 || xhr.status == 0 ) { + + var json = JSON.parse( xhr.responseText ); + context.loadAjaxBuffers( json, callback, binaryPath, texturePath, callbackProgress ); + + } else { + + console.error( "THREE.BinaryLoader: Couldn't load [" + url + "] [" + xhr.status + "]" ); + + } + + } + + }; + + xhr.open( "GET", url, true ); + xhr.send( null ); + +}; + +THREE.BinaryLoader.prototype.loadAjaxBuffers = function ( json, callback, binaryPath, texturePath, callbackProgress ) { + + var scope = this; + + var xhr = new XMLHttpRequest(), + url = binaryPath + json.buffers; + + xhr.addEventListener( 'load', function ( event ) { + + var buffer = xhr.response; + + if ( buffer === undefined ) { + + // IEWEBGL needs this + buffer = ( new Uint8Array( xhr.responseBody ) ).buffer; + + } + + if ( buffer.byteLength == 0 ) { // iOS and other XMLHttpRequest level 1 + + var buffer = new ArrayBuffer( xhr.responseText.length ); + + var bufView = new Uint8Array( buffer ); + + for ( var i = 0, l = xhr.responseText.length; i < l; i ++ ) { + + bufView[ i ] = xhr.responseText.charCodeAt( i ) & 0xff; + + } + + } + + scope.createBinModel( buffer, callback, texturePath, json.materials ); + + }, false ); + + if ( callbackProgress !== undefined ) { + + xhr.addEventListener( 'progress', function ( event ) { + + if ( event.lengthComputable ) { + + callbackProgress( event ); + + } + + }, false ); + + } + + xhr.addEventListener( 'error', function ( event ) { + + console.error( "THREE.BinaryLoader: Couldn't load [" + url + "] [" + xhr.status + "]" ); + + }, false ); + + + xhr.open( "GET", url, true ); + xhr.responseType = "arraybuffer"; + if ( xhr.overrideMimeType ) xhr.overrideMimeType( "text/plain; charset=x-user-defined" ); + xhr.send( null ); + +}; + +// Binary AJAX parser + +THREE.BinaryLoader.prototype.createBinModel = function ( data, callback, texturePath, jsonMaterials ) { + + var Model = function ( texturePath ) { + + var scope = this, + currentOffset = 0, + md, + normals = [], + uvs = [], + start_tri_flat, start_tri_smooth, start_tri_flat_uv, start_tri_smooth_uv, + start_quad_flat, start_quad_smooth, start_quad_flat_uv, start_quad_smooth_uv, + tri_size, quad_size, + len_tri_flat, len_tri_smooth, len_tri_flat_uv, len_tri_smooth_uv, + len_quad_flat, len_quad_smooth, len_quad_flat_uv, len_quad_smooth_uv; + + + THREE.Geometry.call( this ); + + md = parseMetaData( data, currentOffset ); + + currentOffset += md.header_bytes; + /* + md.vertex_index_bytes = Uint32Array.BYTES_PER_ELEMENT; + md.material_index_bytes = Uint16Array.BYTES_PER_ELEMENT; + md.normal_index_bytes = Uint32Array.BYTES_PER_ELEMENT; + md.uv_index_bytes = Uint32Array.BYTES_PER_ELEMENT; + */ + // buffers sizes + + tri_size = md.vertex_index_bytes * 3 + md.material_index_bytes; + quad_size = md.vertex_index_bytes * 4 + md.material_index_bytes; + + len_tri_flat = md.ntri_flat * ( tri_size ); + len_tri_smooth = md.ntri_smooth * ( tri_size + md.normal_index_bytes * 3 ); + len_tri_flat_uv = md.ntri_flat_uv * ( tri_size + md.uv_index_bytes * 3 ); + len_tri_smooth_uv = md.ntri_smooth_uv * ( tri_size + md.normal_index_bytes * 3 + md.uv_index_bytes * 3 ); + + len_quad_flat = md.nquad_flat * ( quad_size ); + len_quad_smooth = md.nquad_smooth * ( quad_size + md.normal_index_bytes * 4 ); + len_quad_flat_uv = md.nquad_flat_uv * ( quad_size + md.uv_index_bytes * 4 ); + len_quad_smooth_uv = md.nquad_smooth_uv * ( quad_size + md.normal_index_bytes * 4 + md.uv_index_bytes * 4 ); + + // read buffers + + currentOffset += init_vertices( currentOffset ); + + currentOffset += init_normals( currentOffset ); + currentOffset += handlePadding( md.nnormals * 3 ); + + currentOffset += init_uvs( currentOffset ); + + start_tri_flat = currentOffset; + start_tri_smooth = start_tri_flat + len_tri_flat + handlePadding( md.ntri_flat * 2 ); + start_tri_flat_uv = start_tri_smooth + len_tri_smooth + handlePadding( md.ntri_smooth * 2 ); + start_tri_smooth_uv = start_tri_flat_uv + len_tri_flat_uv + handlePadding( md.ntri_flat_uv * 2 ); + + start_quad_flat = start_tri_smooth_uv + len_tri_smooth_uv + handlePadding( md.ntri_smooth_uv * 2 ); + start_quad_smooth = start_quad_flat + len_quad_flat + handlePadding( md.nquad_flat * 2 ); + start_quad_flat_uv = start_quad_smooth + len_quad_smooth + handlePadding( md.nquad_smooth * 2 ); + start_quad_smooth_uv= start_quad_flat_uv + len_quad_flat_uv + handlePadding( md.nquad_flat_uv * 2 ); + + // have to first process faces with uvs + // so that face and uv indices match + + init_triangles_flat_uv( start_tri_flat_uv ); + init_triangles_smooth_uv( start_tri_smooth_uv ); + + init_quads_flat_uv( start_quad_flat_uv ); + init_quads_smooth_uv( start_quad_smooth_uv ); + + // now we can process untextured faces + + init_triangles_flat( start_tri_flat ); + init_triangles_smooth( start_tri_smooth ); + + init_quads_flat( start_quad_flat ); + init_quads_smooth( start_quad_smooth ); + + this.computeFaceNormals(); + + function handlePadding( n ) { + + return ( n % 4 ) ? ( 4 - n % 4 ) : 0; + + }; + + function parseMetaData( data, offset ) { + + var metaData = { + + 'signature' :parseString( data, offset, 12 ), + 'header_bytes' :parseUChar8( data, offset + 12 ), + + 'vertex_coordinate_bytes' :parseUChar8( data, offset + 13 ), + 'normal_coordinate_bytes' :parseUChar8( data, offset + 14 ), + 'uv_coordinate_bytes' :parseUChar8( data, offset + 15 ), + + 'vertex_index_bytes' :parseUChar8( data, offset + 16 ), + 'normal_index_bytes' :parseUChar8( data, offset + 17 ), + 'uv_index_bytes' :parseUChar8( data, offset + 18 ), + 'material_index_bytes' :parseUChar8( data, offset + 19 ), + + 'nvertices' :parseUInt32( data, offset + 20 ), + 'nnormals' :parseUInt32( data, offset + 20 + 4*1 ), + 'nuvs' :parseUInt32( data, offset + 20 + 4*2 ), + + 'ntri_flat' :parseUInt32( data, offset + 20 + 4*3 ), + 'ntri_smooth' :parseUInt32( data, offset + 20 + 4*4 ), + 'ntri_flat_uv' :parseUInt32( data, offset + 20 + 4*5 ), + 'ntri_smooth_uv' :parseUInt32( data, offset + 20 + 4*6 ), + + 'nquad_flat' :parseUInt32( data, offset + 20 + 4*7 ), + 'nquad_smooth' :parseUInt32( data, offset + 20 + 4*8 ), + 'nquad_flat_uv' :parseUInt32( data, offset + 20 + 4*9 ), + 'nquad_smooth_uv' :parseUInt32( data, offset + 20 + 4*10 ) + + }; + /* + console.log( "signature: " + metaData.signature ); + + console.log( "header_bytes: " + metaData.header_bytes ); + console.log( "vertex_coordinate_bytes: " + metaData.vertex_coordinate_bytes ); + console.log( "normal_coordinate_bytes: " + metaData.normal_coordinate_bytes ); + console.log( "uv_coordinate_bytes: " + metaData.uv_coordinate_bytes ); + + console.log( "vertex_index_bytes: " + metaData.vertex_index_bytes ); + console.log( "normal_index_bytes: " + metaData.normal_index_bytes ); + console.log( "uv_index_bytes: " + metaData.uv_index_bytes ); + console.log( "material_index_bytes: " + metaData.material_index_bytes ); + + console.log( "nvertices: " + metaData.nvertices ); + console.log( "nnormals: " + metaData.nnormals ); + console.log( "nuvs: " + metaData.nuvs ); + + console.log( "ntri_flat: " + metaData.ntri_flat ); + console.log( "ntri_smooth: " + metaData.ntri_smooth ); + console.log( "ntri_flat_uv: " + metaData.ntri_flat_uv ); + console.log( "ntri_smooth_uv: " + metaData.ntri_smooth_uv ); + + console.log( "nquad_flat: " + metaData.nquad_flat ); + console.log( "nquad_smooth: " + metaData.nquad_smooth ); + console.log( "nquad_flat_uv: " + metaData.nquad_flat_uv ); + console.log( "nquad_smooth_uv: " + metaData.nquad_smooth_uv ); + + var total = metaData.header_bytes + + metaData.nvertices * metaData.vertex_coordinate_bytes * 3 + + metaData.nnormals * metaData.normal_coordinate_bytes * 3 + + metaData.nuvs * metaData.uv_coordinate_bytes * 2 + + metaData.ntri_flat * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes ) + + metaData.ntri_smooth * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.normal_index_bytes*3 ) + + metaData.ntri_flat_uv * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.uv_index_bytes*3 ) + + metaData.ntri_smooth_uv * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.normal_index_bytes*3 + metaData.uv_index_bytes*3 ) + + metaData.nquad_flat * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes ) + + metaData.nquad_smooth * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.normal_index_bytes*4 ) + + metaData.nquad_flat_uv * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.uv_index_bytes*4 ) + + metaData.nquad_smooth_uv * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.normal_index_bytes*4 + metaData.uv_index_bytes*4 ); + console.log( "total bytes: " + total ); + */ + + return metaData; + + }; + + function parseString( data, offset, length ) { + + var charArray = new Uint8Array( data, offset, length ); + + var text = ""; + + for ( var i = 0; i < length; i ++ ) { + + text += String.fromCharCode( charArray[ offset + i ] ); + + } + + return text; + + }; + + function parseUChar8( data, offset ) { + + var charArray = new Uint8Array( data, offset, 1 ); + + return charArray[ 0 ]; + + }; + + function parseUInt32( data, offset ) { + + var intArray = new Uint32Array( data, offset, 1 ); + + return intArray[ 0 ]; + + }; + + function init_vertices( start ) { + + var nElements = md.nvertices; + + var coordArray = new Float32Array( data, start, nElements * 3 ); + + var i, x, y, z; + + for( i = 0; i < nElements; i ++ ) { + + x = coordArray[ i * 3 ]; + y = coordArray[ i * 3 + 1 ]; + z = coordArray[ i * 3 + 2 ]; + + scope.vertices.push( new THREE.Vector3( x, y, z ) ); + + } + + return nElements * 3 * Float32Array.BYTES_PER_ELEMENT; + + }; + + function init_normals( start ) { + + var nElements = md.nnormals; + + if ( nElements ) { + + var normalArray = new Int8Array( data, start, nElements * 3 ); + + var i, x, y, z; + + for( i = 0; i < nElements; i ++ ) { + + x = normalArray[ i * 3 ]; + y = normalArray[ i * 3 + 1 ]; + z = normalArray[ i * 3 + 2 ]; + + normals.push( x/127, y/127, z/127 ); + + } + + } + + return nElements * 3 * Int8Array.BYTES_PER_ELEMENT; + + }; + + function init_uvs( start ) { + + var nElements = md.nuvs; + + if ( nElements ) { + + var uvArray = new Float32Array( data, start, nElements * 2 ); + + var i, u, v; + + for( i = 0; i < nElements; i ++ ) { + + u = uvArray[ i * 2 ]; + v = uvArray[ i * 2 + 1 ]; + + uvs.push( u, v ); + + } + + } + + return nElements * 2 * Float32Array.BYTES_PER_ELEMENT; + + }; + + function init_uvs3( nElements, offset ) { + + var i, uva, uvb, uvc, u1, u2, u3, v1, v2, v3; + + var uvIndexBuffer = new Uint32Array( data, offset, 3 * nElements ); + + for( i = 0; i < nElements; i ++ ) { + + uva = uvIndexBuffer[ i * 3 ]; + uvb = uvIndexBuffer[ i * 3 + 1 ]; + uvc = uvIndexBuffer[ i * 3 + 2 ]; + + u1 = uvs[ uva*2 ]; + v1 = uvs[ uva*2 + 1 ]; + + u2 = uvs[ uvb*2 ]; + v2 = uvs[ uvb*2 + 1 ]; + + u3 = uvs[ uvc*2 ]; + v3 = uvs[ uvc*2 + 1 ]; + + scope.faceVertexUvs[ 0 ].push( [ + new THREE.Vector2( u1, v1 ), + new THREE.Vector2( u2, v2 ), + new THREE.Vector2( u3, v3 ) + ] ); + + } + + }; + + function init_uvs4( nElements, offset ) { + + var i, uva, uvb, uvc, uvd, u1, u2, u3, u4, v1, v2, v3, v4; + + var uvIndexBuffer = new Uint32Array( data, offset, 4 * nElements ); + + for( i = 0; i < nElements; i ++ ) { + + uva = uvIndexBuffer[ i * 4 ]; + uvb = uvIndexBuffer[ i * 4 + 1 ]; + uvc = uvIndexBuffer[ i * 4 + 2 ]; + uvd = uvIndexBuffer[ i * 4 + 3 ]; + + u1 = uvs[ uva*2 ]; + v1 = uvs[ uva*2 + 1 ]; + + u2 = uvs[ uvb*2 ]; + v2 = uvs[ uvb*2 + 1 ]; + + u3 = uvs[ uvc*2 ]; + v3 = uvs[ uvc*2 + 1 ]; + + u4 = uvs[ uvd*2 ]; + v4 = uvs[ uvd*2 + 1 ]; + + scope.faceVertexUvs[ 0 ].push( [ + new THREE.Vector2( u1, v1 ), + new THREE.Vector2( u2, v2 ), + new THREE.Vector2( u4, v4 ) + ] ); + + scope.faceVertexUvs[ 0 ].push( [ + new THREE.Vector2( u2, v2 ), + new THREE.Vector2( u3, v3 ), + new THREE.Vector2( u4, v4 ) + ] ); + + } + + }; + + function init_faces3_flat( nElements, offsetVertices, offsetMaterials ) { + + var i, a, b, c, m; + + var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 3 * nElements ); + var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); + + for( i = 0; i < nElements; i ++ ) { + + a = vertexIndexBuffer[ i * 3 ]; + b = vertexIndexBuffer[ i * 3 + 1 ]; + c = vertexIndexBuffer[ i * 3 + 2 ]; + + m = materialIndexBuffer[ i ]; + + scope.faces.push( new THREE.Face3( a, b, c, null, null, m ) ); + + } + + }; + + function init_faces4_flat( nElements, offsetVertices, offsetMaterials ) { + + var i, a, b, c, d, m; + + var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 4 * nElements ); + var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); + + for( i = 0; i < nElements; i ++ ) { + + a = vertexIndexBuffer[ i * 4 ]; + b = vertexIndexBuffer[ i * 4 + 1 ]; + c = vertexIndexBuffer[ i * 4 + 2 ]; + d = vertexIndexBuffer[ i * 4 + 3 ]; + + m = materialIndexBuffer[ i ]; + + scope.faces.push( new THREE.Face3( a, b, d, null, null, m ) ); + scope.faces.push( new THREE.Face3( b, c, d, null, null, m ) ); + + } + + }; + + function init_faces3_smooth( nElements, offsetVertices, offsetNormals, offsetMaterials ) { + + var i, a, b, c, m; + var na, nb, nc; + + var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 3 * nElements ); + var normalIndexBuffer = new Uint32Array( data, offsetNormals, 3 * nElements ); + var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); + + for( i = 0; i < nElements; i ++ ) { + + a = vertexIndexBuffer[ i * 3 ]; + b = vertexIndexBuffer[ i * 3 + 1 ]; + c = vertexIndexBuffer[ i * 3 + 2 ]; + + na = normalIndexBuffer[ i * 3 ]; + nb = normalIndexBuffer[ i * 3 + 1 ]; + nc = normalIndexBuffer[ i * 3 + 2 ]; + + m = materialIndexBuffer[ i ]; + + var nax = normals[ na*3 ], + nay = normals[ na*3 + 1 ], + naz = normals[ na*3 + 2 ], + + nbx = normals[ nb*3 ], + nby = normals[ nb*3 + 1 ], + nbz = normals[ nb*3 + 2 ], + + ncx = normals[ nc*3 ], + ncy = normals[ nc*3 + 1 ], + ncz = normals[ nc*3 + 2 ]; + + scope.faces.push( new THREE.Face3( a, b, c, [ + new THREE.Vector3( nax, nay, naz ), + new THREE.Vector3( nbx, nby, nbz ), + new THREE.Vector3( ncx, ncy, ncz ) + ], null, m ) ); + + } + + }; + + function init_faces4_smooth( nElements, offsetVertices, offsetNormals, offsetMaterials ) { + + var i, a, b, c, d, m; + var na, nb, nc, nd; + + var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 4 * nElements ); + var normalIndexBuffer = new Uint32Array( data, offsetNormals, 4 * nElements ); + var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); + + for( i = 0; i < nElements; i ++ ) { + + a = vertexIndexBuffer[ i * 4 ]; + b = vertexIndexBuffer[ i * 4 + 1 ]; + c = vertexIndexBuffer[ i * 4 + 2 ]; + d = vertexIndexBuffer[ i * 4 + 3 ]; + + na = normalIndexBuffer[ i * 4 ]; + nb = normalIndexBuffer[ i * 4 + 1 ]; + nc = normalIndexBuffer[ i * 4 + 2 ]; + nd = normalIndexBuffer[ i * 4 + 3 ]; + + m = materialIndexBuffer[ i ]; + + var nax = normals[ na*3 ], + nay = normals[ na*3 + 1 ], + naz = normals[ na*3 + 2 ], + + nbx = normals[ nb*3 ], + nby = normals[ nb*3 + 1 ], + nbz = normals[ nb*3 + 2 ], + + ncx = normals[ nc*3 ], + ncy = normals[ nc*3 + 1 ], + ncz = normals[ nc*3 + 2 ], + + ndx = normals[ nd*3 ], + ndy = normals[ nd*3 + 1 ], + ndz = normals[ nd*3 + 2 ]; + + scope.faces.push( new THREE.Face3( a, b, d, [ + new THREE.Vector3( nax, nay, naz ), + new THREE.Vector3( nbx, nby, nbz ), + new THREE.Vector3( ndx, ndy, ndz ) + ], null, m ) ); + + scope.faces.push( new THREE.Face3( b, c, d, [ + new THREE.Vector3( nbx, nby, nbz ), + new THREE.Vector3( ncx, ncy, ncz ), + new THREE.Vector3( ndx, ndy, ndz ) + ], null, m ) ); + + } + + }; + + function init_triangles_flat( start ) { + + var nElements = md.ntri_flat; + + if ( nElements ) { + + var offsetMaterials = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + init_faces3_flat( nElements, start, offsetMaterials ); + + } + + }; + + function init_triangles_flat_uv( start ) { + + var nElements = md.ntri_flat_uv; + + if ( nElements ) { + + var offsetUvs = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + + init_faces3_flat( nElements, start, offsetMaterials ); + init_uvs3( nElements, offsetUvs ); + + } + + }; + + function init_triangles_smooth( start ) { + + var nElements = md.ntri_smooth; + + if ( nElements ) { + + var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + var offsetMaterials = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + + init_faces3_smooth( nElements, start, offsetNormals, offsetMaterials ); + + } + + }; + + function init_triangles_smooth_uv( start ) { + + var nElements = md.ntri_smooth_uv; + + if ( nElements ) { + + var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + var offsetUvs = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + + init_faces3_smooth( nElements, start, offsetNormals, offsetMaterials ); + init_uvs3( nElements, offsetUvs ); + + } + + }; + + function init_quads_flat( start ) { + + var nElements = md.nquad_flat; + + if ( nElements ) { + + var offsetMaterials = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + init_faces4_flat( nElements, start, offsetMaterials ); + + } + + }; + + function init_quads_flat_uv( start ) { + + var nElements = md.nquad_flat_uv; + + if ( nElements ) { + + var offsetUvs = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + + init_faces4_flat( nElements, start, offsetMaterials ); + init_uvs4( nElements, offsetUvs ); + + } + + }; + + function init_quads_smooth( start ) { + + var nElements = md.nquad_smooth; + + if ( nElements ) { + + var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + var offsetMaterials = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + + init_faces4_smooth( nElements, start, offsetNormals, offsetMaterials ); + + } + + }; + + function init_quads_smooth_uv( start ) { + + var nElements = md.nquad_smooth_uv; + + if ( nElements ) { + + var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + var offsetUvs = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + + init_faces4_smooth( nElements, start, offsetNormals, offsetMaterials ); + init_uvs4( nElements, offsetUvs ); + + } + + }; + + }; + + Model.prototype = Object.create( THREE.Geometry.prototype ); + + var geometry = new Model( texturePath ); + var materials = this.initMaterials( jsonMaterials, texturePath ); + + if ( this.needsTangents( materials ) ) geometry.computeTangents(); + + callback( geometry, materials ); + +}; \ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/components/3d/loaders/ColladaLoader.js b/docdoku-dplm/ui/app/js/components/3d/loaders/ColladaLoader.js new file mode 100644 index 0000000000..2cc84e26f8 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/3d/loaders/ColladaLoader.js @@ -0,0 +1,5026 @@ +/** + * @author Tim Knip / http://www.floorplanner.com/ / tim at floorplanner.com + * @author Tony Parisi / http://www.tonyparisi.com/ + */ + +THREE.ColladaLoader = function () { + + var COLLADA = null; + var scene = null; + var daeScene; + + var readyCallbackFunc = null; + + var sources = {}; + var images = {}; + var animations = {}; + var controllers = {}; + var geometries = {}; + var materials = {}; + var effects = {}; + var cameras = {}; + var lights = {}; + + var animData; + var visualScenes; + var baseUrl; + var morphs; + var skins; + + var flip_uv = true; + var preferredShading = THREE.SmoothShading; + + var options = { + // Force Geometry to always be centered at the local origin of the + // containing Mesh. + centerGeometry: false, + + // Axis conversion is done for geometries, animations, and controllers. + // If we ever pull cameras or lights out of the COLLADA file, they'll + // need extra work. + convertUpAxis: false, + + subdivideFaces: true, + + upAxis: 'Y', + + // For reflective or refractive materials we'll use this cubemap + defaultEnvMap: null + + }; + + var colladaUnit = 1.0; + var colladaUp = 'Y'; + var upConversion = null; + + function load ( url, readyCallback, progressCallback ) { + + var length = 0; + + if ( document.implementation && document.implementation.createDocument ) { + + var request = new XMLHttpRequest(); + + request.onreadystatechange = function() { + + if( request.readyState == 4 ) { + + if( request.status == 0 || request.status == 200 ) { + + + if ( request.responseXML ) { + + readyCallbackFunc = readyCallback; + parse( request.responseXML, undefined, url ); + + } else if ( request.responseText ) { + + readyCallbackFunc = readyCallback; + var xmlParser = new DOMParser(); + var responseXML = xmlParser.parseFromString( request.responseText, "application/xml" ); + parse( responseXML, undefined, url ); + + } else { + + console.error( "ColladaLoader: Empty or non-existing file (" + url + ")" ); + + } + + } + + } else if ( request.readyState == 3 ) { + + if ( progressCallback ) { + + if ( length == 0 ) { + + length = request.getResponseHeader( "Content-Length" ); + + } + + progressCallback( { total: length, loaded: request.responseText.length } ); + + } + + } + + } + + request.open( "GET", url, true ); + request.send( null ); + + } else { + + alert( "Don't know how to parse XML!" ); + + } + + } + + function parse( doc, callBack, url ) { + + COLLADA = doc; + callBack = callBack || readyCallbackFunc; + + if ( url !== undefined ) { + + var parts = url.split( '/' ); + parts.pop(); + baseUrl = ( parts.length < 1 ? '.' : parts.join( '/' ) ) + '/'; + + } + + parseAsset(); + setUpConversion(); + images = parseLib( "library_images image", _Image, "image" ); + materials = parseLib( "library_materials material", Material, "material" ); + effects = parseLib( "library_effects effect", Effect, "effect" ); + geometries = parseLib( "library_geometries geometry", Geometry, "geometry" ); + cameras = parseLib( "library_cameras camera", Camera, "camera" ); + lights = parseLib( "library_lights light", Light, "light" ); + controllers = parseLib( "library_controllers controller", Controller, "controller" ); + animations = parseLib( "library_animations animation", Animation, "animation" ); + visualScenes = parseLib( "library_visual_scenes visual_scene", VisualScene, "visual_scene" ); + + morphs = []; + skins = []; + + daeScene = parseScene(); + scene = new THREE.Object3D(); + + for ( var i = 0; i < daeScene.nodes.length; i ++ ) { + + scene.add( createSceneGraph( daeScene.nodes[ i ] ) ); + + } + + // unit conversion + scene.scale.multiplyScalar( colladaUnit ); + + createAnimations(); + + var result = { + + scene: scene, + morphs: morphs, + skins: skins, + animations: animData, + dae: { + images: images, + materials: materials, + cameras: cameras, + lights: lights, + effects: effects, + geometries: geometries, + controllers: controllers, + animations: animations, + visualScenes: visualScenes, + scene: daeScene + } + + }; + + if ( callBack ) { + + callBack( result ); + + } + + return result; + + } + + function setPreferredShading ( shading ) { + + preferredShading = shading; + + } + + function parseAsset () { + + var elements = COLLADA.querySelectorAll('asset'); + + var element = elements[0]; + + if ( element && element.childNodes ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'unit': + + var meter = child.getAttribute( 'meter' ); + + if ( meter ) { + + colladaUnit = parseFloat( meter ); + + } + + break; + + case 'up_axis': + + colladaUp = child.textContent.charAt(0); + break; + + } + + } + + } + + } + + function parseLib ( q, classSpec, prefix ) { + + var elements = COLLADA.querySelectorAll(q); + + var lib = {}; + + var i = 0; + + var elementsLength = elements.length; + + for ( var j = 0; j < elementsLength; j ++ ) { + + var element = elements[j]; + var daeElement = ( new classSpec() ).parse( element ); + + if ( !daeElement.id || daeElement.id.length == 0 ) daeElement.id = prefix + ( i ++ ); + lib[ daeElement.id ] = daeElement; + + } + + return lib; + + } + + function parseScene() { + + var sceneElement = COLLADA.querySelectorAll('scene instance_visual_scene')[0]; + + if ( sceneElement ) { + + var url = sceneElement.getAttribute( 'url' ).replace( /^#/, '' ); + return visualScenes[ url.length > 0 ? url : 'visual_scene0' ]; + + } else { + + return null; + + } + + } + + function createAnimations() { + + animData = []; + + // fill in the keys + recurseHierarchy( scene ); + + } + + function recurseHierarchy( node ) { + + var n = daeScene.getChildById( node.id, true ), + newData = null; + + if ( n && n.keys ) { + + newData = { + fps: 60, + hierarchy: [ { + node: n, + keys: n.keys, + sids: n.sids + } ], + node: node, + name: 'animation_' + node.name, + length: 0 + }; + + animData.push(newData); + + for ( var i = 0, il = n.keys.length; i < il; i++ ) { + + newData.length = Math.max( newData.length, n.keys[i].time ); + + } + + } else { + + newData = { + hierarchy: [ { + keys: [], + sids: [] + } ] + } + + } + + for ( var i = 0, il = node.children.length; i < il; i++ ) { + + var d = recurseHierarchy( node.children[i] ); + + for ( var j = 0, jl = d.hierarchy.length; j < jl; j ++ ) { + + newData.hierarchy.push( { + keys: [], + sids: [] + } ); + + } + + } + + return newData; + + } + + function calcAnimationBounds () { + + var start = 1000000; + var end = -start; + var frames = 0; + var ID; + for ( var id in animations ) { + + var animation = animations[ id ]; + ID = ID || animation.id; + for ( var i = 0; i < animation.sampler.length; i ++ ) { + + var sampler = animation.sampler[ i ]; + + sampler.create(); + + start = Math.min( start, sampler.startTime ); + end = Math.max( end, sampler.endTime ); + frames = Math.max( frames, sampler.input.length ); + + } + + } + + return { start:start, end:end, frames:frames,ID:ID }; + + } + + function createMorph ( geometry, ctrl ) { + + var morphCtrl = ctrl instanceof InstanceController ? controllers[ ctrl.url ] : ctrl; + + if ( !morphCtrl || !morphCtrl.morph ) { + + console.log("could not find morph controller!"); + return; + + } + + var morph = morphCtrl.morph; + + for ( var i = 0; i < morph.targets.length; i ++ ) { + + var target_id = morph.targets[ i ]; + var daeGeometry = geometries[ target_id ]; + + if ( !daeGeometry.mesh || + !daeGeometry.mesh.primitives || + !daeGeometry.mesh.primitives.length ) { + continue; + } + + var target = daeGeometry.mesh.primitives[ 0 ].geometry; + + if ( target.vertices.length === geometry.vertices.length ) { + + geometry.morphTargets.push( { name: "target_1", vertices: target.vertices } ); + + } + + } + + geometry.morphTargets.push( { name: "target_Z", vertices: geometry.vertices } ); + + }; + + function createSkin ( geometry, ctrl, applyBindShape ) { + + var skinCtrl = controllers[ ctrl.url ]; + + if ( !skinCtrl || !skinCtrl.skin ) { + + console.log( "could not find skin controller!" ); + return; + + } + + if ( !ctrl.skeleton || !ctrl.skeleton.length ) { + + console.log( "could not find the skeleton for the skin!" ); + return; + + } + + var skin = skinCtrl.skin; + var skeleton = daeScene.getChildById( ctrl.skeleton[ 0 ] ); + var hierarchy = []; + + applyBindShape = applyBindShape !== undefined ? applyBindShape : true; + + var bones = []; + geometry.skinWeights = []; + geometry.skinIndices = []; + + //createBones( geometry.bones, skin, hierarchy, skeleton, null, -1 ); + //createWeights( skin, geometry.bones, geometry.skinIndices, geometry.skinWeights ); + + /* + geometry.animation = { + name: 'take_001', + fps: 30, + length: 2, + JIT: true, + hierarchy: hierarchy + }; + */ + + if ( applyBindShape ) { + + for ( var i = 0; i < geometry.vertices.length; i ++ ) { + + geometry.vertices[ i ].applyMatrix4( skin.bindShapeMatrix ); + + } + + } + + } + + function setupSkeleton ( node, bones, frame, parent ) { + + node.world = node.world || new THREE.Matrix4(); + node.localworld = node.localworld || new THREE.Matrix4(); + node.world.copy( node.matrix ); + node.localworld.copy( node.matrix ); + + if ( node.channels && node.channels.length ) { + + var channel = node.channels[ 0 ]; + var m = channel.sampler.output[ frame ]; + + if ( m instanceof THREE.Matrix4 ) { + + node.world.copy( m ); + node.localworld.copy(m); + if(frame == 0) + node.matrix.copy(m); + } + + } + + if ( parent ) { + + node.world.multiplyMatrices( parent, node.world ); + + } + + bones.push( node ); + + for ( var i = 0; i < node.nodes.length; i ++ ) { + + setupSkeleton( node.nodes[ i ], bones, frame, node.world ); + + } + + } + + function setupSkinningMatrices ( bones, skin ) { + + // FIXME: this is dumb... + + for ( var i = 0; i < bones.length; i ++ ) { + + var bone = bones[ i ]; + var found = -1; + + if ( bone.type != 'JOINT' ) continue; + + for ( var j = 0; j < skin.joints.length; j ++ ) { + + if ( bone.sid == skin.joints[ j ] ) { + + found = j; + break; + + } + + } + + if ( found >= 0 ) { + + var inv = skin.invBindMatrices[ found ]; + + bone.invBindMatrix = inv; + bone.skinningMatrix = new THREE.Matrix4(); + bone.skinningMatrix.multiplyMatrices(bone.world, inv); // (IBMi * JMi) + bone.animatrix = new THREE.Matrix4(); + + bone.animatrix.copy(bone.localworld); + bone.weights = []; + + for ( var j = 0; j < skin.weights.length; j ++ ) { + + for (var k = 0; k < skin.weights[ j ].length; k ++ ) { + + var w = skin.weights[ j ][ k ]; + + if ( w.joint == found ) { + + bone.weights.push( w ); + + } + + } + + } + + } else { + + console.warn( "ColladaLoader: Could not find joint '" + bone.sid + "'." ); + + bone.skinningMatrix = new THREE.Matrix4(); + bone.weights = []; + + } + } + + } + + //Walk the Collada tree and flatten the bones into a list, extract the position, quat and scale from the matrix + function flattenSkeleton(skeleton) { + + var list = []; + var walk = function(parentid, node, list) { + + var bone = {}; + bone.name = node.sid; + bone.parent = parentid; + bone.matrix = node.matrix; + var data = [new THREE.Vector3(),new THREE.Quaternion(),new THREE.Vector3()]; + bone.matrix.decompose(data[0],data[1],data[2]); + + bone.pos = [data[0].x,data[0].y,data[0].z]; + + bone.scl = [data[2].x,data[2].y,data[2].z]; + bone.rotq = [data[1].x,data[1].y,data[1].z,data[1].w]; + list.push(bone); + + for(var i in node.nodes) { + + walk(node.sid,node.nodes[i],list); + + } + + }; + + walk(-1,skeleton,list); + return list; + + } + + //Move the vertices into the pose that is proper for the start of the animation + function skinToBindPose(geometry,skeleton,skinController) { + + var bones = []; + setupSkeleton( skeleton, bones, -1 ); + setupSkinningMatrices( bones, skinController.skin ); + v = new THREE.Vector3(); + var skinned = []; + + for(var i =0; i < geometry.vertices.length; i++) { + + skinned.push(new THREE.Vector3()); + + } + + for ( i = 0; i < bones.length; i ++ ) { + + if ( bones[ i ].type != 'JOINT' ) continue; + + for ( j = 0; j < bones[ i ].weights.length; j ++ ) { + + w = bones[ i ].weights[ j ]; + vidx = w.index; + weight = w.weight; + + o = geometry.vertices[vidx]; + s = skinned[vidx]; + + v.x = o.x; + v.y = o.y; + v.z = o.z; + + v.applyMatrix4( bones[i].skinningMatrix ); + + s.x += (v.x * weight); + s.y += (v.y * weight); + s.z += (v.z * weight); + } + + } + + for(var i =0; i < geometry.vertices.length; i++) { + + geometry.vertices[i] = skinned[i]; + + } + + } + + function applySkin ( geometry, instanceCtrl, frame ) { + + // TODO: get this from the renderer or options + var maxbones = 30; + + var skinController = controllers[ instanceCtrl.url ]; + + frame = frame !== undefined ? frame : 40; + + if ( !skinController || !skinController.skin ) { + + console.log( 'ColladaLoader: Could not find skin controller.' ); + return; + + } + + if ( !instanceCtrl.skeleton || !instanceCtrl.skeleton.length ) { + + console.log( 'ColladaLoader: Could not find the skeleton for the skin. ' ); + return; + + } + + var animationBounds = calcAnimationBounds(); + var skeleton = daeScene.getChildById( instanceCtrl.skeleton[0], true ) || + daeScene.getChildBySid( instanceCtrl.skeleton[0], true ); + + //flatten the skeleton into a list of bones + var bonelist = flattenSkeleton(skeleton); + var joints = skinController.skin.joints; + + //sort that list so that the order reflects the order in the joint list + var sortedbones = []; + for(var i = 0; i < joints.length; i++) { + + for(var j =0; j < bonelist.length; j++) { + + if(bonelist[j].name == joints[i]) { + + sortedbones[i] = bonelist[j]; + + } + + } + + } + + //hook up the parents by index instead of name + for(var i = 0; i < sortedbones.length; i++) { + + for(var j =0; j < sortedbones.length; j++) { + + if(sortedbones[i].parent == sortedbones[j].name) { + + sortedbones[i].parent = j; + + } + + } + + } + + + var i, j, w, vidx, weight; + var v = new THREE.Vector3(), o, s; + + // move vertices to bind shape + for ( i = 0; i < geometry.vertices.length; i ++ ) { + geometry.vertices[i].applyMatrix4( skinController.skin.bindShapeMatrix ); + } + + var skinIndices = []; + var skinWeights = []; + var weights = skinController.skin.weights; + + //hook up the skin weights + // TODO - this might be a good place to choose greatest 4 weights + for(var i =0; i < weights.length; i++) { + + var indicies = new THREE.Vector4(weights[i][0]?weights[i][0].joint:0,weights[i][1]?weights[i][1].joint:0,weights[i][2]?weights[i][2].joint:0,weights[i][3]?weights[i][3].joint:0); + var weight = new THREE.Vector4(weights[i][0]?weights[i][0].weight:0,weights[i][1]?weights[i][1].weight:0,weights[i][2]?weights[i][2].weight:0,weights[i][3]?weights[i][3].weight:0); + + skinIndices.push(indicies); + skinWeights.push(weight); + + } + + geometry.skinIndices = skinIndices; + geometry.skinWeights = skinWeights; + geometry.bones = sortedbones; + // process animation, or simply pose the rig if no animation + + //create an animation for the animated bones + //NOTE: this has no effect when using morphtargets + var animationdata = {"name":animationBounds.ID,"fps":30,"length":animationBounds.frames/30,"hierarchy":[]}; + + for(var j =0; j < sortedbones.length; j++) { + + animationdata.hierarchy.push({parent:sortedbones[j].parent, name:sortedbones[j].name, keys:[]}); + + } + + //if using hardware skinning, move the vertices into the binding pose + if(sortedbones.length < maxbones) { + + skinToBindPose(geometry,skeleton,skinController); + + } + + for ( frame = 0; frame < animationBounds.frames; frame ++ ) { + + var bones = []; + var skinned = []; + // process the frame and setup the rig with a fresh + // transform, possibly from the bone's animation channel(s) + + setupSkeleton( skeleton, bones, frame ); + setupSkinningMatrices( bones, skinController.skin ); + + //if using hardware skinning, just hook up the animiation data + if(sortedbones.length < maxbones) { + + for(var i = 0; i < bones.length; i ++) { + + for(var j = 0; j < animationdata.hierarchy.length; j ++) { + + if(animationdata.hierarchy[j].name == bones[i].sid) { + + var key = {}; + key.time = (frame/30); + key.matrix = bones[i].animatrix; + + if(frame == 0) + bones[i].matrix = key.matrix; + + var data = [new THREE.Vector3(),new THREE.Quaternion(),new THREE.Vector3()]; + key.matrix.decompose(data[0],data[1],data[2]); + + key.pos = [data[0].x,data[0].y,data[0].z]; + + key.scl = [data[2].x,data[2].y,data[2].z]; + key.rot = data[1]; + + animationdata.hierarchy[j].keys.push(key); + + } + + } + + } + + geometry.animation = animationdata; + + } else { + + // otherwise, process the animation into morphtargets + + for ( i = 0; i < geometry.vertices.length; i++ ) { + + skinned.push( new THREE.Vector3() ); + + } + + for ( i = 0; i < bones.length; i ++ ) { + + if ( bones[ i ].type != 'JOINT' ) continue; + + for ( j = 0; j < bones[ i ].weights.length; j ++ ) { + + w = bones[ i ].weights[ j ]; + vidx = w.index; + weight = w.weight; + + o = geometry.vertices[vidx]; + s = skinned[vidx]; + + v.x = o.x; + v.y = o.y; + v.z = o.z; + + v.applyMatrix4( bones[i].skinningMatrix ); + + s.x += (v.x * weight); + s.y += (v.y * weight); + s.z += (v.z * weight); + + } + + } + + geometry.morphTargets.push( { name: "target_" + frame, vertices: skinned } ); + + } + + } + + }; + + function createSceneGraph ( node, parent ) { + + var obj = new THREE.Object3D(); + var skinned = false; + var skinController; + var morphController; + var i, j; + + // FIXME: controllers + + for ( i = 0; i < node.controllers.length; i ++ ) { + + var controller = controllers[ node.controllers[ i ].url ]; + + switch ( controller.type ) { + + case 'skin': + + if ( geometries[ controller.skin.source ] ) { + + var inst_geom = new InstanceGeometry(); + + inst_geom.url = controller.skin.source; + inst_geom.instance_material = node.controllers[ i ].instance_material; + + node.geometries.push( inst_geom ); + skinned = true; + skinController = node.controllers[ i ]; + + } else if ( controllers[ controller.skin.source ] ) { + + // urgh: controller can be chained + // handle the most basic case... + + var second = controllers[ controller.skin.source ]; + morphController = second; + // skinController = node.controllers[i]; + + if ( second.morph && geometries[ second.morph.source ] ) { + + var inst_geom = new InstanceGeometry(); + + inst_geom.url = second.morph.source; + inst_geom.instance_material = node.controllers[ i ].instance_material; + + node.geometries.push( inst_geom ); + + } + + } + + break; + + case 'morph': + + if ( geometries[ controller.morph.source ] ) { + + var inst_geom = new InstanceGeometry(); + + inst_geom.url = controller.morph.source; + inst_geom.instance_material = node.controllers[ i ].instance_material; + + node.geometries.push( inst_geom ); + morphController = node.controllers[ i ]; + + } + + console.log( 'ColladaLoader: Morph-controller partially supported.' ); + + default: + break; + + } + + } + + // geometries + + var double_sided_materials = {}; + + for ( i = 0; i < node.geometries.length; i ++ ) { + + var instance_geometry = node.geometries[i]; + var instance_materials = instance_geometry.instance_material; + var geometry = geometries[ instance_geometry.url ]; + var used_materials = {}; + var used_materials_array = []; + var num_materials = 0; + var first_material; + + if ( geometry ) { + + if ( !geometry.mesh || !geometry.mesh.primitives ) + continue; + + if ( obj.name.length == 0 ) { + + obj.name = geometry.id; + + } + + // collect used fx for this geometry-instance + + if ( instance_materials ) { + + for ( j = 0; j < instance_materials.length; j ++ ) { + + var instance_material = instance_materials[ j ]; + var mat = materials[ instance_material.target ]; + var effect_id = mat.instance_effect.url; + var shader = effects[ effect_id ].shader; + var material3js = shader.material; + + if ( geometry.doubleSided ) { + + if ( !( instance_material.symbol in double_sided_materials ) ) { + + var _copied_material = material3js.clone(); + _copied_material.side = THREE.DoubleSide; + double_sided_materials[ instance_material.symbol ] = _copied_material; + + } + + material3js = double_sided_materials[ instance_material.symbol ]; + + } + + material3js.opacity = !material3js.opacity ? 1 : material3js.opacity; + used_materials[ instance_material.symbol ] = num_materials; + used_materials_array.push( material3js ); + first_material = material3js; + first_material.name = mat.name == null || mat.name === '' ? mat.id : mat.name; + num_materials ++; + + } + + } + + var mesh; + var material = first_material || new THREE.MeshLambertMaterial( { color: 0xdddddd, shading: THREE.FlatShading, side: geometry.doubleSided ? THREE.DoubleSide : THREE.FrontSide } ); + var geom = geometry.mesh.geometry3js; + + if ( num_materials > 1 ) { + + material = new THREE.MeshFaceMaterial( used_materials_array ); + + for ( j = 0; j < geom.faces.length; j ++ ) { + + var face = geom.faces[ j ]; + face.materialIndex = used_materials[ face.daeMaterial ] + + } + + } + + if ( skinController !== undefined ) { + + + applySkin( geom, skinController ); + + if(geom.morphTargets.length > 0) { + + material.morphTargets = true; + material.skinning = false; + + } else { + + material.morphTargets = false; + material.skinning = true; + + } + + + mesh = new THREE.SkinnedMesh( geom, material, false ); + + + //mesh.skeleton = skinController.skeleton; + //mesh.skinController = controllers[ skinController.url ]; + //mesh.skinInstanceController = skinController; + mesh.name = 'skin_' + skins.length; + + + + //mesh.animationHandle.setKey(0); + skins.push( mesh ); + + } else if ( morphController !== undefined ) { + + createMorph( geom, morphController ); + + material.morphTargets = true; + + mesh = new THREE.Mesh( geom, material ); + mesh.name = 'morph_' + morphs.length; + + morphs.push( mesh ); + + } else { + + mesh = new THREE.Mesh( geom, material ); + // mesh.geom.name = geometry.id; + + } + + // N.B.: TP says this is not a great default behavior. It's a nice + // optimization to flatten the hierarchy but this should be done + // only if requested by the user via a flag. For now I undid it + // and fixed the character animation example that uses it + // node.geometries.length > 1 ? obj.add( mesh ) : obj = mesh; + obj.add(mesh); + + } + + } + + for ( i = 0; i < node.cameras.length; i ++ ) { + + var instance_camera = node.cameras[i]; + var cparams = cameras[instance_camera.url]; + + var cam = new THREE.PerspectiveCamera(cparams.yfov, parseFloat(cparams.aspect_ratio), + parseFloat(cparams.znear), parseFloat(cparams.zfar)); + + obj.add(cam); + } + + for ( i = 0; i < node.lights.length; i ++ ) { + + var light = null; + var instance_light = node.lights[i]; + var lparams = lights[instance_light.url]; + + if ( lparams && lparams.technique ) { + + var color = lparams.color.getHex(); + var intensity = lparams.intensity; + var distance = 0; + var angle = lparams.falloff_angle; + var exponent; // Intentionally undefined, don't know what this is yet + + switch ( lparams.technique ) { + + case 'directional': + + light = new THREE.DirectionalLight( color, intensity, distance ); + light.position.set(0, 0, 1); + break; + + case 'point': + + light = new THREE.PointLight( color, intensity, distance ); + break; + + case 'spot': + + light = new THREE.SpotLight( color, intensity, distance, angle, exponent ); + light.position.set(0, 0, 1); + break; + + case 'ambient': + + light = new THREE.AmbientLight( color ); + break; + + } + + } + + if (light) { + obj.add(light); + } + } + + obj.name = node.name || node.id || ""; + obj.id = node.id || ""; + obj.layer = node.layer || ""; + obj.matrix = node.matrix; + obj.matrix.decompose( obj.position, obj.quaternion, obj.scale ); + + if ( options.centerGeometry && obj.geometry ) { + + var delta = THREE.GeometryUtils.center( obj.geometry ); + delta.multiply( obj.scale ); + delta.applyQuaternion( obj.quaternion ); + + obj.position.sub( delta ); + + } + + for ( i = 0; i < node.nodes.length; i ++ ) { + + obj.add( createSceneGraph( node.nodes[i], node ) ); + + } + + return obj; + + }; + + function getJointId( skin, id ) { + + for ( var i = 0; i < skin.joints.length; i ++ ) { + + if ( skin.joints[ i ] == id ) { + + return i; + + } + + } + + }; + + function getLibraryNode( id ) { + + var nodes = COLLADA.querySelectorAll('library_nodes node'); + + for ( var i = 0; i < nodes.length; i++ ) { + + var attObj = nodes[i].attributes.getNamedItem('id'); + if ( attObj && attObj.value === id ) { + return nodes[i]; + } + } + + return undefined; + + }; + + function getChannelsForNode (node ) { + + var channels = []; + var startTime = 1000000; + var endTime = -1000000; + + for ( var id in animations ) { + + var animation = animations[id]; + + for ( var i = 0; i < animation.channel.length; i ++ ) { + + var channel = animation.channel[i]; + var sampler = animation.sampler[i]; + var id = channel.target.split('/')[0]; + + if ( id == node.id ) { + + sampler.create(); + channel.sampler = sampler; + startTime = Math.min(startTime, sampler.startTime); + endTime = Math.max(endTime, sampler.endTime); + channels.push(channel); + + } + + } + + } + + if ( channels.length ) { + + node.startTime = startTime; + node.endTime = endTime; + + } + + return channels; + + }; + + function calcFrameDuration( node ) { + + var minT = 10000000; + + for ( var i = 0; i < node.channels.length; i ++ ) { + + var sampler = node.channels[i].sampler; + + for ( var j = 0; j < sampler.input.length - 1; j ++ ) { + + var t0 = sampler.input[ j ]; + var t1 = sampler.input[ j + 1 ]; + minT = Math.min( minT, t1 - t0 ); + + } + } + + return minT; + + }; + + function calcMatrixAt( node, t ) { + + var animated = {}; + + var i, j; + + for ( i = 0; i < node.channels.length; i ++ ) { + + var channel = node.channels[ i ]; + animated[ channel.sid ] = channel; + + } + + var matrix = new THREE.Matrix4(); + + for ( i = 0; i < node.transforms.length; i ++ ) { + + var transform = node.transforms[ i ]; + var channel = animated[ transform.sid ]; + + if ( channel !== undefined ) { + + var sampler = channel.sampler; + var value; + + for ( j = 0; j < sampler.input.length - 1; j ++ ) { + + if ( sampler.input[ j + 1 ] > t ) { + + value = sampler.output[ j ]; + //console.log(value.flatten) + break; + + } + + } + + if ( value !== undefined ) { + + if ( value instanceof THREE.Matrix4 ) { + + matrix.multiplyMatrices( matrix, value ); + + } else { + + // FIXME: handle other types + + matrix.multiplyMatrices( matrix, transform.matrix ); + + } + + } else { + + matrix.multiplyMatrices( matrix, transform.matrix ); + + } + + } else { + + matrix.multiplyMatrices( matrix, transform.matrix ); + + } + + } + + return matrix; + + }; + + function bakeAnimations ( node ) { + + if ( node.channels && node.channels.length ) { + + var keys = [], + sids = []; + + for ( var i = 0, il = node.channels.length; i < il; i++ ) { + + var channel = node.channels[i], + fullSid = channel.fullSid, + sampler = channel.sampler, + input = sampler.input, + transform = node.getTransformBySid( channel.sid ), + member; + + if ( channel.arrIndices ) { + + member = []; + + for ( var j = 0, jl = channel.arrIndices.length; j < jl; j++ ) { + + member[ j ] = getConvertedIndex( channel.arrIndices[ j ] ); + + } + + } else { + + member = getConvertedMember( channel.member ); + + } + + if ( transform ) { + + if ( sids.indexOf( fullSid ) === -1 ) { + + sids.push( fullSid ); + + } + + for ( var j = 0, jl = input.length; j < jl; j++ ) { + + var time = input[j], + data = sampler.getData( transform.type, j, member ), + key = findKey( keys, time ); + + if ( !key ) { + + key = new Key( time ); + var timeNdx = findTimeNdx( keys, time ); + keys.splice( timeNdx == -1 ? keys.length : timeNdx, 0, key ); + + } + + key.addTarget( fullSid, transform, member, data ); + + } + + } else { + + console.log( 'Could not find transform "' + channel.sid + '" in node ' + node.id ); + + } + + } + + // post process + for ( var i = 0; i < sids.length; i++ ) { + + var sid = sids[ i ]; + + for ( var j = 0; j < keys.length; j++ ) { + + var key = keys[ j ]; + + if ( !key.hasTarget( sid ) ) { + + interpolateKeys( keys, key, j, sid ); + + } + + } + + } + + node.keys = keys; + node.sids = sids; + + } + + }; + + function findKey ( keys, time) { + + var retVal = null; + + for ( var i = 0, il = keys.length; i < il && retVal == null; i++ ) { + + var key = keys[i]; + + if ( key.time === time ) { + + retVal = key; + + } else if ( key.time > time ) { + + break; + + } + + } + + return retVal; + + }; + + function findTimeNdx ( keys, time) { + + var ndx = -1; + + for ( var i = 0, il = keys.length; i < il && ndx == -1; i++ ) { + + var key = keys[i]; + + if ( key.time >= time ) { + + ndx = i; + + } + + } + + return ndx; + + }; + + function interpolateKeys ( keys, key, ndx, fullSid ) { + + var prevKey = getPrevKeyWith( keys, fullSid, ndx ? ndx-1 : 0 ), + nextKey = getNextKeyWith( keys, fullSid, ndx+1 ); + + if ( prevKey && nextKey ) { + + var scale = (key.time - prevKey.time) / (nextKey.time - prevKey.time), + prevTarget = prevKey.getTarget( fullSid ), + nextData = nextKey.getTarget( fullSid ).data, + prevData = prevTarget.data, + data; + + if ( prevTarget.type === 'matrix' ) { + + data = prevData; + + } else if ( prevData.length ) { + + data = []; + + for ( var i = 0; i < prevData.length; ++i ) { + + data[ i ] = prevData[ i ] + ( nextData[ i ] - prevData[ i ] ) * scale; + + } + + } else { + + data = prevData + ( nextData - prevData ) * scale; + + } + + key.addTarget( fullSid, prevTarget.transform, prevTarget.member, data ); + + } + + }; + + // Get next key with given sid + + function getNextKeyWith( keys, fullSid, ndx ) { + + for ( ; ndx < keys.length; ndx++ ) { + + var key = keys[ ndx ]; + + if ( key.hasTarget( fullSid ) ) { + + return key; + + } + + } + + return null; + + }; + + // Get previous key with given sid + + function getPrevKeyWith( keys, fullSid, ndx ) { + + ndx = ndx >= 0 ? ndx : ndx + keys.length; + + for ( ; ndx >= 0; ndx-- ) { + + var key = keys[ ndx ]; + + if ( key.hasTarget( fullSid ) ) { + + return key; + + } + + } + + return null; + + }; + + function _Image() { + + this.id = ""; + this.init_from = ""; + + }; + + _Image.prototype.parse = function(element) { + + this.id = element.getAttribute('id'); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + if ( child.nodeName == 'init_from' ) { + + this.init_from = child.textContent; + + } + + } + + return this; + + }; + + function Controller() { + + this.id = ""; + this.name = ""; + this.type = ""; + this.skin = null; + this.morph = null; + + }; + + Controller.prototype.parse = function( element ) { + + this.id = element.getAttribute('id'); + this.name = element.getAttribute('name'); + this.type = "none"; + + for ( var i = 0; i < element.childNodes.length; i++ ) { + + var child = element.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'skin': + + this.skin = (new Skin()).parse(child); + this.type = child.nodeName; + break; + + case 'morph': + + this.morph = (new Morph()).parse(child); + this.type = child.nodeName; + break; + + default: + break; + + } + } + + return this; + + }; + + function Morph() { + + this.method = null; + this.source = null; + this.targets = null; + this.weights = null; + + }; + + Morph.prototype.parse = function( element ) { + + var sources = {}; + var inputs = []; + var i; + + this.method = element.getAttribute( 'method' ); + this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); + + for ( i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'source': + + var source = ( new Source() ).parse( child ); + sources[ source.id ] = source; + break; + + case 'targets': + + inputs = this.parseInputs( child ); + break; + + default: + + console.log( child.nodeName ); + break; + + } + + } + + for ( i = 0; i < inputs.length; i ++ ) { + + var input = inputs[ i ]; + var source = sources[ input.source ]; + + switch ( input.semantic ) { + + case 'MORPH_TARGET': + + this.targets = source.read(); + break; + + case 'MORPH_WEIGHT': + + this.weights = source.read(); + break; + + default: + break; + + } + } + + return this; + + }; + + Morph.prototype.parseInputs = function(element) { + + var inputs = []; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + if ( child.nodeType != 1) continue; + + switch ( child.nodeName ) { + + case 'input': + + inputs.push( (new Input()).parse(child) ); + break; + + default: + break; + } + } + + return inputs; + + }; + + function Skin() { + + this.source = ""; + this.bindShapeMatrix = null; + this.invBindMatrices = []; + this.joints = []; + this.weights = []; + + }; + + Skin.prototype.parse = function( element ) { + + var sources = {}; + var joints, weights; + + this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); + this.invBindMatrices = []; + this.joints = []; + this.weights = []; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'bind_shape_matrix': + + var f = _floats(child.textContent); + this.bindShapeMatrix = getConvertedMat4( f ); + break; + + case 'source': + + var src = new Source().parse(child); + sources[ src.id ] = src; + break; + + case 'joints': + + joints = child; + break; + + case 'vertex_weights': + + weights = child; + break; + + default: + + console.log( child.nodeName ); + break; + + } + } + + this.parseJoints( joints, sources ); + this.parseWeights( weights, sources ); + + return this; + + }; + + Skin.prototype.parseJoints = function ( element, sources ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + + var input = ( new Input() ).parse( child ); + var source = sources[ input.source ]; + + if ( input.semantic == 'JOINT' ) { + + this.joints = source.read(); + + } else if ( input.semantic == 'INV_BIND_MATRIX' ) { + + this.invBindMatrices = source.read(); + + } + + break; + + default: + break; + } + + } + + }; + + Skin.prototype.parseWeights = function ( element, sources ) { + + var v, vcount, inputs = []; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + + inputs.push( ( new Input() ).parse( child ) ); + break; + + case 'v': + + v = _ints( child.textContent ); + break; + + case 'vcount': + + vcount = _ints( child.textContent ); + break; + + default: + break; + + } + + } + + var index = 0; + + for ( var i = 0; i < vcount.length; i ++ ) { + + var numBones = vcount[i]; + var vertex_weights = []; + + for ( var j = 0; j < numBones; j++ ) { + + var influence = {}; + + for ( var k = 0; k < inputs.length; k ++ ) { + + var input = inputs[ k ]; + var value = v[ index + input.offset ]; + + switch ( input.semantic ) { + + case 'JOINT': + + influence.joint = value;//this.joints[value]; + break; + + case 'WEIGHT': + + influence.weight = sources[ input.source ].data[ value ]; + break; + + default: + break; + + } + + } + + vertex_weights.push( influence ); + index += inputs.length; + } + + for ( var j = 0; j < vertex_weights.length; j ++ ) { + + vertex_weights[ j ].index = i; + + } + + this.weights.push( vertex_weights ); + + } + + }; + + function VisualScene () { + + this.id = ""; + this.name = ""; + this.nodes = []; + this.scene = new THREE.Object3D(); + + }; + + VisualScene.prototype.getChildById = function( id, recursive ) { + + for ( var i = 0; i < this.nodes.length; i ++ ) { + + var node = this.nodes[ i ].getChildById( id, recursive ); + + if ( node ) { + + return node; + + } + + } + + return null; + + }; + + VisualScene.prototype.getChildBySid = function( sid, recursive ) { + + for ( var i = 0; i < this.nodes.length; i ++ ) { + + var node = this.nodes[ i ].getChildBySid( sid, recursive ); + + if ( node ) { + + return node; + + } + + } + + return null; + + }; + + VisualScene.prototype.parse = function( element ) { + + this.id = element.getAttribute( 'id' ); + this.name = element.getAttribute( 'name' ); + this.nodes = []; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'node': + + this.nodes.push( ( new Node() ).parse( child ) ); + break; + + default: + break; + + } + + } + + return this; + + }; + + function Node() { + + this.id = ""; + this.name = ""; + this.sid = ""; + this.nodes = []; + this.controllers = []; + this.transforms = []; + this.geometries = []; + this.channels = []; + this.matrix = new THREE.Matrix4(); + + }; + + Node.prototype.getChannelForTransform = function( transformSid ) { + + for ( var i = 0; i < this.channels.length; i ++ ) { + + var channel = this.channels[i]; + var parts = channel.target.split('/'); + var id = parts.shift(); + var sid = parts.shift(); + var dotSyntax = (sid.indexOf(".") >= 0); + var arrSyntax = (sid.indexOf("(") >= 0); + var arrIndices; + var member; + + if ( dotSyntax ) { + + parts = sid.split("."); + sid = parts.shift(); + member = parts.shift(); + + } else if ( arrSyntax ) { + + arrIndices = sid.split("("); + sid = arrIndices.shift(); + + for ( var j = 0; j < arrIndices.length; j ++ ) { + + arrIndices[ j ] = parseInt( arrIndices[ j ].replace( /\)/, '' ) ); + + } + + } + + if ( sid == transformSid ) { + + channel.info = { sid: sid, dotSyntax: dotSyntax, arrSyntax: arrSyntax, arrIndices: arrIndices }; + return channel; + + } + + } + + return null; + + }; + + Node.prototype.getChildById = function ( id, recursive ) { + + if ( this.id == id ) { + + return this; + + } + + if ( recursive ) { + + for ( var i = 0; i < this.nodes.length; i ++ ) { + + var n = this.nodes[ i ].getChildById( id, recursive ); + + if ( n ) { + + return n; + + } + + } + + } + + return null; + + }; + + Node.prototype.getChildBySid = function ( sid, recursive ) { + + if ( this.sid == sid ) { + + return this; + + } + + if ( recursive ) { + + for ( var i = 0; i < this.nodes.length; i ++ ) { + + var n = this.nodes[ i ].getChildBySid( sid, recursive ); + + if ( n ) { + + return n; + + } + + } + } + + return null; + + }; + + Node.prototype.getTransformBySid = function ( sid ) { + + for ( var i = 0; i < this.transforms.length; i ++ ) { + + if ( this.transforms[ i ].sid == sid ) return this.transforms[ i ]; + + } + + return null; + + }; + + Node.prototype.parse = function( element ) { + + var url; + + this.id = element.getAttribute('id'); + this.sid = element.getAttribute('sid'); + this.name = element.getAttribute('name'); + this.type = element.getAttribute('type'); + this.layer = element.getAttribute('layer'); + + this.type = this.type == 'JOINT' ? this.type : 'NODE'; + + this.nodes = []; + this.transforms = []; + this.geometries = []; + this.cameras = []; + this.lights = []; + this.controllers = []; + this.matrix = new THREE.Matrix4(); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'node': + + this.nodes.push( ( new Node() ).parse( child ) ); + break; + + case 'instance_camera': + + this.cameras.push( ( new InstanceCamera() ).parse( child ) ); + break; + + case 'instance_controller': + + this.controllers.push( ( new InstanceController() ).parse( child ) ); + break; + + case 'instance_geometry': + + this.geometries.push( ( new InstanceGeometry() ).parse( child ) ); + break; + + case 'instance_light': + + this.lights.push( ( new InstanceLight() ).parse( child ) ); + break; + + case 'instance_node': + + url = child.getAttribute( 'url' ).replace( /^#/, '' ); + var iNode = getLibraryNode( url ); + + if ( iNode ) { + + this.nodes.push( ( new Node() ).parse( iNode )) ; + + } + + break; + + case 'rotate': + case 'translate': + case 'scale': + case 'matrix': + case 'lookat': + case 'skew': + + this.transforms.push( ( new Transform() ).parse( child ) ); + break; + + case 'extra': + break; + + default: + + console.log( child.nodeName ); + break; + + } + + } + + this.channels = getChannelsForNode( this ); + bakeAnimations( this ); + + this.updateMatrix(); + + return this; + + }; + + Node.prototype.updateMatrix = function () { + + this.matrix.identity(); + + for ( var i = 0; i < this.transforms.length; i ++ ) { + + this.transforms[ i ].apply( this.matrix ); + + } + + }; + + function Transform () { + + this.sid = ""; + this.type = ""; + this.data = []; + this.obj = null; + + }; + + Transform.prototype.parse = function ( element ) { + + this.sid = element.getAttribute( 'sid' ); + this.type = element.nodeName; + this.data = _floats( element.textContent ); + this.convert(); + + return this; + + }; + + Transform.prototype.convert = function () { + + switch ( this.type ) { + + case 'matrix': + + this.obj = getConvertedMat4( this.data ); + break; + + case 'rotate': + + this.angle = THREE.Math.degToRad( this.data[3] ); + + case 'translate': + + fixCoords( this.data, -1 ); + this.obj = new THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] ); + break; + + case 'scale': + + fixCoords( this.data, 1 ); + this.obj = new THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] ); + break; + + default: + console.log( 'Can not convert Transform of type ' + this.type ); + break; + + } + + }; + + Transform.prototype.apply = function () { + + var m1 = new THREE.Matrix4(); + + return function ( matrix ) { + + switch ( this.type ) { + + case 'matrix': + + matrix.multiply( this.obj ); + + break; + + case 'translate': + + matrix.multiply( m1.makeTranslation( this.obj.x, this.obj.y, this.obj.z ) ); + + break; + + case 'rotate': + + matrix.multiply( m1.makeRotationAxis( this.obj, this.angle ) ); + + break; + + case 'scale': + + matrix.scale( this.obj ); + + break; + + } + + }; + + }(); + + Transform.prototype.update = function ( data, member ) { + + var members = [ 'X', 'Y', 'Z', 'ANGLE' ]; + + switch ( this.type ) { + + case 'matrix': + + if ( ! member ) { + + this.obj.copy( data ); + + } else if ( member.length === 1 ) { + + switch ( member[ 0 ] ) { + + case 0: + + this.obj.n11 = data[ 0 ]; + this.obj.n21 = data[ 1 ]; + this.obj.n31 = data[ 2 ]; + this.obj.n41 = data[ 3 ]; + + break; + + case 1: + + this.obj.n12 = data[ 0 ]; + this.obj.n22 = data[ 1 ]; + this.obj.n32 = data[ 2 ]; + this.obj.n42 = data[ 3 ]; + + break; + + case 2: + + this.obj.n13 = data[ 0 ]; + this.obj.n23 = data[ 1 ]; + this.obj.n33 = data[ 2 ]; + this.obj.n43 = data[ 3 ]; + + break; + + case 3: + + this.obj.n14 = data[ 0 ]; + this.obj.n24 = data[ 1 ]; + this.obj.n34 = data[ 2 ]; + this.obj.n44 = data[ 3 ]; + + break; + + } + + } else if ( member.length === 2 ) { + + var propName = 'n' + ( member[ 0 ] + 1 ) + ( member[ 1 ] + 1 ); + this.obj[ propName ] = data; + + } else { + + console.log('Incorrect addressing of matrix in transform.'); + + } + + break; + + case 'translate': + case 'scale': + + if ( Object.prototype.toString.call( member ) === '[object Array]' ) { + + member = members[ member[ 0 ] ]; + + } + + switch ( member ) { + + case 'X': + + this.obj.x = data; + break; + + case 'Y': + + this.obj.y = data; + break; + + case 'Z': + + this.obj.z = data; + break; + + default: + + this.obj.x = data[ 0 ]; + this.obj.y = data[ 1 ]; + this.obj.z = data[ 2 ]; + break; + + } + + break; + + case 'rotate': + + if ( Object.prototype.toString.call( member ) === '[object Array]' ) { + + member = members[ member[ 0 ] ]; + + } + + switch ( member ) { + + case 'X': + + this.obj.x = data; + break; + + case 'Y': + + this.obj.y = data; + break; + + case 'Z': + + this.obj.z = data; + break; + + case 'ANGLE': + + this.angle = THREE.Math.degToRad( data ); + break; + + default: + + this.obj.x = data[ 0 ]; + this.obj.y = data[ 1 ]; + this.obj.z = data[ 2 ]; + this.angle = THREE.Math.degToRad( data[ 3 ] ); + break; + + } + break; + + } + + }; + + function InstanceController() { + + this.url = ""; + this.skeleton = []; + this.instance_material = []; + + }; + + InstanceController.prototype.parse = function ( element ) { + + this.url = element.getAttribute('url').replace(/^#/, ''); + this.skeleton = []; + this.instance_material = []; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'skeleton': + + this.skeleton.push( child.textContent.replace(/^#/, '') ); + break; + + case 'bind_material': + + var instances = child.querySelectorAll('instance_material'); + + for ( var j = 0; j < instances.length; j ++ ){ + + var instance = instances[j]; + this.instance_material.push( (new InstanceMaterial()).parse(instance) ); + + } + + + break; + + case 'extra': + break; + + default: + break; + + } + } + + return this; + + }; + + function InstanceMaterial () { + + this.symbol = ""; + this.target = ""; + + }; + + InstanceMaterial.prototype.parse = function ( element ) { + + this.symbol = element.getAttribute('symbol'); + this.target = element.getAttribute('target').replace(/^#/, ''); + return this; + + }; + + function InstanceGeometry() { + + this.url = ""; + this.instance_material = []; + + }; + + InstanceGeometry.prototype.parse = function ( element ) { + + this.url = element.getAttribute('url').replace(/^#/, ''); + this.instance_material = []; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + if ( child.nodeType != 1 ) continue; + + if ( child.nodeName == 'bind_material' ) { + + var instances = child.querySelectorAll('instance_material'); + + for ( var j = 0; j < instances.length; j ++ ) { + + var instance = instances[j]; + this.instance_material.push( (new InstanceMaterial()).parse(instance) ); + + } + + break; + + } + + } + + return this; + + }; + + function Geometry() { + + this.id = ""; + this.mesh = null; + + }; + + Geometry.prototype.parse = function ( element ) { + + this.id = element.getAttribute('id'); + + extractDoubleSided( this, element ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + + switch ( child.nodeName ) { + + case 'mesh': + + this.mesh = (new Mesh(this)).parse(child); + break; + + case 'extra': + + // console.log( child ); + break; + + default: + break; + } + } + + return this; + + }; + + function Mesh( geometry ) { + + this.geometry = geometry.id; + this.primitives = []; + this.vertices = null; + this.geometry3js = null; + + }; + + Mesh.prototype.parse = function( element ) { + + this.primitives = []; + + var i, j; + + for ( i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'source': + + _source( child ); + break; + + case 'vertices': + + this.vertices = ( new Vertices() ).parse( child ); + break; + + case 'triangles': + + this.primitives.push( ( new Triangles().parse( child ) ) ); + break; + + case 'polygons': + + this.primitives.push( ( new Polygons().parse( child ) ) ); + break; + + case 'polylist': + + this.primitives.push( ( new Polylist().parse( child ) ) ); + break; + + default: + break; + + } + + } + + this.geometry3js = new THREE.Geometry(); + + var vertexData = sources[ this.vertices.input['POSITION'].source ].data; + + for ( i = 0; i < vertexData.length; i += 3 ) { + + this.geometry3js.vertices.push( getConvertedVec3( vertexData, i ).clone() ); + + } + + for ( i = 0; i < this.primitives.length; i ++ ) { + + var primitive = this.primitives[ i ]; + primitive.setVertices( this.vertices ); + this.handlePrimitive( primitive, this.geometry3js ); + + } + + this.geometry3js.computeFaceNormals(); + + if ( this.geometry3js.calcNormals ) { + + this.geometry3js.computeVertexNormals(); + delete this.geometry3js.calcNormals; + + } + + // this.geometry3js.computeBoundingBox(); + + return this; + + }; + + Mesh.prototype.handlePrimitive = function( primitive, geom ) { + + var j, k, pList = primitive.p, inputs = primitive.inputs; + var input, index, idx32; + var source, numParams; + var vcIndex = 0, vcount = 3, maxOffset = 0; + var texture_sets = []; + + for ( j = 0; j < inputs.length; j ++ ) { + + input = inputs[ j ]; + + var offset = input.offset + 1; + maxOffset = (maxOffset < offset)? offset : maxOffset; + + switch ( input.semantic ) { + + case 'TEXCOORD': + texture_sets.push( input.set ); + break; + + } + + } + + for ( var pCount = 0; pCount < pList.length; ++pCount ) { + + var p = pList[ pCount ], i = 0; + + while ( i < p.length ) { + + var vs = []; + var ns = []; + var ts = null; + var cs = []; + + if ( primitive.vcount ) { + + vcount = primitive.vcount.length ? primitive.vcount[ vcIndex ++ ] : primitive.vcount; + + } else { + + vcount = p.length / maxOffset; + + } + + + for ( j = 0; j < vcount; j ++ ) { + + for ( k = 0; k < inputs.length; k ++ ) { + + input = inputs[ k ]; + source = sources[ input.source ]; + + index = p[ i + ( j * maxOffset ) + input.offset ]; + numParams = source.accessor.params.length; + idx32 = index * numParams; + + switch ( input.semantic ) { + + case 'VERTEX': + + vs.push( index ); + + break; + + case 'NORMAL': + + ns.push( getConvertedVec3( source.data, idx32 ) ); + + break; + + case 'TEXCOORD': + + ts = ts || { }; + if ( ts[ input.set ] === undefined ) ts[ input.set ] = []; + // invert the V + ts[ input.set ].push( new THREE.Vector2( source.data[ idx32 ], source.data[ idx32 + 1 ] ) ); + + break; + + case 'COLOR': + + cs.push( new THREE.Color().setRGB( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) ); + + break; + + default: + + break; + + } + + } + + } + + if ( ns.length == 0 ) { + + // check the vertices inputs + input = this.vertices.input.NORMAL; + + if ( input ) { + + source = sources[ input.source ]; + numParams = source.accessor.params.length; + + for ( var ndx = 0, len = vs.length; ndx < len; ndx++ ) { + + ns.push( getConvertedVec3( source.data, vs[ ndx ] * numParams ) ); + + } + + } else { + + geom.calcNormals = true; + + } + + } + + if ( !ts ) { + + ts = { }; + // check the vertices inputs + input = this.vertices.input.TEXCOORD; + + if ( input ) { + + texture_sets.push( input.set ); + source = sources[ input.source ]; + numParams = source.accessor.params.length; + + for ( var ndx = 0, len = vs.length; ndx < len; ndx++ ) { + + idx32 = vs[ ndx ] * numParams; + if ( ts[ input.set ] === undefined ) ts[ input.set ] = [ ]; + // invert the V + ts[ input.set ].push( new THREE.Vector2( source.data[ idx32 ], 1.0 - source.data[ idx32 + 1 ] ) ); + + } + + } + + } + + if ( cs.length == 0 ) { + + // check the vertices inputs + input = this.vertices.input.COLOR; + + if ( input ) { + + source = sources[ input.source ]; + numParams = source.accessor.params.length; + + for ( var ndx = 0, len = vs.length; ndx < len; ndx++ ) { + + idx32 = vs[ ndx ] * numParams; + cs.push( new THREE.Color().setRGB( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) ); + + } + + } + + } + + var face = null, faces = [], uv, uvArr; + + if ( vcount === 3 ) { + + faces.push( new THREE.Face3( vs[0], vs[1], vs[2], ns, cs.length ? cs : new THREE.Color() ) ); + + } else if ( vcount === 4 ) { + + faces.push( new THREE.Face3( vs[0], vs[1], vs[3], [ns[0], ns[1], ns[3]], cs.length ? [cs[0], cs[1], cs[3]] : new THREE.Color() ) ); + + faces.push( new THREE.Face3( vs[1], vs[2], vs[3], [ns[1], ns[2], ns[3]], cs.length ? [cs[1], cs[2], cs[3]] : new THREE.Color() ) ); + + } else if ( vcount > 4 && options.subdivideFaces ) { + + var clr = cs.length ? cs : new THREE.Color(), + vec1, vec2, vec3, v1, v2, norm; + + // subdivide into multiple Face3s + + for ( k = 1; k < vcount - 1; ) { + + // FIXME: normals don't seem to be quite right + + faces.push( new THREE.Face3( vs[0], vs[k], vs[k+1], [ ns[0], ns[k++], ns[k] ], clr ) ); + + } + + } + + if ( faces.length ) { + + for ( var ndx = 0, len = faces.length; ndx < len; ndx ++ ) { + + face = faces[ndx]; + face.daeMaterial = primitive.material; + geom.faces.push( face ); + + for ( k = 0; k < texture_sets.length; k++ ) { + + uv = ts[ texture_sets[k] ]; + + if ( vcount > 4 ) { + + // Grab the right UVs for the vertices in this face + uvArr = [ uv[0], uv[ndx+1], uv[ndx+2] ]; + + } else if ( vcount === 4 ) { + + if ( ndx === 0 ) { + + uvArr = [ uv[0], uv[1], uv[3] ]; + + } else { + + uvArr = [ uv[1].clone(), uv[2], uv[3].clone() ]; + + } + + } else { + + uvArr = [ uv[0], uv[1], uv[2] ]; + + } + + if ( geom.faceVertexUvs[k] === undefined ) { + + geom.faceVertexUvs[k] = []; + + } + + geom.faceVertexUvs[k].push( uvArr ); + + } + + } + + } else { + + console.log( 'dropped face with vcount ' + vcount + ' for geometry with id: ' + geom.id ); + + } + + i += maxOffset * vcount; + + } + } + + }; + + function Polygons () { + + this.material = ""; + this.count = 0; + this.inputs = []; + this.vcount = null; + this.p = []; + this.geometry = new THREE.Geometry(); + + }; + + Polygons.prototype.setVertices = function ( vertices ) { + + for ( var i = 0; i < this.inputs.length; i ++ ) { + + if ( this.inputs[ i ].source == vertices.id ) { + + this.inputs[ i ].source = vertices.input[ 'POSITION' ].source; + + } + + } + + }; + + Polygons.prototype.parse = function ( element ) { + + this.material = element.getAttribute( 'material' ); + this.count = _attr_as_int( element, 'count', 0 ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'input': + + this.inputs.push( ( new Input() ).parse( element.childNodes[ i ] ) ); + break; + + case 'vcount': + + this.vcount = _ints( child.textContent ); + break; + + case 'p': + + this.p.push( _ints( child.textContent ) ); + break; + + case 'ph': + + console.warn( 'polygon holes not yet supported!' ); + break; + + default: + break; + + } + + } + + return this; + + }; + + function Polylist () { + + Polygons.call( this ); + + this.vcount = []; + + }; + + Polylist.prototype = Object.create( Polygons.prototype ); + + function Triangles () { + + Polygons.call( this ); + + this.vcount = 3; + + }; + + Triangles.prototype = Object.create( Polygons.prototype ); + + function Accessor() { + + this.source = ""; + this.count = 0; + this.stride = 0; + this.params = []; + + }; + + Accessor.prototype.parse = function ( element ) { + + this.params = []; + this.source = element.getAttribute( 'source' ); + this.count = _attr_as_int( element, 'count', 0 ); + this.stride = _attr_as_int( element, 'stride', 0 ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + if ( child.nodeName == 'param' ) { + + var param = {}; + param[ 'name' ] = child.getAttribute( 'name' ); + param[ 'type' ] = child.getAttribute( 'type' ); + this.params.push( param ); + + } + + } + + return this; + + }; + + function Vertices() { + + this.input = {}; + + }; + + Vertices.prototype.parse = function ( element ) { + + this.id = element.getAttribute('id'); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + if ( element.childNodes[i].nodeName == 'input' ) { + + var input = ( new Input() ).parse( element.childNodes[ i ] ); + this.input[ input.semantic ] = input; + + } + + } + + return this; + + }; + + function Input () { + + this.semantic = ""; + this.offset = 0; + this.source = ""; + this.set = 0; + + }; + + Input.prototype.parse = function ( element ) { + + this.semantic = element.getAttribute('semantic'); + this.source = element.getAttribute('source').replace(/^#/, ''); + this.set = _attr_as_int(element, 'set', -1); + this.offset = _attr_as_int(element, 'offset', 0); + + if ( this.semantic == 'TEXCOORD' && this.set < 0 ) { + + this.set = 0; + + } + + return this; + + }; + + function Source ( id ) { + + this.id = id; + this.type = null; + + }; + + Source.prototype.parse = function ( element ) { + + this.id = element.getAttribute( 'id' ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + + switch ( child.nodeName ) { + + case 'bool_array': + + this.data = _bools( child.textContent ); + this.type = child.nodeName; + break; + + case 'float_array': + + this.data = _floats( child.textContent ); + this.type = child.nodeName; + break; + + case 'int_array': + + this.data = _ints( child.textContent ); + this.type = child.nodeName; + break; + + case 'IDREF_array': + case 'Name_array': + + this.data = _strings( child.textContent ); + this.type = child.nodeName; + break; + + case 'technique_common': + + for ( var j = 0; j < child.childNodes.length; j ++ ) { + + if ( child.childNodes[ j ].nodeName == 'accessor' ) { + + this.accessor = ( new Accessor() ).parse( child.childNodes[ j ] ); + break; + + } + } + break; + + default: + // console.log(child.nodeName); + break; + + } + + } + + return this; + + }; + + Source.prototype.read = function () { + + var result = []; + + //for (var i = 0; i < this.accessor.params.length; i++) { + + var param = this.accessor.params[ 0 ]; + + //console.log(param.name + " " + param.type); + + switch ( param.type ) { + + case 'IDREF': + case 'Name': case 'name': + case 'float': + + return this.data; + + case 'float4x4': + + for ( var j = 0; j < this.data.length; j += 16 ) { + + var s = this.data.slice( j, j + 16 ); + var m = getConvertedMat4( s ); + result.push( m ); + } + + break; + + default: + + console.log( 'ColladaLoader: Source: Read dont know how to read ' + param.type + '.' ); + break; + + } + + //} + + return result; + + }; + + function Material () { + + this.id = ""; + this.name = ""; + this.instance_effect = null; + + }; + + Material.prototype.parse = function ( element ) { + + this.id = element.getAttribute( 'id' ); + this.name = element.getAttribute( 'name' ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + if ( element.childNodes[ i ].nodeName == 'instance_effect' ) { + + this.instance_effect = ( new InstanceEffect() ).parse( element.childNodes[ i ] ); + break; + + } + + } + + return this; + + }; + + function ColorOrTexture () { + + this.color = new THREE.Color(); + this.color.setRGB( Math.random(), Math.random(), Math.random() ); + this.color.a = 1.0; + + this.texture = null; + this.texcoord = null; + this.texOpts = null; + + }; + + ColorOrTexture.prototype.isColor = function () { + + return ( this.texture == null ); + + }; + + ColorOrTexture.prototype.isTexture = function () { + + return ( this.texture != null ); + + }; + + ColorOrTexture.prototype.parse = function ( element ) { + + if (element.nodeName == 'transparent') { + + this.opaque = element.getAttribute('opaque'); + + } + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'color': + + var rgba = _floats( child.textContent ); + this.color = new THREE.Color(); + this.color.setRGB( rgba[0], rgba[1], rgba[2] ); + this.color.a = rgba[3]; + break; + + case 'texture': + + this.texture = child.getAttribute('texture'); + this.texcoord = child.getAttribute('texcoord'); + // Defaults from: + // https://collada.org/mediawiki/index.php/Maya_texture_placement_MAYA_extension + this.texOpts = { + offsetU: 0, + offsetV: 0, + repeatU: 1, + repeatV: 1, + wrapU: 1, + wrapV: 1 + }; + this.parseTexture( child ); + break; + + default: + break; + + } + + } + + return this; + + }; + + ColorOrTexture.prototype.parseTexture = function ( element ) { + + if ( ! element.childNodes ) return this; + + // This should be supported by Maya, 3dsMax, and MotionBuilder + + if ( element.childNodes[1] && element.childNodes[1].nodeName === 'extra' ) { + + element = element.childNodes[1]; + + if ( element.childNodes[1] && element.childNodes[1].nodeName === 'technique' ) { + + element = element.childNodes[1]; + + } + + } + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'offsetU': + case 'offsetV': + case 'repeatU': + case 'repeatV': + + this.texOpts[ child.nodeName ] = parseFloat( child.textContent ); + + break; + + case 'wrapU': + case 'wrapV': + + // some dae have a value of true which becomes NaN via parseInt + + if ( child.textContent.toUpperCase() === 'TRUE' ) { + + this.texOpts[ child.nodeName ] = 1; + + } else { + + this.texOpts[ child.nodeName ] = parseInt( child.textContent ); + + } + break; + + default: + + this.texOpts[ child.nodeName ] = child.textContent; + + break; + + } + + } + + return this; + + }; + + function Shader ( type, effect ) { + + this.type = type; + this.effect = effect; + this.material = null; + + }; + + Shader.prototype.parse = function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'ambient': + case 'emission': + case 'diffuse': + case 'specular': + case 'transparent': + + this[ child.nodeName ] = ( new ColorOrTexture() ).parse( child ); + break; + + case 'bump': + + // If 'bumptype' is 'heightfield', create a 'bump' property + // Else if 'bumptype' is 'normalmap', create a 'normal' property + // (Default to 'bump') + var bumpType = child.getAttribute( 'bumptype' ); + if ( bumpType ) { + if ( bumpType.toLowerCase() === "heightfield" ) { + this[ 'bump' ] = ( new ColorOrTexture() ).parse( child ); + } else if ( bumpType.toLowerCase() === "normalmap" ) { + this[ 'normal' ] = ( new ColorOrTexture() ).parse( child ); + } else { + console.error( "Shader.prototype.parse: Invalid value for attribute 'bumptype' (" + bumpType + + ") - valid bumptypes are 'HEIGHTFIELD' and 'NORMALMAP' - defaulting to 'HEIGHTFIELD'" ); + this[ 'bump' ] = ( new ColorOrTexture() ).parse( child ); + } + } else { + console.warn( "Shader.prototype.parse: Attribute 'bumptype' missing from bump node - defaulting to 'HEIGHTFIELD'" ); + this[ 'bump' ] = ( new ColorOrTexture() ).parse( child ); + } + + break; + + case 'shininess': + case 'reflectivity': + case 'index_of_refraction': + case 'transparency': + + var f = child.querySelectorAll('float'); + + if ( f.length > 0 ) + this[ child.nodeName ] = parseFloat( f[ 0 ].textContent ); + + break; + + default: + break; + + } + + } + + this.create(); + return this; + + }; + + Shader.prototype.create = function() { + + var props = {}; + + var transparent = false; + + if (this['transparency'] !== undefined && this['transparent'] !== undefined) { + // convert transparent color RBG to average value + var transparentColor = this['transparent']; + var transparencyLevel = (this.transparent.color.r + + this.transparent.color.g + + this.transparent.color.b) + / 3 * this.transparency; + + if (transparencyLevel > 0) { + transparent = true; + props[ 'transparent' ] = true; + props[ 'opacity' ] = 1 - transparencyLevel; + + } + + } + + var keys = { + 'diffuse':'map', + 'ambient':'lightMap' , + 'specular':'specularMap', + 'emission':'emissionMap', + 'bump':'bumpMap', + 'normal':'normalMap' + }; + + for ( var prop in this ) { + + switch ( prop ) { + + case 'ambient': + case 'emission': + case 'diffuse': + case 'specular': + case 'bump': + case 'normal': + + var cot = this[ prop ]; + + if ( cot instanceof ColorOrTexture ) { + + if ( cot.isTexture() ) { + + var samplerId = cot.texture; + var surfaceId = this.effect.sampler[samplerId]; + + if ( surfaceId !== undefined && surfaceId.source !== undefined ) { + + var surface = this.effect.surface[surfaceId.source]; + var image = images[surface.init_from]; + + if (image) { + + var texture = THREE.ImageUtils.loadTexture(baseUrl + image.init_from); + texture.wrapS = cot.texOpts.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + texture.wrapT = cot.texOpts.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + texture.offset.x = cot.texOpts.offsetU; + texture.offset.y = cot.texOpts.offsetV; + texture.repeat.x = cot.texOpts.repeatU; + texture.repeat.y = cot.texOpts.repeatV; + props[keys[prop]] = texture; + + // Texture with baked lighting? + if (prop === 'emission') props['emissive'] = 0xffffff; + + } + + } + + } else if ( prop === 'diffuse' || !transparent ) { + + if ( prop === 'emission' ) { + + props[ 'emissive' ] = cot.color.getHex(); + + } else { + + props[ prop ] = cot.color.getHex(); + + } + + } + + } + + break; + + case 'shininess': + + props[ prop ] = this[ prop ]; + break; + + case 'reflectivity': + + props[ prop ] = this[ prop ]; + if( props[ prop ] > 0.0 ) props['envMap'] = options.defaultEnvMap; + props['combine'] = THREE.MixOperation; //mix regular shading with reflective component + break; + + case 'index_of_refraction': + + props[ 'refractionRatio' ] = this[ prop ]; //TODO: "index_of_refraction" becomes "refractionRatio" in shader, but I'm not sure if the two are actually comparable + if ( this[ prop ] !== 1.0 ) props['envMap'] = options.defaultEnvMap; + break; + + case 'transparency': + // gets figured out up top + break; + + default: + break; + + } + + } + + props[ 'shading' ] = preferredShading; + props[ 'side' ] = this.effect.doubleSided ? THREE.DoubleSide : THREE.FrontSide; + + switch ( this.type ) { + + case 'constant': + + if (props.emissive != undefined) props.color = props.emissive; + this.material = new THREE.MeshBasicMaterial( props ); + break; + + case 'phong': + case 'blinn': + + if (props.diffuse != undefined) props.color = props.diffuse; + this.material = new THREE.MeshPhongMaterial( props ); + break; + + case 'lambert': + default: + + if (props.diffuse != undefined) props.color = props.diffuse; + this.material = new THREE.MeshLambertMaterial( props ); + break; + + } + + return this.material; + + }; + + function Surface ( effect ) { + + this.effect = effect; + this.init_from = null; + this.format = null; + + }; + + Surface.prototype.parse = function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'init_from': + + this.init_from = child.textContent; + break; + + case 'format': + + this.format = child.textContent; + break; + + default: + + console.log( "unhandled Surface prop: " + child.nodeName ); + break; + + } + + } + + return this; + + }; + + function Sampler2D ( effect ) { + + this.effect = effect; + this.source = null; + this.wrap_s = null; + this.wrap_t = null; + this.minfilter = null; + this.magfilter = null; + this.mipfilter = null; + + }; + + Sampler2D.prototype.parse = function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'source': + + this.source = child.textContent; + break; + + case 'minfilter': + + this.minfilter = child.textContent; + break; + + case 'magfilter': + + this.magfilter = child.textContent; + break; + + case 'mipfilter': + + this.mipfilter = child.textContent; + break; + + case 'wrap_s': + + this.wrap_s = child.textContent; + break; + + case 'wrap_t': + + this.wrap_t = child.textContent; + break; + + default: + + console.log( "unhandled Sampler2D prop: " + child.nodeName ); + break; + + } + + } + + return this; + + }; + + function Effect () { + + this.id = ""; + this.name = ""; + this.shader = null; + this.surface = {}; + this.sampler = {}; + + }; + + Effect.prototype.create = function () { + + if ( this.shader == null ) { + + return null; + + } + + }; + + Effect.prototype.parse = function ( element ) { + + this.id = element.getAttribute( 'id' ); + this.name = element.getAttribute( 'name' ); + + extractDoubleSided( this, element ); + + this.shader = null; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'profile_COMMON': + + this.parseTechnique( this.parseProfileCOMMON( child ) ); + break; + + default: + break; + + } + + } + + return this; + + }; + + Effect.prototype.parseNewparam = function ( element ) { + + var sid = element.getAttribute( 'sid' ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'surface': + + this.surface[sid] = ( new Surface( this ) ).parse( child ); + break; + + case 'sampler2D': + + this.sampler[sid] = ( new Sampler2D( this ) ).parse( child ); + break; + + case 'extra': + + break; + + default: + + console.log( child.nodeName ); + break; + + } + + } + + }; + + Effect.prototype.parseProfileCOMMON = function ( element ) { + + var technique; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'profile_COMMON': + + this.parseProfileCOMMON( child ); + break; + + case 'technique': + + technique = child; + break; + + case 'newparam': + + this.parseNewparam( child ); + break; + + case 'image': + + var _image = ( new _Image() ).parse( child ); + images[ _image.id ] = _image; + break; + + case 'extra': + break; + + default: + + console.log( child.nodeName ); + break; + + } + + } + + return technique; + + }; + + Effect.prototype.parseTechnique= function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'constant': + case 'lambert': + case 'blinn': + case 'phong': + + this.shader = ( new Shader( child.nodeName, this ) ).parse( child ); + break; + case 'extra': + this.parseExtra(child); + break; + default: + break; + + } + + } + + }; + + Effect.prototype.parseExtra = function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique': + this.parseExtraTechnique( child ); + break; + default: + break; + + } + + } + + }; + + Effect.prototype.parseExtraTechnique= function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'bump': + this.shader.parse( element ); + break; + default: + break; + + } + + } + + }; + + function InstanceEffect () { + + this.url = ""; + + }; + + InstanceEffect.prototype.parse = function ( element ) { + + this.url = element.getAttribute( 'url' ).replace( /^#/, '' ); + return this; + + }; + + function Animation() { + + this.id = ""; + this.name = ""; + this.source = {}; + this.sampler = []; + this.channel = []; + + }; + + Animation.prototype.parse = function ( element ) { + + this.id = element.getAttribute( 'id' ); + this.name = element.getAttribute( 'name' ); + this.source = {}; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'animation': + + var anim = ( new Animation() ).parse( child ); + + for ( var src in anim.source ) { + + this.source[ src ] = anim.source[ src ]; + + } + + for ( var j = 0; j < anim.channel.length; j ++ ) { + + this.channel.push( anim.channel[ j ] ); + this.sampler.push( anim.sampler[ j ] ); + + } + + break; + + case 'source': + + var src = ( new Source() ).parse( child ); + this.source[ src.id ] = src; + break; + + case 'sampler': + + this.sampler.push( ( new Sampler( this ) ).parse( child ) ); + break; + + case 'channel': + + this.channel.push( ( new Channel( this ) ).parse( child ) ); + break; + + default: + break; + + } + + } + + return this; + + }; + + function Channel( animation ) { + + this.animation = animation; + this.source = ""; + this.target = ""; + this.fullSid = null; + this.sid = null; + this.dotSyntax = null; + this.arrSyntax = null; + this.arrIndices = null; + this.member = null; + + }; + + Channel.prototype.parse = function ( element ) { + + this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); + this.target = element.getAttribute( 'target' ); + + var parts = this.target.split( '/' ); + + var id = parts.shift(); + var sid = parts.shift(); + + var dotSyntax = ( sid.indexOf(".") >= 0 ); + var arrSyntax = ( sid.indexOf("(") >= 0 ); + + if ( dotSyntax ) { + + parts = sid.split("."); + this.sid = parts.shift(); + this.member = parts.shift(); + + } else if ( arrSyntax ) { + + var arrIndices = sid.split("("); + this.sid = arrIndices.shift(); + + for (var j = 0; j < arrIndices.length; j ++ ) { + + arrIndices[j] = parseInt( arrIndices[j].replace(/\)/, '') ); + + } + + this.arrIndices = arrIndices; + + } else { + + this.sid = sid; + + } + + this.fullSid = sid; + this.dotSyntax = dotSyntax; + this.arrSyntax = arrSyntax; + + return this; + + }; + + function Sampler ( animation ) { + + this.id = ""; + this.animation = animation; + this.inputs = []; + this.input = null; + this.output = null; + this.strideOut = null; + this.interpolation = null; + this.startTime = null; + this.endTime = null; + this.duration = 0; + + }; + + Sampler.prototype.parse = function ( element ) { + + this.id = element.getAttribute( 'id' ); + this.inputs = []; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + + this.inputs.push( (new Input()).parse( child ) ); + break; + + default: + break; + + } + + } + + return this; + + }; + + Sampler.prototype.create = function () { + + for ( var i = 0; i < this.inputs.length; i ++ ) { + + var input = this.inputs[ i ]; + var source = this.animation.source[ input.source ]; + + switch ( input.semantic ) { + + case 'INPUT': + + this.input = source.read(); + break; + + case 'OUTPUT': + + this.output = source.read(); + this.strideOut = source.accessor.stride; + break; + + case 'INTERPOLATION': + + this.interpolation = source.read(); + break; + + case 'IN_TANGENT': + + break; + + case 'OUT_TANGENT': + + break; + + default: + + console.log(input.semantic); + break; + + } + + } + + this.startTime = 0; + this.endTime = 0; + this.duration = 0; + + if ( this.input.length ) { + + this.startTime = 100000000; + this.endTime = -100000000; + + for ( var i = 0; i < this.input.length; i ++ ) { + + this.startTime = Math.min( this.startTime, this.input[ i ] ); + this.endTime = Math.max( this.endTime, this.input[ i ] ); + + } + + this.duration = this.endTime - this.startTime; + + } + + }; + + Sampler.prototype.getData = function ( type, ndx, member ) { + + var data; + + if ( type === 'matrix' && this.strideOut === 16 ) { + + data = this.output[ ndx ]; + + } else if ( this.strideOut > 1 ) { + + data = []; + ndx *= this.strideOut; + + for ( var i = 0; i < this.strideOut; ++i ) { + + data[ i ] = this.output[ ndx + i ]; + + } + + if ( this.strideOut === 3 ) { + + switch ( type ) { + + case 'rotate': + case 'translate': + + fixCoords( data, -1 ); + break; + + case 'scale': + + fixCoords( data, 1 ); + break; + + } + + } else if ( this.strideOut === 4 && type === 'matrix' ) { + + fixCoords( data, -1 ); + + } + + } else { + + data = this.output[ ndx ]; + + if ( member && type == 'translate' ) { + data = getConvertedTranslation( member, data ); + } + + } + + return data; + + }; + + function Key ( time ) { + + this.targets = []; + this.time = time; + + }; + + Key.prototype.addTarget = function ( fullSid, transform, member, data ) { + + this.targets.push( { + sid: fullSid, + member: member, + transform: transform, + data: data + } ); + + }; + + Key.prototype.apply = function ( opt_sid ) { + + for ( var i = 0; i < this.targets.length; ++i ) { + + var target = this.targets[ i ]; + + if ( !opt_sid || target.sid === opt_sid ) { + + target.transform.update( target.data, target.member ); + + } + + } + + }; + + Key.prototype.getTarget = function ( fullSid ) { + + for ( var i = 0; i < this.targets.length; ++i ) { + + if ( this.targets[ i ].sid === fullSid ) { + + return this.targets[ i ]; + + } + + } + + return null; + + }; + + Key.prototype.hasTarget = function ( fullSid ) { + + for ( var i = 0; i < this.targets.length; ++i ) { + + if ( this.targets[ i ].sid === fullSid ) { + + return true; + + } + + } + + return false; + + }; + + // TODO: Currently only doing linear interpolation. Should support full COLLADA spec. + Key.prototype.interpolate = function ( nextKey, time ) { + + for ( var i = 0, l = this.targets.length; i < l; i ++ ) { + + var target = this.targets[ i ], + nextTarget = nextKey.getTarget( target.sid ), + data; + + if ( target.transform.type !== 'matrix' && nextTarget ) { + + var scale = ( time - this.time ) / ( nextKey.time - this.time ), + nextData = nextTarget.data, + prevData = target.data; + + if ( scale < 0 ) scale = 0; + if ( scale > 1 ) scale = 1; + + if ( prevData.length ) { + + data = []; + + for ( var j = 0; j < prevData.length; ++j ) { + + data[ j ] = prevData[ j ] + ( nextData[ j ] - prevData[ j ] ) * scale; + + } + + } else { + + data = prevData + ( nextData - prevData ) * scale; + + } + + } else { + + data = target.data; + + } + + target.transform.update( data, target.member ); + + } + + }; + + // Camera + function Camera() { + + this.id = ""; + this.name = ""; + this.technique = ""; + + }; + + Camera.prototype.parse = function ( element ) { + + this.id = element.getAttribute( 'id' ); + this.name = element.getAttribute( 'name' ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'optics': + + this.parseOptics( child ); + break; + + default: + break; + + } + + } + + return this; + + }; + + Camera.prototype.parseOptics = function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + if ( element.childNodes[ i ].nodeName == 'technique_common' ) { + + var technique = element.childNodes[ i ]; + + for ( var j = 0; j < technique.childNodes.length; j ++ ) { + + this.technique = technique.childNodes[ j ].nodeName; + + if ( this.technique == 'perspective' ) { + + var perspective = technique.childNodes[ j ]; + + for ( var k = 0; k < perspective.childNodes.length; k ++ ) { + + var param = perspective.childNodes[ k ]; + + switch ( param.nodeName ) { + + case 'yfov': + this.yfov = param.textContent; + break; + case 'xfov': + this.xfov = param.textContent; + break; + case 'znear': + this.znear = param.textContent; + break; + case 'zfar': + this.zfar = param.textContent; + break; + case 'aspect_ratio': + this.aspect_ratio = param.textContent; + break; + + } + + } + + } else if ( this.technique == 'orthographic' ) { + + var orthographic = technique.childNodes[ j ]; + + for ( var k = 0; k < orthographic.childNodes.length; k ++ ) { + + var param = orthographic.childNodes[ k ]; + + switch ( param.nodeName ) { + + case 'xmag': + this.xmag = param.textContent; + break; + case 'ymag': + this.ymag = param.textContent; + break; + case 'znear': + this.znear = param.textContent; + break; + case 'zfar': + this.zfar = param.textContent; + break; + case 'aspect_ratio': + this.aspect_ratio = param.textContent; + break; + + } + + } + + } + + } + + } + + } + + return this; + + }; + + function InstanceCamera() { + + this.url = ""; + + }; + + InstanceCamera.prototype.parse = function ( element ) { + + this.url = element.getAttribute('url').replace(/^#/, ''); + + return this; + + }; + + // Light + + function Light() { + + this.id = ""; + this.name = ""; + this.technique = ""; + + }; + + Light.prototype.parse = function ( element ) { + + this.id = element.getAttribute( 'id' ); + this.name = element.getAttribute( 'name' ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique_common': + + this.parseCommon( child ); + break; + + case 'technique': + + this.parseTechnique( child ); + break; + + default: + break; + + } + + } + + return this; + + }; + + Light.prototype.parseCommon = function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + switch ( element.childNodes[ i ].nodeName ) { + + case 'directional': + case 'point': + case 'spot': + case 'ambient': + + this.technique = element.childNodes[ i ].nodeName; + + var light = element.childNodes[ i ]; + + for ( var j = 0; j < light.childNodes.length; j ++ ) { + + var child = light.childNodes[j]; + + switch ( child.nodeName ) { + + case 'color': + + var rgba = _floats( child.textContent ); + this.color = new THREE.Color(0); + this.color.setRGB( rgba[0], rgba[1], rgba[2] ); + this.color.a = rgba[3]; + break; + + case 'falloff_angle': + + this.falloff_angle = parseFloat( child.textContent ); + break; + } + + } + + } + + } + + return this; + + }; + + Light.prototype.parseTechnique = function ( element ) { + + this.profile = element.getAttribute( 'profile' ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'intensity': + + this.intensity = parseFloat(child.textContent); + break; + + } + + } + + return this; + + }; + + function InstanceLight() { + + this.url = ""; + + }; + + InstanceLight.prototype.parse = function ( element ) { + + this.url = element.getAttribute('url').replace(/^#/, ''); + + return this; + + }; + + function _source( element ) { + + var id = element.getAttribute( 'id' ); + + if ( sources[ id ] != undefined ) { + + return sources[ id ]; + + } + + sources[ id ] = ( new Source(id )).parse( element ); + return sources[ id ]; + + }; + + function _nsResolver( nsPrefix ) { + + if ( nsPrefix == "dae" ) { + + return "http://www.collada.org/2005/11/COLLADASchema"; + + } + + return null; + + }; + + function _bools( str ) { + + var raw = _strings( str ); + var data = []; + + for ( var i = 0, l = raw.length; i < l; i ++ ) { + + data.push( (raw[i] == 'true' || raw[i] == '1') ? true : false ); + + } + + return data; + + }; + + function _floats( str ) { + + var raw = _strings(str); + var data = []; + + for ( var i = 0, l = raw.length; i < l; i ++ ) { + + data.push( parseFloat( raw[ i ] ) ); + + } + + return data; + + }; + + function _ints( str ) { + + var raw = _strings( str ); + var data = []; + + for ( var i = 0, l = raw.length; i < l; i ++ ) { + + data.push( parseInt( raw[ i ], 10 ) ); + + } + + return data; + + }; + + function _strings( str ) { + + return ( str.length > 0 ) ? _trimString( str ).split( /\s+/ ) : []; + + }; + + function _trimString( str ) { + + return str.replace( /^\s+/, "" ).replace( /\s+$/, "" ); + + }; + + function _attr_as_float( element, name, defaultValue ) { + + if ( element.hasAttribute( name ) ) { + + return parseFloat( element.getAttribute( name ) ); + + } else { + + return defaultValue; + + } + + }; + + function _attr_as_int( element, name, defaultValue ) { + + if ( element.hasAttribute( name ) ) { + + return parseInt( element.getAttribute( name ), 10) ; + + } else { + + return defaultValue; + + } + + }; + + function _attr_as_string( element, name, defaultValue ) { + + if ( element.hasAttribute( name ) ) { + + return element.getAttribute( name ); + + } else { + + return defaultValue; + + } + + }; + + function _format_float( f, num ) { + + if ( f === undefined ) { + + var s = '0.'; + + while ( s.length < num + 2 ) { + + s += '0'; + + } + + return s; + + } + + num = num || 2; + + var parts = f.toString().split( '.' ); + parts[ 1 ] = parts.length > 1 ? parts[ 1 ].substr( 0, num ) : "0"; + + while( parts[ 1 ].length < num ) { + + parts[ 1 ] += '0'; + + } + + return parts.join( '.' ); + + }; + + function extractDoubleSided( obj, element ) { + + obj.doubleSided = false; + + var node = element.querySelectorAll('extra double_sided')[0]; + + if ( node ) { + + if ( node && parseInt( node.textContent, 10 ) === 1 ) { + + obj.doubleSided = true; + + } + + } + + }; + + // Up axis conversion + + function setUpConversion() { + + if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) { + + upConversion = null; + + } else { + + switch ( colladaUp ) { + + case 'X': + + upConversion = options.upAxis === 'Y' ? 'XtoY' : 'XtoZ'; + break; + + case 'Y': + + upConversion = options.upAxis === 'X' ? 'YtoX' : 'YtoZ'; + break; + + case 'Z': + + upConversion = options.upAxis === 'X' ? 'ZtoX' : 'ZtoY'; + break; + + } + + } + + }; + + function fixCoords( data, sign ) { + + if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) { + + return; + + } + + switch ( upConversion ) { + + case 'XtoY': + + var tmp = data[ 0 ]; + data[ 0 ] = sign * data[ 1 ]; + data[ 1 ] = tmp; + break; + + case 'XtoZ': + + var tmp = data[ 2 ]; + data[ 2 ] = data[ 1 ]; + data[ 1 ] = data[ 0 ]; + data[ 0 ] = tmp; + break; + + case 'YtoX': + + var tmp = data[ 0 ]; + data[ 0 ] = data[ 1 ]; + data[ 1 ] = sign * tmp; + break; + + case 'YtoZ': + + var tmp = data[ 1 ]; + data[ 1 ] = sign * data[ 2 ]; + data[ 2 ] = tmp; + break; + + case 'ZtoX': + + var tmp = data[ 0 ]; + data[ 0 ] = data[ 1 ]; + data[ 1 ] = data[ 2 ]; + data[ 2 ] = tmp; + break; + + case 'ZtoY': + + var tmp = data[ 1 ]; + data[ 1 ] = data[ 2 ]; + data[ 2 ] = sign * tmp; + break; + + } + + }; + + function getConvertedTranslation( axis, data ) { + + if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) { + + return data; + + } + + switch ( axis ) { + case 'X': + data = upConversion == 'XtoY' ? data * -1 : data; + break; + case 'Y': + data = upConversion == 'YtoZ' || upConversion == 'YtoX' ? data * -1 : data; + break; + case 'Z': + data = upConversion == 'ZtoY' ? data * -1 : data ; + break; + default: + break; + } + + return data; + }; + + function getConvertedVec3( data, offset ) { + + var arr = [ data[ offset ], data[ offset + 1 ], data[ offset + 2 ] ]; + fixCoords( arr, -1 ); + return new THREE.Vector3( arr[ 0 ], arr[ 1 ], arr[ 2 ] ); + + }; + + function getConvertedMat4( data ) { + + if ( options.convertUpAxis ) { + + // First fix rotation and scale + + // Columns first + var arr = [ data[ 0 ], data[ 4 ], data[ 8 ] ]; + fixCoords( arr, -1 ); + data[ 0 ] = arr[ 0 ]; + data[ 4 ] = arr[ 1 ]; + data[ 8 ] = arr[ 2 ]; + arr = [ data[ 1 ], data[ 5 ], data[ 9 ] ]; + fixCoords( arr, -1 ); + data[ 1 ] = arr[ 0 ]; + data[ 5 ] = arr[ 1 ]; + data[ 9 ] = arr[ 2 ]; + arr = [ data[ 2 ], data[ 6 ], data[ 10 ] ]; + fixCoords( arr, -1 ); + data[ 2 ] = arr[ 0 ]; + data[ 6 ] = arr[ 1 ]; + data[ 10 ] = arr[ 2 ]; + // Rows second + arr = [ data[ 0 ], data[ 1 ], data[ 2 ] ]; + fixCoords( arr, -1 ); + data[ 0 ] = arr[ 0 ]; + data[ 1 ] = arr[ 1 ]; + data[ 2 ] = arr[ 2 ]; + arr = [ data[ 4 ], data[ 5 ], data[ 6 ] ]; + fixCoords( arr, -1 ); + data[ 4 ] = arr[ 0 ]; + data[ 5 ] = arr[ 1 ]; + data[ 6 ] = arr[ 2 ]; + arr = [ data[ 8 ], data[ 9 ], data[ 10 ] ]; + fixCoords( arr, -1 ); + data[ 8 ] = arr[ 0 ]; + data[ 9 ] = arr[ 1 ]; + data[ 10 ] = arr[ 2 ]; + + // Now fix translation + arr = [ data[ 3 ], data[ 7 ], data[ 11 ] ]; + fixCoords( arr, -1 ); + data[ 3 ] = arr[ 0 ]; + data[ 7 ] = arr[ 1 ]; + data[ 11 ] = arr[ 2 ]; + + } + + return new THREE.Matrix4( + data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7], + data[8], data[9], data[10], data[11], + data[12], data[13], data[14], data[15] + ); + + }; + + function getConvertedIndex( index ) { + + if ( index > -1 && index < 3 ) { + + var members = ['X', 'Y', 'Z'], + indices = { X: 0, Y: 1, Z: 2 }; + + index = getConvertedMember( members[ index ] ); + index = indices[ index ]; + + } + + return index; + + }; + + function getConvertedMember( member ) { + + if ( options.convertUpAxis ) { + + switch ( member ) { + + case 'X': + + switch ( upConversion ) { + + case 'XtoY': + case 'XtoZ': + case 'YtoX': + + member = 'Y'; + break; + + case 'ZtoX': + + member = 'Z'; + break; + + } + + break; + + case 'Y': + + switch ( upConversion ) { + + case 'XtoY': + case 'YtoX': + case 'ZtoX': + + member = 'X'; + break; + + case 'XtoZ': + case 'YtoZ': + case 'ZtoY': + + member = 'Z'; + break; + + } + + break; + + case 'Z': + + switch ( upConversion ) { + + case 'XtoZ': + + member = 'X'; + break; + + case 'YtoZ': + case 'ZtoX': + case 'ZtoY': + + member = 'Y'; + break; + + } + + break; + + } + + } + + return member; + + }; + + return { + + load: load, + parse: parse, + setPreferredShading: setPreferredShading, + applySkin: applySkin, + geometries : geometries, + options: options + + }; + +}; diff --git a/docdoku-dplm/ui/app/js/components/3d/loaders/OBJLoader.js b/docdoku-dplm/ui/app/js/components/3d/loaders/OBJLoader.js new file mode 100644 index 0000000000..9aed9ba413 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/3d/loaders/OBJLoader.js @@ -0,0 +1,327 @@ +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.OBJLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.OBJLoader.prototype = { + + constructor: THREE.OBJLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.XHRLoader( scope.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( text ) ); + + } ); + + }, + + parse: function ( text ) { + + function vector( x, y, z ) { + + return new THREE.Vector3( parseFloat( x ), parseFloat( y ), parseFloat( z ) ); + + } + + function uv( u, v ) { + + return new THREE.Vector2( parseFloat( u ), parseFloat( v ) ); + + } + + function face3( a, b, c, normals ) { + + return new THREE.Face3( a, b, c, normals ); + + } + + var object = new THREE.Object3D(); + var geometry, material, mesh; + + function parseVertexIndex( index ) { + + index = parseInt( index ); + + return index >= 0 ? index - 1 : index + vertices.length; + + } + + function parseNormalIndex( index ) { + + index = parseInt( index ); + + return index >= 0 ? index - 1 : index + normals.length; + + } + + function parseUVIndex( index ) { + + index = parseInt( index ); + + return index >= 0 ? index - 1 : index + uvs.length; + + } + + function add_face( a, b, c, normals_inds ) { + + if ( normals_inds === undefined ) { + + geometry.faces.push( face3( + vertices[ parseVertexIndex( a ) ] - 1, + vertices[ parseVertexIndex( b ) ] - 1, + vertices[ parseVertexIndex( c ) ] - 1 + ) ); + + } else { + + geometry.faces.push( face3( + vertices[ parseVertexIndex( a ) ] - 1, + vertices[ parseVertexIndex( b ) ] - 1, + vertices[ parseVertexIndex( c ) ] - 1, + [ + normals[ parseNormalIndex( normals_inds[ 0 ] ) ].clone(), + normals[ parseNormalIndex( normals_inds[ 1 ] ) ].clone(), + normals[ parseNormalIndex( normals_inds[ 2 ] ) ].clone() + ] + ) ); + + } + + } + + function add_uvs( a, b, c ) { + + geometry.faceVertexUvs[ 0 ].push( [ + uvs[ parseUVIndex( a ) ].clone(), + uvs[ parseUVIndex( b ) ].clone(), + uvs[ parseUVIndex( c ) ].clone() + ] ); + + } + + function handle_face_line(faces, uvs, normals_inds) { + + if ( faces[ 3 ] === undefined ) { + + add_face( faces[ 0 ], faces[ 1 ], faces[ 2 ], normals_inds ); + + if ( uvs !== undefined && uvs.length > 0 ) { + + add_uvs( uvs[ 0 ], uvs[ 1 ], uvs[ 2 ] ); + + } + + } else { + + if ( normals_inds !== undefined && normals_inds.length > 0 ) { + + add_face( faces[ 0 ], faces[ 1 ], faces[ 3 ], [ normals_inds[ 0 ], normals_inds[ 1 ], normals_inds[ 3 ] ] ); + add_face( faces[ 1 ], faces[ 2 ], faces[ 3 ], [ normals_inds[ 1 ], normals_inds[ 2 ], normals_inds[ 3 ] ] ); + + } else { + + add_face( faces[ 0 ], faces[ 1 ], faces[ 3 ] ); + add_face( faces[ 1 ], faces[ 2 ], faces[ 3 ] ); + + } + + if ( uvs !== undefined && uvs.length > 0 ) { + + add_uvs( uvs[ 0 ], uvs[ 1 ], uvs[ 3 ] ); + add_uvs( uvs[ 1 ], uvs[ 2 ], uvs[ 3 ] ); + + } + + } + + } + + // create mesh if no objects in text + + if ( /^o /gm.test( text ) === false ) { + + geometry = new THREE.Geometry(); + material = new THREE.MeshLambertMaterial(); + mesh = new THREE.Mesh( geometry, material ); + object.add( mesh ); + + } + + var vertices = []; + var normals = []; + var uvs = []; + + // v float float float + + var vertex_pattern = /v( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; + + // vn float float float + + var normal_pattern = /vn( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; + + // vt float float + + var uv_pattern = /vt( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; + + // f vertex vertex vertex ... + + var face_pattern1 = /f( +-?\d+)( +-?\d+)( +-?\d+)( +-?\d+)?/; + + // f vertex/uv vertex/uv vertex/uv ... + + var face_pattern2 = /f( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))?/; + + // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... + + var face_pattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/; + + // f vertex//normal vertex//normal vertex//normal ... + + var face_pattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/ + + // + + var lines = text.split( '\n' ); + + for ( var i = 0; i < lines.length; i ++ ) { + + var line = lines[ i ]; + line = line.trim(); + + var result; + + if ( line.length === 0 || line.charAt( 0 ) === '#' ) { + + continue; + + } else if ( ( result = vertex_pattern.exec( line ) ) !== null ) { + + // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] + + vertices.push( + geometry.vertices.push( + vector( + result[ 1 ], result[ 2 ], result[ 3 ] + ) + ) + ); + + } else if ( ( result = normal_pattern.exec( line ) ) !== null ) { + + // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] + + normals.push( + vector( + result[ 1 ], result[ 2 ], result[ 3 ] + ) + ); + + } else if ( ( result = uv_pattern.exec( line ) ) !== null ) { + + // ["vt 0.1 0.2", "0.1", "0.2"] + + uvs.push( + uv( + result[ 1 ], result[ 2 ] + ) + ); + + } else if ( ( result = face_pattern1.exec( line ) ) !== null ) { + + // ["f 1 2 3", "1", "2", "3", undefined] + + handle_face_line( + [ result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] ] + ); + + } else if ( ( result = face_pattern2.exec( line ) ) !== null ) { + + // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined] + + handle_face_line( + [ result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ] ], //faces + [ result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] ] //uv + ); + + } else if ( ( result = face_pattern3.exec( line ) ) !== null ) { + + // ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined] + + handle_face_line( + [ result[ 2 ], result[ 6 ], result[ 10 ], result[ 14 ] ], //faces + [ result[ 3 ], result[ 7 ], result[ 11 ], result[ 15 ] ], //uv + [ result[ 4 ], result[ 8 ], result[ 12 ], result[ 16 ] ] //normal + ); + + } else if ( ( result = face_pattern4.exec( line ) ) !== null ) { + + // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined] + + handle_face_line( + [ result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ] ], //faces + [ ], //uv + [ result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] ] //normal + ); + + } else if ( /^o /.test( line ) ) { + + geometry = new THREE.Geometry(); + material = new THREE.MeshLambertMaterial(); + + mesh = new THREE.Mesh( geometry, material ); + mesh.name = line.substring( 2 ).trim(); + object.add( mesh ); + + } else if ( /^g /.test( line ) ) { + + // group + + } else if ( /^usemtl /.test( line ) ) { + + // material + + material.name = line.substring( 7 ).trim(); + + } else if ( /^mtllib /.test( line ) ) { + + // mtl file + + } else if ( /^s /.test( line ) ) { + + // smooth shading + + } else { + + // console.log( "THREE.OBJLoader: Unhandled line " + line ); + + } + + } + + var children = object.children; + + for ( var i = 0, l = children.length; i < l; i ++ ) { + + var geometry = children[ i ].geometry; + + geometry.computeFaceNormals(); + geometry.computeBoundingSphere(); + + } + + return object; + + } + +}; \ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/components/3d/loaders/PLYLoader.js b/docdoku-dplm/ui/app/js/components/3d/loaders/PLYLoader.js new file mode 100644 index 0000000000..8fcc7916fe --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/3d/loaders/PLYLoader.js @@ -0,0 +1,441 @@ +/** + * @author Wei Meng / http://about.me/menway + * + * Description: A THREE loader for PLY ASCII files (known as the Polygon File Format or the Stanford Triangle Format). + * + * Currently only supports ASCII encoded files. + * + * Limitations: ASCII decoding assumes file is UTF-8. + * + * Usage: + * var loader = new THREE.PLYLoader(); + * loader.addEventListener( 'load', function ( event ) { + * + * var geometry = event.content; + * scene.add( new THREE.Mesh( geometry ) ); + * + * } ); + * loader.load( './models/ply/ascii/dolphins.ply' ); + */ + + +THREE.PLYLoader = function () {}; + +THREE.PLYLoader.prototype = { + + constructor: THREE.PLYLoader, + + load: function ( url, callback ) { + + var scope = this; + var request = new XMLHttpRequest(); + + request.addEventListener( 'load', function ( event ) { + + var geometry = scope.parse( event.target.response ); + + scope.dispatchEvent( { type: 'load', content: geometry } ); + + if ( callback ) callback( geometry ); + + }, false ); + + request.addEventListener( 'progress', function ( event ) { + + scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } ); + + }, false ); + + request.addEventListener( 'error', function () { + + scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } ); + + }, false ); + + request.open( 'GET', url, true ); + request.responseType = "arraybuffer"; + request.send( null ); + + }, + + bin2str: function (buf) { + + var array_buffer = new Uint8Array(buf); + var str = ''; + for(var i = 0; i < buf.byteLength; i++) { + str += String.fromCharCode(array_buffer[i]); // implicitly assumes little-endian + } + + return str; + + }, + + isASCII: function( data ){ + + var header = this.parseHeader( this.bin2str( data ) ); + + return header.format === "ascii"; + + }, + + parse: function ( data ) { + + if ( data instanceof ArrayBuffer ) { + + return this.isASCII( data ) + ? this.parseASCII( this.bin2str( data ) ) + : this.parseBinary( data ); + + } else { + + return this.parseASCII( data ); + + } + + }, + + parseHeader: function ( data ) { + + var patternHeader = /ply([\s\S]*)end_header\n/; + var headerText = ""; + if ( ( result = patternHeader.exec( data ) ) != null ) { + headerText = result [ 1 ]; + } + + var header = new Object(); + header.comments = []; + header.elements = []; + header.headerLength = result[0].length; + + var lines = headerText.split( '\n' ); + var currentElement = undefined; + var lineType, lineValues; + + function make_ply_element_property(propertValues) { + + var property = Object(); + + property.type = propertValues[0] + + if ( property.type === "list" ) { + + property.name = propertValues[3] + property.countType = propertValues[1] + property.itemType = propertValues[2] + + } else { + + property.name = propertValues[1] + + } + + return property + + } + + for ( var i = 0; i < lines.length; i ++ ) { + + var line = lines[ i ]; + line = line.trim() + if ( line === "" ) { continue; } + lineValues = line.split( /\s+/ ); + lineType = lineValues.shift() + line = lineValues.join(" ") + + switch( lineType ) { + + case "format": + + header.format = lineValues[0]; + header.version = lineValues[1]; + + break; + + case "comment": + + header.comments.push(line); + + break; + + case "element": + + if ( !(currentElement === undefined) ) { + + header.elements.push(currentElement); + + } + + currentElement = Object(); + currentElement.name = lineValues[0]; + currentElement.count = parseInt( lineValues[1] ); + currentElement.properties = []; + + break; + + case "property": + + currentElement.properties.push( make_ply_element_property( lineValues ) ); + + break; + + + default: + + console.log("unhandled", lineType, lineValues); + + } + + } + + if ( !(currentElement === undefined) ) { + + header.elements.push(currentElement); + + } + + return header; + + }, + + parseASCIINumber: function ( n, type ) { + + switch( type ) { + + case 'char': case 'uchar': case 'short': case 'ushort': case 'int': case 'uint': + case 'int8': case 'uint8': case 'int16': case 'uint16': case 'int32': case 'uint32': + + return parseInt( n ); + + case 'float': case 'double': case 'float32': case 'float64': + + return parseFloat( n ); + + } + + }, + + parseASCIIElement: function ( properties, line ) { + + values = line.split( /\s+/ ); + + var element = Object(); + + for ( var i = 0; i < properties.length; i ++ ) { + + if ( properties[i].type === "list" ) { + + var list = []; + var n = this.parseASCIINumber( values.shift(), properties[i].countType ); + + for ( j = 0; j < n; j ++ ) { + + list.push( this.parseASCIINumber( values.shift(), properties[i].itemType ) ); + + } + + element[ properties[i].name ] = list; + + } else { + + element[ properties[i].name ] = this.parseASCIINumber( values.shift(), properties[i].type ); + + } + + } + + return element; + + }, + + parseASCII: function ( data ) { + + // PLY ascii format specification, as per http://en.wikipedia.org/wiki/PLY_(file_format) + + var geometry = new THREE.Geometry(); + + var result; + + var header = this.parseHeader( data ); + + var patternBody = /end_header\n([\s\S]*)$/; + var body = ""; + if ( ( result = patternBody.exec( data ) ) != null ) { + body = result [ 1 ]; + } + + var lines = body.split( '\n' ); + var currentElement = 0; + var currentElementCount = 0; + geometry.useColor = false; + + for ( var i = 0; i < lines.length; i ++ ) { + + var line = lines[ i ]; + line = line.trim() + if ( line === "" ) { continue; } + + if ( currentElementCount >= header.elements[currentElement].count ) { + + currentElement++; + currentElementCount = 0; + + } + + var element = this.parseASCIIElement( header.elements[currentElement].properties, line ); + + this.handleElement( geometry, header.elements[currentElement].name, element ); + + currentElementCount++; + + } + + return this.postProcess( geometry ); + + }, + + postProcess: function ( geometry ) { + + if ( geometry.useColor ) { + + for ( var i = 0; i < geometry.faces.length; i ++ ) { + + geometry.faces[i].vertexColors = [ + geometry.colors[geometry.faces[i].a], + geometry.colors[geometry.faces[i].b], + geometry.colors[geometry.faces[i].c] + ]; + + } + + geometry.elementsNeedUpdate = true; + + } + + geometry.computeBoundingSphere(); + + return geometry; + + }, + + handleElement: function ( geometry, elementName, element ) { + + if ( elementName === "vertex" ) { + + geometry.vertices.push( + new THREE.Vector3( element.x, element.y, element.z ) + ); + + if ( 'red' in element && 'green' in element && 'blue' in element ) { + + geometry.useColor = true; + + color = new THREE.Color(); + color.setRGB( element.red / 255.0, element.green / 255.0, element.blue / 255.0 ); + geometry.colors.push( color ); + + } + + } else if ( elementName === "face" ) { + + geometry.faces.push( + new THREE.Face3( element.vertex_indices[0], element.vertex_indices[1], element.vertex_indices[2] ) + ); + + } + + }, + + binaryRead: function ( dataview, at, type, little_endian ) { + + switch( type ) { + + // corespondences for non-specific length types here match rply: + case 'int8': case 'char': return [ dataview.getInt8( at ), 1 ]; + + case 'uint8': case 'uchar': return [ dataview.getUint8( at ), 1 ]; + + case 'int16': case 'short': return [ dataview.getInt16( at, little_endian ), 2 ]; + + case 'uint16': case 'ushort': return [ dataview.getUint16( at, little_endian ), 2 ]; + + case 'int32': case 'int': return [ dataview.getInt32( at, little_endian ), 4 ]; + + case 'uint32': case 'uint': return [ dataview.getUint32( at, little_endian ), 4 ]; + + case 'float32': case 'float': return [ dataview.getFloat32( at, little_endian ), 4 ]; + + case 'float64': case 'double': return [ dataview.getFloat64( at, little_endian ), 8 ]; + + } + + }, + + binaryReadElement: function ( dataview, at, properties, little_endian ) { + + var element = Object(); + var result, read = 0; + + for ( var i = 0; i < properties.length; i ++ ) { + + if ( properties[i].type === "list" ) { + + var list = []; + + result = this.binaryRead( dataview, at+read, properties[i].countType, little_endian ); + var n = result[0]; + read += result[1]; + + for ( j = 0; j < n; j ++ ) { + + result = this.binaryRead( dataview, at+read, properties[i].itemType, little_endian ); + list.push( result[0] ); + read += result[1]; + + } + + element[ properties[i].name ] = list; + + } else { + + result = this.binaryRead( dataview, at+read, properties[i].type, little_endian ); + element[ properties[i].name ] = result[0]; + read += result[1]; + + } + + } + + return [ element, read ]; + + }, + + parseBinary: function ( data ) { + + var geometry = new THREE.Geometry(); + + var header = this.parseHeader( this.bin2str( data ) ); + var little_endian = (header.format === "binary_little_endian"); + var body = new DataView( data, header.headerLength ); + var result, loc = 0; + + for ( var currentElement = 0; currentElement < header.elements.length; currentElement ++ ) { + + for ( var currentElementCount = 0; currentElementCount < header.elements[currentElement].count; currentElementCount ++ ) { + + result = this.binaryReadElement( body, loc, header.elements[currentElement].properties, little_endian ); + loc += result[1]; + var element = result[0]; + + this.handleElement( geometry, header.elements[currentElement].name, element ); + + } + + } + + return this.postProcess( geometry ); + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.PLYLoader.prototype ); diff --git a/docdoku-dplm/ui/app/js/components/3d/loaders/STLLoader.js b/docdoku-dplm/ui/app/js/components/3d/loaders/STLLoader.js new file mode 100644 index 0000000000..30f9cd9a49 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/3d/loaders/STLLoader.js @@ -0,0 +1,378 @@ +/** + * @author aleeper / http://adamleeper.com/ + * @author mrdoob / http://mrdoob.com/ + * @author gero3 / https://github.com/gero3 + * + * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs. + * + * Supports both binary and ASCII encoded files, with automatic detection of type. + * + * Limitations: + * Binary decoding ignores header. There doesn't seem to be much of a use for it. + * There is perhaps some question as to how valid it is to always assume little-endian-ness. + * ASCII decoding assumes file is UTF-8. Seems to work for the examples... + * + * Usage: + * var loader = new THREE.STLLoader(); + * loader.addEventListener( 'load', function ( event ) { + * + * var geometry = event.content; + * scene.add( new THREE.Mesh( geometry ) ); + * + * } ); + * loader.load( './models/stl/slotted_disk.stl' ); + */ + + +THREE.STLLoader = function () {}; + +THREE.STLLoader.prototype = { + + constructor: THREE.STLLoader + +}; + +THREE.STLLoader.prototype.load = function ( url, callback ) { + + var scope = this; + + var xhr = new XMLHttpRequest(); + + function onloaded( event ) { + + if ( event.target.status === 200 || event.target.status === 0 ) { + + var geometry = scope.parse( event.target.response || event.target.responseText ); + + scope.dispatchEvent( { type: 'load', content: geometry } ); + + if ( callback ) callback( geometry ); + + } else { + + scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']', response: event.target.responseText } ); + + } + + } + + xhr.addEventListener( 'load', onloaded, false ); + + xhr.addEventListener( 'progress', function ( event ) { + + scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } ); + + }, false ); + + xhr.addEventListener( 'error', function () { + + scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } ); + + }, false ); + + if ( xhr.overrideMimeType ) xhr.overrideMimeType( 'text/plain; charset=x-user-defined' ); + xhr.open( 'GET', url, true ); + xhr.responseType = 'arraybuffer'; + xhr.send( null ); + +}; + +THREE.STLLoader.prototype.parse = function ( data ) { + + + var isBinary = function () { + + var expect, face_size, n_faces, reader; + reader = new DataView( binData ); + face_size = (32 / 8 * 3) + ((32 / 8 * 3) * 3) + (16 / 8); + n_faces = reader.getUint32(80,true); + expect = 80 + (32 / 8) + (n_faces * face_size); + return expect === reader.byteLength; + + }; + + var binData = this.ensureBinary( data ); + + return isBinary() + ? this.parseBinary( binData ) + : this.parseASCII( this.ensureString( data ) ); + +}; + +THREE.STLLoader.prototype.parseBinary = function ( data ) { + + var reader = new DataView( data ); + var faces = reader.getUint32( 80, true ); + var dataOffset = 84; + var faceLength = 12 * 4 + 2; + + var offset = 0; + + var geometry = new THREE.TypedGeometry( faces ); + + for ( var face = 0; face < faces; face ++ ) { + + var start = dataOffset + face * faceLength; + + for ( var i = 1; i <= 3; i ++ ) { + + var vertexstart = start + i * 12; + + geometry.vertices[ offset ] = reader.getFloat32( vertexstart, true ); + geometry.vertices[ offset + 1 ] = reader.getFloat32( vertexstart + 4, true ); + geometry.vertices[ offset + 2 ] = reader.getFloat32( vertexstart + 8, true ); + + geometry.normals[ offset ] = reader.getFloat32( start , true ); + geometry.normals[ offset + 1 ] = reader.getFloat32( start + 4, true ); + geometry.normals[ offset + 2 ] = reader.getFloat32( start + 8, true ); + + offset += 3; + + } + + } + + return geometry; + +}; + +THREE.STLLoader.prototype.parseASCII = function (data) { + + var geometry, length, normal, patternFace, patternNormal, patternVertex, result, text; + geometry = new THREE.Geometry(); + patternFace = /facet([\s\S]*?)endfacet/g; + + while ( ( result = patternFace.exec( data ) ) !== null ) { + + text = result[0]; + patternNormal = /normal[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g; + + while ( ( result = patternNormal.exec( text ) ) !== null ) { + + normal = new THREE.Vector3( parseFloat( result[ 1 ] ), parseFloat( result[ 3 ] ), parseFloat( result[ 5 ] ) ); + + } + + patternVertex = /vertex[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g; + + while ( ( result = patternVertex.exec( text ) ) !== null ) { + + geometry.vertices.push( new THREE.Vector3( parseFloat( result[ 1 ] ), parseFloat( result[ 3 ] ), parseFloat( result[ 5 ] ) ) ); + + } + + length = geometry.vertices.length; + + geometry.faces.push( new THREE.Face3( length - 3, length - 2, length - 1, normal ) ); + + } + + geometry.computeBoundingBox(); + geometry.computeBoundingSphere(); + + return geometry; + +}; + +THREE.STLLoader.prototype.ensureString = function (buf) { + + if (typeof buf !== "string"){ + var array_buffer = new Uint8Array(buf); + var str = ''; + for(var i = 0; i < buf.byteLength; i++) { + str += String.fromCharCode(array_buffer[i]); // implicitly assumes little-endian + } + return str; + } else { + return buf; + } + +}; + +THREE.STLLoader.prototype.ensureBinary = function (buf) { + + if (typeof buf === "string"){ + var array_buffer = new Uint8Array(buf.length); + for(var i = 0; i < buf.length; i++) { + array_buffer[i] = buf.charCodeAt(i) & 0xff; // implicitly assumes little-endian + } + return array_buffer.buffer || array_buffer; + } else { + return buf; + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.STLLoader.prototype ); + +if ( typeof DataView === 'undefined'){ + + DataView = function(buffer, byteOffset, byteLength){ + + this.buffer = buffer; + this.byteOffset = byteOffset || 0; + this.byteLength = byteLength || buffer.byteLength || buffer.length; + this._isString = typeof buffer === "string"; + + } + + DataView.prototype = { + + _getCharCodes:function(buffer,start,length){ + start = start || 0; + length = length || buffer.length; + var end = start + length; + var codes = []; + for (var i = start; i < end; i++) { + codes.push(buffer.charCodeAt(i) & 0xff); + } + return codes; + }, + + _getBytes: function (length, byteOffset, littleEndian) { + + var result; + + // Handle the lack of endianness + if (littleEndian === undefined) { + + littleEndian = this._littleEndian; + + } + + // Handle the lack of byteOffset + if (byteOffset === undefined) { + + byteOffset = this.byteOffset; + + } else { + + byteOffset = this.byteOffset + byteOffset; + + } + + if (length === undefined) { + + length = this.byteLength - byteOffset; + + } + + // Error Checking + if (typeof byteOffset !== 'number') { + + throw new TypeError('DataView byteOffset is not a number'); + + } + + if (length < 0 || byteOffset + length > this.byteLength) { + + throw new Error('DataView length or (byteOffset+length) value is out of bounds'); + + } + + if (this.isString){ + + result = this._getCharCodes(this.buffer, byteOffset, byteOffset + length); + + } else { + + result = this.buffer.slice(byteOffset, byteOffset + length); + + } + + if (!littleEndian && length > 1) { + + if (!(result instanceof Array)) { + + result = Array.prototype.slice.call(result); + + } + + result.reverse(); + } + + return result; + + }, + + // Compatibility functions on a String Buffer + + getFloat64: function (byteOffset, littleEndian) { + + var b = this._getBytes(8, byteOffset, littleEndian), + + sign = 1 - (2 * (b[7] >> 7)), + exponent = ((((b[7] << 1) & 0xff) << 3) | (b[6] >> 4)) - ((1 << 10) - 1), + + // Binary operators such as | and << operate on 32 bit values, using + and Math.pow(2) instead + mantissa = ((b[6] & 0x0f) * Math.pow(2, 48)) + (b[5] * Math.pow(2, 40)) + (b[4] * Math.pow(2, 32)) + + (b[3] * Math.pow(2, 24)) + (b[2] * Math.pow(2, 16)) + (b[1] * Math.pow(2, 8)) + b[0]; + + if (exponent === 1024) { + if (mantissa !== 0) { + return NaN; + } else { + return sign * Infinity; + } + } + + if (exponent === -1023) { // Denormalized + return sign * mantissa * Math.pow(2, -1022 - 52); + } + + return sign * (1 + mantissa * Math.pow(2, -52)) * Math.pow(2, exponent); + + }, + + getFloat32: function (byteOffset, littleEndian) { + + var b = this._getBytes(4, byteOffset, littleEndian), + + sign = 1 - (2 * (b[3] >> 7)), + exponent = (((b[3] << 1) & 0xff) | (b[2] >> 7)) - 127, + mantissa = ((b[2] & 0x7f) << 16) | (b[1] << 8) | b[0]; + + if (exponent === 128) { + if (mantissa !== 0) { + return NaN; + } else { + return sign * Infinity; + } + } + + if (exponent === -127) { // Denormalized + return sign * mantissa * Math.pow(2, -126 - 23); + } + + return sign * (1 + mantissa * Math.pow(2, -23)) * Math.pow(2, exponent); + }, + + getInt32: function (byteOffset, littleEndian) { + var b = this._getBytes(4, byteOffset, littleEndian); + return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]; + }, + + getUint32: function (byteOffset, littleEndian) { + return this.getInt32(byteOffset, littleEndian) >>> 0; + }, + + getInt16: function (byteOffset, littleEndian) { + return (this.getUint16(byteOffset, littleEndian) << 16) >> 16; + }, + + getUint16: function (byteOffset, littleEndian) { + var b = this._getBytes(2, byteOffset, littleEndian); + return (b[1] << 8) | b[0]; + }, + + getInt8: function (byteOffset) { + return (this.getUint8(byteOffset) << 24) >> 24; + }, + + getUint8: function (byteOffset) { + return this._getBytes(1, byteOffset)[0]; + } + + }; + +} diff --git a/docdoku-dplm/ui/app/js/components/3d/loaders/VRMLLoader.js b/docdoku-dplm/ui/app/js/components/3d/loaders/VRMLLoader.js new file mode 100644 index 0000000000..397450d1b1 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/3d/loaders/VRMLLoader.js @@ -0,0 +1,845 @@ +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.VRMLLoader = function () {}; + +THREE.VRMLLoader.prototype = { + + constructor: THREE.VRMLLoader, + + // for IndexedFaceSet support + isRecordingPoints: false, + isRecordingFaces: false, + points: [], + indexes : [], + + // for Background support + isRecordingAngles: false, + isRecordingColors: false, + angles: [], + colors: [], + + recordingFieldname: null, + + load: function ( url, callback ) { + + var scope = this; + var request = new XMLHttpRequest(); + + request.addEventListener( 'load', function ( event ) { + + var object = scope.parse( event.target.responseText ); + + scope.dispatchEvent( { type: 'load', content: object } ); + + if ( callback ) callback( object ); + + }, false ); + + request.addEventListener( 'progress', function ( event ) { + + scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } ); + + }, false ); + + request.addEventListener( 'error', function () { + + scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } ); + + }, false ); + + request.open( 'GET', url, true ); + request.send( null ); + + }, + + parse: function ( data ) { + + var parseV1 = function ( lines, scene ) { + + console.warn( 'VRML V1.0 not supported yet' ); + + }; + + var parseV2 = function ( lines, scene ) { + var defines = {}; + var float_pattern = /(\b|\-|\+)([\d\.e]+)/; + var float3_pattern = /([\d\.\+\-e]+),?\s+([\d\.\+\-e]+),?\s+([\d\.\+\-e]+)/; + + /** + * Interpolates colors a and b following their relative distance + * expressed by t. + * + * @param float a + * @param float b + * @param float t + * @returns {Color} + */ + var interpolateColors = function(a, b, t) { + var deltaR = a.r - b.r; + var deltaG = a.g - b.g; + var deltaB = a.b - b.b; + + var c = new THREE.Color(); + + c.r = a.r - t * deltaR; + c.g = a.g - t * deltaG; + c.b = a.b - t * deltaB; + + return c; + }; + + /** + * Vertically paints the faces interpolating between the + * specified colors at the specified angels. This is used for the Background + * node, but could be applied to other nodes with multiple faces as well. + * + * When used with the Background node, default is directionIsDown is true if + * interpolating the skyColor down from the Zenith. When interpolationg up from + * the Nadir i.e. interpolating the groundColor, the directionIsDown is false. + * + * The first angle is never specified, it is the Zenith (0 rad). Angles are specified + * in radians. The geometry is thought a sphere, but could be anything. The color interpolation + * is linear along the Y axis in any case. + * + * You must specify one more color than you have angles at the beginning of the colors array. + * This is the color of the Zenith (the top of the shape). + * + * @param geometry + * @param radius + * @param angles + * @param colors + * @param boolean directionIsDown Whether to work bottom up or top down. + */ + var paintFaces = function (geometry, radius, angles, colors, directionIsDown) { + + var f, n, p, vertexIndex, color; + + var direction = directionIsDown ? 1 : -1; + + var faceIndices = [ 'a', 'b', 'c', 'd' ]; + + var coord = [ ], aColor, bColor, t = 1, A = {}, B = {}, applyColor = false, colorIndex; + + for ( var k = 0; k < angles.length; k++ ) { + + var vec = { }; + + // push the vector at which the color changes + vec.y = direction * ( Math.cos( angles[k] ) * radius); + + vec.x = direction * ( Math.sin( angles[k] ) * radius); + + coord.push( vec ); + + } + + // painting the colors on the faces + for ( var i = 0; i < geometry.faces.length ; i++ ) { + + f = geometry.faces[ i ]; + + n = ( f instanceof THREE.Face3 ) ? 3 : 4; + + for ( var j = 0; j < n; j++ ) { + + vertexIndex = f[ faceIndices[ j ] ]; + + p = geometry.vertices[ vertexIndex ]; + + for ( var index = 0; index < colors.length; index++ ) { + + // linear interpolation between aColor and bColor, calculate proportion + // A is previous point (angle) + if ( index === 0 ) { + + A.x = 0; + A.y = directionIsDown ? radius : -1 * radius; + + } else { + + A.x = coord[ index-1 ].x; + A.y = coord[ index-1 ].y; + + } + + // B is current point (angle) + B = coord[index]; + + if ( undefined !== B ) { + // p has to be between the points A and B which we interpolate + applyColor = directionIsDown ? p.y <= A.y && p.y > B.y : p.y >= A.y && p.y < B.y; + + if (applyColor) { + + bColor = colors[ index + 1 ]; + + aColor = colors[ index ]; + + // below is simple linear interpolation + t = Math.abs( p.y - A.y ) / ( A.y - B.y ); + + // to make it faster, you can only calculate this if the y coord changes, the color is the same for points with the same y + color = interpolateColors( aColor, bColor, t ); + + f.vertexColors[ j ] = color; + } + + } else if ( undefined === f.vertexColors[ j ] ) { + colorIndex = directionIsDown ? colors.length -1 : 0; + f.vertexColors[ j ] = colors[ colorIndex ]; + + } + } + + } + + } + }; + + var parseProperty = function (node, line) { + + var parts = [], part, property = {}, fieldName; + + /** + * Expression for matching relevant information, such as a name or value, but not the separators + * @type {RegExp} + */ + var regex = /[^\s,\[\]]+/g; + + var point, index, angles, colors; + + while (null != ( part = regex.exec(line) ) ) { + parts.push(part[0]); + } + + fieldName = parts[0]; + + + // trigger several recorders + switch (fieldName) { + case 'skyAngle': + case 'groundAngle': + this.recordingFieldname = fieldName; + this.isRecordingAngles = true; + this.angles = []; + break; + case 'skyColor': + case 'groundColor': + this.recordingFieldname = fieldName; + this.isRecordingColors = true; + this.colors = []; + break; + case 'point': + this.recordingFieldname = fieldName; + this.isRecordingPoints = true; + this.points = []; + break; + case 'coordIndex': + this.recordingFieldname = fieldName; + this.isRecordingFaces = true; + this.indexes = []; + break; + } + + if (this.isRecordingFaces) { + + // the parts hold the indexes as strings + if (parts.length > 0) { + index = []; + + for (var ind = 0;ind < parts.length; ind++) { + + // the part should either be positive integer or -1 + if (!/(-?\d+)/.test( parts[ind]) ) { + continue; + } + + // end of current face + if (parts[ind] === "-1") { + if (index.length > 0) { + this.indexes.push(index); + } + + // start new one + index = []; + } else { + index.push(parseInt( parts[ind]) ); + } + } + + } + + // end + if (/]/.exec(line)) { + this.isRecordingFaces = false; + node.coordIndex = this.indexes; + } + + } else if (this.isRecordingPoints) { + + parts = float3_pattern.exec(line); + + // parts may be empty on first and last line + if (null != parts) { + point = { + x: parseFloat(parts[1]), + y: parseFloat(parts[2]), + z: parseFloat(parts[3]) + }; + + this.points.push(point); + } + + // end + if ( /]/.exec(line) ) { + this.isRecordingPoints = false; + node.points = this.points; + } + + } else if ( this.isRecordingAngles ) { + + // the parts hold the angles as strings + if ( parts.length > 0 ) { + + for ( var ind = 0;ind < parts.length; ind++ ) { + + // the part should be a float + if ( ! float_pattern.test( parts[ind] ) ) { + continue; + } + + this.angles.push( parseFloat( parts[ind] ) ); + } + + } + + // end + if ( /]/.exec(line) ) { + this.isRecordingAngles = false; + node[this.recordingFieldname] = this.angles; + } + + } else if (this.isRecordingColors) { + // this is the float3 regex with the g modifier added, you could also explode the line by comma first (faster probably) + var float3_repeatable = /([\d\.\+\-e]+),?\s+([\d\.\+\-e]+),?\s+([\d\.\+\-e]+)/g; + + while( null !== (parts = float3_repeatable.exec(line) ) ) { + + color = { + r: parseFloat(parts[1]), + g: parseFloat(parts[2]), + b: parseFloat(parts[3]) + }; + + this.colors.push(color); + + } + + // end + if (/]/.exec(line)) { + this.isRecordingColors = false; + node[this.recordingFieldname] = this.colors; + } + + } else if ( parts[parts.length -1] !== 'NULL' && fieldName !== 'children') { + + switch (fieldName) { + + case 'diffuseColor': + case 'emissiveColor': + case 'specularColor': + case 'color': + + if (parts.length != 4) { + console.warn('Invalid color format detected for ' + fieldName ); + break; + } + + property = { + 'r' : parseFloat(parts[1]), + 'g' : parseFloat(parts[2]), + 'b' : parseFloat(parts[3]) + } + + break; + + case 'translation': + case 'scale': + case 'size': + if (parts.length != 4) { + console.warn('Invalid vector format detected for ' + fieldName); + break; + } + + property = { + 'x' : parseFloat(parts[1]), + 'y' : parseFloat(parts[2]), + 'z' : parseFloat(parts[3]) + } + + break; + + case 'radius': + case 'topRadius': + case 'bottomRadius': + case 'height': + case 'transparency': + case 'shininess': + case 'ambientIntensity': + if (parts.length != 2) { + console.warn('Invalid single float value specification detected for ' + fieldName); + break; + } + + property = parseFloat(parts[1]); + + break; + + case 'rotation': + if (parts.length != 5) { + console.warn('Invalid quaternion format detected for ' + fieldName); + break; + } + + property = { + 'x' : parseFloat(parts[1]), + 'y' : parseFloat(parts[2]), + 'z' : parseFloat(parts[3]), + 'w' : parseFloat(parts[4]) + } + + break; + + case 'ccw': + case 'solid': + case 'colorPerVertex': + case 'convex': + if (parts.length != 2) { + console.warn('Invalid format detected for ' + fieldName); + break; + } + + property = parts[1] === 'TRUE' ? true : false; + + break; + } + + node[fieldName] = property; + } + + return property; + }; + + var getTree = function ( lines ) { + + var tree = { 'string': 'Scene', children: [] }; + var current = tree; + var matches; + var specification; + + for ( var i = 0; i < lines.length; i ++ ) { + + var comment = ''; + + var line = lines[ i ]; + + // omit whitespace only lines + if ( null !== ( result = /^\s+?$/g.exec( line ) ) ) { + continue; + } + + line = line.trim(); + + // skip empty lines + if (line === '') { + continue; + } + + if ( /#/.exec( line ) ) { + + var parts = line.split('#'); + + // discard everything after the #, it is a comment + line = parts[0]; + + // well, let's also keep the comment + comment = parts[1]; + } + + if ( matches = /([^\s]*){1}\s?{/.exec( line ) ) { // first subpattern should match the Node name + + var block = { 'nodeType' : matches[1], 'string': line, 'parent': current, 'children': [],'comment' : comment}; + current.children.push( block ); + current = block; + + if ( /}/.exec( line ) ) { + // example: geometry Box { size 1 1 1 } # all on the same line + specification = /{(.*)}/.exec( line )[ 1 ]; + + // todo: remove once new parsing is complete? + block.children.push( specification ); + + parseProperty(current, specification); + + current = current.parent; + + } + + } else if ( /}/.exec( line ) ) { + + current = current.parent; + + } else if ( line !== '' ) { + + parseProperty(current, line); + // todo: remove once new parsing is complete? we still do not parse geometry and appearance the new way + current.children.push( line ); + + } + + } + + return tree; + } + + var parseNode = function ( data, parent ) { + + // console.log( data ); + + if ( typeof data === 'string' ) { + + if ( /USE/.exec( data ) ) { + + var defineKey = /USE\s+?(\w+)/.exec( data )[ 1 ]; + + if (undefined == defines[defineKey]) { + console.warn(defineKey + ' is not defined.'); + } else { + + if ( /appearance/.exec( data ) && defineKey ) { + + parent.material = defines[ defineKey ].clone(); + + } else if ( /geometry/.exec( data ) && defineKey ) { + + parent.geometry = defines[ defineKey ].clone(); + + // the solid property is not cloned with clone(), is only needed for VRML loading, so we need to transfer it + if (undefined !== defines[ defineKey ].solid && defines[ defineKey ].solid === false) { + parent.geometry.solid = false; + parent.material.side = THREE.DoubleSide; + } + + } else if (defineKey){ + + var object = defines[ defineKey ].clone(); + parent.add( object ); + + } + + } + + } + + return; + + } + + var object = parent; + + if ( 'Transform' === data.nodeType || 'Group' === data.nodeType ) { + + object = new THREE.Object3D(); + + if ( /DEF/.exec( data.string ) ) { + object.name = /DEF\s+(\w+)/.exec( data.string )[ 1 ]; + defines[ object.name ] = object; + } + + if ( undefined !== data['translation'] ) { + + var t = data.translation; + + object.position.set(t.x, t.y, t.z); + + } + + if ( undefined !== data.rotation ) { + + var r = data.rotation; + + object.quaternion.setFromAxisAngle( new THREE.Vector3( r.x, r.y, r.z ), r.w ); + + } + + if ( undefined !== data.scale ) { + + var s = data.scale; + + object.scale.set( s.x, s.y, s.z ); + + } + + parent.add( object ); + + } else if ( 'Shape' === data.nodeType ) { + + object = new THREE.Mesh(); + + if ( /DEF/.exec( data.string ) ) { + + object.name = /DEF (\w+)/.exec( data.string )[ 1 ]; + + defines[ object.name ] = object; + } + + parent.add( object ); + + } else if ( 'Background' === data.nodeType ) { + + var segments = 20; + + // sky (full sphere): + var radius = 2e4; + + var skyGeometry = new THREE.SphereGeometry( radius, segments, segments ); + + var skyMaterial = new THREE.MeshBasicMaterial( { color: 'white', vertexColors: THREE.VertexColors, shading: THREE.NoShading } ); + + skyMaterial.side = THREE.BackSide; + + skyMaterial.fog = false; + + skyMaterial.color = new THREE.Color(); + + paintFaces( skyGeometry, radius, data.skyAngle, data.skyColor, true ); + + var sky = new THREE.Mesh( skyGeometry, skyMaterial ); + + scene.add( sky ); + + // ground (half sphere): + + radius = 1.2e4; + + var groundGeometry = new THREE.SphereGeometry( radius, segments, segments, 0, 2 * Math.PI, 0.5 * Math.PI, 1.5 * Math.PI ); + + var groundMaterial = new THREE.MeshBasicMaterial( { color: 'white', vertexColors: THREE.VertexColors, shading: THREE.NoShading } ); + + groundMaterial.side = THREE.BackSide; + + groundMaterial.fog = false; + + groundMaterial.color = new THREE.Color(); + + paintFaces( groundGeometry, radius, data.groundAngle, data.groundColor, false ); + + var ground = new THREE.Mesh( groundGeometry, groundMaterial ); + + scene.add( ground ); + + } else if ( /geometry/.exec( data.string ) ) { + + if ( 'Box' === data.nodeType ) { + + var s = data.size; + + parent.geometry = new THREE.BoxGeometry( s.x, s.y, s.z ); + + } else if ( 'Cylinder' === data.nodeType ) { + + parent.geometry = new THREE.CylinderGeometry( data.radius, data.radius, data.height ); + + } else if ( 'Cone' === data.nodeType ) { + + parent.geometry = new THREE.CylinderGeometry( data.topRadius, data.bottomRadius, data.height ); + + } else if ( 'Sphere' === data.nodeType ) { + + parent.geometry = new THREE.SphereGeometry( data.radius ); + + } else if ( 'IndexedFaceSet' === data.nodeType ) { + + var geometry = new THREE.Geometry(); + + var indexes; + + for ( var i = 0, j = data.children.length; i < j; i++ ) { + + var child = data.children[ i ]; + + var vec; + + if ( 'Coordinate' === child.nodeType ) { + + for ( var k = 0, l = child.points.length; k < l; k++ ) { + + var point = child.points[ k ]; + + vec = new THREE.Vector3( point.x, point.y, point.z ); + + geometry.vertices.push( vec ); + } + + break; + } + } + + var skip = 0; + + // read this: http://math.hws.edu/eck/cs424/notes2013/16_Threejs_Advanced.html + for ( var i = 0, j = data.coordIndex.length; i < j; i++ ) { + + indexes = data.coordIndex[i]; + + // vrml support multipoint indexed face sets (more then 3 vertices). You must calculate the composing triangles here + skip = 0; + + // todo: this is the time to check if the faces are ordered ccw or not (cw) + + // Face3 only works with triangles, but IndexedFaceSet allows shapes with more then three vertices, build them of triangles + while ( indexes.length >= 3 && skip < ( indexes.length -2 ) ) { + + var face = new THREE.Face3( + indexes[0], + indexes[skip + 1], + indexes[skip + 2], + null // normal, will be added later + // todo: pass in the color, if a color index is present + ); + + skip++; + + geometry.faces.push( face ); + + } + + + } + + if ( false === data.solid ) { + parent.material.side = THREE.DoubleSide; + } + + // we need to store it on the geometry for use with defines + geometry.solid = data.solid; + + geometry.computeFaceNormals(); + //geometry.computeVertexNormals(); // does not show + geometry.computeBoundingSphere(); + + // see if it's a define + if ( /DEF/.exec( data.string ) ) { + geometry.name = /DEF (\w+)/.exec( data.string )[ 1 ]; + defines[ geometry.name ] = geometry; + } + + parent.geometry = geometry; + } + + return; + + } else if ( /appearance/.exec( data.string ) ) { + + for ( var i = 0; i < data.children.length; i ++ ) { + + var child = data.children[ i ]; + + if ( 'Material' === child.nodeType ) { + var material = new THREE.MeshPhongMaterial(); + + if ( undefined !== child.diffuseColor ) { + + var d = child.diffuseColor; + + material.color.setRGB( d.r, d.g, d.b ); + + } + + if ( undefined !== child.emissiveColor ) { + + var e = child.emissiveColor; + + material.emissive.setRGB( e.r, e.g, e.b ); + + } + + if ( undefined !== child.specularColor ) { + + var s = child.specularColor; + + material.specular.setRGB( s.r, s.g, s.b ); + + } + + if ( undefined !== child.transparency ) { + + var t = child.transparency; + + // transparency is opposite of opacity + material.opacity = Math.abs( 1 - t ); + + material.transparent = true; + + } + + if ( /DEF/.exec( data.string ) ) { + + material.name = /DEF (\w+)/.exec( data.string )[ 1 ]; + + defines[ material.name ] = material; + + } + + parent.material = material; + + // material found, stop looping + break; + } + + } + + return; + + } + + for ( var i = 0, l = data.children.length; i < l; i ++ ) { + + var child = data.children[ i ]; + + parseNode( data.children[ i ], object ); + + } + + } + + parseNode( getTree( lines ), scene ); + + }; + + var scene = new THREE.Scene(); + + var lines = data.split( '\n' ); + + var header = lines.shift(); + + if ( /V1.0/.exec( header ) ) { + + parseV1( lines, scene ); + + } else if ( /V2.0/.exec( header ) ) { + + parseV2( lines, scene ); + + } + + return scene; + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.VRMLLoader.prototype ); + diff --git a/docdoku-dplm/ui/app/js/components/3d/services-3d.js b/docdoku-dplm/ui/app/js/components/3d/services-3d.js new file mode 100644 index 0000000000..31020fd248 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/3d/services-3d.js @@ -0,0 +1,256 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.services.3d',[]) + + .factory('AvailableLoaders',function(){ + return ['js','json','obj','stl','dae','ply','wrl','bin']; + }) + + .service('ModelLoaderService',function($q){ + + // Returns a THREE.Mesh in a promise + + this.load = function(filename){ + + var fs = require('fs'); + var deferred = $q.defer(); + var extension = filename.split('.').pop(); + var material = new THREE.MeshNormalMaterial(); + + function mergeToSingleGeometry(object){ + var geometries = []; + object.traverse(function(child){ + if (child instanceof THREE.Mesh && child.geometry) { + geometries.push(child.geometry); + } + }); + var combined = new THREE.Geometry(); + angular.forEach(geometries,function(geometry){ + THREE.GeometryUtils.merge(combined, geometry); + }); + return combined; + } + + switch (extension){ + case 'obj': + + fs.readFile(filename, 'utf-8' ,function (err, data) { + if (err) deferred.reject(err); + else{ + var loader = new THREE.OBJLoader(); + var object = loader.parse(data); + var geometry = mergeToSingleGeometry(object); + deferred.resolve(new THREE.Mesh(geometry, material)); + } + }); + + break; + + case 'stl': + + fs.readFile(filename, 'binary', function (err, data) { + if (err) deferred.reject(err); + else{ + var loader = new THREE.STLLoader(); + var geometry = loader.parse(data); + deferred.resolve(new THREE.Mesh(geometry,material)); + } + }); + + break; + + case 'wrl': + + fs.readFile(filename, 'utf-8', function (err, data) { + if (err) deferred.reject(err); + else{ + var loader = new THREE.VRMLLoader(); + var object = loader.parse(data); + var geometry = mergeToSingleGeometry(object); + deferred.resolve(new THREE.Mesh(geometry, material)); + } + }); + + break; + + case 'bin': + + fs.readFile(filename, 'binary', function (err, data) { + if (err) deferred.reject(err); + else{ + var loader = new THREE.BinaryLoader(); + var str = data.toString(); + var buffer = new ArrayBuffer( str.length ); + var bufView = new Uint8Array( buffer ); + for ( var i = 0, l = str.length; i < l; i ++ ) { + bufView[ i ] = str.charCodeAt( i ) & 0xff; + } + loader.createBinModel( buffer, function(geometry){ + deferred.resolve(new THREE.Mesh(geometry, material)); + }, '', {} ); + } + }); + + break; + + case 'dae': + + fs.readFile(filename, 'utf-8', function (err, data) { + if (err) deferred.reject(err); + else{ + var loader = new THREE.ColladaLoader(); + var xmlParser = new DOMParser(); + var doc = xmlParser.parseFromString( data, "application/xml" ); + loader.parse( doc, function(object){ + var geometry = mergeToSingleGeometry(object); + deferred.resolve(new THREE.Mesh(geometry, material)); + }); + } + }); + + break; + + case 'js': + case 'json': + + fs.readFile(filename, 'utf-8', function (err, data) { + if (err) deferred.reject(err); + else{ + var loader = new THREE.JSONLoader(); + var object = loader.parse(JSON.parse(data),''); + deferred.resolve(new THREE.Mesh(object.geometry, material)); + } + }); + + break; + + case 'ply': + + fs.readFile(filename, 'binary', function (err, data) { + if (err) deferred.reject(err); + else{ + var loader = new THREE.PLYLoader(); + var str = data.toString(); + var buffer = new ArrayBuffer( str.length ); + var bufView = new Uint8Array( buffer ); + for ( var i = 0, l = str.length; i < l; i ++ ) { + bufView[ i ] = str.charCodeAt( i ) & 0xff; + } + var geometry = loader.parse(buffer); + deferred.resolve(new THREE.Mesh(geometry, material)); + } + }); + + break; + + default : + deferred.reject(); + break; + } + + return deferred.promise; + + }; + + }) + + .directive('modelViewer',function(ModelLoaderService,$filter){ + + return { + restrict: 'A', + scope: { + 'width': '=', + 'height': '=', + 'fillcontainer': '=', + 'filename':'=' + }, + + link: function postLink(scope, element, attrs) { + + var camera, scene, renderer, + light, controls, + contW = (scope.fillcontainer) ? element[0].clientWidth : scope.width, + contH = scope.height, + windowHalfX = contW / 2, + windowHalfY = contH / 2, + destroy = false, + spin = document.createElement('i'); + + spin.classList.add('fa'); + spin.classList.add('fa-spinner'); + spin.classList.add('fa-spin'); + + element[0].appendChild(spin); + + var onWindowResize = function () { + contW = (scope.fillcontainer) ? + element[0].clientWidth : scope.width; + contH = scope.height; + + windowHalfX = contW / 2; + windowHalfY = contH / 2; + + camera.aspect = contW / contH; + camera.updateProjectionMatrix(); + + renderer.setSize( contW, contH ); + }; + + var onMeshLoaded = function(mesh){ + + element[0].removeChild(spin); + scene.add( mesh ); + mesh.geometry.computeBoundingBox(); + var center = mesh.geometry.boundingBox.center(); + controls.target.copy(center); + camera.position.copy(center); + camera.position.z -= mesh.geometry.boundingBox.size().length() * 2; + animate(); + }; + + var onError = function(){ + element[0].removeChild(spin); + destroy = true; + }; + + var init = function () { + camera = new THREE.PerspectiveCamera( 20, contW / contH, 1, 1000000 ); + camera.position.z = 1800; + scene = new THREE.Scene(); + light = new THREE.DirectionalLight( 0xffffff ); + light.position.set( 0, 0, 1 ); + scene.add( light ); + var canvas = document.createElement( 'canvas' ); + canvas.width = 128; + canvas.height = 128; + renderer = new THREE.WebGLRenderer( { antialias: true , alpha:true} ); + renderer.setClearColor( 0x000000, 0 ); + renderer.setSize( contW, contH ); + element[0].appendChild( renderer.domElement ); + controls = new THREE.OrbitControls( camera,element[0]); + window.addEventListener( 'resize', onWindowResize, false ); + }; + + var animate = function () { + if(!destroy){ + requestAnimationFrame(animate); + } + controls.update(); + renderer.render( scene, camera ); + }; + + scope.$watch('fillcontainer + width + height', onWindowResize); + + scope.$on('$destroy',function(){ + destroy = true; + }); + + init(); + ModelLoaderService.load(scope.filename).then(onMeshLoaded,onError); + + } + }; + }); +})(); diff --git a/docdoku-dplm/ui/app/js/components/3d/utils/TypedGeometry.js b/docdoku-dplm/ui/app/js/components/3d/utils/TypedGeometry.js new file mode 100644 index 0000000000..829b955cb9 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/3d/utils/TypedGeometry.js @@ -0,0 +1,121 @@ +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.TypedGeometry = function ( size ) { + + THREE.BufferGeometry.call( this ); + + if ( size !== undefined ) { + + this.vertices = new Float32Array( size * 3 * 3 ); + this.normals = new Float32Array( size * 3 * 3 ); + this.uvs = new Float32Array( size * 3 * 2 ); + + this.attributes[ 'position' ] = { array: this.vertices, itemSize: 3 }; + this.attributes[ 'normal' ] = { array: this.normals, itemSize: 3 }; + this.attributes[ 'uv' ] = { array: this.uvs, itemSize: 2 }; + + } + +}; + +THREE.TypedGeometry.prototype = Object.create( THREE.BufferGeometry.prototype ); + +THREE.TypedGeometry.prototype.setArrays = function ( vertices, normals, uvs ) { + + this.vertices = vertices; + this.normals = normals; + this.uvs = uvs; + + this.attributes[ 'position' ] = { array: vertices, itemSize: 3 }; + this.attributes[ 'normal' ] = { array: normals, itemSize: 3 }; + this.attributes[ 'uv' ] = { array: uvs, itemSize: 2 }; + + return this; + +}; + +THREE.TypedGeometry.prototype.merge = ( function () { + + var offset = 0; + var normalMatrix = new THREE.Matrix3(); + + return function ( geometry, matrix, startOffset ) { + + if ( startOffset !== undefined ) offset = startOffset; + + var offset2 = offset * 2; + var offset3 = offset * 3; + + var vertices = this.attributes[ 'position' ].array; + var normals = this.attributes[ 'normal' ].array; + var uvs = this.attributes[ 'uv' ].array; + + if ( geometry instanceof THREE.TypedGeometry ) { + + var vertices2 = geometry.attributes[ 'position' ].array; + var normals2 = geometry.attributes[ 'normal' ].array; + var uvs2 = geometry.attributes[ 'uv' ].array; + + for ( var i = 0, l = vertices2.length; i < l; i += 3 ) { + + vertices[ i + offset3 ] = vertices2[ i ]; + vertices[ i + offset3 + 1 ] = vertices2[ i + 1 ]; + vertices[ i + offset3 + 2 ] = vertices2[ i + 2 ]; + + normals[ i + offset3 ] = normals2[ i ]; + normals[ i + offset3 + 1 ] = normals2[ i + 1 ]; + normals[ i + offset3 + 2 ] = normals2[ i + 2 ]; + + uvs[ i + offset2 ] = uvs2[ i ]; + uvs[ i + offset2 + 1 ] = uvs2[ i + 1 ]; + + } + + } else if ( geometry instanceof THREE.IndexedTypedGeometry ) { + + var indices2 = geometry.attributes[ 'index' ].array; + var vertices2 = geometry.attributes[ 'position' ].array; + var normals2 = geometry.attributes[ 'normal' ].array; + var uvs2 = geometry.attributes[ 'uv' ].array; + + for ( var i = 0, l = indices2.length; i < l; i ++ ) { + + var index = indices2[ i ]; + + var index3 = index * 3; + var i3 = i * 3; + + vertices[ i3 + offset3 ] = vertices2[ index3 ]; + vertices[ i3 + offset3 + 1 ] = vertices2[ index3 + 1 ]; + vertices[ i3 + offset3 + 2 ] = vertices2[ index3 + 2 ]; + + normals[ i3 + offset3 ] = normals2[ index3 ]; + normals[ i3 + offset3 + 1 ] = normals2[ index3 + 1 ]; + normals[ i3 + offset3 + 2 ] = normals2[ index3 + 2 ]; + + var index2 = index * 2; + var i2 = i * 2; + + uvs[ i2 + offset2 ] = uvs2[ index2 ]; + uvs[ i2 + offset2 + 1 ] = uvs2[ index2 + 1 ]; + + } + + if ( matrix !== undefined ) { + + matrix.applyToVector3Array( vertices, offset3, indices2.length * 3 ); + + normalMatrix.getNormalMatrix( matrix ); + normalMatrix.applyToVector3Array( normals, offset3, indices2.length * 3 ); + + } + + offset += indices2.length; + + } + + }; + +} )(); \ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/components/cli.js b/docdoku-dplm/ui/app/js/components/cli.js new file mode 100644 index 0000000000..f47a7c4c6e --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/cli.js @@ -0,0 +1,634 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.services.cli', []) + + .service('CliService', function (ConfigurationService, NotificationService, $q, $log) { + + var configuration = ConfigurationService.configuration; + var classPath = process.cwd() + '/docdoku-cli-jar-with-dependencies.jar'; + var mainClass = 'com.docdoku.cli.MainCommand'; + var memOptions = '-Xmx1024M'; + + var run = function (args, silent, onOutput) { + + if(configuration.ssl){ + args.push('--ssl'); + } + + var deferred = $q.defer(); + + $log.info(([memOptions, '-cp', classPath, mainClass].concat(args)).join(' ')); + + var spawn = require('child_process').spawn; + var cliProcess = spawn(configuration.java ||'java', [memOptions, '-cp', classPath, mainClass].concat(args)); + + var objects = []; + var errors = []; + + cliProcess.stdout.on('data', function (data) { + $log.log(data.toString()); + var entries = data.toString().split('\n'); + angular.forEach(entries, function (entry) { + if (entry && entry.trim()) { + var object = JSON.parse(entry); + + if (object.progress) { + deferred.notify(object.progress); + } else if (object.info) { + + if(typeof onOutput === 'function'){ + onOutput(object); + } + + if (!silent) { + NotificationService.toast(object.info); + } + console.info(object.info); + } + objects.push(object); + } + }); + }); + + cliProcess.stderr.on('data', function (data) { + $log.warn('STDERR ' + data.toString()); + var entries = data.toString().split('\n'); + angular.forEach(entries, function (entry) { + if (entry && entry.trim()) { + var object = JSON.parse(entry); + + if(typeof onOutput === 'function'){ + onOutput(object); + } + + if (object.error) { + if (!silent) { + NotificationService.toast(object.error); + } + } + errors.push(object); + } + }); + }); + + cliProcess.stderr.on('close', function () { + if (objects.length) { + deferred.resolve(objects[objects.length - 1]); + } + else if (errors.length) { + deferred.reject(errors[errors.length - 1]); + } else { + deferred.resolve(); + } + }); + + return deferred.promise; + + }; + + ////////////////////////////////////////////// + // Common services + + this.getWorkspaces = function () { + var args = [ + 'wl', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password + ]; + + return run(args); + + }; + + this.fetchAccount = function(){ + + var args = [ + 'a', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password + ]; + + return run(args); + }; + + this.getStatusForFile = function (file) { + + var args = [ + 'st', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + file.path + ]; + + return run(args, true).then(function (status) { + + if(status.id){ + if (!file.document) { + file.document = {}; + } + angular.extend(file.document, status); + }else if(status.partNumber){ + if (!file.part) { + file.part = {}; + } + angular.extend(file.part, status); + } + + }); + }; + + + ////////////////////////////////////////////// + // Part services + + + this.getStatusForPart = function (part) { + + var args = [ + 'st', 'part', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', part.workspace, + '-o', part.partNumber, + '-r', part.version + ]; + + return run(args, true).then(function (newPart) { + angular.extend(part, newPart); + }); + + }; + + this.checkoutPart = function (part, path, options, onOutput) { + + var args = [ + 'co', 'part', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', part.workspace, + '-o', part.partNumber, + '-r', part.version + ]; + + if (options.recursive) { + args.push('-R'); + } + if (options.force) { + args.push('-f'); + } + if (options.baseline) { + args.push('-b'); + args.push(options.baseline); + } + + args.push(path); + + return run(args, true, onOutput); + + }; + + this.checkinPart = function (part,options, onOutput) { + + var args = [ + 'ci', 'part', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', part.workspace, + '-o', part.partNumber, + '-r', part.version + ]; + + if(options && options.message){ + args.push('-m'); + args.push(options.message); + } + + if(options && options.path){ + args.push(options.path); + } + + return run(args,true,onOutput); + + }; + + this.undoCheckoutPart = function (part, onOutput) { + + var args = [ + 'uco', 'part', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', part.workspace, + '-o', part.partNumber, + '-r', part.version + ]; + + return run(args, true, onOutput); + }; + + this.downloadNativeCad = function (part, path, options, cbOutput) { + + var args = [ + 'get', 'part', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', part.workspace, + '-o', part.partNumber, + '-r', part.version + ]; + + if (options.recursive) { + args.push('-R'); + } + if (options.force) { + args.push('-f'); + } + if (options.baseline) { + args.push('-b'); + args.push(options.baseline); + } + + args.push(path); + + return run(args, true, cbOutput); + + }; + + this.putCADFile = function (workspace, file, onOutput) { + + var args = [ + 'put', 'part', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', workspace + ]; + + args.push(file); + + return run(args, true, onOutput); + + }; + + this.createPart = function (part, filePath, onOutput) { + + var args = [ + 'cr', 'part', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', part.workspace, + '-o', part.partNumber + ]; + + if (part.name) { + args.push('-N'); + args.push(part.name); + } + if (part.description) { + args.push('-d'); + args.push(part.description); + } + + if (part.standard) { + args.push('-s'); + } + + args.push(filePath); + + return run(args,true,onOutput); + + }; + + this.getPartMastersCount = function (workspace) { + + var args = [ + 'l', 'part', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', workspace, + '-c' + ]; + + return run(args); + + }; + + this.getPartMasters = function (workspace, start, max) { + + var args = [ + 'l', 'part', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-s', Number(start).toString(), + '-m', Number(max).toString(), + '-w', workspace + ]; + + return run(args); + + }; + + this.searchPartMasters = function (workspace, search) { + + var args = [ + 's', 'part', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', workspace, + '-s', search + ]; + + return run(args); + }; + + this.getBaselines = function (part) { + var args = [ + 'bl', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', part.workspace, + '-o', part.partNumber, + '-r', part.version + ]; + + return run(args); + }; + + this.getConversionStatus = function (part, onOutput) { + + var args = [ + 'cv', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', part.workspace, + '-o', part.partNumber, + '-r', part.version, + '-i', part.iterations[part.iterations.length-1] + ]; + + return run(args,true,onOutput); + + }; + + + ////////////////////////////////////////////// + // Document services + + this.getFolders=function(workspace,folder){ + var args = [ + 'f', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', workspace + ]; + + if(folder){ + args.push('-f'); + args.push(folder); + } + + return run(args); + }; + + this.getDocumentsRevisionsInFolder=function(workspace,folder){ + var args = [ + 'l', 'document', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', workspace + ]; + + if(folder){ + args.push('-f'); + args.push(folder); + } + + return run(args); + }; + + this.getCheckedOutDocumentsRevisions=function(workspace){ + var args = [ + 'l', 'document', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', workspace, + '-c' + ]; + + return run(args); + }; + + this.downloadDocumentFiles=function(document,path,options,onOutput){ + + var args = [ + 'get', 'document', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', document.workspace, + '-o', document.id, + '-r', document.version + ]; + + if (options.force) { + args.push('-f'); + } + + args.push(path); + + return run(args,true,onOutput); + }; + + this.getStatusForDocument = function(document) { + + var args = [ + 'st', 'document', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', document.workspace, + '-o', document.id, + '-r', document.version + ]; + + return run(args, true).then(function (newDocument) { + angular.extend(document,newDocument); + }); + + }; + + this.checkoutDocument = function (document, path, options, onOutput) { + + var args = [ + 'co', 'document', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', document.workspace, + '-o', document.id, + '-r', document.version + ]; + + if (options.force) { + args.push('-f'); + } + + args.push(path); + + return run(args, true, onOutput); + + }; + + this.checkinDocument = function (document, options, onOutput) { + + var args = [ + 'ci', 'document', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', document.workspace, + '-o', document.id, + '-r', document.version + ]; + + if(options && options.message){ + args.push('-m'); + args.push(options.message); + } + + if(options && options.path){ + args.push(options.path); + } + + return run(args, true, onOutput); + + }; + + this.undoCheckoutDocument = function (document, onOutput) { + + var args = [ + 'uco', 'document', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', document.workspace, + '-o', document.id, + '-r', document.version + ]; + + return run(args, true, onOutput); + }; + + this.putDocumentFile = function (workspace, file, onOutput) { + + var args = [ + 'put', 'document', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', workspace + ]; + + args.push(file); + + return run(args, true, onOutput); + + }; + + this.createDocument = function (document, filePath, onOutput) { + + var args = [ + 'cr', 'document', + '-F', 'json', + '-h', configuration.host, + '-P', configuration.port, + '-u', configuration.user, + '-p', configuration.password, + '-w', document.workspace, + '-o', document.id + ]; + + if (document.title) { + args.push('-N'); + args.push(document.title); + } + if (document.description) { + args.push('-d'); + args.push(document.description); + } + + args.push(filePath); + + return run(args, true, onOutput); + + }; + + + }); +})(); \ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/components/configuration.js b/docdoku-dplm/ui/app/js/components/configuration.js new file mode 100644 index 0000000000..e2f91491d5 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/configuration.js @@ -0,0 +1,91 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.services.configuration', []) + + .service('ConfigurationService', function ($q, $log, $filter, $location, NotificationService) { + + var _this = this; + + this.error = 'CONFIG_SERVICE_ERROR'; + + this.configuration = JSON.parse(localStorage.configuration || '{}'); + + this.save = function () { + localStorage.configuration = JSON.stringify(_this.configuration); + }; + + var checkForJava = function(){ + + var deferred = $q.defer(); + + try{ + + NotificationService.toast($filter('translate')('CHECKING_FOR_JAVA')); + + var spawn = require('child_process').spawn(_this.configuration.java || 'java', ['-version']); + + spawn.on('error', function (err) { + $log.error(err); + deferred.reject(); + }); + + spawn.stderr.on('data', function (data) { + data = data.toString().split('\n')[0]; + var javaVersion = new RegExp('java|openjdk version').test(data) ? data.split(' ')[2].replace(/"/g, '') : false; + if (javaVersion && javaVersion >= '1.7') { + $log.info('Java version found' + javaVersion); + deferred.resolve(); + } else { + deferred.reject(); + } + }); + + } catch(e){ + deferred.reject(e); + } + + return deferred.promise; + }; + + var checkAtStartupPromise; + + this.checkAtStartup = function () { + + if(checkAtStartupPromise){ + return checkAtStartupPromise; + } + + var deferred = $q.defer(); + checkAtStartupPromise = deferred.promise; + + if (!_this.configuration.user || !_this.configuration.password) { + NotificationService.toast($filter('translate')('CONFIGURATION_MISSING')); + $location.path('settings'); + deferred.reject(); + }else{ + checkForJava().then(function(){ + deferred.resolve(); + },function(){ + NotificationService.toast($filter('translate')('NO_SUITABLE_JAVA')); + $location.path('settings'); + deferred.reject(); + }); + } + + return deferred.promise; + + }; + + this.reset = function(){ + checkAtStartupPromise = null; + }; + + this.resolveUrl = function () { + var protocole = this.configuration.ssl ? 'https' : 'http'; + return protocole + '://' + this.configuration.host + ':' + this.configuration.port; + }; + + }); +})(); diff --git a/docdoku-dplm/ui/app/js/components/confirm/confirm.html b/docdoku-dplm/ui/app/js/components/confirm/confirm.html new file mode 100644 index 0000000000..b188941fb8 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/confirm/confirm.html @@ -0,0 +1,13 @@ + + +

{{title}}

+
+
+ + {{'NO' | translate }} + + + {{'YES' | translate }} + +
+
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/components/confirm/confirm.js b/docdoku-dplm/ui/app/js/components/confirm/confirm.js new file mode 100644 index 0000000000..14ec5b3534 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/confirm/confirm.js @@ -0,0 +1,42 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.services.confirm',[]) + + .service('ConfirmService',function($q,$mdDialog){ + + this.confirm = function($event,confirmOptions){ + var deferred = $q.defer(); + var confirmed = false; + $mdDialog.show({ + targetEvent: $event, + templateUrl:'js/components/confirm/confirm.html' , + controller: function($scope){ + $scope.title=confirmOptions.content; + $scope.cancel = function(){ + $mdDialog.hide(); + }; + $scope.confirm = function(){ + confirmed = true; + $mdDialog.hide(); + }; + }, + onComplete: afterShowAnimation + }).finally(function() { + if(confirmed){ + deferred.resolve(); + }else{ + deferred.reject(); + } + }); + // When the 'enter' animation finishes... + function afterShowAnimation(scope, element, options) { + } + + return deferred.promise; + }; + + }); + +})(); diff --git a/docdoku-dplm/ui/app/js/components/context-menu.js b/docdoku-dplm/ui/app/js/components/context-menu.js new file mode 100644 index 0000000000..c1a4301dac --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/context-menu.js @@ -0,0 +1,51 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.contextmenu', []) + .run(function ($document, $filter) { + + function Menu(cutLabel, copyLabel, pasteLabel) { + + var gui = require('nw.gui'), + menu = new gui.Menu(), + cut = new gui.MenuItem({ + label: cutLabel || "Cut", + click: function () { + document.execCommand("cut"); + } + }), + copy = new gui.MenuItem({ + label: copyLabel || "Copy", + click: function () { + document.execCommand("copy"); + } + }), + paste = new gui.MenuItem({ + label: pasteLabel || "Paste", + click: function () { + document.execCommand("paste"); + } + }); + + menu.append(cut); + menu.append(copy); + menu.append(paste); + + return menu; + } + + var translate = $filter('translate'); + var menu = new Menu(translate('CUT'), translate('COPY'), translate('PASTE')); + + $document.on("contextmenu", function (e) { + if(e.target.tagName === 'CANVAS'){ + return; + } + e.preventDefault(); + menu.popup(e.x, e.y); + }); + + }); + +})(); diff --git a/docdoku-dplm/ui/app/js/components/dragdrop.js b/docdoku-dplm/ui/app/js/components/dragdrop.js new file mode 100644 index 0000000000..886eb7b723 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/dragdrop.js @@ -0,0 +1,137 @@ +(function(){ + + 'use strict'; + + angular.module('ngDragDrop', []) + .directive('uiDraggable', [ + '$parse', + '$rootScope', + function ($parse, $rootScope) { + return { + restrict: 'A', + link: function (scope, element, attrs) { + if (window.jQuery && !window.jQuery.event.props.dataTransfer) { + window.jQuery.event.props.push('dataTransfer'); + } + element.attr('draggable', false); + attrs.$observe('uiDraggable', function (newValue) { + element.attr('draggable', newValue); + }); + var dragData = ''; + scope.$watch(attrs.drag, function (newValue) { + dragData = newValue; + }); + element.bind('dragstart', function (e) { + var sendData = angular.toJson(dragData); + var sendChannel = attrs.dragChannel || 'defaultchannel'; + e.dataTransfer.setData('Text', sendData); + $rootScope.$broadcast('ANGULAR_DRAG_START', sendChannel); + + }); + + element.bind('dragend', function (e) { + var sendChannel = attrs.dragChannel || 'defaultchannel'; + $rootScope.$broadcast('ANGULAR_DRAG_END', sendChannel); + if (e.dataTransfer && e.dataTransfer.dropEffect !== 'none') { + if (attrs.onDropSuccess) { + var fn = $parse(attrs.onDropSuccess); + scope.$apply(function () { + fn(scope, {$event: e}); + }); + } + } + }); + } + }; + } + ]) + .directive('uiOnDrop', [ + '$parse', + '$rootScope', + function ($parse, $rootScope) { + return function (scope, element, attr) { + var dropChannel = 'defaultchannel'; + var dragChannel = ''; + var dragEnterClass = attr.dragEnterClass || 'on-drag-enter'; + var dragHoverClass = attr.dragHoverClass || 'on-drag-hover'; + + function onDragOver(e) { + + if (e.preventDefault) { + e.preventDefault(); // Necessary. Allows us to drop. + } + + if (e.stopPropagation) { + e.stopPropagation(); + } + e.dataTransfer.dropEffect = 'move'; + return false; + } + + function onDragEnter(e) { + $rootScope.$broadcast('ANGULAR_HOVER', dropChannel); + element.addClass(dragHoverClass); + } + + function onDrop(e) { + if (e.preventDefault) { + e.preventDefault(); // Necessary. Allows us to drop. + } + if (e.stopPropagation) { + e.stopPropagation(); // Necessary. Allows us to drop. + } + var data = e.dataTransfer.getData('Text'); + data = angular.fromJson(data); + var fn = $parse(attr.uiOnDrop); + scope.$apply(function () { + fn(scope, {$data: data, $event: e}); + }); + element.removeClass(dragEnterClass); + } + + + $rootScope.$on('ANGULAR_DRAG_START', function (event, channel) { + dragChannel = channel; + if (dropChannel === channel) { + + element.bind('dragover', onDragOver); + element.bind('dragenter', onDragEnter); + + element.bind('drop', onDrop); + element.addClass(dragEnterClass); + } + + }); + + + $rootScope.$on('ANGULAR_DRAG_END', function (e, channel) { + dragChannel = ''; + if (dropChannel === channel) { + + element.unbind('dragover', onDragOver); + element.unbind('dragenter', onDragEnter); + + element.unbind('drop', onDrop); + element.removeClass(dragHoverClass); + element.removeClass(dragEnterClass); + } + }); + + + $rootScope.$on('ANGULAR_HOVER', function (e, channel) { + if (dropChannel === channel) { + element.removeClass(dragHoverClass); + } + }); + + if (attr.dropChannel){ + attr.$observe('dropChannel', function (value) { + if (value) { + dropChannel = value; + } + }); + } + }; + } + ]); +})(); diff --git a/docdoku-dplm/ui/app/js/components/file-change.js b/docdoku-dplm/ui/app/js/components/file-change.js new file mode 100644 index 0000000000..6698b584af --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/file-change.js @@ -0,0 +1,21 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.directives.filechange', []) + .directive('fileChange', ['$parse', function ($parse) { + return { + restrict: 'A', + link: function ($scope, element, attrs) { + var attrHandler = $parse(attrs.fileChange); + var handler = function (e) { + $scope.$apply(function () { + attrHandler($scope, {$event: e, files: e.target.files}); + }); + }; + element[0].addEventListener('change', handler, false); + } + }; + }]); + +})(); diff --git a/docdoku-dplm/ui/app/js/components/file-drop.js b/docdoku-dplm/ui/app/js/components/file-drop.js new file mode 100644 index 0000000000..1fb4aa069e --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/file-drop.js @@ -0,0 +1,44 @@ +(function() { + + 'use strict'; + + angular.module('dplm.directives.filedrop', []) + .directive('droppable',function(FolderService){ + return { + restrict:'A', + scope:{ + onFileDropped:'&' + }, + compile: function compile(tElement, tAttrs, transclude) { + return { + post: function postLink(scope, iElement, iAttrs, controller) { + + window.ondragover = function(e) {e.preventDefault(); return false; }; + window.ondrop = function(e) { e.preventDefault(); return false; }; + + var holder = iElement; + holder.on('dragover', function () { holder.addClass('hover'); return false; }); + holder.on('dragend',function () { holder.removeClass('hover'); return false; }); + holder.on('dragleave',function () { holder.removeClass('hover'); return false; }); + holder.on('drop',function (e) { + + holder.removeClass('hover'); + + var file = e.dataTransfer.files[0]; + + FolderService.isFolder(file.path).then(function(){ + scope.onFileDropped({path:file.path}); + }); + + e.preventDefault(); + + return false; + }); + + } + }; + } + }; + }); + +})(); \ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/components/file-short-name.js b/docdoku-dplm/ui/app/js/components/file-short-name.js new file mode 100644 index 0000000000..3bac70f0be --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/file-short-name.js @@ -0,0 +1,11 @@ +(function(){ + 'use strict'; + + angular.module('dplm.filters.fileshortname', []) + .filter('fileshortname', function () { + return function (path) { + return path.replace(/^.*[\\\/]/, ''); + }; + }); + +})(); diff --git a/docdoku-dplm/ui/app/js/components/file-size.js b/docdoku-dplm/ui/app/js/components/file-size.js new file mode 100644 index 0000000000..aca0e7aa8f --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/file-size.js @@ -0,0 +1,25 @@ +(function(){ + 'use strict'; + + function humanFileSize(bytes, si) { + var thresh = si ? 1000 : 1024; + if(Math.abs(bytes) < thresh) { + return bytes + ' B'; + } + var units = si ? ['kB','MB','GB','TB','PB','EB','ZB','YB'] : ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB']; + var u = -1; + do { + bytes /= thresh; + ++u; + } while(Math.abs(bytes) >= thresh && u < units.length - 1); + return bytes.toFixed(1)+' '+units[u]; + } + + angular.module('dplm.filters.humanreadablesize', []) + .filter('humanreadablesize', function () { + return function (bytes) { + return humanFileSize(bytes, true); + }; + }); + +})(); diff --git a/docdoku-dplm/ui/app/js/components/folders.js b/docdoku-dplm/ui/app/js/components/folders.js new file mode 100644 index 0000000000..a07175f886 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/folders.js @@ -0,0 +1,150 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.services.folders', []) + .service('FolderService', function (uuid4, $q, $filter, CliService) { + + var _this = this; + + var ignoreList = ['.dplm']; + + // HashMap for cache by folder id + this.cache={}; + + this.folders = angular.fromJson(localStorage.folders || '[]'); + + var alreadyHave = function (path) { + return _this.folders.filter(function (folder) { + return folder.path == path; + }).length > 0; + }; + + var statFiles = function (fileNames) { + var promises = []; + var fs = require('fs'); + var isWindows = require('os').type() === 'Windows_NT'; + angular.forEach(fileNames, function (fileName) { + promises.push($q(function (resolve, reject) { + fs.stat(fileName, function (err, stats) { + if(isWindows){ + fileName = fileName.charAt(0).toUpperCase()+ fileName.slice(1); + } + var file = {path: fileName}; + + if (err) reject(file); + else resolve(angular.extend(stats, file)); + }); + })); + }); + return $q.all(promises); + }; + + this.getFolder = function (params) { + return $filter('filter')(_this.folders, params)[0]; + }; + + this.add = function (path) { + if (alreadyHave(path)) { + return; + } + _this.folders.push({ + uuid: uuid4.generate(), + path: path, + favorite: false + }); + _this.save(); + }; + + this.delete = function(folder){ + if (alreadyHave(folder.path)) { + _this.folders.splice(_this.folders.indexOf(folder),1); + _this.save(); + } + }; + + this.save = function () { + localStorage.folders = angular.toJson(_this.folders); + }; + + this.recursiveReadDir = function (path) { + return $q(function (resolve, reject) { + var recursive = require('recursive-readdir'); + recursive(path, ignoreList, function (err, files) { + if (err) { + reject(err); + } + else { + resolve(files); + } + }); + }).then(statFiles); + }; + + this.getFilesCount = function(path){ + return $q(function (resolve, reject) { + var recursive = require('recursive-readdir'); + recursive(path, ignoreList, function (err, files) { + if (err) { + reject(err); + } + else { + resolve(files.length); + } + }); + }); + }; + + this.fetchFileStatus = function (file) { + return CliService.getStatusForFile(file).then(function () { + var userModif = parseInt(file.mtime.getTime() / 1000); + var lastModified = file.part ? file.part.lastModified : file.document ? file.document.lastModified:0; + var dplmModif = parseInt(lastModified / 1000); + file.modified = userModif > dplmModif; + file.sync = !file.modified; + file.notSync = false; + }, function () { + file.sync = false; + file.modified = false; + file.notSync = true; + }); + }; + + this.reveal = function (path) { + var os = require('os'); + var command = ''; + switch (os.type()) { + case 'Windows_NT' : + command = 'explorer'; + break; + case 'Darwin' : + command = 'open'; + break; + default : + command = 'nautilus'; + break; + } + require('child_process').spawn(command, [path]); + + }; + + this.isFolder = function(path){ + return $q(function(resolve,reject){ + var fs = require('fs'); + fs.stat(path, function(err,stats){ + if(err){ + reject(err); + } + else if(stats.isDirectory()){ + resolve(); + }else{ + reject(null); + } + }); + }); + + }; + + }); + +})(); diff --git a/docdoku-dplm/ui/app/js/components/join.js b/docdoku-dplm/ui/app/js/components/join.js new file mode 100644 index 0000000000..7b3b8493e4 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/join.js @@ -0,0 +1,15 @@ +(function() { + + 'use strict'; + + angular.module('dplm.filters.join', []) + .filter('join', function () { + return function (input, sep) { + if(angular.isArray(input)){ + return input.join(sep); + } + return null; + }; + }); + +})(); \ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/components/last.js b/docdoku-dplm/ui/app/js/components/last.js new file mode 100644 index 0000000000..2abeb8aa07 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/last.js @@ -0,0 +1,12 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.filters.last', []) + .filter('last', function () { + return function (arr) { + return arr.length ? arr[arr.length - 1] : null; + }; + }); + +})(); \ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/components/notification.js b/docdoku-dplm/ui/app/js/components/notification.js new file mode 100644 index 0000000000..c13e2db03e --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/notification.js @@ -0,0 +1,64 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.services.notification', []) + .service('NotificationService', function ($mdToast) { + + var that = this; + + this.toastUpRight = function (message, length) { + that.toast(message, { + bottom: false, + top: true, + left: false, + right: true + }, length); + }; + + this.toastBottomRight = function (message, length) { + that.toast(message, { + bottom: true, + top: false, + left: false, + right: true + }, length); + }; + + this.toast = function (message, pos, length) { + + var getToastPosition = function () { + + var toastPosition = pos || { + bottom: false, + top: true, + left: false, + right: true + }; + + return Object.keys(toastPosition) + .filter(function (pos) { + return toastPosition[pos]; + }) + .join(' '); + + }; + + $mdToast.show({ + controller: function ($scope) { + $scope.message = message; + }, + template: '{{message}}', + hideDelay: length || 6000, + position: getToastPosition() + }); + + }; + + this.hide = function(){ + $mdToast.hide(); + }; + + }); + +})(); diff --git a/docdoku-dplm/ui/app/js/components/output/output.html b/docdoku-dplm/ui/app/js/components/output/output.html new file mode 100644 index 0000000000..d6df7f7ef3 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/output/output.html @@ -0,0 +1,2 @@ +{{entry.info}} +{{entry.error}} \ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/components/output/output.js b/docdoku-dplm/ui/app/js/components/output/output.js new file mode 100644 index 0000000000..23f3f1113a --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/output/output.js @@ -0,0 +1,15 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.services.output',[]) + .directive('output',function(){ + return { + restrict: 'E', + scope:{ + 'entry':'=' + }, + templateUrl:'js/components/output/output.html' + }; + }); +})(); diff --git a/docdoku-dplm/ui/app/js/components/prompt/prompt.html b/docdoku-dplm/ui/app/js/components/prompt/prompt.html new file mode 100644 index 0000000000..257d2980bb --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/prompt/prompt.html @@ -0,0 +1,16 @@ + + +

{{title}}

+ + + +
+
+ + {{'CANCEL' | translate }} + + + {{'CHECKIN' | translate }} + +
+
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/components/prompt/prompt.js b/docdoku-dplm/ui/app/js/components/prompt/prompt.js new file mode 100644 index 0000000000..a2efb193f7 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/prompt/prompt.js @@ -0,0 +1,45 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.services.prompt',[]) + + .service('PromptService',function($q,$mdDialog){ + + this.prompt = function($event,confirmOptions){ + var deferred = $q.defer(); + var confirmed = false; + var value = null; + $mdDialog.show({ + targetEvent: $event, + templateUrl:'js/components/prompt/prompt.html' , + controller: function($scope){ + $scope.value=''; + $scope.title=confirmOptions.title; + $scope.cancel = function(){ + $mdDialog.hide(); + }; + $scope.confirm = function(){ + confirmed = true; + value=$scope.value; + $mdDialog.hide(); + }; + }, + onComplete: afterShowAnimation + }).finally(function() { + if(confirmed){ + deferred.resolve(value); + }else{ + deferred.reject(); + } + }); + // When the 'enter' animation finishes... + function afterShowAnimation(scope, element, options) { + } + + return deferred.promise; + }; + + }); + +})(); diff --git a/docdoku-dplm/ui/app/js/components/scrollend.js b/docdoku-dplm/ui/app/js/components/scrollend.js new file mode 100644 index 0000000000..531b3802a5 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/scrollend.js @@ -0,0 +1,16 @@ +(function(){ + 'use strict'; + angular.module('dplm.directives.scrollend', []).directive('onScrollEnd', function () { + return { + restrict: 'A', + link: function (scope, element, attrs) { + var raw = element[0]; + element.bind('scroll', function () { + if (raw.scrollTop + raw.offsetHeight >= raw.scrollHeight) { + scope.$apply(attrs.onScrollEnd); + } + }); + } + }; + }); +})(); diff --git a/docdoku-dplm/ui/app/js/components/timeago.js b/docdoku-dplm/ui/app/js/components/timeago.js new file mode 100644 index 0000000000..f9a0cafcca --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/timeago.js @@ -0,0 +1,12 @@ +(function(){ + 'use strict'; + + angular.module('dplm.filters.timeago', []) + .filter('timeago', function () { + return function (date) { + var moment = require('moment'); + moment.locale(localStorage.lang); + return moment(date).fromNow(); + }; + }); +})(); diff --git a/docdoku-dplm/ui/app/js/components/translation.js b/docdoku-dplm/ui/app/js/components/translation.js new file mode 100644 index 0000000000..6d1ea07416 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/translation.js @@ -0,0 +1,208 @@ +(function(){ + 'use strict'; + + angular.module('dplm.services.translations', ['pascalprecht.translate']) + .config(function ($translateProvider) { + + $translateProvider + .translations('en', { + CUT: 'Cut', + COPY: 'Copy', + PASTE: 'Paste', + CHECKOUT: 'Checkout', + CHECKIN: 'Checkin', + UNDO_CHECKOUT: 'Undo checkout', + PUT: 'Put', + FORCE: 'Force rewrite', + CREATE_PART: 'Create a new part', + CREATE_DOCUMENT: 'Create a new document', + STANDARD: 'Standard part', + WORKSPACE: 'Workspace', + REFRESH_WORKSPACES: 'Refresh workspaces list', + SAVE: 'Save', + OPEN_IN_EXPLORER: 'Open in explorer', + OPEN_IN_BROWSER: 'Open in browser', + FAVORITE: 'Favorite', + REFRESH_FOLDER: 'Refresh folder', + DOWNLOAD: 'Download', + HOME: 'Home', + SETTINGS: 'Settings', + LANG: 'Lang', + FR: 'French', + EN: 'English', + SAVE_TO: 'Save to', + ADD_FOLDER: 'Add folder', + RECURSIVE: 'Recursive', + PARTS: 'Parts', + NO_PARTS: 'No parts', + CONFIGURATION_ERROR: 'Configuration error', + REQUIREMENTS_MISSING: 'Requirements not satisfied', + PART_NUMBER: 'Part number', + PART_NAME: 'Part name', + PART_DESCRIPTION: 'Description', + DOCUMENT_ID: 'Document id', + DOCUMENT_TITLE: 'Document title', + DOCUMENT_DESCRIPTION: 'Description', + SEARCH: 'Search', + USER: 'User login', + PASSWORD: 'Password', + HOST: 'Host', + PORT: 'Port', + ITEMS: 'item(s)', + LOADING_MORE: 'Loading more', + FOLDERS: 'Folders', + CHECKOUT_BY: 'Checked out by', + LAST_ACTION: 'Last action', + LAST_MODIFIED: 'Downloaded', + NO_CAD_FILE: 'No CAD file', + DELETE_FOLDER:'Delete folder', + NO_FILES:'Folder is empty', + FOLDER:'Folder', + CONFIGURATION_MISSING:'Configuration missing, please fill in the required fields', + CHECKING_FOR_JAVA:'Looking for suitable Java version ...', + NO_SUITABLE_JAVA:'No suitable Java version found', + FETCHING_WORKSPACES:'Receiving workspaces list', + LATEST:'Latest', + BASELINE:'Baseline', + DELETE_FOLDER_CONFIRM_TITLE:'Are you sure to want to remove this folder ?', + DELETE_FOLDER_CONFIRMED:'Folder removed', + YES:'Yes', + NO:'No', + GREETINGS:'Welcome', + FILES:'file(s)', + FAVORITE_FOLDERS:'Favorite folders', + NO_FOLDERS:'No folders', + CONNECTED_TO:'connected to', + NEW_STUFF:'New files', + AVAILABLE:'Available', + CHECKED_OUT_BY_ME:'Checked out by me', + RELEASED:'Released', + LOCKED:'Locked', + UP_TO_DATE:'Up to date', + MODIFIED:'Modified', + NOT_SYNC:'Not synchronised', + LAST_WORKSPACES_VISITED:'Last workspaces visited', + NOTHING_TO_SHOW:'Nothing to show', + CONVERSION_STATUS:'Conversion status', + PENDING:'Pending', + SUCCESS:'Success', + FAIL:'Fail', + NO_CONVERSION:'No conversion currently', + NO_ATTACHED_FILES:'No attached files', + DOCUMENTS:'Documents', + UNKNOWN:'Unknown', + CHECKED_OUT:'Checked out', + HOME_FOLDER:'Home folder', + ROOT_FOLDER:'Root folder', + NO_DOCUMENTS:'No documents', + DOWNLOADS_FINISHED:'All downloads are finished', + SYNC_ALL:'Synchronise all', + CHECKIN_MESSAGE:'Add an iteration note', + DOWNLOAD_ALL:'Download all files', + FILE_ANALYSIS:'Files analysis', + DOWNLOADING:'Download in progress', + SIZE:'Size' + }) + .translations('fr', { + CUT: 'Couper', + COPY: 'Copier', + PASTE: 'Coller', + CHECKOUT: 'Réserver', + CHECKIN: 'Libérer', + UNDO_CHECKOUT: 'Annuler la réservation', + PUT: 'Envoyer', + FORCE: 'Ecraser', + CREATE_PART: 'Créer un nouvel article', + CREATE_DOCUMENT: 'Créer un nouveau document', + STANDARD: 'Article standard', + WORKSPACE: 'Espace de travail', + REFRESH_WORKSPACES: 'Rafraîchir la liste', + SAVE: 'Enregistrer', + OPEN_IN_EXPLORER: 'Ouvrir le dossier', + OPEN_IN_BROWSER: 'Ouvrir dans un navigateur', + FAVORITE: 'Favoris', + REFRESH_FOLDER: 'Rafraîchir le dossier', + DOWNLOAD: 'Télécharger', + HOME: 'Accueil', + SETTINGS: 'Paramètres', + LANG: 'Langue', + FR: 'Français', + EN: 'Anglais', + SAVE_TO: 'Enregistrer dans', + ADD_FOLDER: 'Ajouter un dossier', + RECURSIVE: 'Récursif', + PARTS: 'Articles', + NO_PARTS: 'Aucun article', + CONFIGURATION_ERROR: 'Erreur de configuration', + REQUIREMENTS_MISSING: 'Prérequis non satisfaits', + PART_NUMBER: 'Identifiant de l\'article', + PART_NAME: 'Nom de l\'article', + PART_DESCRIPTION: 'Description', + DOCUMENT_ID: 'Identifiant du document', + DOCUMENT_TITLE: 'Titre du document', + DOCUMENT_DESCRIPTION: 'Description', + SEARCH: 'Rechercher', + USER: 'Identifiant', + PASSWORD: 'Mot de passe', + HOST: 'Hôte', + PORT: 'Port', + ITEMS: 'entrée(s)', + LOADING_MORE: 'Chargement d\'entrées supplémentaires', + FOLDERS: 'Dossiers', + CHECKOUT_BY: 'Réservé par', + LAST_ACTION: 'Dernière action', + LAST_MODIFIED: 'Dernier téléchargement', + NO_CAD_FILE: 'Aucun fichier CAD', + DELETE_FOLDER:'Supprimer le dossier', + NO_FILES:'Le dossier est vide', + FOLDER:'Dossier', + CONFIGURATION_MISSING:'Configuration incomplète, veuillez renseigner les champs obligatoires', + CHECKING_FOR_JAVA:'Recherche de java ...', + NO_SUITABLE_JAVA:'Pas de version de Java trouvée', + FETCHING_WORKSPACES:'Récupération de la liste des espaces de travail ...', + LATEST:'Dernière', + BASELINE:'Baseline', + DELETE_FOLDER_CONFIRM_TITLE:'Êtes-vous sûr de vouloir supprimer ce dossier ?', + DELETE_FOLDER_CONFIRMED:'Dossier supprimé', + YES:'Oui', + NO:'Non', + GREETINGS:'Bienvenue', + FILES:'fichier(s)', + FAVORITE_FOLDERS:'Dossiers favoris', + NO_FOLDERS:'Aucun dossier', + CONNECTED_TO:'connecté à', + NEW_STUFF:'Nouveaux fichiers', + AVAILABLE:'Disponible', + CHECKED_OUT_BY_ME:'Réservé par moi', + RELEASED:'Finalisée', + LOCKED:'Vérrouillé', + UP_TO_DATE:'À jour', + MODIFIED:'Modifié', + NOT_SYNC:'Non synchronisé', + LAST_WORKSPACES_VISITED:'Derniers espaces de travail utilisés', + NOTHING_TO_SHOW:'Aucune entrée', + CONVERSION_STATUS:'Statut de la conversion', + PENDING:'En cours', + SUCCESS:'Succès', + FAIL:'Echoué', + NO_CONVERSION:'Pas de conversion en cours', + NO_ATTACHED_FILES:'Pas de fichiers joints', + DOCUMENTS:'Documents', + NO_DOCUMENTS:'Aucun document', + UNKNOWN:'Inconnus', + CHECKED_OUT:'Réservés', + HOME_FOLDER:'Dossier personnel', + ROOT_FOLDER:'Dossier racine', + DOWNLOADS_FINISHED:'Tous les téléchargements sont terminés', + SYNC_ALL:'Tout synchroniser', + CHECKIN_MESSAGE:'Ajouter une note d\'itération', + DOWNLOAD_ALL:'Télécharger tous les fichiers', + FILE_ANALYSIS:'Analyse des fichiers', + DOWNLOADING:'Téléchargement en cours', + SIZE:'Taille' + }); + + $translateProvider.preferredLanguage(localStorage.lang || 'en'); + + }); +})(); diff --git a/docdoku-dplm/ui/app/js/components/workspaces.js b/docdoku-dplm/ui/app/js/components/workspaces.js new file mode 100644 index 0000000000..0eff367c90 --- /dev/null +++ b/docdoku-dplm/ui/app/js/components/workspaces.js @@ -0,0 +1,66 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.services.workspaces', []) + .service('WorkspaceService', function ($log, $filter, $q, $location, CliService, NotificationService) { + + var _this = this; + + this.workspaces = []; + + var lastVisitedWorkspaces = JSON.parse(localStorage.lastVisitedWorkspaces || '[]'); + + this.output = { + error: null + }; + + this.getWorkspaces = function () { + + _this.output.error = null; + + var deferred = $q.defer(); + + if(_this.workspaces.length){ + deferred.resolve(); + return deferred.promise; + } + + NotificationService.toast($filter('translate')('FETCHING_WORKSPACES')); + + CliService.getWorkspaces().then(function (workspaces) { + angular.copy(workspaces,_this.workspaces); + NotificationService.hide(); + deferred.resolve(); + },function(error){ + _this.output.error = error; + // Should be offline, or auth error + $location.path('settings'); + deferred.reject(); + }); + + return deferred.promise; + }; + + this.reset = function(){ + _this.workspaces.length = 0; + }; + + this.getLastVisitedWorkspaces = function(){ + return lastVisitedWorkspaces; + }; + + this.addLastVisited = function(workspace){ + + var alreadyIndexed = lastVisitedWorkspaces.indexOf(workspace); + if(alreadyIndexed !== -1){ + lastVisitedWorkspaces.splice(alreadyIndexed,1); + } + + lastVisitedWorkspaces.unshift(workspace); + lastVisitedWorkspaces.splice(4,lastVisitedWorkspaces.length-1); + localStorage.lastVisitedWorkspaces = JSON.stringify(lastVisitedWorkspaces); + }; + + }); +})(); diff --git a/docdoku-dplm/ui/app/js/dev.js b/docdoku-dplm/ui/app/js/dev.js new file mode 100644 index 0000000000..300ae77cf9 --- /dev/null +++ b/docdoku-dplm/ui/app/js/dev.js @@ -0,0 +1,5 @@ +(function(){ + 'use strict'; + angular.module('dplm.templates',[]); +})(); + diff --git a/docdoku-dplm/ui/app/js/folder/file-document-actions.html b/docdoku-dplm/ui/app/js/folder/file-document-actions.html new file mode 100644 index 0000000000..e77fa2e462 --- /dev/null +++ b/docdoku-dplm/ui/app/js/folder/file-document-actions.html @@ -0,0 +1,27 @@ + +
+ + {{'FORCE'|translate}} + +
+
+ + {{'CHECKOUT' | translate}} + + + {{'CHECKIN' | translate}} + + + {{'UNDO_CHECKOUT' | translate}} + + + {{'PUT' | translate}} + +
+ +
+ +
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/folder/file-part-actions.html b/docdoku-dplm/ui/app/js/folder/file-part-actions.html new file mode 100644 index 0000000000..fcf7885604 --- /dev/null +++ b/docdoku-dplm/ui/app/js/folder/file-part-actions.html @@ -0,0 +1,44 @@ + +
+ + {{'FORCE'|translate}} + + + {{'RECURSIVE'|translate}} + +
+ +
+ + + {{'3D' | translate}} + + + + {{'CHECKOUT' | translate}} + + + {{'CHECKIN' | translate}} + + + {{'UNDO_CHECKOUT' | translate}} + + + {{'PUT' | translate}} + + + {{'CONVERSION_STATUS' | translate}} + +
+ +
+
+
+ +
+ +
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/folder/file-unknown-actions.html b/docdoku-dplm/ui/app/js/folder/file-unknown-actions.html new file mode 100644 index 0000000000..77b92198a9 --- /dev/null +++ b/docdoku-dplm/ui/app/js/folder/file-unknown-actions.html @@ -0,0 +1,74 @@ + + +
+ +
+
+ + {{'CREATE_PART'|translate}} + +
+
+ + {{'CREATE_DOCUMENT'|translate}} + +
+
+ +
+
+
+ + + +
+
+ + + + + + + {{'STANDARD'|translate}} + +
+
+ + {{'SAVE'|translate}} + +
+
+
+
+
+
+ + + +
+
+ + + +
+
+ + {{'SAVE'|translate}} + +
+
+
+
+ + +
+ +
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/folder/folder-document.html b/docdoku-dplm/ui/app/js/folder/folder-document.html new file mode 100644 index 0000000000..fb4c318f7d --- /dev/null +++ b/docdoku-dplm/ui/app/js/folder/folder-document.html @@ -0,0 +1,46 @@ +
+ +
+ + + +

+ + + + + + {{file.path | fileshortname}} + + {{file.document.id}}-{{file.document.version}}-{{file.document.iterations | last}} + +

+ +
+ {{'CHECKOUT_BY' | translate }} {{file.document.checkoutUser}} {{file.document.checkoutDate | + timeago}} +
+
+ {{'LAST_MODIFIED' | translate }} {{file.document.lastModified | timeago}} +
+
+ {{'LAST_ACTION' | translate }} {{file.mtime | timeago}} +
+
+ {{'SIZE' | translate}} : {{file.size | humanreadablesize}} +
+
+ {{file.path}} +
+
+ +
+ +
+ + + +
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/folder/folder-part.html b/docdoku-dplm/ui/app/js/folder/folder-part.html new file mode 100644 index 0000000000..727f3d28f1 --- /dev/null +++ b/docdoku-dplm/ui/app/js/folder/folder-part.html @@ -0,0 +1,45 @@ +
+ +
+ + + +

+ + + + + + {{file.path | fileshortname}} + + {{file.part.partNumber}}-{{file.part.version}}-{{file.part.iterations | last}} + +

+
+ {{'CHECKOUT_BY' | translate }} {{file.part.checkoutUser}} {{file.part.checkoutDate | + timeago}} +
+
+ {{'LAST_MODIFIED' | translate }} {{file.part.lastModified | timeago}} +
+
+ {{'LAST_ACTION' | translate }} {{file.mtime | timeago}} +
+
+ {{'SIZE' | translate}} : {{file.size | humanreadablesize}} +
+
+ {{file.path}} +
+
+ +
+ +
+ + + +
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/folder/folder-unknown.html b/docdoku-dplm/ui/app/js/folder/folder-unknown.html new file mode 100644 index 0000000000..34d82ec2a1 --- /dev/null +++ b/docdoku-dplm/ui/app/js/folder/folder-unknown.html @@ -0,0 +1,23 @@ +
+ +
+ +

+ {{file.path | fileshortname}} +

+
+ {{'LAST_ACTION' | translate }} {{file.mtime | timeago}} +
+
+ {{'SIZE' | translate}} : {{file.size | humanreadablesize}} +
+
+ {{file.path}} +
+
+ +
+ +
+ +
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/folder/folder.html b/docdoku-dplm/ui/app/js/folder/folder.html new file mode 100644 index 0000000000..360cf9781b --- /dev/null +++ b/docdoku-dplm/ui/app/js/folder/folder.html @@ -0,0 +1,109 @@ + +
+

+ + + {{'FOLDER' | translate}} : + {{ folder.path | fileshortname }} ({{counts.totalFiles}} {{'FILES' |translate}}) + + {{'OPEN_IN_EXPLORER'|translate}} + + + + + + {{'FAVORITE'|translate}} + + + + + + {{'REFRESH_FOLDER'|translate}} + + + + + + {{'DELETE_FOLDER'|translate}} + + +

+
+
+ + +
+ + + +
+
+ +
+
+ + + + + + +
+
+ + + + + + + {{'SYNC_ALL'|translate}} + + {{'SYNC_ALL'|translate}} + + + + + {{'FILE_ANALYSIS' | translate}} ... {{counts.inProgress}}/{{counts.totalFiles}} + + + + + + + +
+ +
+
+ + + + +
+ +
+
+
+
+ +
+
+

{{'NO_FILES'|translate}}

+
+
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/folder/folder.js b/docdoku-dplm/ui/app/js/folder/folder.js new file mode 100644 index 0000000000..9f42a26348 --- /dev/null +++ b/docdoku-dplm/ui/app/js/folder/folder.js @@ -0,0 +1,402 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.folder', []) + + .config(function ($routeProvider) { + + $routeProvider + .when('/folder/:uuid', { + template: '', + controller: function($location,$routeParams){ + $location.path('folder/'+$routeParams.uuid+'/documents'); + } + }) + .when('/folder/:uuid/:entity', { + templateUrl: 'js/folder/folder.html', + controller: 'FolderController', + resolve:{ + conf:function(ConfigurationService,WorkspaceService){ + return ConfigurationService.checkAtStartup().then(WorkspaceService.getWorkspaces); + } + } + }); + }) + + .controller('FolderController', function ($scope, $q, $location, $routeParams, $filter, ConfirmService, ConfigurationService, FolderService, NotificationService) { + + $scope.tabs={ + selected: + $routeParams.entity==='documents' ? 0 : + $routeParams.entity==='parts' ? 1 : + $routeParams.entity==='unknown' ? 2 : + 0, + documents:0, + parts:1, + unknown:2 + }; + + $scope.folder = FolderService.getFolder({uuid:$routeParams.uuid}); + + $scope.configuration = ConfigurationService.configuration; + $scope.files = []; + + $scope.filters = { + sync: true, + modified: true, + entity:$routeParams.entity + }; + + $scope.counts = { + documents:0, + parts:0, + unknown:0, + totalFiles:0, + inProgress:0 + }; + + var resetFiles = function(files){ + $scope.files.length = 0; + $scope.counts.totalFiles = files.length; + $scope.counts.documents = 0; + $scope.counts.parts = 0; + $scope.counts.unknown = 0; + $scope.counts.inProgress=0; + }; + + var onFile = function(file){ + $scope.counts.inProgress++; + $scope.counts.documents+=file.document?1:0; + $scope.counts.parts+=file.part?1:0; + $scope.counts.unknown+=!file.part && !file.document?1:0; + if(file.document && $scope.filters.entity == 'documents'){ + $scope.files.push(file); + }else if(file.part && $scope.filters.entity == 'parts'){ + $scope.files.push(file); + }else if(!file.part && !file.document && $scope.filters.entity == 'unknown'){ + $scope.files.push(file); + } + }; + + $scope.refresh = function (forceRefresh) { + + resetFiles([]); + + if(!forceRefresh && FolderService.cache[$scope.folder.uuid]){ + var files = FolderService.cache[$scope.folder.uuid]; + resetFiles(files); + angular.forEach(files, onFile); + }else{ + + $scope.loadingFiles = true; + FolderService.recursiveReadDir($scope.folder.path).then(function (files) { + + resetFiles(files); + + var toCache = []; + var statusRequestChain = $q.when(); + angular.forEach(files,function(file){ + statusRequestChain = statusRequestChain.then(function(){ + return FolderService.fetchFileStatus(file).then(function () { + onFile(file); + toCache.push(file); + }); + }); + }); + + statusRequestChain.then(function(){ + $scope.loadingFiles = false; + FolderService.cache[$scope.folder.uuid] = toCache; + }); + + }, function () { + $scope.loadingFiles = false; + }).then(function(){ + $scope.folder.newStuff = false; + }); + } + + }; + + $scope.reveal = function ($event) { + FolderService.reveal($scope.folder.path); + $event.stopPropagation(); + }; + + $scope.toggleFavorite = function () { + $scope.folder.favorite = !$scope.folder.favorite; + FolderService.save(); + }; + + $scope.delete = function(ev){ + ConfirmService.confirm(ev,{ + content:$filter('translate')('DELETE_FOLDER_CONFIRM_TITLE') + }).then(function(){ + FolderService.delete($scope.folder); + NotificationService.toast($filter('translate')('DELETE_FOLDER_CONFIRMED')); + $location.path('/'); + }); + }; + + var getUploadableFiles = function(){ + return $filter('filter')($scope.files,{modified:true, sync:false}); + }; + + $scope.hasUploadableFiles = function(){ + return ($scope.tabs.selected === $scope.tabs.documents || $scope.tabs.selected === $scope.tabs.parts ) && getUploadableFiles().length; + }; + + $scope.syncAll = function(){ + $scope.$broadcast('sync:all'); + }; + + $scope.refresh(); + + }) + + .filter('filterFiles', function () { + return function (arr, filters) { + if (!arr) { + return []; + } + return arr.filter(function (file) { + if (!filters.sync && file.sync) { + return false; + } + if (!filters.modified && file.modified) { + return false; + } + return true; + + }); + }; + }) + + .controller('FileController', function ($scope, FolderService, AvailableLoaders) { + + $scope.openedFile = false; + + $scope.toggleOpenedFile = function (scope) { + $scope.openedFile = !$scope.openedFile; + }; + + $scope.fetchStatus = function () { + $scope.loading = true; + FolderService.fetchFileStatus($scope.file).then(function () { + $scope.loading = false; + }, function () { + $scope.loading = false; + }); + }; + + $scope.onFinish = function () { + $scope.file.busy = false; + $scope.file.progress = 0; + }; + + $scope.onProgress = function (progress) { + $scope.file.progress = progress; + }; + + $scope.isViewable = AvailableLoaders.indexOf($scope.file.path.split('.').pop()) !== -1; + + + + }) + + .directive('filePartActions', function () { + + return { + + templateUrl: 'js/folder/file-part-actions.html', + scope:false, + controller: function ($scope, $element, $attrs, $transclude, $timeout, $filter, CliService, WorkspaceService, NotificationService, PromptService) { + + $scope.options = {force: true,recursive:true}; + $scope.workspaces = WorkspaceService.workspaces; + $scope.show3D = false; + + var onError = function(error){ + $scope.error = error; + }; + + $scope.output = []; + + var onOutput = function(o){ + $scope.output.push(o); + }; + + $scope.$on('sync:all',function(){ + if(!$scope.file.sync && $scope.file.modified && !$scope.file.busy){ + $scope.put(); + } + }); + + $scope.put = function () { + $scope.file.busy = true; + CliService.putCADFile($scope.file.part.workspace, $scope.file.path, onOutput).then(function () { + return $scope.fetchStatus(); + }, onError, $scope.onProgress).then($scope.onFinish); + }; + + $scope.checkout = function () { + $scope.file.busy = true; + CliService.checkoutPart($scope.file.part, $scope.folder.path, $scope.options, onOutput).then(function () { + return $scope.fetchStatus(); + }, onError, $scope.onProgress).then($scope.onFinish); + }; + + $scope.checkin = function (e) { + PromptService.prompt(e, {title:$filter('translate')('CHECKIN_MESSAGE')}).then(function(message){ + $scope.file.busy = true; + CliService.checkinPart($scope.file.part,{path:$scope.folder.path,message: message}, onOutput).then(function () { + return $scope.fetchStatus(); + }, onError, $scope.onProgress).then($scope.onFinish); + }); + }; + + $scope.undoCheckout = function () { + $scope.file.busy = true; + CliService.undoCheckoutPart($scope.file.part, onOutput).then(function () { + return $scope.fetchStatus(); + }, onError).then($scope.onFinish); + }; + + $scope.conversionStatus = function () { + CliService.getConversionStatus($scope.file.part, onOutput).then(function (conversion) { + var message = conversion.pending ? $filter('translate')('PENDING') : + conversion.succeed ? $filter('translate')('SUCCESS') : + $filter('translate')('FAIL'); + onOutput({info:message}); + },function(){ + onOutput({info:$filter('translate')('NO_CONVERSION')}); + }); + }; + + $scope.toggle3D = function(){ + $scope.show3D = !$scope.show3D; + }; + + } + + }; + }) + + + + .directive('fileDocumentActions', function () { + + return { + + templateUrl: 'js/folder/file-document-actions.html', + scope:false, + controller: function ($scope, $element, $attrs, $transclude, $timeout, $filter, CliService, WorkspaceService, PromptService) { + + $scope.options = {force: true,recursive:true}; + $scope.workspaces = WorkspaceService.workspaces; + + var onError = function(error){ + $scope.error = error; + }; + + $scope.output = []; + + var onOutput = function(o){ + $scope.output.push(o); + }; + + $scope.$on('sync:all',function(){ + if(!$scope.file.sync && $scope.file.modified && !$scope.file.busy){ + $scope.put(); + } + }); + + $scope.put = function () { + $scope.file.busy = true; + CliService.putDocumentFile($scope.file.document.workspace, $scope.file.path, onOutput).then(function () { + return $scope.fetchStatus(); + }, onError, $scope.onProgress).then($scope.onFinish); + + }; + + $scope.checkout = function () { + $scope.file.busy = true; + CliService.checkoutDocument($scope.file.document, $scope.folder.path, $scope.options, onOutput).then(function () { + return $scope.fetchStatus(); + }, onError, $scope.onProgress).then($scope.onFinish); + }; + + $scope.checkin = function (e) { + PromptService.prompt(e, {title:$filter('translate')('CHECKIN_MESSAGE')}).then(function(message){ + $scope.file.busy = true; + CliService.checkinDocument($scope.file.document,{path:$scope.folder.path,message:message}, onOutput).then(function () { + return $scope.fetchStatus(); + }, onError, $scope.onProgress).then($scope.onFinish); + }); + + }; + + + $scope.undoCheckout = function () { + $scope.file.busy = true; + CliService.undoCheckoutDocument($scope.file.document, onOutput).then(function () { + return $scope.fetchStatus(); + }, onError).then($scope.onFinish); + }; + + } + + }; + }) + .directive('fileUnknownActions', function () { + + return { + + templateUrl: 'js/folder/file-unknown-actions.html', + scope:false, + controller: function ($scope, $element, $attrs, $transclude, $timeout, $filter, CliService, WorkspaceService, NotificationService) { + + $scope.options = {force: true,recursive:true}; + $scope.workspaces = WorkspaceService.workspaces; + $scope.newPart = {workspace: $scope.workspaces[0]}; + $scope.newDocument = {workspace: $scope.workspaces[0]}; + + $scope.form={ + selected:0, + part:0, + document:1 + }; + + var onError = function(error){ + $scope.error = error; + }; + + $scope.output = []; + + var onOutput = function(o){ + $scope.output.push(o); + }; + + $scope.createPart = function () { + $scope.file.busy = true; + CliService.createPart($scope.newPart, $scope.file.path, onOutput).then(function () { + $scope.newPart = {workspace: $scope.workspaces[0]}; + return $scope.fetchStatus(); + }, onError, $scope.onProgress).then($scope.onFinish); + }; + + $scope.createDocument = function () { + $scope.file.busy = true; + CliService.createDocument($scope.newDocument, $scope.file.path, onOutput).then(function () { + $scope.newDocument = {workspace: $scope.workspaces[0]}; + return $scope.fetchStatus(); + }, onError, $scope.onProgress).then($scope.onFinish); + }; + + } + + }; + }); + +})(); diff --git a/docdoku-dplm/ui/app/js/folder/part-dropped.html b/docdoku-dplm/ui/app/js/folder/part-dropped.html new file mode 100644 index 0000000000..c9f66e17fc --- /dev/null +++ b/docdoku-dplm/ui/app/js/folder/part-dropped.html @@ -0,0 +1,19 @@ + + + + + {{part.partNumber + '-' + part.version}} >> {{folder.path | fileshortname}} + + + +
+ + {{'DOWNLOAD' | translate}} + + + {{'CHECKOUT' | translate}} + +
+ +
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/home/home.html b/docdoku-dplm/ui/app/js/home/home.html new file mode 100644 index 0000000000..2a728413ed --- /dev/null +++ b/docdoku-dplm/ui/app/js/home/home.html @@ -0,0 +1,65 @@ + +
+

+ + + + + {{'HOME' | translate}} + +

+
+
+
+ + + {{'GREETINGS'|translate}} {{configuration.user}}, {{'CONNECTED_TO' | translate}} {{configuration.host}} + + + +

{{'FAVORITE_FOLDERS' | translate}}

+ + + + {{'NO_FOLDERS' | translate}} + + + + +
+

+ {{folder.path | fileshortname}} +

+
+
+ {{details.count}} {{'FILES' | translate }} +
+
+ +
+
+
+ + +

{{'LAST_WORKSPACES_VISITED' | translate}}

+ + + + {{'NOTHING_TO_SHOW' | translate}} + + + + +
+ {{workspace}} +
+
+ +
+
+ +
+
+
+ +
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/home/home.js b/docdoku-dplm/ui/app/js/home/home.js new file mode 100644 index 0000000000..d193460589 --- /dev/null +++ b/docdoku-dplm/ui/app/js/home/home.js @@ -0,0 +1,40 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.home', []) + .config(function ($routeProvider) { + $routeProvider.when('/', { + controller: function ($scope, $filter, ConfigurationService,FolderService,WorkspaceService) { + $scope.configuration = ConfigurationService.configuration; + $scope.save = ConfigurationService.save; + $scope.folders = $filter('filter')(FolderService.folders,{favorite:true}); + $scope.workspaces = WorkspaceService.getLastVisitedWorkspaces(); + }, + resolve:{ + conf:function(ConfigurationService,WorkspaceService,CliService,$translate){ + return ConfigurationService.checkAtStartup() + .then(WorkspaceService.getWorkspaces) + .then(CliService.fetchAccount) + .then(function(account){ + localStorage.lang = account.language || 'en'; + $translate.use(localStorage.lang); + }); + } + }, + templateUrl: 'js/home/home.html' + }); + }) + .controller('FolderDetailsController',function($scope,FolderService){ + + $scope.details = { + count: 0 + }; + + FolderService.getFilesCount($scope.folder.path).then(function(count){ + $scope.details.count = count; + }); + + }); + +})(); diff --git a/docdoku-dplm/ui/app/js/menu/menu-button.html b/docdoku-dplm/ui/app/js/menu/menu-button.html new file mode 100644 index 0000000000..8572a86845 --- /dev/null +++ b/docdoku-dplm/ui/app/js/menu/menu-button.html @@ -0,0 +1,6 @@ + + + + {{'MENU' | translate }} + + \ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/menu/menu.html b/docdoku-dplm/ui/app/js/menu/menu.html new file mode 100644 index 0000000000..3701b8320f --- /dev/null +++ b/docdoku-dplm/ui/app/js/menu/menu.html @@ -0,0 +1,82 @@ + +

+ + {{title}} + + + + {{'SETTINGS' | translate }} + + +

+
+ + + + + + + + +
+ + + + {{folder.path | fileshortname}} + + {{folder.path}} + + +
+
+
+
+
+ + +

+ + {{ configuration.host + ':' + configuration.port }} + + + + {{'REFRESH_WORKSPACES'|translate}} + + +

+ + + + + +
+ + + {{workspace}} + +
+
+
+
+
+ + + +
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/menu/menu.js b/docdoku-dplm/ui/app/js/menu/menu.js new file mode 100644 index 0000000000..5725744fa7 --- /dev/null +++ b/docdoku-dplm/ui/app/js/menu/menu.js @@ -0,0 +1,40 @@ +angular.module('dplm.menu', []) + .directive('menuButton',function(){ + return { + restrict:'E', + templateUrl:'js/menu/menu-button.html', + scope:false + }; + }) + .controller('MenuController', function ($scope,FolderService) { + $scope.toggleFolders = function(){ + $scope.foldersExpanded=!$scope.foldersExpanded; + }; + $scope.toggleWorkspaces = function(){ + $scope.workspacesExpanded=!$scope.workspacesExpanded; + }; + $scope.onFileDropped = function(path){ + if(path){ + FolderService.add(path); + } + }; + + }) + + .controller('FolderMenuController', function ($scope) { + + $scope.onDrop = function () { + }; + + }) + .controller('WorkspaceMenuController', function ($scope, WorkspaceService) { + + $scope.onDrop = function () { + }; + + $scope.refreshWorkspaces = function(){ + WorkspaceService.reset(); + WorkspaceService.getWorkspaces(); + }; + + }); \ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/settings/settings.html b/docdoku-dplm/ui/app/js/settings/settings.html new file mode 100644 index 0000000000..70a9aa60ad --- /dev/null +++ b/docdoku-dplm/ui/app/js/settings/settings.html @@ -0,0 +1,39 @@ + +
+

+ + + {{'SETTINGS' | translate}} + +

+
+
+ +
+ + +
+ +
+
+
+ + +
+
+ + + + {{'SSL'|translate}} + +
+
+ {{'SAVE'|translate}} + +
+
+
+ +
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/settings/settings.js b/docdoku-dplm/ui/app/js/settings/settings.js new file mode 100644 index 0000000000..aed70a74d8 --- /dev/null +++ b/docdoku-dplm/ui/app/js/settings/settings.js @@ -0,0 +1,30 @@ +(function(){ + 'use strict'; + + angular.module('dplm.settings', []) + + .config(function ($routeProvider) { + $routeProvider.when('/settings', { + controller: 'SettingsController', + templateUrl: 'js/settings/settings.html' + }); + }) + + .controller('SettingsController', function ($scope, $location, $translate, ConfigurationService, WorkspaceService) { + $scope.configuration = ConfigurationService.configuration; + $scope.output = WorkspaceService.output; + + $scope.save = function () { + ConfigurationService.save(); + ConfigurationService.reset(); + WorkspaceService.reset(); + $location.path('home'); + }; + + $scope.onSslChange = function() { + $scope.configuration.port = $scope.configuration.ssl === true ? 443 : $scope.configuration.port; + }; + + + }); +})(); diff --git a/docdoku-dplm/ui/app/js/workspace/document-actions.html b/docdoku-dplm/ui/app/js/workspace/document-actions.html new file mode 100644 index 0000000000..26f3510cec --- /dev/null +++ b/docdoku-dplm/ui/app/js/workspace/document-actions.html @@ -0,0 +1,38 @@ + +
+ + + + +
+ +
+ + {{'FORCE'|translate}} + + + {{'DOWNLOAD' | translate}} + + + {{'CHECKOUT' | translate}} + + + {{'CHECKIN' | translate}} + + + {{'UNDO_CHECKOUT' | translate}} + +
+
+ +
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/workspace/part-actions.html b/docdoku-dplm/ui/app/js/workspace/part-actions.html new file mode 100644 index 0000000000..6675ea3ad3 --- /dev/null +++ b/docdoku-dplm/ui/app/js/workspace/part-actions.html @@ -0,0 +1,51 @@ + +
+ + + + +
+
+ + + + + + + {{'FORCE'|translate}} + + + {{'RECURSIVE'|translate}} + +
+
+ + {{'DOWNLOAD' | translate}} + + + {{'CHECKOUT' | translate}} + + + {{'CHECKIN' | translate}} + + + {{'UNDO_CHECKOUT' | translate}} + +
+
+ +
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/workspace/workspace-documents.html b/docdoku-dplm/ui/app/js/workspace/workspace-documents.html new file mode 100644 index 0000000000..3bc391a88e --- /dev/null +++ b/docdoku-dplm/ui/app/js/workspace/workspace-documents.html @@ -0,0 +1,92 @@ + + + + +
+ +
+
+
+ {{'DOWNLOAD_ALL' | translate}} + + + + + +
+
+
+ +
+ + {{ 'DOWNLOADING' | translate }} ... ({{downloadAllData.inProgress}}/{{downloadAllData.total}}) + + + + + + + +
+ +
+
+ + + + +
+ +
+ +
+ + +

+ {{document.id}} {{document.version}}-{{document.iterations | last}} +
+ + {{ document.files.length ? document.files.length + ' ' + ('FILES' | translate) : ( 'NO_ATTACHED_FILES' | translate ) }} + +
    +
  • + + +
  • +
+

+ +

+ {{'CHECKOUT_BY' | translate }} {{document.checkoutUser}} {{document.checkoutDate | timeago}} +

+
+ +
+ +
+ +
+ +
+ +
+
+ +

{{'NO_DOCUMENTS'|translate}}

+ +
+ diff --git a/docdoku-dplm/ui/app/js/workspace/workspace-documents.js b/docdoku-dplm/ui/app/js/workspace/workspace-documents.js new file mode 100644 index 0000000000..fb3f8e5857 --- /dev/null +++ b/docdoku-dplm/ui/app/js/workspace/workspace-documents.js @@ -0,0 +1,255 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.workspace.documents', []) + + .controller('WorkspaceDocumentsController', function ($scope, $q, $filter, $window, $timeout, $routeParams, CliService, ConfigurationService, FolderService, NotificationService) { + + $scope.path = $routeParams.path; + $scope.decodedPath = $filter('decodePath')($routeParams.path); + $scope.remoteFolders = []; + $scope.pathParts = []; + + if($scope.path){ + var parts = $scope.path.split(':'); + var fullPath=''; + angular.forEach(parts,function(part){ + if(fullPath){ + fullPath+=':'; + } + fullPath+=part; + $scope.pathParts.push(fullPath); + }); + } + + $scope.action = { + selected: + $routeParams.action==='folders'?0: + $routeParams.action==='checkedOut'?1: + 0, + folders:0, + checkedOut:1 + }; + + $scope.folders = FolderService.folders; + $scope.options = {force: true}; + $scope.folder = angular.copy($scope.folders[0])||{}; + $scope.documents = []; + $scope.loadingDocuments = true; + $scope.loadingMore = false; + + var onListResults = function (documents) { + $scope.loadingDocuments = false; + $scope.loadingMore = false; + angular.forEach(documents, function (document) { + $scope.documents.push(document); + }); + }; + + var onError = function(error){ + $scope.error = error; + $scope.loadingDocuments = false; + }; + + var getDocumentsRevisions = function () { + $scope.error = null; + return CliService.getDocumentsRevisionsInFolder($scope.workspace, $filter('decodePath')($scope.path)) + .then(onListResults, onError); + }; + + var getCheckedOutDocumentsRevisions = function(){ + $scope.error = null; + return CliService.getCheckedOutDocumentsRevisions($scope.workspace, $filter('decodePath')($scope.path)) + .then(onListResults, onError); + }; + + var showInBrowser = function () { + $window.open(ConfigurationService.resolveUrl() + '/product-management/#' + $scope.workspace); + }; + + var resetList = function () { + $scope.documents.length = 0; + $scope.loadingDocuments = true; + $scope.loadingMore = false; + getDocumentsRevisions(); + }; + + $scope.refreshCurrent = function(){ + var chain = $q.when(); + + angular.forEach($scope.documents, function (document) { + chain = chain.then(function(){ + document.busy=true; + return CliService.getStatusForDocument(document).then(function(){ + document.busy = false; + }); + }); + }); + }; + + $scope.$on('refresh',resetList); + $scope.$on('showInBrowser',showInBrowser); + + $scope.downloadAllData = { + downloading:false, + inProgress:0, + total:0 + }; + + $scope.downloadAll = function(){ + + $scope.downloadAllData.inProgress = 0; + $scope.downloadAllData.total = $scope.documents.length; + $scope.downloadAllData.downloading = true; + + var chain = $q.when(); + angular.forEach($scope.documents,function(document){ + chain = chain.then(function(){ + $scope.downloadAllData.inProgress++; + return CliService.downloadDocumentFiles(document, $scope.folder.path, $scope.options); + }); + }); + + chain.then(function(){ + $scope.downloadAllData.downloading = false; + NotificationService.toast($filter('translate')('DOWNLOADS_FINISHED')); + FolderService.getFolder({path:$scope.folder.path}).newStuff = true; + }); + }; + + // if action = folders + if($routeParams.action === 'folders'){ + $scope.error = null; + CliService.getFolders($scope.workspace,$filter('decodePath')($scope.path)).then(function(folders){ + + angular.forEach(folders,function(folder){ + var fullPath = ''; + if($scope.path){ + fullPath = $scope.path + ':'; + } + fullPath += folder; + $scope.remoteFolders.push(fullPath); + }); + + }).then(getDocumentsRevisions).catch(onError); + + } else if($routeParams.action === 'checkedOut'){ + getCheckedOutDocumentsRevisions(); + } + }) + + .filter('folderShortName',function () { + return function (path) { + return path ? path.substr(path.lastIndexOf(':')+1,path.length):''; + }; + }) + + .filter('decodePath',function () { + return function (path) { + return path ? path.replace(/:/g,'/') : ''; + }; + }) + + .filter('encodePath',function () { + return function (path) { + return path ? path.replace(/\//g,':') : ''; + }; + }) + + .controller('DocumentController', function ($scope, ConfigurationService) { + $scope.configuration = ConfigurationService.configuration; + $scope.actions = false; + $scope.openedDocument= false; + $scope.toggleOpenedDocument = function () { + $scope.openedDocument = ! $scope.openedDocument; + }; + }) + + .directive('folders', function () { + return { + templateUrl: 'js/workspace/workspace-folders.html', + restrict:'E', + controller: function ($scope,CliService) { + + } + }; + }) + + .directive('documentActions', function () { + + return { + + templateUrl: 'js/workspace/document-actions.html', + + controller: function ($scope, $filter,$element, $attrs, $transclude, $timeout, CliService, FolderService, PromptService) { + + $scope.folders = FolderService.folders; + $scope.options = {force: true}; + $scope.folder = {}; + $scope.folder.path = FolderService.folders.length ? FolderService.folders[0].path : ''; + + var onFinish = function () { + $scope.document.busy = false; + $scope.document.progress = 0; + }; + + var onProgress = function (progress) { + $scope.document.progress = progress; + }; + + var newStuff = function () { + FolderService.getFolder({path:$scope.folder.path}).newStuff = true; + }; + + var checkForWorkspaceReload = function(){ + if($scope.options.recursive){ + $scope.refreshCurrent(); + } + }; + + $scope.output = []; + + var onOutput = function(o){ + $scope.output.push(o); + }; + + var onError = function(error){ + $scope.error = error; + }; + + $scope.download = function () { + $scope.document.busy = true; + CliService.downloadDocumentFiles($scope.document, $scope.folder.path, $scope.options, onOutput).then(function () { + return CliService.getStatusForDocument($scope.document); + }, onError, onProgress).then(onFinish).then(newStuff); + }; + + $scope.checkout = function () { + $scope.document.busy = true; + CliService.checkoutDocument($scope.document, $scope.folder.path, $scope.options, onOutput).then(function () { + return CliService.getStatusForDocument($scope.document); + }, onError, onProgress).then(onFinish).then(newStuff).then(checkForWorkspaceReload); + }; + + $scope.checkin = function (e) { + PromptService.prompt(e, {title:$filter('translate')('CHECKIN_MESSAGE')}).then(function(message) { + $scope.document.busy = true; + CliService.checkinDocument($scope.document,{message:message}, onOutput).then(function () { + return CliService.getStatusForDocument($scope.document); + }, onError, onProgress).then(onFinish); + }); + }; + + $scope.undoCheckout = function () { + $scope.document.busy = true; + CliService.undoCheckoutDocument($scope.document, onOutput).then(function () { + return CliService.getStatusForDocument($scope.document); + }, onError).then(onFinish); + }; + + } + + }; + }); +})(); diff --git a/docdoku-dplm/ui/app/js/workspace/workspace-folders.html b/docdoku-dplm/ui/app/js/workspace/workspace-folders.html new file mode 100644 index 0000000000..1caf831274 --- /dev/null +++ b/docdoku-dplm/ui/app/js/workspace/workspace-folders.html @@ -0,0 +1,39 @@ + +
+ + + + {{'HOME_FOLDER'|translate}} + + {{'HOME_FOLDER'|translate}} + + + + + + {{'ROOT_FOLDER'|translate}} + + {{'ROOT_FOLDER'|translate}} + + + + + + {{pathPart | folderShortName}} + + {{pathPart}} + + + +
+ +
+ + + + {{remoteFolder | folderShortName}} + + +
+ +
\ No newline at end of file diff --git a/docdoku-dplm/ui/app/js/workspace/workspace-parts.html b/docdoku-dplm/ui/app/js/workspace/workspace-parts.html new file mode 100644 index 0000000000..2c028caed1 --- /dev/null +++ b/docdoku-dplm/ui/app/js/workspace/workspace-parts.html @@ -0,0 +1,77 @@ +
+
+ +
+ +
+ + + + + + + + + + + + +
+
+ + +
+ +
+ + + + + +
+ +
+
+ + + + +
+ +
+ +
+ + +

+ {{part.partNumber}} {{part.version}}-{{part.iterations | last}} +
+ + {{part.cadFileName || 'NO_CAD_FILE' | translate }} + +

+ +

+ {{'CHECKOUT_BY' | translate }} {{part.checkoutUser}} {{part.checkoutDate | timeago}} +

+
+ +
+ +
+ +
+ +
+ +
+
+ +

{{'NO_PARTS'|translate}}

+ +
+ diff --git a/docdoku-dplm/ui/app/js/workspace/workspace-parts.js b/docdoku-dplm/ui/app/js/workspace/workspace-parts.js new file mode 100644 index 0000000000..46ff5d8bc6 --- /dev/null +++ b/docdoku-dplm/ui/app/js/workspace/workspace-parts.js @@ -0,0 +1,242 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.workspace.parts', []) + + .controller('WorkspacePartsController', function ($scope, $q, $filter, $window, $timeout, $routeParams, CliService, ConfigurationService, NotificationService, WorkspaceService) { + + $scope.count = 0; + $scope.start = 0; + $scope.max = 20; + $scope.parts = []; + $scope.loadingParts = true; + $scope.loadingMore = false; + + $scope.filters = { + checkoutable: true, + checkedOutByMe: true, + isReleased: true, + checkedOut: true, + search:'' + }; + + var resetList = function () { + $scope.start = 0; + $scope.parts.length = 0; + $scope.loadingParts = true; + $scope.loadingMore = false; + getPartMasters(); + }; + + var onSearchResults = function (parts) { + $scope.loadingParts = false; + $scope.parts = parts; + }; + + var onListResults = function (parts) { + $scope.loadingParts = false; + $scope.loadingMore = false; + angular.forEach(parts, function (part) { + $scope.parts.push(part); + }); + }; + + var onError = function(error){ + $scope.error = error; + $scope.loadingParts = false; + }; + + var runSearch = function (search) { + $scope.error = null; + $scope.loadingParts = true; + return CliService.searchPartMasters($scope.workspace, search) + .then(onSearchResults, onError); + }; + + var getPartMasters = function () { + $scope.error = null; + return CliService.getPartMasters($scope.workspace, $scope.start, $scope.max) + .then(onListResults, onError); + }; + + var showInBrowser = function () { + $window.open(ConfigurationService.resolveUrl() + '/product-management/#' + $scope.workspace); + }; + + var searchTimeout; + + $scope.$watch('filters.search', function (newValue, oldValue) { + if (searchTimeout) { + $timeout.cancel(searchTimeout); + } + if (newValue) { + searchTimeout = $timeout(function () { + runSearch(newValue); + }, 750); + } else if (oldValue) { + resetList(); + } + }); + + $scope.$on('refresh',resetList); + $scope.$on('showInBrowser',showInBrowser); + + $scope.onScrollEnd = function () { + if (!$scope.loadingParts && !$scope.search && $scope.start < $scope.count) { + $scope.start += $scope.max; + $scope.loadingMore = true; + NotificationService.toastBottomRight($filter('translate')('LOADING_MORE')); + getPartMasters().then(NotificationService.hide); + } + }; + + $scope.refreshCurrent = function(){ + + var chain = $q.when(); + + angular.forEach($scope.parts, function (part) { + chain = chain.then(function(){ + part.busy=true; + return CliService.getStatusForPart(part).then(function(){ + part.busy = false; + }); + }); + }); + + }; + + CliService.getPartMastersCount($routeParams.workspace).then(function (data) { + $scope.count = data.count; + }).then(getPartMasters).catch(onError); + + }) + .filter('filterParts', function (ConfigurationService) { + return function (arr, filters) { + + if (!arr) { + return []; + } + + return arr.filter(function (part) { + + + if (!filters.isReleased && part.isReleased) { + return false; + } + + if (!filters.checkoutable && !part.checkoutUser && !part.isReleased) { + return false; + } + + if (!filters.checkedOut && part.checkoutUser && part.checkoutUser !== ConfigurationService.configuration.user) { + return false; + } + + if (!filters.checkedOutByMe && part.checkoutUser && part.checkoutUser === ConfigurationService.configuration.user) { + return false; + } + + return true; + + }); + + }; + }) + .controller('PartController', function ($scope, ConfigurationService) { + $scope.configuration = ConfigurationService.configuration; + $scope.actions = false; + $scope.openedPart = false; + $scope.toggleOpenedPart = function () { + $scope.openedPart = !$scope.openedPart; + }; + }) + + .directive('partActions', function () { + + return { + + templateUrl: 'js/workspace/part-actions.html', + scope:false, + controller: function ($scope, $filter, $element, $attrs, $timeout, CliService, FolderService, PromptService) { + + $scope.folders = FolderService.folders; + $scope.options = {force: true, recursive: true}; + $scope.baselines = [{name:$filter('translate')('LATEST'),id:''}]; + $scope.baseline = $scope.baselines[0]; + + CliService.getBaselines($scope.part).then(function(baselines){ + angular.forEach(baselines,function(baseline){ + $scope.baselines.push(baseline); + }); + }); + + $scope.folder = {}; + $scope.folder.path = FolderService.folders.length ? FolderService.folders[0].path : ''; + + var onError = function(error){ + $scope.error = error; + }; + + var onFinish = function () { + $scope.part.busy = false; + $scope.part.progress = 0; + }; + + var onProgress = function (progress) { + $scope.part.progress = progress; + }; + + var newStuff = function () { + FolderService.getFolder({path:$scope.folder.path}).newStuff = true; + }; + + var checkForWorkspaceReload = function(){ + if($scope.options.recursive){ + $scope.refreshCurrent(); + } + }; + + $scope.output = []; + + var onOutput = function(o){ + $scope.$apply(function(){ + $scope.output.push(o); + }); + }; + + $scope.download = function () { + $scope.part.busy = true; + CliService.downloadNativeCad($scope.part, $scope.folder.path, $scope.options, onOutput).then(function () { + return CliService.getStatusForPart($scope.part); + }, onError, onProgress).then(onFinish).then(newStuff); + }; + + $scope.checkout = function () { + $scope.part.busy = true; + CliService.checkoutPart($scope.part, $scope.folder.path, $scope.options, onOutput).then(function () { + return CliService.getStatusForPart($scope.part); + }, onError, onProgress).then(onFinish).then(newStuff).then(checkForWorkspaceReload); + }; + + $scope.checkin = function (e) { + PromptService.prompt(e, {title:$filter('translate')('CHECKIN_MESSAGE')}).then(function(message) { + $scope.part.busy = true; + CliService.checkinPart($scope.part, {message:message}, onOutput).then(function () { + return CliService.getStatusForPart($scope.part); + }, onError, onProgress).then(onFinish); + }); + }; + + $scope.undoCheckout = function () { + $scope.part.busy = true; + CliService.undoCheckoutPart($scope.part, onOutput).then(function () { + return CliService.getStatusForPart($scope.part); + }, onError).then(onFinish); + }; + + } + + }; + }); +})(); diff --git a/docdoku-dplm/ui/app/js/workspace/workspace.html b/docdoku-dplm/ui/app/js/workspace/workspace.html new file mode 100644 index 0000000000..a1fe10b6b7 --- /dev/null +++ b/docdoku-dplm/ui/app/js/workspace/workspace.html @@ -0,0 +1,34 @@ + +
+

+ + + + {{'WORKSPACE' | translate}} : {{workspace}} + + {{'OPEN_IN_BROWSER'|translate}} + + +

+ +
+
+ +
+ + +
+
+
+ +
+
+
+
diff --git a/docdoku-dplm/ui/app/js/workspace/workspace.js b/docdoku-dplm/ui/app/js/workspace/workspace.js new file mode 100644 index 0000000000..66b54b1c10 --- /dev/null +++ b/docdoku-dplm/ui/app/js/workspace/workspace.js @@ -0,0 +1,52 @@ +(function(){ + + 'use strict'; + + angular.module('dplm.workspace', ['dplm.workspace.documents','dplm.workspace.parts']) + + .config(function ($routeProvider) { + + $routeProvider + .when('/workspace/:workspace', { + template:'', + controller: function($routeParams,$location){ + $location.path('workspace/'+$routeParams.workspace+'/documents/folders'); + } + }) + .when('/workspace/:workspace/:entity/:action/:path?', { + controller: 'WorkspaceController', + templateUrl: 'js/workspace/workspace.html', + resolve:{ + conf:function(ConfigurationService,WorkspaceService){ + return ConfigurationService.checkAtStartup().then(WorkspaceService.getWorkspaces); + } + } + }); + }) + + .controller('WorkspaceController', function ($scope,$routeParams,WorkspaceService) { + + $scope.tabs={ + selected: + $routeParams.entity==='documents' ? 0 : + $routeParams.entity==='parts' ? 1 : + 0, + documents:0, + parts:1 + }; + + $scope.refresh = function() { + $scope.$broadcast('refresh'); + }; + + $scope.showInBrowser = function () { + $scope.$broadcast('showInBrowser'); + }; + + $scope.workspace = $routeParams.workspace; + + WorkspaceService.addLastVisited($scope.workspace); + + }); + +})(); diff --git a/docdoku-dplm/ui/app/package.json b/docdoku-dplm/ui/app/package.json new file mode 100644 index 0000000000..77c22531bc --- /dev/null +++ b/docdoku-dplm/ui/app/package.json @@ -0,0 +1,17 @@ +{ + "name": "DPLM", + "main": "index.html", + "locale": "fr_FR", + "dependencies": { + "moment": "^2.8.3", + "recursive-readdir": "^1.2.0" + }, + "window": { + "toolbar": false, + "position": "center", + "width": 960, + "height": 500, + "resizable": true, + "icon": "img/icon.png" + } +} diff --git a/docdoku-dplm/ui/bower.json b/docdoku-dplm/ui/bower.json new file mode 100644 index 0000000000..f5e412e0f0 --- /dev/null +++ b/docdoku-dplm/ui/bower.json @@ -0,0 +1,31 @@ +{ + "name": "docdoku-dplm", + "version": "0.2.0", + "homepage": "https://github.com/docdoku/docdoku-plm", + "authors": [ + "morgan " + ], + "description": "Docdoku PLM client", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "angular": "~1.3.1", + "angular-material": "~0.5.1", + "angular-route": "~1.3.3", + "angular-translate": "~2.4.2", + "angular-uuid4": "~0.3.0", + "fontawesome": "~4.2.0", + "angular-aria": "~1.3.3", + "threejs": "r67" + }, + "resolutions": { + "angular": "1.3.1", + "angular-aria": "~1.3.3" + } +} diff --git a/docdoku-dplm/ui/package.json b/docdoku-dplm/ui/package.json new file mode 100644 index 0000000000..4dca825bde --- /dev/null +++ b/docdoku-dplm/ui/package.json @@ -0,0 +1,22 @@ +{ + "name": "docdoku-dplm", + "version": "0.2.0", + "description": "", + "author": "morgan.guimard@docdoku.com", + "scripts": { + "postinstall": "bower install && cd app && npm install" + }, + "devDependencies": { + "grunt": "~0.4.5", + "grunt-contrib-clean": "~0.5.0", + "grunt-contrib-concat": "~0.4.0", + "grunt-contrib-copy": "^0.7.0", + "grunt-contrib-cssmin": "^0.10.0", + "grunt-contrib-jshint": "^0.10.0", + "grunt-contrib-uglify": "~0.5.0", + "grunt-html2js": "~0.2.7", + "grunt-ng-annotate": "^2.0.2", + "grunt-text-replace": "^0.4.0", + "grunt-usemin": "^2.6.2" + } +} diff --git a/docdoku-dplm/windows-all-platforms.sh b/docdoku-dplm/windows-all-platforms.sh new file mode 100755 index 0000000000..d5e50525f5 --- /dev/null +++ b/docdoku-dplm/windows-all-platforms.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +TMP_DIR=${BASE_DIR}/tmp +OUT_DIR=${BASE_DIR}/target + +cd ${TMP_DIR}; + +mkdir -p win-ia32 && rm -rf win-ia32/*; +mkdir -p win-x64 && rm -rf win-x64/*; + +[[ -d node-webkit-v0.11.1-win-ia32 ]] || unzip node-webkit-v0.11.1-win-ia32.zip +[[ -d node-webkit-v0.11.1-win-x64 ]] || unzip node-webkit-v0.11.1-win-x64.zip + +echo "Building windows 32 bits app ..."; + +# prepare binary and DLLs +/bin/cat ${TMP_DIR}/node-webkit-v0.11.1-win-ia32/nw.exe ${TMP_DIR}/app.nw > ${TMP_DIR}/win-ia32/dplm.exe +chmod +x ${TMP_DIR}/win-ia32/dplm.exe +cp ${TMP_DIR}/node-webkit-v0.11.1-win-ia32/nw.pak ${TMP_DIR}/win-ia32 +cp ${TMP_DIR}/node-webkit-v0.11.1-win-ia32/*.dll ${TMP_DIR}/win-ia32 +cp ${TMP_DIR}/node-webkit-v0.11.1-win-ia32/*.dat ${TMP_DIR}/win-ia32 +cd ${TMP_DIR}/win-ia32; +zip dplm-win-32.zip *; +mv dplm-win-32.zip ${OUT_DIR}; + +echo "... done" +echo "Building windows 64 bits app ..."; + +# prepare binary and DLLs +/bin/cat ${TMP_DIR}/node-webkit-v0.11.1-win-x64/nw.exe ${TMP_DIR}/app.nw > ${TMP_DIR}/win-x64/dplm.exe +chmod +x ${TMP_DIR}/win-x64/dplm.exe +cp ${TMP_DIR}/node-webkit-v0.11.1-win-x64/nw.pak ${TMP_DIR}/win-x64 +cp ${TMP_DIR}/node-webkit-v0.11.1-win-x64/*.dll ${TMP_DIR}/win-x64 +cp ${TMP_DIR}/node-webkit-v0.11.1-win-x64/*.dat ${TMP_DIR}/win-x64 +cd ${TMP_DIR}/win-x64; +zip dplm-win-64.zip *; +mv dplm-win-64.zip ${OUT_DIR}; + +echo "... done" diff --git a/docdoku-dplm/zip-ui.sh b/docdoku-dplm/zip-ui.sh new file mode 100755 index 0000000000..2693d90875 --- /dev/null +++ b/docdoku-dplm/zip-ui.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +TMP_DIR=${BASE_DIR}/tmp +SOURCES=${BASE_DIR}/ui/dist + +echo "Creating nw archive ..." + +cd ${TMP_DIR}; + +rm -rf dist; + +cp -R ${SOURCES} ${TMP_DIR}; + +# Remove old nw app if any +if [ -f "app.nw" ] ; then + echo "Removing old archive" + rm -f "app.nw"; +fi + +# Zip the source in a zip archive +cd dist; +/usr/bin/zip -r ../app.nw *; +echo "... done"; \ No newline at end of file diff --git a/docdoku-node-server/.gitignore b/docdoku-node-server/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docdoku-node-server/box-calculator.js b/docdoku-node-server/box-calculator.js new file mode 100644 index 0000000000..ede638c788 --- /dev/null +++ b/docdoku-node-server/box-calculator.js @@ -0,0 +1,72 @@ +var THREE = require("three"); +var fs = require('fs'); + +require("./lib/OBJLoader"); + +var getMeshGeometries = function (object, geometries) { + if (object) { + object.children.forEach(function (child) { + if (child instanceof THREE.Mesh && child.geometry) { + geometries.push(child.geometry); + } + getMeshGeometries(child, geometries); + }); + } +}; + +var calculateBox = function (data, callback) { + + var loader = new THREE.OBJLoader(); + + var geometries = []; + var combined = new THREE.Geometry(); + + var object = loader.parse(data); + + getMeshGeometries(object, geometries); + + geometries.forEach(function (geometry) { + THREE.GeometryUtils.merge(combined, geometry); + }); + + combined.mergeVertices(); + + combined.computeBoundingBox(); + callback(combined.boundingBox); + +}; + +module.exports = { + handlePost:function(req, res, next) { + + var filename = req.body.filename; + + if (!filename) { + console.log("Filename not specified. Exiting."); + res.end(); + return; + } + + try { + fs.statSync(filename); + } catch (ex) { + console.log("File '" + filename + "' does not exist. Exiting."); + res.end(); + return; + } + + fs.readFile(filename, 'utf-8' ,function (err, data) { + if (err) { + console.log("Cannot read the file, is it corrupted or in an other format ?"); + res.end(); + } + else{ + calculateBox(data,function(box){ + res.write(JSON.stringify(box)); + res.end(); + }); + } + }); + + } +}; \ No newline at end of file diff --git a/docdoku-node-server/lib/OBJLoader.js b/docdoku-node-server/lib/OBJLoader.js new file mode 100644 index 0000000000..a468bcc879 --- /dev/null +++ b/docdoku-node-server/lib/OBJLoader.js @@ -0,0 +1,330 @@ +/** + * @author mrdoob / http://mrdoob.com/ + */ +var THREE = require("three"); + +THREE.OBJLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.OBJLoader.prototype = { + + constructor: THREE.OBJLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.XHRLoader( scope.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( text ) ); + + } ); + + }, + + parse: function ( text ) { + + function vector( x, y, z ) { + + return new THREE.Vector3( parseFloat( x ), parseFloat( y ), parseFloat( z ) ); + + } + + function uv( u, v ) { + + return new THREE.Vector2( parseFloat( u ), parseFloat( v ) ); + + } + + function face3( a, b, c, normals ) { + + return new THREE.Face3( a, b, c, normals ); + + } + + var object = new THREE.Object3D(); + var geometry, material, mesh; + + function parseVertexIndex( index ) { + + index = parseInt( index ); + + return index >= 0 ? index - 1 : index + vertices.length; + + } + + function parseNormalIndex( index ) { + + index = parseInt( index ); + + return index >= 0 ? index - 1 : index + normals.length; + + } + + function parseUVIndex( index ) { + + index = parseInt( index ); + + return index >= 0 ? index - 1 : index + uvs.length; + + } + + function add_face( a, b, c, normals_inds ) { + + if ( normals_inds === undefined ) { + + geometry.faces.push( face3( + vertices[ parseVertexIndex( a ) ] - 1, + vertices[ parseVertexIndex( b ) ] - 1, + vertices[ parseVertexIndex( c ) ] - 1 + ) ); + + } else { + + geometry.faces.push( face3( + vertices[ parseVertexIndex( a ) ] - 1, + vertices[ parseVertexIndex( b ) ] - 1, + vertices[ parseVertexIndex( c ) ] - 1, + [ + normals[ parseNormalIndex( normals_inds[ 0 ] ) ].clone(), + normals[ parseNormalIndex( normals_inds[ 1 ] ) ].clone(), + normals[ parseNormalIndex( normals_inds[ 2 ] ) ].clone() + ] + ) ); + + } + + } + + function add_uvs( a, b, c ) { + + geometry.faceVertexUvs[ 0 ].push( [ + uvs[ parseUVIndex( a ) ].clone(), + uvs[ parseUVIndex( b ) ].clone(), + uvs[ parseUVIndex( c ) ].clone() + ] ); + + } + + function handle_face_line(faces, uvs, normals_inds) { + + if ( faces[ 3 ] === undefined ) { + + add_face( faces[ 0 ], faces[ 1 ], faces[ 2 ], normals_inds ); + + if ( uvs !== undefined && uvs.length > 0 ) { + + add_uvs( uvs[ 0 ], uvs[ 1 ], uvs[ 2 ] ); + + } + + } else { + + if ( normals_inds !== undefined && normals_inds.length > 0 ) { + + add_face( faces[ 0 ], faces[ 1 ], faces[ 3 ], [ normals_inds[ 0 ], normals_inds[ 1 ], normals_inds[ 3 ] ] ); + add_face( faces[ 1 ], faces[ 2 ], faces[ 3 ], [ normals_inds[ 1 ], normals_inds[ 2 ], normals_inds[ 3 ] ] ); + + } else { + + add_face( faces[ 0 ], faces[ 1 ], faces[ 3 ] ); + add_face( faces[ 1 ], faces[ 2 ], faces[ 3 ] ); + + } + + if ( uvs !== undefined && uvs.length > 0 ) { + + add_uvs( uvs[ 0 ], uvs[ 1 ], uvs[ 3 ] ); + add_uvs( uvs[ 1 ], uvs[ 2 ], uvs[ 3 ] ); + + } + + } + + } + + // create mesh if no objects in text + + if ( /^o /gm.test( text ) === false ) { + + geometry = new THREE.Geometry(); + material = new THREE.MeshLambertMaterial(); + mesh = new THREE.Mesh( geometry, material ); + object.add( mesh ); + + } + + var vertices = []; + var normals = []; + var uvs = []; + + // v float float float + + var vertex_pattern = /v( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; + + // vn float float float + + var normal_pattern = /vn( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; + + // vt float float + + var uv_pattern = /vt( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; + + // f vertex vertex vertex ... + + var face_pattern1 = /f( +-?\d+)( +-?\d+)( +-?\d+)( +-?\d+)?/; + + // f vertex/uv vertex/uv vertex/uv ... + + var face_pattern2 = /f( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))?/; + + // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... + + var face_pattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/; + + // f vertex//normal vertex//normal vertex//normal ... + + var face_pattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/ + + // fixes + + text = text.replace( /\\\r\n/g, '' ); // handles line continuations \ + + var lines = text.split( '\n' ); + + for ( var i = 0; i < lines.length; i ++ ) { + + var line = lines[ i ]; + line = line.trim(); + + var result; + + if ( line.length === 0 || line.charAt( 0 ) === '#' ) { + + continue; + + } else if ( ( result = vertex_pattern.exec( line ) ) !== null ) { + + // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] + + vertices.push( + geometry.vertices.push( + vector( + result[ 1 ], result[ 2 ], result[ 3 ] + ) + ) + ); + + } else if ( ( result = normal_pattern.exec( line ) ) !== null ) { + + // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] + + normals.push( + vector( + result[ 1 ], result[ 2 ], result[ 3 ] + ) + ); + + } else if ( ( result = uv_pattern.exec( line ) ) !== null ) { + + // ["vt 0.1 0.2", "0.1", "0.2"] + + uvs.push( + uv( + result[ 1 ], result[ 2 ] + ) + ); + + } else if ( ( result = face_pattern1.exec( line ) ) !== null ) { + + // ["f 1 2 3", "1", "2", "3", undefined] + + handle_face_line( + [ result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] ] + ); + + } else if ( ( result = face_pattern2.exec( line ) ) !== null ) { + + // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined] + + handle_face_line( + [ result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ] ], //faces + [ result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] ] //uv + ); + + } else if ( ( result = face_pattern3.exec( line ) ) !== null ) { + + // ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined] + + handle_face_line( + [ result[ 2 ], result[ 6 ], result[ 10 ], result[ 14 ] ], //faces + [ result[ 3 ], result[ 7 ], result[ 11 ], result[ 15 ] ], //uv + [ result[ 4 ], result[ 8 ], result[ 12 ], result[ 16 ] ] //normal + ); + + } else if ( ( result = face_pattern4.exec( line ) ) !== null ) { + + // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined] + + handle_face_line( + [ result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ] ], //faces + [ ], //uv + [ result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] ] //normal + ); + + } else if ( /^o /.test( line ) ) { + + geometry = new THREE.Geometry(); + material = new THREE.MeshLambertMaterial(); + + mesh = new THREE.Mesh( geometry, material ); + mesh.name = line.substring( 2 ).trim(); + object.add( mesh ); + + } else if ( /^g /.test( line ) ) { + + // group + + } else if ( /^usemtl /.test( line ) ) { + + // material + + material.name = line.substring( 7 ).trim(); + + } else if ( /^mtllib /.test( line ) ) { + + // mtl file + + } else if ( /^s /.test( line ) ) { + + // smooth shading + + } else { + + // console.log( "THREE.OBJLoader: Unhandled line " + line ); + + } + + } + + var children = object.children; + + for ( var i = 0, l = children.length; i < l; i ++ ) { + + var geometry = children[ i ].geometry; + + geometry.computeFaceNormals(); + geometry.computeBoundingSphere(); + + } + + return object; + + } + +}; diff --git a/docdoku-node-server/package.json b/docdoku-node-server/package.json new file mode 100644 index 0000000000..55472f1ddd --- /dev/null +++ b/docdoku-node-server/package.json @@ -0,0 +1,13 @@ +{ + "name": "geometry-parser", + "version": "0.2.0", + "private": true, + "dependencies": { + "express": "3.1.0", + "three":"0.69.0" + }, + "scripts":{ + "prestart":"npm install", + "start":"node server.js" + } +} diff --git a/docdoku-node-server/server.js b/docdoku-node-server/server.js new file mode 100644 index 0000000000..1f42bb9017 --- /dev/null +++ b/docdoku-node-server/server.js @@ -0,0 +1,14 @@ +'use strict'; + +var express = require("express"); +var BoxCalculator = require("./box-calculator"); + +var app = express(); + +app.use(express.bodyParser()); + +app.post('/box', BoxCalculator.handlePost); + +app.listen(8888); + +console.log('Listening on port 8888'); diff --git a/docdoku-plm-api b/docdoku-plm-api deleted file mode 160000 index d3452cb9f3..0000000000 --- a/docdoku-plm-api +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d3452cb9f3951c3c32ed3fc8c891e362bd1c0e3a diff --git a/docdoku-plm-conversion-service b/docdoku-plm-conversion-service deleted file mode 160000 index b25c5efee3..0000000000 --- a/docdoku-plm-conversion-service +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b25c5efee37b1e0c7691ae57c5696b68a8f0a379 diff --git a/docdoku-plm-doc b/docdoku-plm-doc deleted file mode 160000 index c38bf36c17..0000000000 --- a/docdoku-plm-doc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c38bf36c17fecb2b47bf376ea56cbfaff959748c diff --git a/docdoku-plm-docker b/docdoku-plm-docker deleted file mode 160000 index 8085c29cce..0000000000 --- a/docdoku-plm-docker +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8085c29cce085d6ca409cdaf790d159ae6ac5751 diff --git a/docdoku-plm-front b/docdoku-plm-front deleted file mode 160000 index 95bdbe3d45..0000000000 --- a/docdoku-plm-front +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 95bdbe3d45f2f271ea7c5c057e38229b42ec5c03 diff --git a/docdoku-plm-sample-data b/docdoku-plm-sample-data deleted file mode 160000 index 7a6b3e158d..0000000000 --- a/docdoku-plm-sample-data +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7a6b3e158d66dfa4c94645943c24c70a22e645b8 diff --git a/docdoku-plm-server b/docdoku-plm-server deleted file mode 160000 index ca34dac6c8..0000000000 --- a/docdoku-plm-server +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ca34dac6c89d3cea64a735b538f3b48d69bb1eaa diff --git a/docdoku-server/docdoku-server-converter-all/pom.xml b/docdoku-server/docdoku-server-converter-all/pom.xml new file mode 100644 index 0000000000..522bab6d8b --- /dev/null +++ b/docdoku-server/docdoku-server-converter-all/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-converter-all + jar + docdoku-server-converter-all All Converter + + + com.docdoku + docdoku-server-ext + ${project.version} + jar + + + javax + javaee-api + 7.0 + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + ${project.artifactId} + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-all/src/main/java/com/docdoku/server/converters/all/AllFileConverter.java b/docdoku-server/docdoku-server-converter-all/src/main/java/com/docdoku/server/converters/all/AllFileConverter.java new file mode 100644 index 0000000000..33adce4210 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-all/src/main/java/com/docdoku/server/converters/all/AllFileConverter.java @@ -0,0 +1,35 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.all; + + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Retention(RUNTIME) +@Target({TYPE, METHOD, FIELD, PARAMETER}) +public @interface AllFileConverter{ +} diff --git a/docdoku-server/docdoku-server-converter-all/src/main/java/com/docdoku/server/converters/all/AllFileConverterImpl.java b/docdoku-server/docdoku-server-converter-all/src/main/java/com/docdoku/server/converters/all/AllFileConverterImpl.java new file mode 100644 index 0000000000..340ce1daf8 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-all/src/main/java/com/docdoku/server/converters/all/AllFileConverterImpl.java @@ -0,0 +1,120 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.all; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.util.FileIO; +import com.docdoku.server.InternalService; +import com.docdoku.server.converters.CADConverter; +import com.docdoku.server.converters.utils.ConversionResult; +import com.docdoku.server.converters.utils.ConverterUtils; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Properties; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + + +@AllFileConverter +public class AllFileConverterImpl implements CADConverter{ + + private static final String CONF_PROPERTIES="/com/docdoku/server/converters/all/conf.properties"; + private static final Properties CONF = new Properties(); + + @InternalService + @Inject + private IDataManagerLocal dataManager; + + private static final Logger LOGGER = Logger.getLogger(AllFileConverterImpl.class.getName()); + + static{ + try (InputStream inputStream = AllFileConverterImpl.class.getResourceAsStream(CONF_PROPERTIES)){ + CONF.load(inputStream); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + + @Override + public ConversionResult convert(PartIteration partToConvert, final BinaryResource cadFile, File tempDir) throws IOException, InterruptedException, UserNotActiveException, PartRevisionNotFoundException, WorkspaceNotFoundException, CreationException, UserNotFoundException, NotAllowedException, FileAlreadyExistsException, StorageException { + + UUID uuid = UUID.randomUUID(); + String extension = FileIO.getExtension(cadFile.getName()); + File tmpCadFile = new File(tempDir, partToConvert.getKey() + "." + extension); + String convertedFileName = tempDir.getAbsolutePath() + "/" + uuid ; + String meshConvBinary = CONF.getProperty("meshconv_path"); + + File executable = new File(meshConvBinary); + + if(!executable.exists()){ + LOGGER.log(Level.SEVERE, "Cannot convert file \""+cadFile.getName()+"\", \""+meshConvBinary+"\" is not available"); + return null; + } + + if(!executable.canExecute()){ + LOGGER.log(Level.SEVERE, "Cannot convert file \""+cadFile.getName()+"\", \""+meshConvBinary+"\" has no execution rights"); + return null; + } + + try(InputStream in = dataManager.getBinaryResourceInputStream(cadFile)) { + Files.copy(in, tmpCadFile.toPath()); + } catch (StorageException e) { + LOGGER.log(Level.WARNING, null, e); + throw new IOException(e); + } + + + String[] args = {meshConvBinary, tmpCadFile.getAbsolutePath(), "-c" , "obj", "-o", convertedFileName}; + ProcessBuilder pb = new ProcessBuilder(args); + Process proc = pb.start(); + + // Read buffers + String stdOutput = ConverterUtils.getOutput(proc.getInputStream()); + String errorOutput = ConverterUtils.getOutput(proc.getErrorStream()); + + LOGGER.info(stdOutput); + + proc.waitFor(); + + if(proc.exitValue() == 0){ + return new ConversionResult(new File(convertedFileName + ".obj")); + } + + LOGGER.log(Level.SEVERE, "Cannot convert to obj : " + tmpCadFile.getAbsolutePath(), errorOutput); + + return null; + } + + @Override + public boolean canConvertToOBJ(String cadFileExtension) { + return Arrays.asList("stl","off","ply","3ds","wrl").contains(cadFileExtension); + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-all/src/main/resources/META-INF/beans.xml b/docdoku-server/docdoku-server-converter-all/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..5d71326a5c --- /dev/null +++ b/docdoku-server/docdoku-server-converter-all/src/main/resources/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-all/src/main/resources/com/docdoku/server/converters/all/conf.properties b/docdoku-server/docdoku-server-converter-all/src/main/resources/com/docdoku/server/converters/all/conf.properties new file mode 100644 index 0000000000..40d6529fa8 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-all/src/main/resources/com/docdoku/server/converters/all/conf.properties @@ -0,0 +1 @@ +meshconv_path=/opt/meshconv/meshconv \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-catia-product/pom.xml b/docdoku-server/docdoku-server-converter-catia-product/pom.xml new file mode 100644 index 0000000000..183914e023 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-catia-product/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-converter-catia-product + jar + docdoku-server-converter-catia-product CATProduct Parser + + + com.docdoku + docdoku-server-ext + ${project.version} + jar + + + javax + javaee-api + 7.0 + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + ${project.artifactId} + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/CatiaProductFileParser.java b/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/CatiaProductFileParser.java new file mode 100644 index 0000000000..0747bc87cd --- /dev/null +++ b/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/CatiaProductFileParser.java @@ -0,0 +1,35 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.catia.product; + + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Retention(RUNTIME) +@Target({TYPE, METHOD, FIELD, PARAMETER}) +public @interface CatiaProductFileParser { +} diff --git a/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/CatiaProductFileParserImpl.java b/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/CatiaProductFileParserImpl.java new file mode 100644 index 0000000000..ca2c527a8b --- /dev/null +++ b/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/CatiaProductFileParserImpl.java @@ -0,0 +1,206 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.catia.product; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.CADInstance; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartMaster; +import com.docdoku.core.product.PartUsageLink; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.server.InternalService; +import com.docdoku.server.converters.CADConverter; +import com.docdoku.server.converters.catia.product.parser.ComponentDTK; +import com.docdoku.server.converters.catia.product.parser.ComponentDTKSaxHandler; +import com.docdoku.server.converters.utils.ConversionResult; +import com.docdoku.server.converters.utils.ConverterUtils; +import org.xml.sax.SAXException; + +import javax.inject.Inject; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + + +@CatiaProductFileParser +public class CatiaProductFileParserImpl implements CADConverter { + + private static final String CONF_PROPERTIES = "/com/docdoku/server/converters/catia/product/conf.properties"; + private static final Properties CONF = new Properties(); + private static final Logger LOGGER = Logger.getLogger(CatiaProductFileParserImpl.class.getName()); + + @InternalService + @Inject + private IProductManagerLocal productService; + + @InternalService + @Inject + private IDataManagerLocal dataManager; + + static { + try (InputStream inputStream = CatiaProductFileParserImpl.class.getResourceAsStream(CONF_PROPERTIES)){ + CONF.load(inputStream); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + + @Override + public ConversionResult convert(PartIteration partToConvert, final BinaryResource cadFile, File tempDir) throws IOException, InterruptedException, UserNotActiveException, PartRevisionNotFoundException, WorkspaceNotFoundException, CreationException, UserNotFoundException, NotAllowedException, FileAlreadyExistsException, StorageException { + + File tmpCadFile; + File tmpXMLFile = new File(tempDir, cadFile.getName() + "_dtk.xml"); + + String catProductReader = CONF.getProperty("catProductReader"); + + File executable = new File(catProductReader); + + if(!executable.exists()){ + LOGGER.log(Level.SEVERE, "Cannot convert file \""+cadFile.getName()+"\", \""+catProductReader+"\" is not available"); + return null; + } + + if(!executable.canExecute()){ + LOGGER.log(Level.SEVERE, "Cannot convert file \""+cadFile.getName()+"\", \""+catProductReader+"\" has no execution rights"); + return null; + } + + tmpCadFile = new File(tempDir, cadFile.getName()); + + try(InputStream in = dataManager.getBinaryResourceInputStream(cadFile)) { + Files.copy(in, tmpCadFile.toPath()); + } catch (StorageException e) { + LOGGER.log(Level.WARNING, null, e); + throw new IOException(e); + } + + String[] args = {"sh", catProductReader, tmpCadFile.getAbsolutePath()}; + + ProcessBuilder pb = new ProcessBuilder(args); + Process process = pb.start(); + + // Read buffers + String stdOutput = ConverterUtils.getOutput(process.getInputStream()); + String errorOutput = ConverterUtils.getOutput(process.getErrorStream()); + + LOGGER.info(stdOutput); + + process.waitFor(); + + int exitCode = process.exitValue(); + + if (exitCode == 0 && tmpXMLFile.exists() && tmpXMLFile.length() > 0) { + + try { + + SAXParserFactory factory = SAXParserFactory.newInstance(); + SAXParser saxParser = factory.newSAXParser(); + ComponentDTKSaxHandler handler = new ComponentDTKSaxHandler(); + saxParser.parse(tmpXMLFile, handler); + + syncAssembly(handler.getComponent(), partToConvert); + + } catch (ParserConfigurationException | SAXException | IOException e) { + LOGGER.log(Level.INFO, null, e); + } + }else { + LOGGER.log(Level.SEVERE, "Cannot parse catia product file: " + tmpCadFile.getAbsolutePath(), errorOutput); + } + + return null; + } + + private void syncAssembly(ComponentDTK componentDtk, PartIteration partToConvert) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + + List partUsageLinks = new ArrayList<>(); + + Map> mapInstances = new HashMap<>(); + + parseSubComponents(mapInstances, componentDtk); + + for(Map.Entry> entry : mapInstances.entrySet()){ + String cadFileName = entry.getKey(); + List instances = entry.getValue(); + + PartMaster partMaster = productService.findPartMasterByCADFileName(partToConvert.getWorkspaceId(), cadFileName); + if(partMaster != null){ + PartUsageLink partUsageLink = new PartUsageLink(); + partUsageLink.setAmount(instances.size()); + partUsageLink.setComponent(partMaster); + partUsageLink.setCadInstances(instances); + partUsageLinks.add(partUsageLink); + } + + } + + // Erase old structure + partToConvert.setComponents(partUsageLinks); + + } + + + + private void parseSubComponents(Map> mapInstances, ComponentDTK root) { + + List subComponentDtkList = root.getSubComponentDtkList(); + if (subComponentDtkList != null) { + + for (ComponentDTK componentDTK : subComponentDtkList) { + + if (componentDTK.isLinkable()) { + + List cadInstances = mapInstances.get(componentDTK.getName()); + + if (cadInstances == null) { + cadInstances = new LinkedList<>(); + mapInstances.put(componentDTK.getName(),cadInstances); + } + + mapInstances.put(componentDTK.getName(),cadInstances); + + CADInstance instance = componentDTK.getPositioning().toCADInstance(); + + if (instance != null) { + cadInstances.add(instance); + } + } + + parseSubComponents(mapInstances, componentDTK); + } + } + } + + + @Override + public boolean canConvertToOBJ(String cadFileExtension) { + return "catproduct".equals(cadFileExtension); + } + +} diff --git a/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/parser/ComponentDTK.java b/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/parser/ComponentDTK.java new file mode 100644 index 0000000000..2d9cf01ecb --- /dev/null +++ b/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/parser/ComponentDTK.java @@ -0,0 +1,140 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.catia.product.parser; + +import java.util.ArrayList; +import java.util.List; + +public class ComponentDTK { + + private Integer id; + private Integer fatherId; + private String type; + private boolean assembly; + private String name; + private String instanceName; + private Positioning positioning = null; + private List metaDataList = null; + + private static final String INSTANCE = "InstanceComponentType"; + + private List subComponentDtkList = null; + + public ComponentDTK(Integer id, Integer fatherId, String type, boolean assembly) { + this.id = id; + this.fatherId = fatherId; + this.type = type; + this.assembly = assembly; + } + + public void addMetaData(MetaData metaData) { + if (metaDataList == null) { + metaDataList = new ArrayList(); + } + metaDataList.add(metaData); + } + + public void setPositioning(Positioning pPositioning) { + positioning = pPositioning; + } + + public void addSubComponent(ComponentDTK componentDtk) { + + if (subComponentDtkList == null) + subComponentDtkList = new ArrayList(); + + subComponentDtkList.add(componentDtk); + + } + + public void setName(String pName) { + name = pName; + } + + public void setInstanceName(String pInstanceName) { + instanceName = pInstanceName; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getFatherId() { + return fatherId; + } + + public void setFatherId(Integer fatherId) { + this.fatherId = fatherId; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public boolean isAssembly() { + return assembly; + } + + public void setAssembly(boolean assembly) { + this.assembly = assembly; + } + + public String getName() { + return name; + } + + public String getInstanceName() { + return instanceName; + } + + public Positioning getPositioning() { + return positioning; + } + + public List getMetaDataList() { + return metaDataList; + } + + public void setMetaDataList(List metaDataList) { + this.metaDataList = metaDataList; + } + + public List getSubComponentDtkList() { + return subComponentDtkList; + } + + public void setSubComponentDtkList(List subComponentDtkList) { + this.subComponentDtkList = subComponentDtkList; + } + + public boolean isLinkable() { + return INSTANCE.equals(type); + } + +} diff --git a/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/parser/ComponentDTKSaxHandler.java b/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/parser/ComponentDTKSaxHandler.java new file mode 100644 index 0000000000..d520198497 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/parser/ComponentDTKSaxHandler.java @@ -0,0 +1,233 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.catia.product.parser; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.util.EmptyStackException; +import java.util.Stack; + +public class ComponentDTKSaxHandler extends DefaultHandler { + + private final StringBuilder buffer = new StringBuilder(128); + + private static final String COMPONENT = "Component", + ID_ATTR = "id", + FATHER_ID_ATTR = "fatherId", + ASSEMBLY_ATTR = "assembly", + TYPE_ATTR = "type", + NAME = "Name", + POSITIONING = "Positioning", + METADATA = "MetaData", + METADATA_TYPE = "MetaDataType", + METADATA_TITLE = "Title", + METADATA_VALUE = "Value", + METADATA_UNITS = "Units", + INSTANCE_NAME = "InstanceName"; + + + private ComponentDTK componentDtk; + private Stack components = new Stack<>(); + + private MetaData metaData; + private Stack metaDataStack = new Stack<>(); + + public ComponentDTKSaxHandler() { + super(); + } + + public ComponentDTK getComponent() { + return componentDtk; + } + + @Override + public void startDocument() throws SAXException { + // Nothing to do + } + + @Override + public void endDocument() throws SAXException { + // Nothing to do + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + buffer.append(ch, start, length); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + ComponentDTK lastComponentDtk = getLastComponentDtk(); + + if (COMPONENT.equals(qName)) { + + //New component + ComponentDTK currentComponentDtk = new ComponentDTK( + getInt(attributes, ID_ATTR), + getInt(attributes, FATHER_ID_ATTR), + getString(attributes, TYPE_ATTR), + getBoolean(attributes, ASSEMBLY_ATTR) + ); + + if (lastComponentDtk != null) { + // Sub component + lastComponentDtk.addSubComponent(currentComponentDtk); + } else { + // First component + componentDtk = currentComponentDtk; + } + + components.push(currentComponentDtk); + + } else if (POSITIONING.equals(qName)) { + + if (lastComponentDtk != null) { + lastComponentDtk.setPositioning(new Positioning( + getDouble(attributes, "rx"), + getDouble(attributes, "ry"), + getDouble(attributes, "rz"), + getDouble(attributes, "tx"), + getDouble(attributes, "ty"), + getDouble(attributes, "tz") + )); + } + + } else if (METADATA.equals(qName)) { + MetaData currentMetaData = new MetaData(); + MetaData lastMetaData = getLastMetaData(); + if (lastMetaData == null) { + metaData = currentMetaData; + } + metaDataStack.push(metaData); + } + + } + + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (COMPONENT.equals(qName)) { + components.pop(); + } else if (NAME.equals(qName)) { + getLastComponentDtk().setName(getBufferValue()); + } else if (INSTANCE_NAME.equals(qName)) { + getLastComponentDtk().setInstanceName(getBufferValue()); + } else if (METADATA.equals(qName)) { + MetaData lastMetaData = getLastMetaData(); + ComponentDTK lastComponentDtk = getLastComponentDtk(); + if (lastMetaData != null && lastComponentDtk != null) { + lastComponentDtk.addMetaData(lastMetaData); + } + metaDataStack.pop(); + } else if (METADATA_TITLE.equals(qName)) { + MetaData lastMetaData = getLastMetaData(); + if (lastMetaData != null) { + lastMetaData.title = getBufferValue(); + } + } else if (METADATA_TYPE.equals(qName)) { + MetaData lastMetaData = getLastMetaData(); + if (lastMetaData != null) { + lastMetaData.metaDataType = getBufferValue(); + } + } else if (METADATA_UNITS.equals(qName)) { + MetaData lastMetaData = getLastMetaData(); + if (lastMetaData != null) { + lastMetaData.units = getBufferValue(); + } + } else if (METADATA_VALUE.equals(qName)) { + MetaData lastMetaData = getLastMetaData(); + if (lastMetaData != null) { + lastMetaData.value = getBufferValue(); + } + } + + buffer.setLength(0); + } + + + private ComponentDTK getLastComponentDtk() { + try { + return components.peek(); + } catch (EmptyStackException e) { + return null; + } + } + + private MetaData getLastMetaData() { + try { + return metaDataStack.peek(); + } catch (EmptyStackException e) { + return null; + } + } + + private String getBufferValue() { + if (buffer.length() == 0) { + return null; + } + String value = buffer.toString().trim(); + buffer.setLength(0); + return value.length() == 0 ? null : value; + } + + private static boolean getBoolean(Attributes attributes, String name) { + String s = getString(attributes, name); + return "1".equals(s) || Boolean.parseBoolean(s); + } + + private static Integer getInt(Attributes attributes, String name) { + String s = getString(attributes, name); + try { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + return 0; + } + } + + private static Double getDouble(Attributes attributes, String name) { + String s = getString(attributes, name); + try { + return Double.parseDouble(s); + } catch (Exception e) { + return 0.00; + } + } + + private static String getString(Attributes attributes, String name) { + String s = attributes.getValue(name); + if (s == null) { + return null; + } + // trim leading and trailing spaces. + s = s.trim(); + // see if empty string. + if (s.length() == 0) { + return null; + } + return s; + } + + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/parser/MetaData.java b/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/parser/MetaData.java new file mode 100644 index 0000000000..0bf673ecb0 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/parser/MetaData.java @@ -0,0 +1,38 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.catia.product.parser; + +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.InstanceTextAttribute; + +public class MetaData { + public String metaDataType; + public String title; + public String value; + public String units; + + public MetaData() { + } + + public InstanceAttribute toInstanceAttribute() { + return new InstanceTextAttribute(title, value, false); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/parser/Positioning.java b/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/parser/Positioning.java new file mode 100644 index 0000000000..3417ad38cd --- /dev/null +++ b/docdoku-server/docdoku-server-converter-catia-product/src/main/java/com/docdoku/server/converters/catia/product/parser/Positioning.java @@ -0,0 +1,46 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.catia.product.parser; + +import com.docdoku.core.product.CADInstance; + +public class Positioning { + + private double rx; + private double ry; + private double rz; + private double tx; + private double ty; + private double tz; + + public Positioning(double rx, double ry, double rz, double tx, double ty, double tz) { + this.rx = rx; + this.ry = ry; + this.rz = rz; + this.tx = tx; + this.ty = ty; + this.tz = tz; + } + + public CADInstance toCADInstance() { + return new CADInstance(tx, ty, tz, rx, ry, rz); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-catia-product/src/main/resources/com/docdoku/server/converters/catia/product/conf.properties b/docdoku-server/docdoku-server-converter-catia-product/src/main/resources/com/docdoku/server/converters/catia/product/conf.properties new file mode 100644 index 0000000000..7e95f49bfe --- /dev/null +++ b/docdoku-server/docdoku-server-converter-catia-product/src/main/resources/com/docdoku/server/converters/catia/product/conf.properties @@ -0,0 +1 @@ +catProductReader=/opt/catia-converter/catproduct/reader.sh \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-catia/pom.xml b/docdoku-server/docdoku-server-converter-catia/pom.xml new file mode 100644 index 0000000000..46198e73d6 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-catia/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-converter-catia + jar + docdoku-server-converter-catia CATPart Converter + + + com.docdoku + docdoku-server-ext + ${project.version} + jar + + + javax + javaee-api + 7.0 + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + ${project.artifactId} + + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-catia/src/main/java/com/docdoku/server/converters/catia/CatiaFileConverter.java b/docdoku-server/docdoku-server-converter-catia/src/main/java/com/docdoku/server/converters/catia/CatiaFileConverter.java new file mode 100644 index 0000000000..520b49dfba --- /dev/null +++ b/docdoku-server/docdoku-server-converter-catia/src/main/java/com/docdoku/server/converters/catia/CatiaFileConverter.java @@ -0,0 +1,34 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.catia; + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Retention(RUNTIME) +@Target({TYPE, METHOD, FIELD, PARAMETER}) +public @interface CatiaFileConverter { +} diff --git a/docdoku-server/docdoku-server-converter-catia/src/main/java/com/docdoku/server/converters/catia/CatiaFileConverterImpl.java b/docdoku-server/docdoku-server-converter-catia/src/main/java/com/docdoku/server/converters/catia/CatiaFileConverterImpl.java new file mode 100644 index 0000000000..d25276715f --- /dev/null +++ b/docdoku-server/docdoku-server-converter-catia/src/main/java/com/docdoku/server/converters/catia/CatiaFileConverterImpl.java @@ -0,0 +1,137 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.catia; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.server.InternalService; +import com.docdoku.server.converters.CADConverter; +import com.docdoku.server.converters.utils.ConversionResult; +import com.docdoku.server.converters.utils.ConverterUtils; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Properties; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + + +@CatiaFileConverter +public class CatiaFileConverterImpl implements CADConverter{ + + private static final String CONF_PROPERTIES="/com/docdoku/server/converters/catia/conf.properties"; + + private static final Properties CONF = new Properties(); + + @InternalService + @Inject + private IDataManagerLocal dataManager; + + private static final Logger LOGGER = Logger.getLogger(CatiaFileConverterImpl.class.getName()); + + static{ + try (InputStream inputStream = CatiaFileConverterImpl.class.getResourceAsStream(CONF_PROPERTIES)){ + CONF.load(inputStream); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + + @Override + public ConversionResult convert(PartIteration partToConvert, final BinaryResource cadFile, File tempDir) throws IOException, InterruptedException, UserNotActiveException, PartRevisionNotFoundException, WorkspaceNotFoundException, CreationException, UserNotFoundException, NotAllowedException, FileAlreadyExistsException, StorageException { + File tmpCadFile = new File(tempDir, cadFile.getName()); + File tmpDAEFile = new File(tempDir, UUID.randomUUID()+".dae"); + String catPartConverter = CONF.getProperty("catPartConverter"); + + File executable = new File(catPartConverter); + + if(!executable.exists()){ + LOGGER.log(Level.SEVERE, "Cannot convert file \""+cadFile.getName()+"\", \""+catPartConverter+"\" is not available"); + return null; + } + + if(!executable.canExecute()){ + LOGGER.log(Level.SEVERE, "Cannot convert file \""+cadFile.getName()+"\", \""+catPartConverter+"\" has no execution rights"); + return null; + } + + try(InputStream in = dataManager.getBinaryResourceInputStream(cadFile)) { + Files.copy(in, tmpCadFile.toPath()); + } catch (StorageException e) { + LOGGER.log(Level.WARNING, null, e); + throw new IOException(e); + } + + + String[] args = {"sh", catPartConverter, tmpCadFile.getAbsolutePath() , tmpDAEFile.getAbsolutePath()}; + + ProcessBuilder pb = new ProcessBuilder(args); + Process process1 = pb.start(); + + // Read buffers + String stdOutput1 = ConverterUtils.getOutput(process1.getInputStream()); + String errorOutput1 = ConverterUtils.getOutput(process1.getErrorStream()); + + LOGGER.info(stdOutput1); + + process1.waitFor(); + + // Convert to OBJ once converted to DAE + if (process1.exitValue() == 0 && tmpDAEFile.exists() && tmpDAEFile.length() > 0 ){ + + String assimp = CONF.getProperty("assimp"); + String convertedFileName = tempDir.getAbsolutePath() + "/" + UUID.randomUUID() + ".obj"; + String[] argsOBJ = {assimp, "export", tmpDAEFile.getAbsolutePath(), convertedFileName}; + pb = new ProcessBuilder(argsOBJ); + Process process2 = pb.start(); + + // Read buffers + String stdOutput2 = ConverterUtils.getOutput(process2.getInputStream()); + String errorOutput2 = ConverterUtils.getOutput(process2.getErrorStream()); + + LOGGER.info(stdOutput2); + + process2.waitFor(); + + if (process2.exitValue() == 0) { + return new ConversionResult( new File(convertedFileName)); + }else { + LOGGER.log(Level.SEVERE, "Cannot convert to obj : " + tmpCadFile.getAbsolutePath(), errorOutput2); + } + } else { + LOGGER.log(Level.SEVERE, "Cannot convert to dae : " + tmpCadFile.getAbsolutePath(), errorOutput1); + } + + return null; + } + + @Override + public boolean canConvertToOBJ(String cadFileExtension) { + return Arrays.asList("catpart").contains(cadFileExtension); + } +} diff --git a/docdoku-server/docdoku-server-converter-catia/src/main/resources/com/docdoku/server/converters/catia/conf.properties b/docdoku-server/docdoku-server-converter-catia/src/main/resources/com/docdoku/server/converters/catia/conf.properties new file mode 100644 index 0000000000..e7d953523e --- /dev/null +++ b/docdoku-server/docdoku-server-converter-catia/src/main/resources/com/docdoku/server/converters/catia/conf.properties @@ -0,0 +1,2 @@ +catPartConverter=/opt/catia-converter/convert.sh +assimp=assimp \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-dae/pom.xml b/docdoku-server/docdoku-server-converter-dae/pom.xml new file mode 100644 index 0000000000..0140cd0aa9 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-dae/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-converter-dae + jar + docdoku-server-converter-dae DAE Converter + + + com.docdoku + docdoku-server-ext + ${project.version} + jar + + + javax + javaee-api + 7.0 + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + ${project.artifactId} + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-dae/src/main/java/com/docdoku/server/converters/dae/DaeFileConverter.java b/docdoku-server/docdoku-server-converter-dae/src/main/java/com/docdoku/server/converters/dae/DaeFileConverter.java new file mode 100644 index 0000000000..ee9d8793bf --- /dev/null +++ b/docdoku-server/docdoku-server-converter-dae/src/main/java/com/docdoku/server/converters/dae/DaeFileConverter.java @@ -0,0 +1,36 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.dae; + + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Retention(RUNTIME) +@Target({TYPE, METHOD, FIELD, PARAMETER}) +public @interface DaeFileConverter { + +} diff --git a/docdoku-server/docdoku-server-converter-dae/src/main/java/com/docdoku/server/converters/dae/DaeFileConverterImpl.java b/docdoku-server/docdoku-server-converter-dae/src/main/java/com/docdoku/server/converters/dae/DaeFileConverterImpl.java new file mode 100644 index 0000000000..6de4a504d6 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-dae/src/main/java/com/docdoku/server/converters/dae/DaeFileConverterImpl.java @@ -0,0 +1,119 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.dae; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.server.InternalService; +import com.docdoku.server.converters.CADConverter; +import com.docdoku.server.converters.utils.ConversionResult; +import com.docdoku.server.converters.utils.ConverterUtils; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +@DaeFileConverter +public class DaeFileConverterImpl implements CADConverter { + + private static final String CONF_PROPERTIES = "/com/docdoku/server/converters/dae/conf.properties"; + private static final Properties CONF = new Properties(); + private static final Logger LOGGER = Logger.getLogger(DaeFileConverterImpl.class.getName()); + + @InternalService + @Inject + private IDataManagerLocal dataManager; + + static { + try (InputStream inputStream = DaeFileConverterImpl.class.getResourceAsStream(CONF_PROPERTIES)){ + CONF.load(inputStream); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + + @Override + public ConversionResult convert(PartIteration partToConvert, final BinaryResource cadFile, File tempDir) throws IOException, InterruptedException, UserNotActiveException, PartRevisionNotFoundException, WorkspaceNotFoundException, CreationException, UserNotFoundException, NotAllowedException, FileAlreadyExistsException, StorageException { + + String assimp = CONF.getProperty("assimp"); + + File executable = new File(assimp); + + if (!executable.exists()) { + LOGGER.log(Level.SEVERE, "Cannot convert file \"" + cadFile.getName() + "\", \"" + assimp + "\" is not available"); + return null; + } + + if (!executable.canExecute()) { + LOGGER.log(Level.SEVERE, "Cannot convert file \"" + cadFile.getName() + "\", \"" + assimp + "\" has no execution rights"); + return null; + } + + + File tmpCadFile = new File(tempDir, cadFile.getName().trim()); + try (InputStream in = dataManager.getBinaryResourceInputStream(cadFile)) { + Files.copy(in, tmpCadFile.toPath()); + } catch (StorageException e) { + LOGGER.log(Level.WARNING, null, e); + throw new IOException(e); + } + + UUID uuid = UUID.randomUUID(); + String convertedFileName = tempDir.getAbsolutePath() + "/" + uuid + ".obj"; + String convertedMtlFileName = tempDir.getAbsolutePath() + "/" + uuid + ".obj.mtl"; + + String[] args = {assimp, "export", tmpCadFile.getAbsolutePath(), convertedFileName}; + ProcessBuilder pb = new ProcessBuilder(args); + Process process = pb.start(); + + // Read buffers + String stdOutput = ConverterUtils.getOutput(process.getInputStream()); + String errorOutput = ConverterUtils.getOutput(process.getErrorStream()); + + LOGGER.info(stdOutput); + + process.waitFor(); + + if (process.exitValue() == 0) { + List materials = new ArrayList<>(); + materials.add(new File(convertedMtlFileName)); + return new ConversionResult(new File(convertedFileName), materials); + } + + LOGGER.log(Level.SEVERE, "Cannot convert to obj : " + tmpCadFile.getAbsolutePath(), errorOutput); + + return null; + + } + + @Override + public boolean canConvertToOBJ(String cadFileExtension) { + return Arrays.asList("dxf", "dae", "lwo", "x", "ac", "cob", "scn", "ms3d").contains(cadFileExtension); + } + +} diff --git a/docdoku-server/docdoku-server-converter-dae/src/main/resources/META-INF/beans.xml b/docdoku-server/docdoku-server-converter-dae/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..5d71326a5c --- /dev/null +++ b/docdoku-server/docdoku-server-converter-dae/src/main/resources/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-dae/src/main/resources/com/docdoku/server/converters/dae/conf.properties b/docdoku-server/docdoku-server-converter-dae/src/main/resources/com/docdoku/server/converters/dae/conf.properties new file mode 100644 index 0000000000..2a6c918763 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-dae/src/main/resources/com/docdoku/server/converters/dae/conf.properties @@ -0,0 +1 @@ +assimp=/usr/bin/assimp \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-ifc/pom.xml b/docdoku-server/docdoku-server-converter-ifc/pom.xml new file mode 100644 index 0000000000..a2ee89a4ec --- /dev/null +++ b/docdoku-server/docdoku-server-converter-ifc/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-converter-ifc + jar + docdoku-server-converter-ifc IFC Converter + + + com.docdoku + docdoku-server-ext + ${project.version} + jar + + + javax + javaee-api + 7.0 + provided + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + ${project.artifactId} + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-ifc/src/main/java/com/docdoku/server/converters/ifc/IFCFileConverter.java b/docdoku-server/docdoku-server-converter-ifc/src/main/java/com/docdoku/server/converters/ifc/IFCFileConverter.java new file mode 100644 index 0000000000..03db8c6cb5 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-ifc/src/main/java/com/docdoku/server/converters/ifc/IFCFileConverter.java @@ -0,0 +1,35 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.ifc; + + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Retention(RUNTIME) +@Target({TYPE, METHOD, FIELD, PARAMETER}) +public @interface IFCFileConverter{ +} diff --git a/docdoku-server/docdoku-server-converter-ifc/src/main/java/com/docdoku/server/converters/ifc/IFCFileConverterImpl.java b/docdoku-server/docdoku-server-converter-ifc/src/main/java/com/docdoku/server/converters/ifc/IFCFileConverterImpl.java new file mode 100644 index 0000000000..9be9274132 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-ifc/src/main/java/com/docdoku/server/converters/ifc/IFCFileConverterImpl.java @@ -0,0 +1,120 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.ifc; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.util.FileIO; +import com.docdoku.server.InternalService; +import com.docdoku.server.converters.CADConverter; +import com.docdoku.server.converters.utils.ConversionResult; +import com.docdoku.server.converters.utils.ConverterUtils; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + + +@IFCFileConverter +public class IFCFileConverterImpl implements CADConverter{ + + private static final String CONF_PROPERTIES="/com/docdoku/server/converters/ifc/conf.properties"; + private static final Properties CONF = new Properties(); + + @InternalService + @Inject + private IDataManagerLocal dataManager; + + private static final Logger LOGGER = Logger.getLogger(IFCFileConverterImpl.class.getName()); + + static{ + try (InputStream inputStream = IFCFileConverterImpl.class.getResourceAsStream(CONF_PROPERTIES)){ + CONF.load(inputStream); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + + @Override + public ConversionResult convert(PartIteration partToConvert, final BinaryResource cadFile, File tempDir) throws IOException, InterruptedException, UserNotActiveException, PartRevisionNotFoundException, WorkspaceNotFoundException, CreationException, UserNotFoundException, NotAllowedException, FileAlreadyExistsException, StorageException { + + + UUID uuid = UUID.randomUUID(); + String extension = FileIO.getExtension(cadFile.getName()); + File tmpCadFile = new File(tempDir, partToConvert.getKey() + "." + extension); + String convertedFileName = tempDir.getAbsolutePath() + "/" + uuid + ".obj" ; + String convertedMtl = tempDir.getAbsolutePath() + "/" + uuid + ".mtl" ; + String ifcConverter = CONF.getProperty("ifc_convert_path"); + + File executable = new File(ifcConverter); + + if(!executable.exists()){ + LOGGER.log(Level.SEVERE, "Cannot convert file \""+cadFile.getName()+"\", \""+ifcConverter+"\" is not available"); + return null; + } + + if(!executable.canExecute()){ + LOGGER.log(Level.SEVERE, "Cannot convert file \""+cadFile.getName()+"\", \""+ifcConverter+"\" has no execution rights"); + return null; + } + + try(InputStream in = dataManager.getBinaryResourceInputStream(cadFile)) { + Files.copy(in, tmpCadFile.toPath()); + } catch (StorageException e) { + LOGGER.log(Level.WARNING, null, e); + throw new IOException(e); + } + + String[] args = {ifcConverter, "--sew-shells", tmpCadFile.getAbsolutePath(), convertedFileName}; + ProcessBuilder pb = new ProcessBuilder(args); + Process process = pb.start(); + + // Read buffers + String stdOutput = ConverterUtils.getOutput(process.getInputStream()); + String errorOutput = ConverterUtils.getOutput(process.getErrorStream()); + + LOGGER.info(stdOutput); + + process.waitFor(); + + if(process.exitValue() == 0){ + List materials = new ArrayList<>(); + materials.add(new File(convertedMtl)); + return new ConversionResult(new File(convertedFileName), materials); + } + + LOGGER.log(Level.SEVERE, "Cannot convert to obj : " + tmpCadFile.getAbsolutePath(), errorOutput); + return null; + } + + @Override + public boolean canConvertToOBJ(String cadFileExtension) { + return Arrays.asList("ifc").contains(cadFileExtension); + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-ifc/src/main/resources/META-INF/beans.xml b/docdoku-server/docdoku-server-converter-ifc/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..5d71326a5c --- /dev/null +++ b/docdoku-server/docdoku-server-converter-ifc/src/main/resources/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-ifc/src/main/resources/com/docdoku/server/converters/ifc/conf.properties b/docdoku-server/docdoku-server-converter-ifc/src/main/resources/com/docdoku/server/converters/ifc/conf.properties new file mode 100644 index 0000000000..27b569bf12 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-ifc/src/main/resources/com/docdoku/server/converters/ifc/conf.properties @@ -0,0 +1 @@ +ifc_convert_path=/opt/ifcconvert/IfcConvert \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-obj/pom.xml b/docdoku-server/docdoku-server-converter-obj/pom.xml new file mode 100644 index 0000000000..a1aba32e6e --- /dev/null +++ b/docdoku-server/docdoku-server-converter-obj/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-converter-obj + jar + docdoku-server-converter-obj OBJ Converter + + + com.docdoku + docdoku-server-ext + ${project.version} + jar + + + javax + javaee-api + 7.0 + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + ${project.artifactId} + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-obj/src/main/java/com/docdoku/server/converters/obj/ObjFileConverter.java b/docdoku-server/docdoku-server-converter-obj/src/main/java/com/docdoku/server/converters/obj/ObjFileConverter.java new file mode 100644 index 0000000000..478c5adff6 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-obj/src/main/java/com/docdoku/server/converters/obj/ObjFileConverter.java @@ -0,0 +1,35 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.obj; + + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Retention(RUNTIME) +@Target({TYPE, METHOD, FIELD, PARAMETER}) +public @interface ObjFileConverter{ +} diff --git a/docdoku-server/docdoku-server-converter-obj/src/main/java/com/docdoku/server/converters/obj/ObjFileConverterImpl.java b/docdoku-server/docdoku-server-converter-obj/src/main/java/com/docdoku/server/converters/obj/ObjFileConverterImpl.java new file mode 100644 index 0000000000..73fb8c4a16 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-obj/src/main/java/com/docdoku/server/converters/obj/ObjFileConverterImpl.java @@ -0,0 +1,76 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.obj; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.StorageException; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.server.InternalService; +import com.docdoku.server.ServiceLocator; +import com.docdoku.server.converters.CADConverter; +import com.docdoku.server.converters.utils.ConversionResult; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + + +@ObjFileConverter +public class ObjFileConverterImpl implements CADConverter { + + + @InternalService + @Inject + private IDataManagerLocal dataManager; + + private static final Logger LOGGER = Logger.getLogger(ObjFileConverterImpl.class.getName()); + + + @Override + public ConversionResult convert(PartIteration partToConvert, final BinaryResource cadFile, File tempDir) throws Exception { + + File tmpCadFile = new File(tempDir, partToConvert.getKey() + ".obj"); + + try (InputStream in = dataManager.getBinaryResourceInputStream(cadFile)) { + Files.copy(in, tmpCadFile.toPath()); + } catch (StorageException e) { + LOGGER.log(Level.WARNING, null, e); + throw new IOException(e); + } + + return new ConversionResult(tmpCadFile); + } + + @Override + public boolean canConvertToOBJ(String cadFileExtension) { + return Arrays.asList("obj").contains(cadFileExtension); + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-obj/src/main/resources/META-INF/beans.xml b/docdoku-server/docdoku-server-converter-obj/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..5d71326a5c --- /dev/null +++ b/docdoku-server/docdoku-server-converter-obj/src/main/resources/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-step/pom.xml b/docdoku-server/docdoku-server-converter-step/pom.xml new file mode 100644 index 0000000000..9e8edd8d88 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-step/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-converter-step + jar + docdoku-server-converter-step STEP Converter + + + com.docdoku + docdoku-server-ext + ${project.version} + jar + + + javax + javaee-api + 7.0 + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + ${project.artifactId} + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-step/src/main/java/com/docdoku/server/converters/step/StepFileConverter.java b/docdoku-server/docdoku-server-converter-step/src/main/java/com/docdoku/server/converters/step/StepFileConverter.java new file mode 100644 index 0000000000..030de6fe35 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-step/src/main/java/com/docdoku/server/converters/step/StepFileConverter.java @@ -0,0 +1,35 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.step; + + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Retention(RUNTIME) +@Target({TYPE, METHOD, FIELD, PARAMETER}) +public @interface StepFileConverter { +} diff --git a/docdoku-server/docdoku-server-converter-step/src/main/java/com/docdoku/server/converters/step/StepFileConverterImpl.java b/docdoku-server/docdoku-server-converter-step/src/main/java/com/docdoku/server/converters/step/StepFileConverterImpl.java new file mode 100644 index 0000000000..d8f150a777 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-step/src/main/java/com/docdoku/server/converters/step/StepFileConverterImpl.java @@ -0,0 +1,115 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.step; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.util.FileIO; +import com.docdoku.server.InternalService; +import com.docdoku.server.converters.CADConverter; +import com.docdoku.server.converters.utils.ConversionResult; +import com.docdoku.server.converters.utils.ConverterUtils; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Properties; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + + +@StepFileConverter +public class StepFileConverterImpl implements CADConverter { + + private static final String PYTHON_SCRIPT_TO_OBJ = "/com/docdoku/server/converters/step/convert_step_obj.py"; + private static final String CONF_PROPERTIES = "/com/docdoku/server/converters/step/conf.properties"; + private static final Properties CONF = new Properties(); + private static final Logger LOGGER = Logger.getLogger(StepFileConverterImpl.class.getName()); + + @InternalService + @Inject + private IDataManagerLocal dataManager; + + static { + try (InputStream inputStream = StepFileConverterImpl.class.getResourceAsStream(CONF_PROPERTIES)){ + CONF.load(inputStream); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + + @Override + public ConversionResult convert(PartIteration partToConvert, final BinaryResource cadFile, File tempDir) throws IOException, InterruptedException, UserNotActiveException, PartRevisionNotFoundException, WorkspaceNotFoundException, CreationException, UserNotFoundException, NotAllowedException, FileAlreadyExistsException, StorageException { + + String extension = FileIO.getExtension(cadFile.getName()); + File tmpCadFile = new File(tempDir, partToConvert.getKey() + "." + extension); + File tmpOBJFile = new File(tempDir.getAbsolutePath() + "/" + UUID.randomUUID() + ".obj"); + + String pythonInterpreter = CONF.getProperty("pythonInterpreter"); + String freeCadLibPath = CONF.getProperty("freeCadLibPath"); + + File scriptToOBJ; + + try(InputStream scriptStream = StepFileConverterImpl.class.getResourceAsStream(PYTHON_SCRIPT_TO_OBJ)){ + scriptToOBJ = new File(tempDir,"python_script" + UUID.randomUUID() + ".py"); + Files.copy(scriptStream, scriptToOBJ.toPath()); + } + + try (InputStream in = dataManager.getBinaryResourceInputStream(cadFile)) { + Files.copy(in, tmpCadFile.toPath()); + } catch (StorageException e) { + LOGGER.log(Level.WARNING, null, e); + throw new IOException(e); + } + + String[] args = {pythonInterpreter, scriptToOBJ.getAbsolutePath(), "-l", freeCadLibPath, "-i", tmpCadFile.getAbsolutePath(), "-o", tmpOBJFile.getAbsolutePath()}; + ProcessBuilder pb = new ProcessBuilder(args); + + Process process = pb.start(); + + // Read buffers + String stdOutput = ConverterUtils.getOutput(process.getInputStream()); + String errorOutput = ConverterUtils.getOutput(process.getErrorStream()); + + LOGGER.info(stdOutput); + + process.waitFor(); + + if (process.exitValue() == 0) { + return new ConversionResult(tmpOBJFile); + } + + LOGGER.log(Level.SEVERE, "Cannot convert to obj : " + tmpCadFile.getAbsolutePath(), errorOutput); + return null; + } + + @Override + public boolean canConvertToOBJ(String cadFileExtension) { + return Arrays.asList("stp", "step", "igs", "iges").contains(cadFileExtension); + } + +} diff --git a/docdoku-server/docdoku-server-converter-step/src/main/resources/META-INF/beans.xml b/docdoku-server/docdoku-server-converter-step/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..5d71326a5c --- /dev/null +++ b/docdoku-server/docdoku-server-converter-step/src/main/resources/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-step/src/main/resources/com/docdoku/server/converters/step/conf.properties b/docdoku-server/docdoku-server-converter-step/src/main/resources/com/docdoku/server/converters/step/conf.properties new file mode 100644 index 0000000000..742473d848 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-step/src/main/resources/com/docdoku/server/converters/step/conf.properties @@ -0,0 +1,2 @@ +pythonInterpreter=/usr/bin/python +freeCadLibPath=/usr/lib/freecad/lib \ No newline at end of file diff --git a/docdoku-server/docdoku-server-converter-step/src/main/resources/com/docdoku/server/converters/step/convert_step_obj.py b/docdoku-server/docdoku-server-converter-step/src/main/resources/com/docdoku/server/converters/step/convert_step_obj.py new file mode 100644 index 0000000000..1962b59f62 --- /dev/null +++ b/docdoku-server/docdoku-server-converter-step/src/main/resources/com/docdoku/server/converters/step/convert_step_obj.py @@ -0,0 +1,30 @@ +from optparse import OptionParser; +import sys; +import os; + +parser = OptionParser(); + +parser.add_option("-l", "--freeCadLibPath", dest="l", help =""); +parser.add_option("-i", "--inputFile", dest="i", help =""); +parser.add_option("-o", "--outputFile", dest="o", help =""); + +(options, args) = parser.parse_args(); + +freeCadLibPath = options.l; +inputFile = options.i; +outputFile = options.o; + +sys.path.append(freeCadLibPath); + +import FreeCAD; +import Part, Mesh; + +def explodeOBJS(): + if not inputFile or not outputFile: + sys.exit(2); + + Part.open(inputFile); + Mesh.export(FreeCAD.ActiveDocument.Objects,outputFile); + +if __name__ == "__main__": + explodeOBJS(); diff --git a/docdoku-server/docdoku-server-ear/pom.xml b/docdoku-server/docdoku-server-ear/pom.xml new file mode 100644 index 0000000000..74f817790a --- /dev/null +++ b/docdoku-server/docdoku-server-ear/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-ear + ear + docdoku-server-ear Java EE 7 Assembly + + + + org.codehaus.mojo + properties-maven-plugin + 1.0-alpha-2 + + + initialize + + read-project-properties + + + + ../server.properties + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-ear-plugin + 2.9 + + 7 + /lib + true + + + com.docdoku + docdoku-server-ejb + docdoku-server-ejb.jar + + + com.docdoku + docdoku-server-rest + ${server.contextRoot} + docdoku-server-rest.war + + + + + + + ${project.artifactId} + + + + com.docdoku + docdoku-server-ejb + ${project.version} + ejb + + + com.docdoku + docdoku-server-rest + ${project.version} + war + + + diff --git a/docdoku-server/docdoku-server-ear/src/main/application/META-INF/sun-application.xml b/docdoku-server/docdoku-server-ear/src/main/application/META-INF/sun-application.xml new file mode 100644 index 0000000000..c63c921f53 --- /dev/null +++ b/docdoku-server/docdoku-server-ear/src/main/application/META-INF/sun-application.xml @@ -0,0 +1,13 @@ + + + + + users + users + + + admin + admin + + docdokuRealm + diff --git a/docdoku-server/docdoku-server-ejb/pom.xml b/docdoku-server/docdoku-server-ejb/pom.xml new file mode 100644 index 0000000000..52736fc3dc --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/pom.xml @@ -0,0 +1,191 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-ejb + ejb + docdoku-server-ejb Java EE 7 Core + + + javax + javaee-api + 7.0 + provided + + + com.docdoku + docdoku-common + ${project.version} + provided + + + com.docdoku + docdoku-server-converter-step + ${project.version} + jar + + + com.docdoku + docdoku-server-converter-dae + ${project.version} + jar + + + com.docdoku + docdoku-server-converter-all + ${project.version} + jar + + + com.docdoku + docdoku-server-converter-obj + ${project.version} + jar + + + com.docdoku + docdoku-server-converter-ifc + ${project.version} + jar + + + com.docdoku + docdoku-server-converter-catia + ${project.version} + jar + + + com.docdoku + docdoku-server-converter-catia-product + ${project.version} + jar + + + com.docdoku + docdoku-server-ext + ${project.version} + jar + + + com.docdoku + docdoku-server-scorm + ${project.version} + jar + + + com.docdoku + docdoku-server-viewer-image + ${project.version} + jar + + + com.docdoku + docdoku-server-office-doc + ${project.version} + jar + + + com.docdoku + docdoku-server-viewer-multimedia + ${project.version} + jar + + + commons-io + commons-io + 2.4 + + + org.apache.lucene + lucene-core + 4.6.0 + + + org.apache.poi + poi-contrib + 3.6 + + + org.apache.poi + poi-scratchpad + 3.12 + + + org.apache.poi + poi-ooxml + 3.12 + + + org.apache.poi + poi + 3.12 + + + com.github.spullara.mustache.java + compiler + 0.8.11 + + + + org.elasticsearch + elasticsearch + 1.0.0 + + + + junit + junit + 4.11 + test + + + org.mockito + mockito-all + 1.9.5 + test + + + + + + src/main/resources + + **/*.properties + + true + + + src/main/resources + false + + **/*.properties + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-ejb-plugin + 2.3 + + 3.1 + + + + ${project.artifactId} + + diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/AccountManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/AccountManagerBean.java new file mode 100644 index 0000000000..f55907515f --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/AccountManagerBean.java @@ -0,0 +1,161 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.Organization; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.gcm.GCMAccount; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IAccountManagerLocal; +import com.docdoku.core.services.IContextManagerLocal; +import com.docdoku.core.services.IMailerLocal; +import com.docdoku.server.dao.AccountDAO; +import com.docdoku.server.dao.GCMAccountDAO; +import com.docdoku.server.dao.OrganizationDAO; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.Date; +import java.util.Locale; +import java.util.logging.Logger; + +@DeclareRoles({UserGroupMapping.GUEST_PROXY_ROLE_ID, UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) +@Local(IAccountManagerLocal.class) +@Stateless(name = "AccountManagerBean") +public class AccountManagerBean implements IAccountManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IContextManagerLocal contextManager; + + @Inject + private IMailerLocal mailer; + + private static final Logger LOGGER = Logger.getLogger(AccountManagerBean.class.getName()); + + public AccountManagerBean() { + } + + @Override + public Account createAccount(String pLogin, String pName, String pEmail, String pLanguage, String pPassword, String pTimeZone) throws AccountAlreadyExistsException, CreationException { + Date now = new Date(); + Account account = new Account(pLogin, pName, pEmail, pLanguage, now, pTimeZone); + new AccountDAO(new Locale(pLanguage), em).createAccount(account, pPassword); + mailer.sendCredential(account); + return account; + } + + @Override + public Account getAccount(String pLogin) throws AccountNotFoundException { + return new AccountDAO(em).loadAccount(pLogin); + } + + public String getRole(String login) { + UserGroupMapping userGroupMapping = em.find(UserGroupMapping.class, login); + if(userGroupMapping == null){ + return null; + }else{ + return userGroupMapping.getGroupName(); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public Account updateAccount(String pName, String pEmail, String pLanguage, String pPassword, String pTimeZone) throws AccountNotFoundException { + AccountDAO accountDAO = new AccountDAO(new Locale(pLanguage), em); + Account account = accountDAO.loadAccount(contextManager.getCallerPrincipalLogin()); + account.setName(pName); + account.setEmail(pEmail); + account.setLanguage(pLanguage); + account.setTimeZone(pTimeZone); + if (pPassword != null) { + accountDAO.updateCredential(account.getLogin(), pPassword); + } + return account; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public Account checkAdmin(Organization pOrganization) throws AccessRightException, AccountNotFoundException { + Account account = new AccountDAO(em).loadAccount(contextManager.getCallerPrincipalLogin()); + + if (!contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID) && !pOrganization.getOwner().equals(account)) { + throw new AccessRightException(new Locale(account.getLanguage()), account); + } + + return account; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public Account checkAdmin(String pOrganizationName) + throws AccessRightException, AccountNotFoundException, OrganizationNotFoundException { + + Account account = new AccountDAO(em).loadAccount(contextManager.getCallerPrincipalLogin()); + Organization organization = new OrganizationDAO(em).loadOrganization(pOrganizationName); + + if (!contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID) && !organization.getOwner().equals(account)) { + throw new AccessRightException(new Locale(account.getLanguage()), account); + } + + return account; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void setGCMAccount(String gcmId) throws AccountNotFoundException, GCMAccountAlreadyExistsException, CreationException { + String callerLogin = contextManager.getCallerPrincipalLogin(); + Account account = getAccount(callerLogin); + GCMAccountDAO gcmAccountDAO = new GCMAccountDAO(em); + + try { + GCMAccount gcmAccount = gcmAccountDAO.loadGCMAccount(account); + gcmAccount.setGcmId(gcmId); + } catch (GCMAccountNotFoundException e) { + gcmAccountDAO.createGCMAccount(new GCMAccount(account, gcmId)); + } + + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void deleteGCMAccount() throws AccountNotFoundException, GCMAccountNotFoundException { + String callerLogin = contextManager.getCallerPrincipalLogin(); + Account account = getAccount(callerLogin); + GCMAccountDAO gcmAccountDAO = new GCMAccountDAO(new Locale(account.getLanguage()), em); + GCMAccount gcmAccount = gcmAccountDAO.loadGCMAccount(account); + gcmAccountDAO.deleteGCMAccount(gcmAccount); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public Account getMyAccount() throws AccountNotFoundException { + return getAccount(contextManager.getCallerPrincipalName()); + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ActivityCheckerInterceptor.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ActivityCheckerInterceptor.java new file mode 100644 index 0000000000..5c80d7c975 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ActivityCheckerInterceptor.java @@ -0,0 +1,78 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.common.User; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.exceptions.WorkflowNotFoundException; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.core.workflow.Task; +import com.docdoku.core.workflow.TaskKey; +import com.docdoku.core.workflow.Workflow; +import com.docdoku.server.dao.TaskDAO; +import com.docdoku.server.dao.WorkflowDAO; + +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; +import javax.persistence.EntityManager; +import java.util.Locale; + +@CheckActivity +@Interceptor +public class ActivityCheckerInterceptor { + + @Inject + private EntityManagerProducer emf; + + @Inject + private IUserManagerLocal userManager; + + @AroundInvoke + public Object check(InvocationContext ctx) throws Exception { + EntityManager em = emf.create(); + String workspaceId = (String) ctx.getParameters()[0]; + TaskKey taskKey = (TaskKey) ctx.getParameters()[1]; + + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Task task = new TaskDAO(new Locale(user.getLanguage()), em).loadTask(taskKey); + Workflow workflow = task.getActivity().getWorkflow(); + DocumentRevision docR = new WorkflowDAO(em).getDocumentTarget(workflow); + if(docR == null){ + throw new WorkflowNotFoundException(new Locale(user.getLanguage()),workflow.getId()); + } + DocumentIteration doc = docR.getLastIteration(); + if (em.createNamedQuery("findLogByDocumentAndUserAndEvent"). + setParameter("userLogin", user.getLogin()). + setParameter("documentWorkspaceId", doc.getWorkspaceId()). + setParameter("documentId", doc.getId()). + setParameter("documentVersion", doc.getVersion()). + setParameter("documentIteration", doc.getIteration()). + setParameter("event", "DOWNLOAD"). + getResultList().isEmpty()) { + throw new NotAllowedException(new Locale(user.getLanguage()), "NotAllowedException10"); + } + + return ctx.proceed(); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CADConvert.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CADConvert.java new file mode 100644 index 0000000000..ae2620465f --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CADConvert.java @@ -0,0 +1,36 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import javax.interceptor.InterceptorBinding; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Inherited +@InterceptorBinding +@Target({TYPE, METHOD}) +@Retention(RUNTIME) +public @interface CADConvert { +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CADConvertInterceptor.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CADConvertInterceptor.java new file mode 100644 index 0000000000..ae76c73dcc --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CADConvertInterceptor.java @@ -0,0 +1,103 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.exceptions.EntityConstraintException; +import com.docdoku.core.product.Conversion; +import com.docdoku.core.product.PartIterationKey; +import com.docdoku.core.services.IProductManagerLocal; + +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; +import java.util.logging.Level; +import java.util.logging.Logger; + +@CADConvert +@Interceptor +public class CADConvertInterceptor { + + @Inject + private IProductManagerLocal productService; + + private static final Logger LOGGER = Logger.getLogger(CADConvertInterceptor.class.getName()); + + @AroundInvoke + public Object createConversion(InvocationContext ctx) throws Exception { + + PartIterationKey partIterationKey = getPartIterationKey(ctx); + + if (partIterationKey != null) { + + Conversion existingConversion = productService.getConversion(partIterationKey); + + // Don't try to convert if any conversions pending + if(existingConversion != null && existingConversion.isPending()){ + LOGGER.log(Level.SEVERE, "Conversion already running for part iteration " + partIterationKey); + throw new EntityConstraintException(""); + } + + // Clean old non pending conversions + if(existingConversion != null){ + productService.removeConversion(partIterationKey); + } + + // Creates the new one + productService.createConversion(partIterationKey); + + } + + boolean succeed = false; + + try{ + // Run the conversion + Object proceed = ctx.proceed(); + succeed = true; + return proceed; + }catch(Exception e){ + return null; + }finally { + productService.endConversion(partIterationKey,succeed); + } + + } + + + private PartIterationKey getPartIterationKey(InvocationContext ctx){ + if (ctx.getParameters() != null && ctx.getParameters()[0] instanceof PartIterationKey ) { + return (PartIterationKey) ctx.getParameters()[0]; + }else{ + return null; + } + } + + /* + // Needs to fetch the object that was created in an other transaction + Conversion conversion = productService.getConversion(pPartIPK); + + if(conversion != null){ + conversion.setSucceed(succeed); + conversion.setPending(false); + conversion.setEndDate(new Date()); + } +*/ + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CallbackHandlerAdapter.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CallbackHandlerAdapter.java new file mode 100644 index 0000000000..30c8ad8098 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CallbackHandlerAdapter.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server; + +import javax.security.auth.callback.*; +import java.io.IOException; + +public class CallbackHandlerAdapter implements CallbackHandler { + + private String mLogin; + private char[] mPassword; + + public CallbackHandlerAdapter(String pLogin, char[] pPassword) { + mLogin = pLogin; + mPassword = pPassword; + } + + @Override + public void handle(Callback[] pCallbacks) + throws IOException, UnsupportedCallbackException { + for (Callback pCallback : pCallbacks) { + if (pCallback instanceof NameCallback) { + NameCallback nc = (NameCallback) pCallback; + nc.setName(mLogin); + } else if (pCallback instanceof PasswordCallback) { + PasswordCallback pc = (PasswordCallback) pCallback; + pc.setPassword(mPassword); + } else { + throw new UnsupportedCallbackException( + pCallback, + "Unrecognized callback"); + } + } + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CascadeActionManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CascadeActionManagerBean.java new file mode 100644 index 0000000000..fc52b04904 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CascadeActionManagerBean.java @@ -0,0 +1,114 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server; + +import com.docdoku.core.configuration.CascadeResult; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.ConfigurationItemKey; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.ICascadeActionManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.*; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Charles Fallourd on 10/02/16. + */ +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) +@Local(ICascadeActionManagerLocal.class) +@Stateless(name = "CascadeActionManagerBean") +public class CascadeActionManagerBean implements ICascadeActionManagerLocal { + + private static final Logger LOGGER = Logger.getLogger(ProductManagerBean.class.getName()); + + @EJB + private IProductManagerLocal productManager; + + //Every action should be transactional + @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) + @Override + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + public CascadeResult cascadeCheckout(ConfigurationItemKey configurationItemKey, String path) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException, PartUsageLinkNotFoundException, ConfigurationItemNotFoundException { + CascadeResult cascadeResult = new CascadeResult(); + + Set partRevisions = productManager.getWritablePartRevisionsFromPath(configurationItemKey,path); + + for(PartRevision pr : partRevisions) { + try { + productManager.checkOutPart(pr.getKey()); + cascadeResult.incSucceedAttempts(); + } catch (PartRevisionNotFoundException | AccessRightException | NotAllowedException | FileAlreadyExistsException | CreationException e) { + cascadeResult.incFailedAttempts(); + LOGGER.log(Level.SEVERE,null,e); + } + } + return cascadeResult; + } + + @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) + @Override + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + public CascadeResult cascadeUndocheckout(ConfigurationItemKey configurationItemKey, String path) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException, PartUsageLinkNotFoundException, ConfigurationItemNotFoundException { + CascadeResult cascadeResult = new CascadeResult(); + Set partRevisions = productManager.getWritablePartRevisionsFromPath(configurationItemKey, path); + for(PartRevision pr : partRevisions) { + try { + productManager.undoCheckOutPart(pr.getKey()); + cascadeResult.incSucceedAttempts(); + } catch (PartRevisionNotFoundException | AccessRightException | NotAllowedException e) { + cascadeResult.incFailedAttempts(); + LOGGER.log(Level.SEVERE,null,e); + } + } + return cascadeResult; + } + + @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) + @Override + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + public CascadeResult cascadeCheckin(ConfigurationItemKey configurationItemKey, String path, String iterationNote) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartMasterNotFoundException, EntityConstraintException, NotAllowedException, PartUsageLinkNotFoundException, ConfigurationItemNotFoundException { + + CascadeResult cascadeResult = new CascadeResult(); + Set partRevisions = productManager.getWritablePartRevisionsFromPath(configurationItemKey, path); + for(PartRevision pr : partRevisions) { + try { + // Set the iteration note only if param is set and part has no iteration note + if( (iterationNote != null && iterationNote.isEmpty()) && (null == pr.getLastIteration().getIterationNote() && pr.getLastIteration().getIterationNote().isEmpty())){ + productManager.updatePartIteration(pr.getLastIteration().getKey(), iterationNote, null, null, null, null, null, null, null); + } + + productManager.checkInPart(pr.getKey()); + cascadeResult.incSucceedAttempts(); + + } catch (PartRevisionNotFoundException | AccessRightException | NotAllowedException | ESServerException | ListOfValuesNotFoundException e) { + cascadeResult.incFailedAttempts(); + LOGGER.log(Level.SEVERE,null,e); + } + } + return cascadeResult; + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ChangeManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ChangeManagerBean.java new file mode 100644 index 0000000000..cdc315b274 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ChangeManagerBean.java @@ -0,0 +1,977 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.change.*; +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserKey; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentIterationKey; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartIterationKey; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IChangeManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.dao.*; +import com.docdoku.server.factory.ACLFactory; + +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Florent Garin + */ +@Local(IChangeManagerLocal.class) +@Stateless(name = "ChangeManagerBean") +public class ChangeManagerBean implements IChangeManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IUserManagerLocal userManager; + + private static final Logger LOGGER = Logger.getLogger(ChangeManagerBean.class.getName()); + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeIssue getChangeIssue(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeIssue changeIssue = new ChangeItemDAO(new Locale(user.getLanguage()), em).loadChangeIssue(pId); // Load the Change-Issue + checkChangeItemReadAccess(changeIssue, user); // Check if the user can access to the Change-Issue + return changeIssue; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getChangeIssues(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + List allChangeIssues = new ChangeItemDAO(new Locale(user.getLanguage()), + em).findAllChangeIssues(pWorkspaceId); // Load all the Change-Issues + List visibleChangeIssues = new ArrayList<>(); // Create a Change-Issues list to filter it + for (ChangeIssue changeIssue : allChangeIssues) { + try { + checkChangeItemReadAccess(changeIssue, user); // Check if the user can access to this Change-Issue + visibleChangeIssues.add(changeIssue); // Add the Change-Issue to the list + } catch (AccessRightException e) { + LOGGER.log(Level.FINEST, null, e); + } + } + return visibleChangeIssues; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getIssuesWithReference(String pWorkspaceId, String q, int maxResults) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + List allChangeIssues = new ChangeItemDAO(new Locale(user.getLanguage()), + em).findAllChangeIssuesWithReferenceLike(pWorkspaceId, q, maxResults);// Load all the Change-Issues matching this pattern + List visibleChangeIssues = new ArrayList<>(); // Create a Change-Issues list to filter it + for (ChangeIssue changeIssue : allChangeIssues) { + try { + checkChangeItemReadAccess(changeIssue, user); // Check if the user can access to this Change-Issue + visibleChangeIssues.add(changeIssue); // Add the Change-Issue to the list + } catch (AccessRightException e) { + LOGGER.log(Level.FINEST, null, e); + } + } + return visibleChangeIssues; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeIssue createChangeIssue(String pWorkspaceId, String name, String description, String initiator, ChangeItem.Priority priority, String assignee, ChangeItem.Category category) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(pWorkspaceId); // Check the write access to the workspace + User assigneeUser = null; + if (assignee != null && pWorkspaceId != null) { + assigneeUser = em.find(User.class, new UserKey(pWorkspaceId, assignee)); + } + ChangeIssue change = new ChangeIssue(name, // Create the Change-Issue => The Change-Issue's name + user.getWorkspace(), // The Change-Issue's workspace + user, // The Change-Issue's author + assigneeUser, // The Change-Issue's assignee + new Date(), // The Change-Issue's creation date + description, // The Change-Issue's description + priority, // The Change-Issue's priority + category, // The Change-Issue's category + initiator); // The Change-Issue's initiator + new ChangeItemDAO(new Locale(user.getLanguage()), em).createChangeItem(change); // Persist the Change-Issue + return change; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeIssue updateChangeIssue(int pId, String pWorkspaceId, String description, ChangeItem.Priority priority, String assignee, ChangeItem.Category category) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeIssue changeIssue = new ChangeItemDAO(new Locale(user.getLanguage()), em).loadChangeIssue(pId); // Load the Change-Issue + checkChangeItemWriteAccess(changeIssue, user); // Check the write access to the Change-Issue + changeIssue.setDescription(description); // Update the Change-Issue attributes + changeIssue.setPriority(priority); + changeIssue.setCategory(category); + changeIssue.setAssignee(em.find(User.class, new UserKey(pWorkspaceId, assignee))); + return changeIssue; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteChangeIssue(int pId) throws ChangeIssueNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, EntityConstraintException { + ChangeIssue changeIssue = new ChangeItemDAO(em).loadChangeIssue(pId); // Load the Change-Issue + User user = userManager.checkWorkspaceReadAccess(changeIssue.getWorkspaceId()); // Check the read access to the workspace + checkChangeItemWriteAccess(changeIssue, user); // Check the write access to the Change-Issue + + Locale locale = new Locale(user.getLanguage()); + ChangeItemDAO changeItemDAO = new ChangeItemDAO(locale, em); + + if (changeItemDAO.hasChangeRequestsLinked(changeIssue)) { + throw new EntityConstraintException(locale, "EntityConstraintException26"); + } + + new ChangeItemDAO(locale, em).deleteChangeItem(changeIssue); // Delete the Change-Issue + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeIssue saveChangeIssueAffectedDocuments(String pWorkspaceId, int pId, DocumentIterationKey[] pAffectedDocuments) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException, DocumentRevisionNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Locale userLocale = new Locale(user.getLanguage()); // Load the user Locale + ChangeIssue changeIssue = new ChangeItemDAO(userLocale, em).loadChangeIssue(pId); // Load the Change-Issue + checkChangeItemWriteAccess(changeIssue, user); // Check the write access to the Change-Issue + changeIssue.setAffectedDocuments(getDocumentIterations(pAffectedDocuments,userLocale)); // Update the Change-Issue's affected documents list + return changeIssue; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeIssue saveChangeIssueAffectedParts(String pWorkspaceId, int pId, PartIterationKey[] pAffectedParts) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Locale userLocale = new Locale(user.getLanguage()); // Load the user Locale + ChangeIssue changeIssue = new ChangeItemDAO(userLocale, em).loadChangeIssue(pId); // Load the Change-Issue + checkChangeItemWriteAccess(changeIssue, user); // Check the write access to the Change-Issue + + Set partIterations = new HashSet<>(); + PartRevisionDAO partRDAO = new PartRevisionDAO(userLocale, em); + for (PartIterationKey partKey : pAffectedParts) { + try { + partIterations.add(partRDAO.loadPartR(partKey.getPartRevision()).getIteration(partKey.getIteration())); // Add the part iteration to the Change-Issue + } catch (PartRevisionNotFoundException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + + changeIssue.setAffectedParts(partIterations); // Update the Change-Issue's affected part list + return changeIssue; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeIssue saveChangeIssueTags(String pWorkspaceId, int pId, String[] tagsLabel) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Locale userLocale = new Locale(user.getLanguage()); // Load the user Locale + ChangeIssue changeIssue = new ChangeItemDAO(userLocale, em).loadChangeIssue(pId); // Load the Change-Issue + checkChangeItemWriteAccess(changeIssue, user); // Check the write access to the Change-Issue + + Set tags = new HashSet<>(); // Create un Set of the tags + for (String label : tagsLabel) { + tags.add(new Tag(user.getWorkspace(), label)); + } + + TagDAO tagDAO = new TagDAO(userLocale, em); + List existingTags = Arrays.asList(tagDAO.findAllTags(user.getWorkspaceId())); // Load all the existing tags + + Set tagsToCreate = new HashSet<>(tags); + tagsToCreate.removeAll(existingTags); // Get the list of new tag in the tag list + + for (Tag t : tagsToCreate) { // Create the missing tag + try { + tagDAO.createTag(t); + } catch (CreationException | TagAlreadyExistsException ex) { + LOGGER.log(Level.SEVERE, null, ex); + } + } + + changeIssue.setTags(tags); // Update the Change-Issue's tag list + return changeIssue; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeIssue removeChangeIssueTag(String pWorkspaceId, int pId, String tagName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeItemDAO changeItemDAO = new ChangeItemDAO(new Locale(user.getLanguage()), em); + ChangeIssue changeIssue = changeItemDAO.loadChangeIssue(pId); // Load the Change-Issue + checkChangeItemWriteAccess(changeIssue, user); // Check the write access to the Change-Issue + return (ChangeIssue) changeItemDAO.removeTag(changeIssue, tagName); // Update the Change-Issue's tag list + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeRequest getChangeRequest(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeRequest changeRequest = new ChangeItemDAO(new Locale(user.getLanguage()), em).loadChangeRequest(pId); // Load the Change-Request + checkChangeItemReadAccess(changeRequest, user); // Check if the user can access to the Change-Request + return filterLinkedChangeIssues(changeRequest, user); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getChangeRequests(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + List allChangeRequests = new ChangeItemDAO(new Locale(user.getLanguage()), + em).findAllChangeRequests(pWorkspaceId); // Load all the Change-Requests + List visibleChangeRequests = new ArrayList<>(); // Create a Change-Requests list to filter it + + for (ChangeRequest changeRequest : allChangeRequests) { + try { + checkChangeItemReadAccess(changeRequest, user); // Check if the user can access to this Change-Request + visibleChangeRequests.add(filterLinkedChangeIssues(changeRequest, user)); // Add the Change-Request filtered to the list + } catch (AccessRightException e) { + LOGGER.log(Level.FINEST, null, e); + } + } + return visibleChangeRequests; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getRequestsWithReference(String pWorkspaceId, String q, int maxResults) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + List allChangeRequests = new ChangeItemDAO(new Locale(user.getLanguage()), + em).findAllChangeRequestsWithReferenceLike(pWorkspaceId, q, maxResults);// Load all the Change-Requests matching this pattern + List visibleChangeRequests = new ArrayList<>(); // Create a Change-Requests list to filter it + for (ChangeRequest changeRequest : allChangeRequests) { + try { + checkChangeItemReadAccess(changeRequest, user); // Check if the user can access to this Change-Request + visibleChangeRequests.add(filterLinkedChangeIssues(changeRequest, user)); // Add the Change-Request filtered to the list + } catch (AccessRightException e) { + LOGGER.log(Level.FINEST, null, e); + } + } + return visibleChangeRequests; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeRequest createChangeRequest(String pWorkspaceId, String name, String description, int milestoneId, ChangeItem.Priority priority, String assignee, ChangeItem.Category category) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(pWorkspaceId); // Check the write access to the workspace + User assigneeUser = null; + if (assignee != null && pWorkspaceId != null) { + assigneeUser = em.find(User.class, new UserKey(pWorkspaceId, assignee)); + } + ChangeRequest changeRequest = new ChangeRequest(name, // Create the Change-Request => The Change-Request's name + user.getWorkspace(), // The Change-Request's workspace + user, // The Change-Request's author + assigneeUser, // The Change-Request's Assignee + new Date(), // The Change-Request's Creation Date + description, // The Change-Request's Description + priority, // The Change-Request's Priority + category, // The Change-Request's Category + em.find(Milestone.class, milestoneId)); // The Change-Request's Milestone + new ChangeItemDAO(new Locale(user.getLanguage()), em).createChangeItem(changeRequest); // Persist the Change-Request + return changeRequest; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeRequest updateChangeRequest(int pId, String pWorkspaceId, String description, int milestoneId, ChangeItem.Priority priority, String assignee, ChangeItem.Category category) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeRequest changeRequest = new ChangeItemDAO(new Locale(user.getLanguage()), em).loadChangeRequest(pId); // Load the Change-Request + checkChangeItemWriteAccess(changeRequest, user); // Check the write access to the Change-Request + changeRequest.setDescription(description); // Update the Change-Request attributes + changeRequest.setPriority(priority); + changeRequest.setCategory(category); + changeRequest.setAssignee(em.find(User.class, new UserKey(pWorkspaceId, assignee))); + changeRequest.setMilestone(em.find(Milestone.class, milestoneId)); + return changeRequest; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteChangeRequest(String pWorkspaceId, int pId) throws ChangeRequestNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, EntityConstraintException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + Locale userLocale = new Locale(user.getLanguage()); + + ChangeItemDAO changeItemDAO = new ChangeItemDAO(userLocale, em); + ChangeRequest changeRequest = changeItemDAO.loadChangeRequest(pId); + + if (changeItemDAO.hasChangeOrdersLinked(changeRequest)) { + throw new EntityConstraintException(userLocale, "EntityConstraintException10"); + } + + checkChangeItemWriteAccess(changeRequest, user); + changeItemDAO.deleteChangeItem(changeRequest); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeRequest saveChangeRequestAffectedDocuments(String pWorkspaceId, int pId, DocumentIterationKey[] pAffectedDocuments) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException, DocumentRevisionNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Locale userLocale = new Locale(user.getLanguage()); // Load the user Locale + ChangeRequest changeRequest = new ChangeItemDAO(userLocale, em).loadChangeRequest(pId); // Load the Change-Request + checkChangeItemWriteAccess(changeRequest, user); // Check the write access to the Change-Request + changeRequest.setAffectedDocuments(getDocumentIterations(pAffectedDocuments,userLocale)); + return changeRequest; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeRequest saveChangeRequestAffectedParts(String pWorkspaceId, int pId, PartIterationKey[] pAffectedParts) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Locale userLocale = new Locale(user.getLanguage()); // Load the user Locale + ChangeRequest changeRequest = new ChangeItemDAO(userLocale, em).loadChangeRequest(pId); // Load the Change-Request + checkChangeItemWriteAccess(changeRequest, user); // Check the write access to the Change-Request + + Set partIterations = new HashSet<>(); + PartRevisionDAO partRDAO = new PartRevisionDAO(userLocale, em); + for (PartIterationKey partKey : pAffectedParts) { + try { + partIterations.add(partRDAO.loadPartR(partKey.getPartRevision()).getIteration(partKey.getIteration())); // Add the part iteration to the Change-Request + } catch (PartRevisionNotFoundException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + changeRequest.setAffectedParts(partIterations); + return changeRequest; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeRequest saveChangeRequestAffectedIssues(String pWorkspaceId, int pId, int[] pLinkIds) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeItemDAO changeItemDAO = new ChangeItemDAO(new Locale(user.getLanguage()), em); + ChangeRequest changeRequest = changeItemDAO.loadChangeRequest(pId); // Load the Change-Request + checkChangeItemWriteAccess(changeRequest, user); // Check the write access to the Change-Request + + Set changeIssues = new HashSet<>(); + for (int linkId : pLinkIds) { + try { + changeIssues.add(changeItemDAO.loadChangeIssue(linkId)); // Add the issue to the Change-Request + } catch (ChangeIssueNotFoundException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + changeRequest.setAddressedChangeIssues(changeIssues); + return changeRequest; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeRequest saveChangeRequestTags(String pWorkspaceId, int pId, String[] tagsLabel) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Locale userLocale = new Locale(user.getLanguage()); // Load the user Locale + ChangeRequest changeRequest = new ChangeItemDAO(userLocale, em).loadChangeRequest(pId); // Load the Change-Request + checkChangeItemWriteAccess(changeRequest, user); // Check the write access to the Change-Request + + Set tags = new HashSet<>(); // Create un Set of the tags + for (String label : tagsLabel) { + tags.add(new Tag(user.getWorkspace(), label)); + } + + TagDAO tagDAO = new TagDAO(userLocale, em); + List existingTags = Arrays.asList(tagDAO.findAllTags(user.getWorkspaceId())); // Load all the existing tags + + Set tagsToCreate = new HashSet<>(tags); // Get the list of new tag in the tag list + tagsToCreate.removeAll(existingTags); + + for (Tag t : tagsToCreate) { // Create the missing tag + try { + tagDAO.createTag(t); + } catch (CreationException | TagAlreadyExistsException ex) { + LOGGER.log(Level.SEVERE, null, ex); + } + } + + changeRequest.setTags(tags); // Update the Change-Request's tag list + return changeRequest; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeRequest removeChangeRequestTag(String pWorkspaceId, int pId, String tagName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeItemDAO changeItemDAO = new ChangeItemDAO(new Locale(user.getLanguage()), em); + ChangeIssue changeRequest = changeItemDAO.loadChangeIssue(pId); // Load the Change-Request + checkChangeItemWriteAccess(changeRequest, user); // Check the write access to the Change-Request + return (ChangeRequest) changeItemDAO.removeTag(changeRequest, tagName); // Update the Change-Request's tag list + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeOrder getChangeOrder(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeOrder changeOrder = new ChangeItemDAO(new Locale(user.getLanguage()), em).loadChangeOrder(pId); // Load the Change-Order + checkChangeItemReadAccess(changeOrder, user); // Check if the user can access to the Change-Order + return filterLinkedChangeRequests(changeOrder, user); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getChangeOrders(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + List allChangeOrders = new ChangeItemDAO(new Locale(user.getLanguage()), + em).findAllChangeOrders(pWorkspaceId); // Load all the Change-Orders + List visibleChangeOrders = new ArrayList<>(); // Create a Change-Orders list to filter it + for (ChangeOrder changeOrder : allChangeOrders) { + try { + checkChangeItemReadAccess(changeOrder, user); // Check if the user can access to this Change-Order + visibleChangeOrders.add(filterLinkedChangeRequests(changeOrder, user)); // Add the Change-Order filtered to the list + } catch (AccessRightException e) { + LOGGER.log(Level.FINEST, null, e); + } + } + return visibleChangeOrders; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeOrder createChangeOrder(String pWorkspaceId, String name, String description, int milestoneId, ChangeItem.Priority priority, String assignee, ChangeItem.Category category) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(pWorkspaceId); // Check the write access to the workspace + User assigneeUser = null; + if (assignee != null && pWorkspaceId != null) { + assigneeUser = em.find(User.class, new UserKey(pWorkspaceId, assignee)); + } + ChangeOrder changeOrder = new ChangeOrder(name, // Create the Change-Order => The Change-Order's name + user.getWorkspace(), // The Change-Order's workspace + user, // The Change-Order's author + assigneeUser, // The Change-Order's Assignee + new Date(), // The Change-Order's Creation Date + description, // The Change-Order's Description + priority, // The Change-Order's Priority + category, // The Change-Order's Category + em.find(Milestone.class, milestoneId)); // The Change-Order's Milestone + new ChangeItemDAO(new Locale(user.getLanguage()), em).createChangeItem(changeOrder); // Persist the Change-Order + return changeOrder; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeOrder updateChangeOrder(int pId, String pWorkspaceId, String description, int milestoneId, ChangeItem.Priority priority, String assignee, ChangeItem.Category category) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeOrder changeOrder = new ChangeItemDAO(new Locale(user.getLanguage()), em).loadChangeOrder(pId); // Load the Change-Order + checkChangeItemWriteAccess(changeOrder, user); // Check the write access to the Change-Order + changeOrder.setDescription(description); // Update the Change-Order attributes + changeOrder.setPriority(priority); + changeOrder.setCategory(category); + changeOrder.setAssignee(em.find(User.class, new UserKey(pWorkspaceId, assignee))); + changeOrder.setMilestone(em.find(Milestone.class, milestoneId)); + return changeOrder; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteChangeOrder(int pId) throws ChangeOrderNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException { + ChangeOrder changeOrder = new ChangeItemDAO(em).loadChangeOrder(pId); // Load the Change-Order + User user = userManager.checkWorkspaceReadAccess(changeOrder.getWorkspaceId()); // Check the read access to the workspace + checkChangeItemWriteAccess(changeOrder, user); // Check the write access to the Change-Order + new ChangeItemDAO(new Locale(user.getLanguage()), em).deleteChangeItem(changeOrder); // Delete the Change-Order + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeOrder saveChangeOrderAffectedDocuments(String pWorkspaceId, int pId, DocumentIterationKey[] pAffectedDocuments) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException, DocumentRevisionNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Locale userLocale = new Locale(user.getLanguage()); // Load the user Locale + ChangeOrder changeOrder = new ChangeItemDAO(userLocale, em).loadChangeOrder(pId); // Load the Change-Order + checkChangeItemWriteAccess(changeOrder, user); // Check the write access to the Change-Order + changeOrder.setAffectedDocuments(getDocumentIterations(pAffectedDocuments,userLocale)); + return changeOrder; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeOrder saveChangeOrderAffectedParts(String pWorkspaceId, int pId, PartIterationKey[] pAffectedParts) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Locale userLocale = new Locale(user.getLanguage()); // Load the user Locale + ChangeOrder changeOrder = new ChangeItemDAO(userLocale, em).loadChangeOrder(pId); // Load the Change-Order + checkChangeItemWriteAccess(changeOrder, user); // Check the write access to the Change-Order + + Set partIterations = new HashSet<>(); + PartRevisionDAO partRDAO = new PartRevisionDAO(userLocale, em); + for (PartIterationKey partKey : pAffectedParts) { + try { + partIterations.add(partRDAO.loadPartR(partKey.getPartRevision()).getIteration(partKey.getIteration())); // Add the part iteration to the Change-Order + } catch (PartRevisionNotFoundException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + changeOrder.setAffectedParts(partIterations); + return changeOrder; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeOrder saveChangeOrderAffectedRequests(String pWorkspaceId, int pId, int[] pLinkIds) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeItemDAO changeItemDAO = new ChangeItemDAO(new Locale(user.getLanguage()), em); + ChangeOrder changeOrder = changeItemDAO.loadChangeOrder(pId); // Load the Change-Order + checkChangeItemWriteAccess(changeOrder, user); // Check the write access to the Change-Order + + Set changeRequests = new HashSet<>(); + for (int linkId : pLinkIds) { + try { + changeRequests.add(changeItemDAO.loadChangeRequest(linkId)); // Add the request to the Change-Order + } catch (ChangeRequestNotFoundException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + changeOrder.setAddressedChangeRequests(changeRequests); + return changeOrder; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeOrder saveChangeOrderTags(String pWorkspaceId, int pId, String[] tagsLabel) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Locale userLocale = new Locale(user.getLanguage()); // Load the user Locale + ChangeOrder changeOrder = new ChangeItemDAO(userLocale, em).loadChangeOrder(pId); // Load the Change-Order + checkChangeItemWriteAccess(changeOrder, user); // Check the write access to the Change-Order + + Set tags = new HashSet<>(); // Create un Set of the tags + for (String label : tagsLabel) { + tags.add(new Tag(user.getWorkspace(), label)); + } + + TagDAO tagDAO = new TagDAO(userLocale, em); + List existingTags = Arrays.asList(tagDAO.findAllTags(user.getWorkspaceId())); // Load all the existing tags + + Set tagsToCreate = new HashSet<>(tags); // Get the list of new tag in the tag list + tagsToCreate.removeAll(existingTags); + + for (Tag t : tagsToCreate) { + try { + tagDAO.createTag(t); + } catch (CreationException | TagAlreadyExistsException ex) { + LOGGER.log(Level.SEVERE, null, ex); + } + } + changeOrder.setTags(tags); // Update the Change-Order's tag list + return changeOrder; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ChangeOrder removeChangeOrderTag(String pWorkspaceId, int pId, String tagName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeItemDAO changeItemDAO = new ChangeItemDAO(new Locale(user.getLanguage()), em); + ChangeIssue changeOrder = changeItemDAO.loadChangeIssue(pId); // Load the Change-Order + checkChangeItemWriteAccess(changeOrder, user); // Check the write access to the Change-Order + return (ChangeOrder) changeItemDAO.removeTag(changeOrder, tagName); // Update the Change-Order's tag list + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Milestone getMilestone(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, MilestoneNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Milestone milestone = new MilestoneDAO(new Locale(user.getLanguage()), em).loadMilestone(pId); // Load the Milestone + checkMilestoneReadAccess(milestone, user); // Check if the user can access to the Milestone + return milestone; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Milestone getMilestoneByTitle(String pWorkspaceId, String pTitle) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, MilestoneNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Milestone milestone = new MilestoneDAO(new Locale(user.getLanguage()), em).loadMilestone(pTitle, pWorkspaceId); // Load the Milestone + checkMilestoneReadAccess(milestone, user); // Check if the user can access to the Milestone + return milestone; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getMilestones(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + List allMilestones = new MilestoneDAO(new Locale(user.getLanguage()), + em).findAllMilestone(pWorkspaceId); // Load all the Milestones + List visibleMilestones = new ArrayList<>(allMilestones); // Duplicate the Milestones list to filter it + for (Milestone milestone : allMilestones) { + try { + checkMilestoneReadAccess(milestone, user); // Check if the user can access to this Milestone + } catch (AccessRightException e) { + visibleMilestones.remove(milestone); // If access is deny remove it from the result + LOGGER.log(Level.FINEST, null, e); + } + } + return visibleMilestones; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Milestone createMilestone(String pWorkspaceId, String title, String description, Date dueDate) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, MilestoneAlreadyExistsException { + User user = userManager.checkWorkspaceWriteAccess(pWorkspaceId); // Check the write access to the workspace + Milestone milestone = new Milestone(title, // Create the Milestone => The Milestone's title + dueDate, // The Milestone's due date + description, // The Milestone's description + user.getWorkspace()); // The Milestone's workspace + new MilestoneDAO(new Locale(user.getLanguage()), em).createMilestone(milestone); // Persist the Milestone + return milestone; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Milestone updateMilestone(int pId, String pWorkspaceId, String title, String description, Date dueDate) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, MilestoneNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Milestone milestone = new MilestoneDAO(new Locale(user.getLanguage()), em).loadMilestone(pId); // Load the Milestone + checkMilestoneWriteAccess(milestone, user); // Check the write access to the milestone + milestone.setTitle(title); // Update the Milestone + milestone.setDescription(description); + milestone.setDueDate(dueDate); + return milestone; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteMilestone(String pWorkspaceId, int pId) throws MilestoneNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, EntityConstraintException { + + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + Locale userLocale = new Locale(user.getLanguage()); + MilestoneDAO milestoneDAO = new MilestoneDAO(userLocale, em); + + Milestone milestone = milestoneDAO.loadMilestone(pId); + + checkMilestoneWriteAccess(milestone, user); + + int numberOfOrders = milestoneDAO.getNumberOfOrders(milestone.getId(), milestone.getWorkspaceId()); + + if (numberOfOrders > 0) { + throw new EntityConstraintException(userLocale, "EntityConstraintException8"); + } + + int numberOfRequests = milestoneDAO.getNumberOfRequests(milestone.getId(), milestone.getWorkspaceId()); + + if (numberOfRequests > 0) { + throw new EntityConstraintException(userLocale, "EntityConstraintException9"); + } + + milestoneDAO.deleteMilestone(milestone); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getChangeRequestsByMilestone(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, MilestoneNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Milestone milestone = new MilestoneDAO(new Locale(user.getLanguage()), em).loadMilestone(pId); // Load the Milestone + checkMilestoneReadAccess(milestone, user); // Check if the user can access to the Milestone + List affectedRequests = new MilestoneDAO(new Locale(user.getLanguage()), + em).getAllRequests(pId, pWorkspaceId); // Load all affected request + List visibleChangeRequests = new ArrayList<>(); // Create a Change-Requests list to filter it + for (ChangeRequest changeRequest : affectedRequests) { + try { + checkChangeItemReadAccess(changeRequest, user); // Check if the user can access to this Change-Request + visibleChangeRequests.add(filterLinkedChangeIssues(changeRequest, user)); // Add the Change-Request filtered to the list + } catch (AccessRightException e) { + LOGGER.log(Level.FINEST, null, e); + } + } + return visibleChangeRequests; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getChangeOrdersByMilestone(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, MilestoneNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Milestone milestone = new MilestoneDAO(new Locale(user.getLanguage()), em).loadMilestone(pId); // Load the Milestone + checkMilestoneReadAccess(milestone, user); // Check if the user can access to the Milestone + List affectedOrders = new MilestoneDAO(new Locale(user.getLanguage()), + em).getAllOrders(pId, pWorkspaceId); // Load all affected request + List visibleChangeOrders = new ArrayList<>(); // Create a Change-Orders list to filter it + for (ChangeOrder changeOrder : affectedOrders) { + try { + checkChangeItemReadAccess(changeOrder, user); // Check if the user can access to this Change-Order + visibleChangeOrders.add(filterLinkedChangeRequests(changeOrder, user)); // Add the Change-Order filtered to the list + } catch (AccessRightException e) { + LOGGER.log(Level.FINEST, null, e); + } + } + return visibleChangeOrders; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public int getNumberOfRequestByMilestone(String pWorkspaceId, int milestoneId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + return new MilestoneDAO(new Locale(user.getLanguage()), em).getNumberOfRequests(milestoneId, pWorkspaceId); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public int getNumberOfOrderByMilestone(String pWorkspaceId, int milestoneId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + return new MilestoneDAO(new Locale(user.getLanguage()), em).getNumberOfOrders(milestoneId, pWorkspaceId); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void updateACLForChangeIssue(String pWorkspaceId, int pId, Map pUserEntries, Map pGroupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeIssue changeIssue = new ChangeItemDAO(new Locale(user.getLanguage()), em).loadChangeIssue(pId); // Load the change item + checkChangeItemGrantAccess(changeIssue, user); // Check the grant access to the change item + + updateACLForChangeItem(pWorkspaceId, changeIssue, pUserEntries, pGroupEntries); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void updateACLForChangeRequest(String pWorkspaceId, int pId, Map pUserEntries, Map pGroupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeRequest changeRequest = new ChangeItemDAO(new Locale(user.getLanguage()), em).loadChangeRequest(pId); // Load the change item + checkChangeItemGrantAccess(changeRequest, user); // Check the grant access to the change item + + updateACLForChangeItem(pWorkspaceId, changeRequest, pUserEntries, pGroupEntries); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void updateACLForChangeOrder(String pWorkspaceId, int pId, Map pUserEntries, Map pGroupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeOrder changeOrder = new ChangeItemDAO(new Locale(user.getLanguage()), em).loadChangeOrder(pId); // Load the change item + checkChangeItemGrantAccess(changeOrder, user); // Check the grant access to the change item + + updateACLForChangeItem(pWorkspaceId, changeOrder, pUserEntries, pGroupEntries); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void updateACLForMilestone(String pWorkspaceId, int pId, Map pUserEntries, Map pGroupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, MilestoneNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Milestone milestone = new MilestoneDAO(new Locale(user.getLanguage()), em).loadMilestone(pId); // Load the milestone + checkMilestoneWriteAccess(milestone, user); // Check the grant access to the milestone + ACLFactory aclFactory = new ACLFactory(em); + if (milestone.getACL() == null) { + // Check if already a ACL Rule + ACL acl = aclFactory.createACL(pWorkspaceId, pUserEntries, pGroupEntries); + milestone.setACL(acl); + } else { // Else change existing ACL Rule + ACL acl = milestone.getACL(); + aclFactory.updateACL(pWorkspaceId,acl, pUserEntries, pGroupEntries); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void removeACLFromChangeIssue(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeIssueNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeIssue changeIssue = new ChangeItemDAO(new Locale(user.getLanguage()), em).loadChangeIssue(pId); // Load the change item + checkChangeItemGrantAccess(changeIssue, user); // Check the grant access to the change item + + removeACLFromChangeItem(changeIssue); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void removeACLFromChangeRequest(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeRequestNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeRequest changeRequest = new ChangeItemDAO(new Locale(user.getLanguage()), em).loadChangeRequest(pId); // Load the change item + checkChangeItemGrantAccess(changeRequest, user); // Check the grant access to the change item + + removeACLFromChangeItem(changeRequest); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void removeACLFromChangeOrder(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ChangeOrderNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + ChangeOrder changeOrder = new ChangeItemDAO(new Locale(user.getLanguage()), em).loadChangeOrder(pId); // Load the change item + checkChangeItemGrantAccess(changeOrder, user); // Check the grant access to the change item + + removeACLFromChangeItem(changeOrder); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void removeACLFromMilestone(String pWorkspaceId, int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, MilestoneNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); // Check the read access to the workspace + Milestone milestone = new MilestoneDAO(new Locale(user.getLanguage()), em).loadMilestone(pId); // Load the milestone + checkMilestoneWriteAccess(milestone, user); // Check the grant access to the milestone + + ACL acl = milestone.getACL(); + if (acl != null) { + new ACLDAO(em).removeACLEntries(acl); + milestone.setACL(null); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public boolean isChangeItemWritable(ChangeItem pChangeItem) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pChangeItem.getWorkspaceId()); // Check the read access to the workspace + try { + checkChangeItemWriteAccess(pChangeItem, user); // Try to check if the user can write the Change-Item + return true; // Set the writable attribute to XHR + } catch (AccessRightException e) { + LOGGER.log(Level.FINEST, null, e); + return false; + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public boolean isMilestoneWritable(Milestone pMilestone) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pMilestone.getWorkspaceId()); // Check the read access to the workspace + try { + checkMilestoneWriteAccess(pMilestone, user); // Try to check if the user can write the Milestone + return true; + } catch (AccessRightException e) { + LOGGER.log(Level.FINEST, null, e); + return false; + } + } + + private void updateACLForChangeItem(String pWorkspaceId, ChangeItem changeItem, Map pUserEntries, Map pGroupEntries) { + ACLFactory aclFactory = new ACLFactory(em); + if (changeItem.getACL() == null) { // Check if already a ACL Rule + ACL acl = aclFactory.createACL(pWorkspaceId, pUserEntries, pGroupEntries); + changeItem.setACL(acl); + } else { // Else change existing ACL Rule + + aclFactory.updateACL(pWorkspaceId,changeItem.getACL(), pUserEntries, pGroupEntries); + } + } + + private void removeACLFromChangeItem(ChangeItem changeItem) { + ACL acl = changeItem.getACL(); + if (acl != null) { + new ACLDAO(em).removeACLEntries(acl); + changeItem.setACL(null); + } + } + + private User checkChangeItemGrantAccess(ChangeItem pChangeItem, User pUser) throws AccessRightException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + if (pUser.isAdministrator()) { // Check if it is the workspace's administrator + return pUser; + } else if (pUser.getLogin().equals(pChangeItem.getAuthor().getLogin())) { // Check if it the change item owner + checkChangeItemWriteAccess(pChangeItem, pUser); // Check if the owner have right access to the change item + return pUser; + } else { + throw new AccessRightException(new Locale(pUser.getLanguage()), pUser); + } + } + + private User checkChangeItemWriteAccess(ChangeItem pChangeItem, User pUser) throws AccessRightException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + if (pUser.isAdministrator()) { // Check if it is the workspace's administrator + return pUser; + } + if (pChangeItem.getACL() == null) { // Check if the item haven't ACL + return userManager.checkWorkspaceWriteAccess(pChangeItem.getWorkspaceId()); + } else if (pChangeItem.getACL().hasWriteAccess(pUser)) { // Check if there is a write access + return pUser; + } else { // Else throw a AccessRightException + throw new AccessRightException(new Locale(pUser.getLanguage()), pUser); + } + } + + private User checkChangeItemReadAccess(ChangeItem pChangeItem, User pUser) throws AccessRightException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + if (pUser.isAdministrator() || // Check if it is the workspace's administrator + pChangeItem.getACL() == null || // Check if the item haven't ACL + pChangeItem.getACL().hasReadAccess(pUser)) { // Check if ACL grant read access + return pUser; + } else { // Else throw a AccessRightException + throw new AccessRightException(new Locale(pUser.getLanguage()), pUser); + } + } + + private User checkMilestoneWriteAccess(Milestone pMilestone, User pUser) throws AccessRightException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + if (pUser.isAdministrator()) { // Check if it is the workspace's administrator + return pUser; + } + if (pMilestone.getACL() == null) { // Check if the item haven't ACL + return userManager.checkWorkspaceWriteAccess(pMilestone.getWorkspaceId()); + } else if (pMilestone.getACL().hasWriteAccess(pUser)) { // Check if there is a write access + return pUser; + } else { // Else throw a AccessRightException + throw new AccessRightException(new Locale(pUser.getLanguage()), pUser); + } + } + + private User checkMilestoneReadAccess(Milestone pMilestone, User pUser) throws AccessRightException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + if (pUser.isAdministrator() || // Check if it is the workspace's administrator + pMilestone.getACL() == null || // Check if the item haven't ACL + pMilestone.getACL().hasReadAccess(pUser)) { // Check if ACL grant read access + return pUser; + } else { // Else throw a AccessRightException + throw new AccessRightException(new Locale(pUser.getLanguage()), pUser); + } + } + + private ChangeRequest filterLinkedChangeIssues(ChangeRequest changeRequest, User user) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + em.detach(changeRequest); + Set addressedChangeIssues = changeRequest.getAddressedChangeIssues(); + Set visibleChangeIssues = new HashSet<>(); // Create a Change-Issues list to filter it + for (ChangeIssue changeIssue : addressedChangeIssues) { + try { + checkChangeItemReadAccess(changeIssue, user); // Check if the user can access to this Change-Issue + visibleChangeIssues.add(changeIssue); // Add the Change-Issue to the list + } catch (AccessRightException e) { + LOGGER.log(Level.FINEST, null, e); + } + } + changeRequest.setAddressedChangeIssues(visibleChangeIssues); + return changeRequest; + } + + private ChangeOrder filterLinkedChangeRequests(ChangeOrder changeOrder, User user) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + em.detach(changeOrder); + Set allChangeRequests = changeOrder.getAddressedChangeRequests(); + Set visibleChangeRequests = new HashSet<>(); // Create a Change-Requests list to filter it + for (ChangeRequest changeRequest : allChangeRequests) { + try { + checkChangeItemReadAccess(changeRequest, user); // Check if the user can access to this Change-Request + visibleChangeRequests.add(filterLinkedChangeIssues(changeRequest, user)); // Add the Change-Request filtered to the list + } catch (AccessRightException e) { + LOGGER.log(Level.FINEST, null, e); + } + } + changeOrder.setAddressedChangeRequests(visibleChangeRequests); + return changeOrder; + } + + private Set getDocumentIterations(DocumentIterationKey[] pAffectedDocuments, Locale userLocale) throws DocumentRevisionNotFoundException { + + Set documentIterations = new HashSet<>(); + DocumentRevisionDAO documentRevisionDAO = new DocumentRevisionDAO(userLocale, em); + + for (DocumentIterationKey docKey : pAffectedDocuments) { + + DocumentRevision documentRevision = documentRevisionDAO.loadDocR(docKey.getDocumentRevision()); + DocumentIteration iteration; + + if(docKey.getIteration() > 0){ + iteration = documentRevision.getIteration(docKey.getIteration()); + }else{ + iteration = documentRevision.getLastCheckedInIteration(); + } + + if(iteration != null){ + documentIterations.add(iteration); + } + } + return documentIterations; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CheckActivity.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CheckActivity.java new file mode 100644 index 0000000000..c21b335684 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/CheckActivity.java @@ -0,0 +1,36 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import javax.interceptor.InterceptorBinding; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Inherited +@InterceptorBinding +@Target({TYPE, METHOD}) +@Retention(RUNTIME) +public @interface CheckActivity { +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ContextManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ContextManagerBean.java new file mode 100644 index 0000000000..fe28ff71b7 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ContextManagerBean.java @@ -0,0 +1,63 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server; + +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IContextManagerLocal; + +import javax.annotation.Resource; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.SessionContext; +import javax.ejb.Stateless; + +/** + * @author morgan on 07/09/15. + */ + +@DeclareRoles({UserGroupMapping.GUEST_PROXY_ROLE_ID, UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) +@Local(IContextManagerLocal.class) +@Stateless(name = "ContextManagerBean") +public class ContextManagerBean implements IContextManagerLocal{ + + @Resource + private SessionContext ctx; + + @RolesAllowed({UserGroupMapping.GUEST_PROXY_ROLE_ID, UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public boolean isCallerInRole(String role) { + return ctx.isCallerInRole(role); + } + + @RolesAllowed({UserGroupMapping.GUEST_PROXY_ROLE_ID, UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public String getCallerPrincipalLogin() { + return ctx.getCallerPrincipal().toString(); + } + + @RolesAllowed({UserGroupMapping.GUEST_PROXY_ROLE_ID, UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public String getCallerPrincipalName() { + return ctx.getCallerPrincipal().getName(); + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ConverterBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ConverterBean.java new file mode 100644 index 0000000000..ad7ea0d54c --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ConverterBean.java @@ -0,0 +1,250 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.product.Geometry; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartIterationKey; +import com.docdoku.core.services.IConverterManagerLocal; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.core.util.FileIO; +import com.docdoku.core.util.Tools; +import com.docdoku.server.converters.CADConverter; +import com.docdoku.server.converters.utils.ConversionResult; +import com.docdoku.server.converters.utils.GeometryParser; +import com.docdoku.server.dao.PartIterationDAO; +import java.nio.file.Files; + +import javax.ejb.Asynchronous; +import javax.ejb.Stateless; +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.io.*; +import java.util.List; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * CAD File converter + * + * @author Florent.Garin + */ +@Stateless(name = "ConverterBean") +public class ConverterBean implements IConverterManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + @Any + private Instance converters; + + @Inject + private IProductManagerLocal productService; + + @Inject + private IDataManagerLocal dataManager; + + private static final String CONF_PROPERTIES = "/com/docdoku/server/converters/utils/conf.properties"; + private static final Properties CONF = new Properties(); + private static final Logger LOGGER = Logger.getLogger(ConverterBean.class.getName()); + + static { + try (InputStream inputStream = ConverterBean.class.getResourceAsStream(CONF_PROPERTIES)){ + CONF.load(inputStream); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + + @Override + @Asynchronous + @CADConvert + public void convertCADFileToOBJ(PartIterationKey pPartIPK, BinaryResource cadBinaryResource) throws Exception{ + + boolean succeed = false; + + File tempDir = Files.createTempDirectory("docdoku-").toFile(); + + String ext = FileIO.getExtension(cadBinaryResource.getName()); + + CADConverter selectedConverter = null; + + for (CADConverter converter : converters) { + if (converter.canConvertToOBJ(ext)) { + selectedConverter = converter; + break; + } + } + + if (selectedConverter != null) { + + PartIterationDAO partIDAO = new PartIterationDAO(em); + PartIteration partI = partIDAO.loadPartI(pPartIPK); + ConversionResult conversionResult = selectedConverter.convert(partI, cadBinaryResource, tempDir); + + if (conversionResult != null && conversionResult.getConvertedFile() != null) { + + File convertedFile = conversionResult.getConvertedFile(); + + double[] box = GeometryParser.calculateBox(convertedFile); + + //succeed = decimate(pPartIPK, convertedFile, tempDir, box); + + // Copy the converted file if decimation failed, ignore decimated files + if(!succeed){ + saveGeometryFile(pPartIPK, 0, convertedFile, box); + succeed = true; + } + + }else{ + LOGGER.log(Level.WARNING, "Cannot convert " + cadBinaryResource.getName()); + } + + List materials = conversionResult.getMaterials(); + + for(File material:materials){ + saveAttachedFile(pPartIPK,material); + } + + + } else { + LOGGER.log(Level.WARNING, "No CAD converter able to handle " + cadBinaryResource.getName()); + } + + FileIO.rmDir(tempDir); + + if(!succeed){ + throw new Exception("Conversion Failed"); + } + } + + private boolean decimate(PartIterationKey pPartIPK, File file, File tempDir, double[] box) { + + String decimater = CONF.getProperty("decimater"); + + File executable = new File(decimater); + + if(!executable.exists()){ + LOGGER.log(Level.SEVERE, "Cannot decimate file \""+file.getName()+"\", decimater \""+decimater+"\" is not available"); + return false; + } + + if(!executable.canExecute()){ + LOGGER.log(Level.SEVERE, "Cannot decimate file \""+file.getName()+"\", decimater \""+decimater+"\" has no execution rights"); + return false; + } + + boolean decimateSucceed = false; + + try { + String[] args = {decimater, "-i", file.getAbsolutePath(), "-o", tempDir.getAbsolutePath(), "1", "0.6", "0.2"}; + ProcessBuilder pb = new ProcessBuilder(args); + Process proc = pb.start(); + + StringBuilder output = new StringBuilder(); + String line; + // Read buffer + try(InputStreamReader isr = new InputStreamReader(proc.getInputStream(),"UTF-8");BufferedReader br = new BufferedReader(isr)){ + while ((line = br.readLine()) != null){ + output.append(line).append("\n"); + } + } + + proc.waitFor(); + + if (proc.exitValue() == 0) { + String baseName = Tools.unAccent(tempDir.getAbsolutePath() + "/" + FileIO.getFileNameWithoutExtension(file.getName())); + saveGeometryFile(pPartIPK, 0, new File(baseName + "100.obj"), box); + saveGeometryFile(pPartIPK, 1, new File(baseName + "60.obj"), box); + saveGeometryFile(pPartIPK, 2, new File(baseName + "20.obj"), box); + decimateSucceed = true; + } else { + LOGGER.log(Level.SEVERE, "Decimation failed with code = " + proc.exitValue(), output.toString()); + } + + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Decimation failed for " + file.getAbsolutePath(), e); + } + + return decimateSucceed; + } + + private void saveGeometryFile(PartIterationKey partIPK, int quality, File file, double[] box) { + + if(!file.exists()){ + return; + } + + OutputStream os = null; + + try { + Geometry lod = (Geometry) productService.saveGeometryInPartIteration(partIPK, file.getName(), quality, file.length(), box); + os = dataManager.getBinaryResourceOutputStream(lod); + Files.copy(file.toPath(), os); + LOGGER.log(Level.INFO, "Decimation and save done"); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Cannot save geometry to part iteration", e); + } finally { + try { + if(os != null){ + os.close(); + } + } catch (IOException e) { + LOGGER.log(Level.SEVERE,null, e); + } + } + } + + private void saveAttachedFile(PartIterationKey partIPK, File file) { + + if(!file.exists()){ + return; + } + + OutputStream os = null; + + try { + BinaryResource binaryResource = productService.saveFileInPartIteration(partIPK, file.getName(), "attachedfiles" ,file.length()); + os = dataManager.getBinaryResourceOutputStream(binaryResource); + Files.copy(file.toPath(), os); + LOGGER.log(Level.INFO, "Attached file copied"); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Cannot save attached file to part iteration", e); + } finally { + try { + if(os != null){ + os.close(); + } + } catch (IOException e) { + LOGGER.log(Level.SEVERE,null, e); + } + } + } + + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DataManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DataManagerBean.java new file mode 100644 index 0000000000..4a13d36521 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DataManagerBean.java @@ -0,0 +1,186 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.FileNotFoundException; +import com.docdoku.core.exceptions.StorageException; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.server.storage.StorageProvider; +import com.docdoku.server.storage.filesystem.FileStorageProvider; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import javax.annotation.security.DeclareRoles; +import javax.ejb.Local; +import javax.ejb.Stateless; +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Date; +import java.util.List; + +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@Local(IDataManagerLocal.class) +@Stateless(name = "DataManagerBean") +public class DataManagerBean implements IDataManagerLocal { + + @Resource(name = "vaultPath") + private String vaultPath; + + private StorageProvider defaultStorageProvider; + private FileStorageProvider fileStorageProvider; + + @PostConstruct + private void init() { + fileStorageProvider = new FileStorageProvider(vaultPath); + defaultStorageProvider = fileStorageProvider; + } + + @Override + public InputStream getBinaryResourceInputStream(BinaryResource binaryResource) throws StorageException { + try { + return defaultStorageProvider.getBinaryResourceInputStream(binaryResource); + } catch (FileNotFoundException e) { + BinaryResource previous = binaryResource.getPrevious(); + if (previous != null) { + return getBinaryResourceInputStream(previous); + } else { + throw new StorageException(new StringBuilder().append("Can't find ").append(binaryResource.getFullName()).toString()); + } + } + } + + @Override + public InputStream getBinarySubResourceInputStream(BinaryResource binaryResource, String subResourceVirtualPath) throws StorageException { + try { + return fileStorageProvider.getBinarySubResourceInputStream(binaryResource, subResourceVirtualPath); + } catch (FileNotFoundException e) { + BinaryResource previous = binaryResource.getPrevious(); + if (previous != null) { + return getBinarySubResourceInputStream(previous, subResourceVirtualPath); + } else { + throw new StorageException(new StringBuilder().append("Can't find sub resource ").append(subResourceVirtualPath).append(" of ").append(binaryResource.getFullName()).toString()); + } + } + } + + @Override + public OutputStream getBinaryResourceOutputStream(BinaryResource binaryResource) throws StorageException { + return defaultStorageProvider.getBinaryResourceOutputStream(binaryResource); + } + + @Override + public OutputStream getBinarySubResourceOutputStream(BinaryResource binaryResource, String subResourceVirtualPath) throws StorageException { + return fileStorageProvider.getBinarySubResourceOutputStream(binaryResource, subResourceVirtualPath); + } + + @Override + public boolean exists(BinaryResource binaryResource, String subResourceVirtualPath) throws StorageException { + if (fileStorageProvider.exists(binaryResource, subResourceVirtualPath)) { + return true; + } else { + BinaryResource previous = binaryResource.getPrevious(); + return previous != null && exists(previous, subResourceVirtualPath); + } + } + + @Override + public void copyData(BinaryResource source, BinaryResource destination) throws StorageException { + try { + defaultStorageProvider.copyData(source, destination); + fileStorageProvider.copySubResources(source, destination); + } catch (FileNotFoundException e) { + BinaryResource previous = source.getPrevious(); + if (previous != null) { + copyData(previous, destination); + } else { + throw new StorageException(new StringBuilder().append("Can't find source file to copy ").append(source.getFullName()).toString()); + } + } + } + + @Override + public void deleteData(BinaryResource binaryResource) throws StorageException { + defaultStorageProvider.delData(binaryResource); + fileStorageProvider.deleteSubResources(binaryResource); + fileStorageProvider.cleanParentFolders(binaryResource); + } + + + private File getBinarySubResourceFile(BinaryResource binaryResource) throws StorageException { + try { + return fileStorageProvider.getBinaryResourceFile(binaryResource); + } catch (FileNotFoundException e) { + BinaryResource previous = binaryResource.getPrevious(); + if (previous != null) { + return getBinarySubResourceFile(previous); + } else { + throw new StorageException(new StringBuilder().append("Can't find resource ").append(binaryResource.getFullName()).toString()); + } + } + } + + @Override + public void renameFile(BinaryResource binaryResource, String pNewName) throws StorageException, FileNotFoundException { + + File file = getBinarySubResourceFile(binaryResource); + + try { + fileStorageProvider.getBinaryResourceFile(binaryResource); + } catch (FileNotFoundException e) { + file = fileStorageProvider.copyFile(file, binaryResource); + } + + fileStorageProvider.renameData(file, pNewName); + } + + @Override + public Date getLastModified(BinaryResource binaryResource, String subResourceVirtualPath) throws StorageException { + try { + return fileStorageProvider.getLastModified(binaryResource, subResourceVirtualPath); + } catch (FileNotFoundException e) { + BinaryResource previous = binaryResource.getPrevious(); + if (previous != null) { + return getLastModified(previous, subResourceVirtualPath); + } else { + throw new StorageException(new StringBuilder().append("Can't find source file to get last modified date ").append(binaryResource.getFullName()).toString()); + } + } + } + + @Override + public String getExternalStorageURI(BinaryResource binaryResource) { + return defaultStorageProvider.getExternalResourceURI(binaryResource); + } + + @Override + public String getShortenExternalStorageURI(BinaryResource binaryResource) { + return defaultStorageProvider.getShortenExternalResourceURI(binaryResource); + } + + + @Override + public void deleteWorkspaceFolder(String workspaceId, List binaryResourcesInWorkspace) throws StorageException { + defaultStorageProvider.deleteWorkspaceFolder(workspaceId, binaryResourcesInWorkspace); + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentLoggerInterceptor.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentLoggerInterceptor.java new file mode 100644 index 0000000000..ede11b84e0 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentLoggerInterceptor.java @@ -0,0 +1,89 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.log.DocumentLog; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IContextManagerLocal; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.server.dao.BinaryResourceDAO; + +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; +import java.io.Serializable; +import java.util.Date; +import java.util.logging.Level; +import java.util.logging.Logger; + +@LogDocument +@Interceptor +public class DocumentLoggerInterceptor implements Serializable{ + + @Inject + private EntityManagerProducer emf; + + @Inject + private IDocumentManagerLocal documentManager; + + @Inject + private IContextManagerLocal contextManager; + + private static final Logger LOGGER = Logger.getLogger(DocumentLoggerInterceptor.class.getName()); + private static final String EVENT = "DOWNLOAD"; + + @AroundInvoke + public Object log(InvocationContext ctx) throws Exception { + + Object result = ctx.proceed(); + + try { + if (contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID) + && ctx.getParameters() != null && ctx.getParameters().length > 0 && ctx.getParameters()[0] instanceof String) { + String fullName = (String) ctx.getParameters()[0]; + String userLogin = contextManager.getCallerPrincipalLogin(); + + BinaryResourceDAO binDAO = new BinaryResourceDAO(emf.create()); + BinaryResource file = binDAO.loadBinaryResource(fullName); + DocumentIteration document = binDAO.getDocumentHolder(file); + + if (document != null) { + DocumentLog log = new DocumentLog(); + log.setUserLogin(userLogin); + log.setLogDate(new Date()); + log.setDocumentWorkspaceId(document.getWorkspaceId()); + log.setDocumentId(document.getId()); + log.setDocumentVersion(document.getVersion()); + log.setDocumentIteration(document.getIteration()); + log.setEvent(EVENT); + log.setInfo(fullName); + documentManager.createDocumentLog(log); + } + } + } catch (Exception ex) { + LOGGER.log(Level.WARNING, null, ex); + } + + return result; + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentManagerBean.java new file mode 100644 index 0000000000..ef51101769 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentManagerBean.java @@ -0,0 +1,2010 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.change.ChangeItem; +import com.docdoku.core.common.*; +import com.docdoku.core.document.*; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.gcm.GCMAccount; +import com.docdoku.core.log.DocumentLog; +import com.docdoku.core.meta.*; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.query.DocumentSearchQuery; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.ACLUserEntry; +import com.docdoku.core.security.ACLUserGroupEntry; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.*; +import com.docdoku.core.sharing.SharedDocument; +import com.docdoku.core.sharing.SharedEntityKey; +import com.docdoku.core.util.NamingConvention; +import com.docdoku.core.util.Tools; +import com.docdoku.core.workflow.*; +import com.docdoku.server.dao.*; +import com.docdoku.server.esindexer.ESIndexer; +import com.docdoku.server.esindexer.ESSearcher; +import com.docdoku.server.factory.ACLFactory; +import com.docdoku.server.validation.AttributesConsistencyUtils; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.PersistenceContext; +import java.text.ParseException; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) +@Local(IDocumentManagerLocal.class) +@Stateless(name = "DocumentManagerBean") +public class DocumentManagerBean implements IDocumentManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IContextManagerLocal contextManager; + + @Inject + private IMailerLocal mailer; + + @Inject + private IGCMSenderLocal gcmNotifier; + + @Inject + private ESIndexer esIndexer; + + @Inject + private ESSearcher esSearcher; + + @Inject + private IDataManagerLocal dataManager; + + private static final Logger LOGGER = Logger.getLogger(DocumentManagerBean.class.getName()); + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public BinaryResource saveFileInTemplate(DocumentMasterTemplateKey pDocMTemplateKey, String pName, long pSize) throws WorkspaceNotFoundException, NotAllowedException, DocumentMasterTemplateNotFoundException, FileAlreadyExistsException, UserNotFoundException, UserNotActiveException, CreationException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pDocMTemplateKey.getWorkspaceId()); + Locale locale = new Locale(user.getLanguage()); + checkNameFileValidity(pName, locale); + + DocumentMasterTemplateDAO templateDAO = new DocumentMasterTemplateDAO(locale, em); + DocumentMasterTemplate template = templateDAO.loadDocMTemplate(pDocMTemplateKey); + + checkDocumentTemplateWriteAccess(template, user); + + BinaryResource binaryResource = null; + String fullName = template.getWorkspaceId() + "/document-templates/" + template.getId() + "/" + pName; + + for (BinaryResource bin : template.getAttachedFiles()) { + if (bin.getFullName().equals(fullName)) { + binaryResource = bin; + break; + } + } + + if (binaryResource == null) { + binaryResource = new BinaryResource(fullName, pSize, new Date()); + new BinaryResourceDAO(locale, em).createBinaryResource(binaryResource); + template.addFile(binaryResource); + } else { + binaryResource.setContentLength(pSize); + binaryResource.setLastModified(new Date()); + } + return binaryResource; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public BinaryResource saveFileInDocument(DocumentIterationKey pDocPK, String pName, long pSize) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, FileAlreadyExistsException, UserNotFoundException, UserNotActiveException, CreationException, AccessRightException { + User user = checkDocumentRevisionWriteAccess(new DocumentRevisionKey(pDocPK.getWorkspaceId(), pDocPK.getDocumentMasterId(), pDocPK.getDocumentRevisionVersion())); + Locale locale = new Locale(user.getLanguage()); + checkNameFileValidity(pName, locale); + + DocumentRevisionDAO docRDAO = new DocumentRevisionDAO(locale, em); + DocumentRevision docR = docRDAO.loadDocR(new DocumentRevisionKey(pDocPK.getWorkspaceId(), pDocPK.getDocumentMasterId(), pDocPK.getDocumentRevisionVersion())); + DocumentIteration document = docR.getIteration(pDocPK.getIteration()); + + if (isCheckoutByUser(user, docR) && docR.getLastIteration().equals(document)) { + BinaryResource binaryResource = null; + String fullName = docR.getWorkspaceId() + "/documents/" + docR.getId() + "/" + docR.getVersion() + "/" + document.getIteration() + "/" + pName; + + for (BinaryResource bin : document.getAttachedFiles()) { + if (bin.getFullName().equals(fullName)) { + binaryResource = bin; + break; + } + } + if (binaryResource == null) { + binaryResource = new BinaryResource(fullName, pSize, new Date()); + new BinaryResourceDAO(locale, em).createBinaryResource(binaryResource); + document.addFile(binaryResource); + } else { + binaryResource.setContentLength(pSize); + binaryResource.setLastModified(new Date()); + } + return binaryResource; + } else { + throw new NotAllowedException(locale, "NotAllowedException4"); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void setDocumentPublicShared(DocumentRevisionKey pDocRPK, boolean isPublicShared) throws AccessRightException, NotAllowedException, WorkspaceNotFoundException, UserNotFoundException, DocumentRevisionNotFoundException, UserNotActiveException { + DocumentRevision documentRevision = getDocumentRevision(pDocRPK); + documentRevision.setPublicShared(isPublicShared); + } + + @LogDocument + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) + @Override + public BinaryResource getBinaryResource(String pFullName) throws WorkspaceNotFoundException, NotAllowedException, FileNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException { + + if (contextManager.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)) { + return new BinaryResourceDAO(em).loadBinaryResource(pFullName); + } + + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(pFullName)); + Locale userLocale = new Locale(user.getLanguage()); + BinaryResourceDAO binDAO = new BinaryResourceDAO(userLocale, em); + BinaryResource binaryResource = binDAO.loadBinaryResource(pFullName); + + DocumentIteration document = binDAO.getDocumentHolder(binaryResource); + if (document != null) { + DocumentRevision docR = document.getDocumentRevision(); + + if (isACLGrantReadAccess(user, docR)) { + if ((isInAnotherUserHomeFolder(user, docR) || isCheckoutByAnotherUser(user, docR)) && docR.getLastIteration().equals(document)) { + throw new NotAllowedException(new Locale(user.getLanguage()), "NotAllowedException34"); + } else { + return binaryResource; + } + } else { + throw new AccessRightException(userLocale, user); + } + } else { + throw new FileNotFoundException(userLocale, pFullName); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public BinaryResource getTemplateBinaryResource(String pFullName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, FileNotFoundException { + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(pFullName)); + Locale userLocale = new Locale(user.getLanguage()); + BinaryResourceDAO binDAO = new BinaryResourceDAO(userLocale, em); + return binDAO.loadBinaryResource(pFullName); + } + + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public String[] getFolders(String pCompletePath) throws WorkspaceNotFoundException, FolderNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(Folder.parseWorkspaceId(pCompletePath)); + Folder[] subFolders = new FolderDAO(new Locale(user.getLanguage()), em).getSubFolders(pCompletePath); + String[] shortNames = new String[subFolders.length]; + int i = 0; + for (Folder f : subFolders) { + shortNames[i++] = f.getShortName(); + } + return shortNames; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision[] findDocumentRevisionsByFolder(String pCompletePath) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + String workspaceId = Folder.parseWorkspaceId(pCompletePath); + User user = userManager.checkWorkspaceReadAccess(workspaceId); + List docRs = new DocumentRevisionDAO(new Locale(user.getLanguage()), em).findDocRsByFolder(pCompletePath); + ListIterator ite = docRs.listIterator(); + while (ite.hasNext()) { + DocumentRevision docR = ite.next(); + if (!hasDocumentRevisionReadAccess(user, docR)) { + ite.remove(); + } else if (isCheckoutByAnotherUser(user, docR)) { + em.detach(docR); + docR.removeLastIteration(); + } + } + return docRs.toArray(new DocumentRevision[docRs.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision[] findDocumentRevisionsByTag(TagKey pKey) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + String workspaceId = pKey.getWorkspaceId(); + User user = userManager.checkWorkspaceReadAccess(workspaceId); + List docRs = new DocumentRevisionDAO(new Locale(user.getLanguage()), em).findDocRsByTag(new Tag(user.getWorkspace(), pKey.getLabel())); + ListIterator ite = docRs.listIterator(); + while (ite.hasNext()) { + DocumentRevision docR = ite.next(); + if (!hasDocumentRevisionReadAccess(user, docR)) { + ite.remove(); + } else if (isCheckoutByAnotherUser(user, docR)) { + em.detach(docR); + docR.removeLastIteration(); + } + } + return docRs.toArray(new DocumentRevision[docRs.size()]); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) + @Override + public DocumentRevision getDocumentRevision(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, DocumentRevisionNotFoundException, NotAllowedException, UserNotFoundException, UserNotActiveException, AccessRightException { + if (contextManager.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)) { + DocumentRevision documentRevision = new DocumentRevisionDAO(em).loadDocR(pDocRPK); + if (documentRevision.isCheckedOut()) { + em.detach(documentRevision); + documentRevision.removeLastIteration(); + } + return documentRevision; + } + + User user = userManager.checkWorkspaceReadAccess(pDocRPK.getDocumentMaster().getWorkspace()); + Locale userLocale = new Locale(user.getLanguage()); + DocumentRevision docR = new DocumentRevisionDAO(userLocale, em).loadDocR(pDocRPK); + if (isAnotherUserHomeFolder(user, docR.getLocation())) { + throw new NotAllowedException(userLocale, "NotAllowedException5"); + } + + if (hasDocumentRevisionReadAccess(user, docR)) { + if (isCheckoutByAnotherUser(user, docR)) { + em.detach(docR); + docR.removeLastIteration(); + } + return docR; + + } else { + throw new AccessRightException(userLocale, user); + } + + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) + @Override + public DocumentIteration findDocumentIterationByBinaryResource(BinaryResource pBinaryResource) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + if (contextManager.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)) { + return new DocumentRevisionDAO(em).findDocumentIterationByBinaryResource(pBinaryResource); + } + + User user = userManager.checkWorkspaceReadAccess(pBinaryResource.getWorkspaceId()); + DocumentRevisionDAO documentRevisionDAO = new DocumentRevisionDAO(new Locale(user.getLanguage()), em); + return documentRevisionDAO.findDocumentIterationByBinaryResource(pBinaryResource); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void updateDocumentACL(String pWorkspaceId, DocumentRevisionKey docKey, Map pACLUserEntries, Map pACLUserGroupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, AccessRightException, NotAllowedException { + User user = checkDocumentRevisionWriteAccess(docKey); + Locale userLocale = new Locale(user.getLanguage()); + ACLFactory aclFactory = new ACLFactory(em); + + DocumentRevisionDAO documentRevisionDAO = new DocumentRevisionDAO(userLocale, em); + DocumentRevision docR = documentRevisionDAO.loadDocR(docKey); + if (user.isAdministrator() || isAuthor(user, docR)) { + + if (docR.getACL() == null) { + ACL acl = aclFactory.createACL(pWorkspaceId, pACLUserEntries, pACLUserGroupEntries); + docR.setACL(acl); + + } else { + aclFactory.updateACL(pWorkspaceId, docR.getACL(), pACLUserEntries, pACLUserGroupEntries); + } + + } else { + throw new AccessRightException(userLocale, user); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void updateACLForDocumentMasterTemplate(String pWorkspaceId, String pDocMTemplateId, Map userEntries, Map userGroupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, NotAllowedException, DocumentMasterTemplateNotFoundException { + ACLFactory aclFactory = new ACLFactory(em); + + // Check the read access to the workspace + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + // Load the documentTemplateModel + Locale locale = new Locale(user.getLanguage()); + DocumentMasterTemplate docTemplate = new DocumentMasterTemplateDAO(locale, em).loadDocMTemplate(new DocumentMasterTemplateKey(user.getWorkspaceId(), pDocMTemplateId)); + // Check the access to the documentTemplate + checkDocumentTemplateWriteAccess(docTemplate, user); + if (docTemplate.getAcl() == null) { + ACL acl = aclFactory.createACL(pWorkspaceId, userEntries, userGroupEntries); + docTemplate.setAcl(acl); + } else { + aclFactory.updateACL(pWorkspaceId, docTemplate.getAcl(), userEntries, userGroupEntries); + } + } + + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void removeACLFromDocumentRevision(DocumentRevisionKey documentRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(documentRevisionKey.getDocumentMaster().getWorkspace()); + + Locale locale = new Locale(user.getLanguage()); + DocumentRevision docR = new DocumentRevisionDAO(locale, em).getDocRRef(documentRevisionKey); + + if (user.isAdministrator() || isAuthor(user, docR)) { + ACL acl = docR.getACL(); + if (acl != null) { + new ACLDAO(em).removeACLEntries(acl); + docR.setACL(null); + } + } else { + throw new AccessRightException(locale, user); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void removeACLFromDocumentMasterTemplate(String pWorkspaceId, String documentTemplateId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, DocumentMasterTemplateNotFoundException { + + // Check the read access to the workspace + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + // Load the documentTemplateModel + Locale locale = new Locale(user.getLanguage()); + DocumentMasterTemplate docTemplate = new DocumentMasterTemplateDAO(locale, em).loadDocMTemplate(new DocumentMasterTemplateKey(user.getWorkspaceId(), documentTemplateId)); + + // Check the access to the workflow + checkDocumentTemplateWriteAccess(docTemplate, user); + + ACL acl = docTemplate.getAcl(); + if (acl != null) { + new ACLDAO(em).removeACLEntries(acl); + docTemplate.setAcl(null); + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision[] getAllDocumentsInWorkspace(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + List docRs = new DocumentRevisionDAO(new Locale(user.getLanguage()), em).getDocumentRevisionsFiltered(user, workspaceId); + List documentRevisions = new ArrayList<>(); + for (DocumentRevision docR : docRs) { + if (isCheckoutByAnotherUser(user, docR)) { + em.detach(docR); + docR.removeLastIteration(); + } + documentRevisions.add(docR); + } + return documentRevisions.toArray(new DocumentRevision[documentRevisions.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision[] getAllDocumentsInWorkspace(String workspaceId, int start, int pMaxResults) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + List docRs = new DocumentRevisionDAO(new Locale(user.getLanguage()), em).getDocumentRevisionsFiltered(user, workspaceId, start, pMaxResults); + List documentRevisions = new ArrayList<>(); + for (DocumentRevision docR : docRs) { + if (isCheckoutByAnotherUser(user, docR)) { + em.detach(docR); + docR.removeLastIteration(); + } + documentRevisions.add(docR); + } + return documentRevisions.toArray(new DocumentRevision[documentRevisions.size()]); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public int getDocumentsInWorkspaceCount(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + return new DocumentRevisionDAO(new Locale(user.getLanguage()), em).getDocumentRevisionsCountFiltered(user, workspaceId); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public int getTotalNumberOfDocuments(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException { + Account account = userManager.checkAdmin(pWorkspaceId); + DocumentRevisionDAO documentRevisionDAO = new DocumentRevisionDAO(new Locale(account.getLanguage()), em); + return documentRevisionDAO.getTotalNumberOfDocuments(pWorkspaceId); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision[] getCheckedOutDocumentRevisions(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + List docRs = new DocumentRevisionDAO(new Locale(user.getLanguage()), em).findCheckedOutDocRs(user); + return docRs.toArray(new DocumentRevision[docRs.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Task[] getTasks(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + return new TaskDAO(new Locale(user.getLanguage()), em).findTasks(user); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevisionKey[] getIterationChangeEventSubscriptions(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + return new SubscriptionDAO(em).getIterationChangeEventSubscriptions(user); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public DocumentRevisionKey[] getStateChangeEventSubscriptions(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + return new SubscriptionDAO(em).getStateChangeEventSubscriptions(user); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public boolean isUserStateChangeEventSubscribedForGivenDocument(String pWorkspaceId, DocumentRevision docR) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + return new SubscriptionDAO(em).isUserStateChangeEventSubscribedForGivenDocument(user, docR); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public boolean isUserIterationChangeEventSubscribedForGivenDocument(String pWorkspaceId, DocumentRevision docR) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + return new SubscriptionDAO(em).isUserIterationChangeEventSubscribedForGivenDocument(user, docR); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision[] getDocumentRevisionsWithReferenceOrTitle(String pWorkspaceId, String search, int maxResults) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + List docRs = new DocumentRevisionDAO(new Locale(user.getLanguage()), em).findDocsRevisionsWithReferenceOrTitleLike(pWorkspaceId, search, maxResults); + return docRs.toArray(new DocumentRevision[docRs.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public String generateId(String pWorkspaceId, String pDocMTemplateId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, DocumentMasterTemplateNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + Locale locale = new Locale(user.getLanguage()); + DocumentMasterTemplate template = new DocumentMasterTemplateDAO(locale, em).loadDocMTemplate(new DocumentMasterTemplateKey(user.getWorkspaceId(), pDocMTemplateId)); + + String newId = null; + try { + String latestId = new DocumentRevisionDAO(locale, em).findLatestDocMId(pWorkspaceId, template.getDocumentType()); + String inputMask = template.getMask(); + String convertedMask = Tools.convertMask(inputMask); + newId = Tools.increaseId(latestId, convertedMask); + } catch (NoResultException ex) { + LOGGER.log(Level.FINER, null, ex); + //may happen when no document of the specified type has been created + } catch (ParseException ex) { + LOGGER.log(Level.SEVERE, null, ex); + //may happen when a different mask has been used for the same document type + } + return newId; + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision[] searchDocumentRevisions(DocumentSearchQuery pQuery) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, ESServerException { + User user = userManager.checkWorkspaceReadAccess(pQuery.getWorkspaceId()); + List fetchedDocRs = esSearcher.search(pQuery); // Get Search Results + List docList = new ArrayList<>(); + + if (!fetchedDocRs.isEmpty()) { + for (DocumentRevision docR : fetchedDocRs) { + DocumentRevision filteredDocR = applyDocumentRevisionReadAccess(user, docR); + if (filteredDocR != null) { + docList.add(filteredDocR); + } + } + + return docList.toArray(new DocumentRevision[docList.size()]); + } + return new DocumentRevision[0]; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentMasterTemplate[] getDocumentMasterTemplates(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + List templates = new DocumentMasterTemplateDAO(new Locale(user.getLanguage()), em).findAllDocMTemplates(pWorkspaceId); + + ListIterator ite = templates.listIterator(); + + while (ite.hasNext()) { + DocumentMasterTemplate template = ite.next(); + if (!hasDocumentMasterTemplateReadAccess(template, user)) { + ite.remove(); + } + } + + return templates.toArray(new DocumentMasterTemplate[templates.size()]); + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentMasterTemplate getDocumentMasterTemplate(DocumentMasterTemplateKey pKey) + throws WorkspaceNotFoundException, DocumentMasterTemplateNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pKey.getWorkspaceId()); + return new DocumentMasterTemplateDAO(new Locale(user.getLanguage()), em).loadDocMTemplate(pKey); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentMasterTemplate updateDocumentMasterTemplate(DocumentMasterTemplateKey pKey, String pDocumentType, String pWorkflowModelId, String pMask, List pAttributeTemplates, String[] lovNames, boolean idGenerated, boolean attributesLocked) throws WorkspaceNotFoundException, AccessRightException, DocumentMasterTemplateNotFoundException, UserNotFoundException, WorkflowModelNotFoundException, UserNotActiveException, ListOfValuesNotFoundException, NotAllowedException { + User user = userManager.checkWorkspaceReadAccess(pKey.getWorkspaceId()); + Locale locale = new Locale(user.getLanguage()); + + DocumentMasterTemplateDAO templateDAO = new DocumentMasterTemplateDAO(new Locale(user.getLanguage()), em); + DocumentMasterTemplate template = templateDAO.loadDocMTemplate(pKey); + + checkDocumentTemplateWriteAccess(template, user); + + Date now = new Date(); + template.setModificationDate(now); + template.setAuthor(user); + template.setDocumentType(pDocumentType); + template.setMask(pMask); + template.setIdGenerated(idGenerated); + template.setAttributesLocked(attributesLocked); + LOVDAO lovDAO = new LOVDAO(locale, em); + + List attrs = new ArrayList<>(); + for (int i = 0; i < pAttributeTemplates.size(); i++) { + if(attributesLocked) { + pAttributeTemplates.get(i).setLocked(attributesLocked); + } + attrs.add(pAttributeTemplates.get(i)); + if (pAttributeTemplates.get(i) instanceof ListOfValuesAttributeTemplate) { + ListOfValuesAttributeTemplate lovAttr = (ListOfValuesAttributeTemplate) pAttributeTemplates.get(i); + ListOfValuesKey lovKey = new ListOfValuesKey(user.getWorkspaceId(), lovNames[i]); + lovAttr.setLov(lovDAO.loadLOV(lovKey)); + } + } + + if(!AttributesConsistencyUtils.isTemplateAttributesValid(attrs,attributesLocked)) { + throw new NotAllowedException(locale, "NotAllowedException59"); + } + template.setAttributeTemplates(attrs); + + WorkflowModel workflowModel = null; + if (pWorkflowModelId != null) { + workflowModel = new WorkflowModelDAO(locale, em).loadWorkflowModel(new WorkflowModelKey(user.getWorkspaceId(), pWorkflowModelId)); + } + template.setWorkflowModel(workflowModel); + + return template; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteTag(TagKey pKey) throws WorkspaceNotFoundException, AccessRightException, TagNotFoundException, UserNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(pKey.getWorkspaceId()); + Locale userLocale = new Locale(user.getLanguage()); + Tag tagToRemove = new Tag(user.getWorkspace(), pKey.getLabel()); + List docRs = new DocumentRevisionDAO(userLocale, em).findDocRsByTag(tagToRemove); + for (DocumentRevision docR : docRs) { + docR.getTags().remove(tagToRemove); + } + List changeItems = new ChangeItemDAO(userLocale, em).findChangeItemByTag(pKey.getWorkspaceId(), tagToRemove); + for (ChangeItem changeItem : changeItems) { + changeItem.getTags().remove(tagToRemove); + } + + List partRevisions = new PartRevisionDAO(userLocale, em).findPartByTag(tagToRemove); + for (PartRevision partRevision : partRevisions) { + partRevision.getTags().remove(tagToRemove); + } + + new TagDAO(userLocale, em).removeTag(pKey); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void createTag(String pWorkspaceId, String pLabel) throws WorkspaceNotFoundException, AccessRightException, CreationException, TagAlreadyExistsException, UserNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(pWorkspaceId); + Locale userLocale = new Locale(user.getLanguage()); + TagDAO tagDAO = new TagDAO(userLocale, em); + Tag tag = new Tag(user.getWorkspace(), pLabel); + tagDAO.createTag(tag); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision createDocumentMaster(String pParentFolder, String pDocMId, String pTitle, String pDescription, String pDocMTemplateId, String pWorkflowModelId, ACLUserEntry[] pACLUserEntries, ACLUserGroupEntry[] pACLUserGroupEntries, Map> userRoleMapping, Map> groupRoleMapping) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, NotAllowedException, FolderNotFoundException, DocumentMasterTemplateNotFoundException, FileAlreadyExistsException, CreationException, DocumentRevisionAlreadyExistsException, RoleNotFoundException, WorkflowModelNotFoundException, DocumentMasterAlreadyExistsException, UserGroupNotFoundException { + + User user = userManager.checkWorkspaceWriteAccess(Folder.parseWorkspaceId(pParentFolder)); + Locale locale = new Locale(user.getLanguage()); + checkNameValidity(pDocMId, locale); + + Folder folder = new FolderDAO(locale, em).loadFolder(pParentFolder); + checkFolderWritingRight(user, folder); + + DocumentMaster docM; + DocumentRevision docR; + DocumentIteration newDoc; + + DocumentMasterDAO docMDAO = new DocumentMasterDAO(locale, em); + if (pDocMTemplateId == null) { + docM = new DocumentMaster(user.getWorkspace(), pDocMId, user); + //specify an empty type instead of null + //so the search will find it with the % character + docM.setType(""); + docMDAO.createDocM(docM); + docR = docM.createNextRevision(user); + newDoc = docR.createNextIteration(user); + } else { + DocumentMasterTemplate template = new DocumentMasterTemplateDAO(locale, em).loadDocMTemplate(new DocumentMasterTemplateKey(user.getWorkspaceId(), pDocMTemplateId)); + + if (!Tools.validateMask(template.getMask(), pDocMId)) { + throw new NotAllowedException(locale, "NotAllowedException42"); + } + + docM = new DocumentMaster(user.getWorkspace(), pDocMId, user); + docM.setType(template.getDocumentType()); + docM.setAttributesLocked(template.isAttributesLocked()); + + docMDAO.createDocM(docM); + docR = docM.createNextRevision(user); + newDoc = docR.createNextIteration(user); + + + List attrs = new ArrayList<>(); + for (InstanceAttributeTemplate attrTemplate : template.getAttributeTemplates()) { + InstanceAttribute attr = attrTemplate.createInstanceAttribute(); + attrs.add(attr); + } + newDoc.setInstanceAttributes(attrs); + + BinaryResourceDAO binDAO = new BinaryResourceDAO(locale, em); + for (BinaryResource sourceFile : template.getAttachedFiles()) { + String fileName = sourceFile.getName(); + long length = sourceFile.getContentLength(); + Date lastModified = sourceFile.getLastModified(); + String fullName = docM.getWorkspaceId() + "/documents/" + docM.getId() + "/A/1/" + fileName; + BinaryResource targetFile = new BinaryResource(fullName, length, lastModified); + binDAO.createBinaryResource(targetFile); + + newDoc.addFile(targetFile); + try { + dataManager.copyData(sourceFile, targetFile); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + } + + if (pWorkflowModelId != null) { + + UserDAO userDAO = new UserDAO(locale, em); + UserGroupDAO groupDAO = new UserGroupDAO(locale, em); + RoleDAO roleDAO = new RoleDAO(locale, em); + + Map> roleUserMap = new HashMap<>(); + for (Map.Entry> pair : userRoleMapping.entrySet()) { + String roleName = pair.getKey(); + Collection userLogins = pair.getValue(); + Role role = roleDAO.loadRole(new RoleKey(Folder.parseWorkspaceId(pParentFolder), roleName)); + Set users=new HashSet<>(); + roleUserMap.put(role, users); + for(String login:userLogins) { + User u = userDAO.loadUser(new UserKey(Folder.parseWorkspaceId(pParentFolder), login)); + users.add(u); + } + } + + Map> roleGroupMap = new HashMap<>(); + for (Map.Entry> pair : groupRoleMapping.entrySet()) { + String roleName = pair.getKey(); + Collection groupIds = pair.getValue(); + Role role = roleDAO.loadRole(new RoleKey(Folder.parseWorkspaceId(pParentFolder), roleName)); + Set groups=new HashSet<>(); + roleGroupMap.put(role, groups); + for(String groupId:groupIds) { + UserGroup g = groupDAO.loadUserGroup(new UserGroupKey(Folder.parseWorkspaceId(pParentFolder), groupId)); + groups.add(g); + } + } + + WorkflowModel workflowModel = new WorkflowModelDAO(locale, em).loadWorkflowModel(new WorkflowModelKey(user.getWorkspaceId(), pWorkflowModelId)); + Workflow workflow = workflowModel.createWorkflow(roleUserMap, roleGroupMap); + docR.setWorkflow(workflow); + + for(Task task : workflow.getTasks()){ + if(!task.hasPotentialWorker()){ + throw new NotAllowedException(locale,"NotAllowedException56"); + } + } + + Collection runningTasks = workflow.getRunningTasks(); + for (Task runningTask : runningTasks) { + runningTask.start(); + } + + mailer.sendApproval(runningTasks, docR); + } + + docR.setTitle(pTitle); + docR.setDescription(pDescription); + //TODO refactor call ACLFactory + if ((pACLUserEntries != null && pACLUserEntries.length > 0) || (pACLUserGroupEntries != null && pACLUserGroupEntries.length > 0)) { + ACL acl = new ACL(); + if (pACLUserEntries != null) { + for (ACLUserEntry entry : pACLUserEntries) { + acl.addEntry(em.getReference(User.class, new UserKey(user.getWorkspaceId(), entry.getPrincipalLogin())), entry.getPermission()); + } + } + + if (pACLUserGroupEntries != null) { + for (ACLUserGroupEntry entry : pACLUserGroupEntries) { + acl.addEntry(em.getReference(UserGroup.class, new UserGroupKey(user.getWorkspaceId(), entry.getPrincipalId())), entry.getPermission()); + } + } + docR.setACL(acl); + } + Date now = new Date(); + docM.setCreationDate(now); + docR.setCreationDate(now); + docR.setLocation(folder); + docR.setCheckOutUser(user); + docR.setCheckOutDate(now); + newDoc.setCreationDate(now); + new DocumentRevisionDAO(locale, em).createDocR(docR); + return docR; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public DocumentRevision[] getAllCheckedOutDocumentRevisions(String pWorkspaceId) throws WorkspaceNotFoundException, AccountNotFoundException, AccessRightException { + Account account = userManager.checkAdmin(pWorkspaceId); + List docRs = new DocumentRevisionDAO(new Locale(account.getLanguage()), em).findAllCheckedOutDocRevisions(pWorkspaceId); + return docRs.toArray(new DocumentRevision[docRs.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentMasterTemplate createDocumentMasterTemplate(String pWorkspaceId, String pId, String pDocumentType, String pWorkflowModelId, + String pMask, List pAttributeTemplates, String[] lovNames, boolean idGenerated, boolean attributesLocked) throws WorkspaceNotFoundException, AccessRightException, DocumentMasterTemplateAlreadyExistsException, UserNotFoundException, NotAllowedException, CreationException, WorkflowModelNotFoundException, ListOfValuesNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(pWorkspaceId); + Locale locale = new Locale(user.getLanguage()); + checkNameValidity(pId, locale); + + //Check pMask + if (pMask != null && !pMask.isEmpty() && !NamingConvention.correctNameMask(pMask)) { + throw new NotAllowedException(locale, "MaskCreationException"); + } + + DocumentMasterTemplate template = new DocumentMasterTemplate(user.getWorkspace(), pId, user, pDocumentType, pMask); + Date now = new Date(); + template.setCreationDate(now); + template.setIdGenerated(idGenerated); + template.setAttributesLocked(attributesLocked); + + LOVDAO lovDAO = new LOVDAO(locale, em); + + List attrs = new ArrayList<>(); + for (int i = 0; i < pAttributeTemplates.size(); i++) { + attrs.add(pAttributeTemplates.get(i)); + if (pAttributeTemplates.get(i) instanceof ListOfValuesAttributeTemplate) { + ListOfValuesAttributeTemplate lovAttr = (ListOfValuesAttributeTemplate) pAttributeTemplates.get(i); + ListOfValuesKey lovKey = new ListOfValuesKey(user.getWorkspaceId(), lovNames[i]); + lovAttr.setLov(lovDAO.loadLOV(lovKey)); + } + } + if(!AttributesConsistencyUtils.isTemplateAttributesValid(attrs,attributesLocked)) { + throw new NotAllowedException(locale, "NotAllowedException59"); + } + template.setAttributeTemplates(attrs); + + if (pWorkflowModelId != null) { + WorkflowModel workflowModel = new WorkflowModelDAO(locale, em).loadWorkflowModel(new WorkflowModelKey(user.getWorkspaceId(), pWorkflowModelId)); + template.setWorkflowModel(workflowModel); + } + + new DocumentMasterTemplateDAO(locale, em).createDocMTemplate(template); + return template; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision moveDocumentRevision(String pParentFolder, DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, DocumentRevisionNotFoundException, NotAllowedException, AccessRightException, FolderNotFoundException, UserNotFoundException, UserNotActiveException { + //TODO security check if both parameter belong to the same workspace + User user = checkDocumentRevisionWriteAccess(pDocRPK); + + Folder newLocation = new FolderDAO(new Locale(user.getLanguage()), em).loadFolder(pParentFolder); + checkFolderWritingRight(user, newLocation); + DocumentRevisionDAO docRDAO = new DocumentRevisionDAO(new Locale(user.getLanguage()), em); + DocumentRevision docR = docRDAO.loadDocR(pDocRPK); + + // You cannot move a document to someone else's home directory + if (isInAnotherUserHomeFolder(user, docR)) { + throw new NotAllowedException(new Locale(user.getLanguage()), "NotAllowedException6"); + } else { + + docR.setLocation(newLocation); + + if (isCheckoutByAnotherUser(user, docR)) { + // won't persist newLocation if not flushing + em.flush(); + em.detach(docR); + docR.removeLastIteration(); + } + return docR; + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Folder createFolder(String pParentFolder, String pFolder) + throws WorkspaceNotFoundException, NotAllowedException, AccessRightException, FolderNotFoundException, FolderAlreadyExistsException, UserNotFoundException, CreationException { + User user = userManager.checkWorkspaceWriteAccess(Folder.parseWorkspaceId(pParentFolder)); + Locale locale = new Locale(user.getLanguage()); + checkNameValidity(pFolder, locale); + + FolderDAO folderDAO = new FolderDAO(locale, em); + Folder folder = folderDAO.loadFolder(pParentFolder); + checkFoldersStructureChangeRight(user); + checkFolderWritingRight(user, folder); + Folder newFolder = new Folder(pParentFolder, pFolder); + folderDAO.createFolder(newFolder); + return newFolder; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision checkOutDocument(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, AccessRightException, FileAlreadyExistsException, UserNotFoundException, UserNotActiveException, CreationException { + User user = checkDocumentRevisionWriteAccess(pDocRPK); + Locale locale = new Locale(user.getLanguage()); + DocumentRevisionDAO docRDAO = new DocumentRevisionDAO(locale, em); + DocumentRevision docR = docRDAO.loadDocR(pDocRPK); + //Check access rights on docR + + if (docR.isCheckedOut()) { + throw new NotAllowedException(locale, "NotAllowedException37"); + } + + DocumentIteration beforeLastDocument = docR.getLastIteration(); + + DocumentIteration newDoc = docR.createNextIteration(user); + //We persist the doc as a workaround for a bug which was introduced + //since glassfish 3 that set the DTYPE to null in the instance attribute table + em.persist(newDoc); + Date now = new Date(); + newDoc.setCreationDate(now); + docR.setCheckOutUser(user); + docR.setCheckOutDate(now); + + if (beforeLastDocument != null) { + BinaryResourceDAO binDAO = new BinaryResourceDAO(locale, em); + for (BinaryResource sourceFile : beforeLastDocument.getAttachedFiles()) { + String fileName = sourceFile.getName(); + long length = sourceFile.getContentLength(); + Date lastModified = sourceFile.getLastModified(); + String fullName = docR.getWorkspaceId() + "/documents/" + docR.getId() + "/" + docR.getVersion() + "/" + newDoc.getIteration() + "/" + fileName; + BinaryResource targetFile = new BinaryResource(fullName, length, lastModified); + binDAO.createBinaryResource(targetFile); + newDoc.addFile(targetFile); + } + + Set links = new HashSet<>(); + for (DocumentLink link : beforeLastDocument.getLinkedDocuments()) { + DocumentLink newLink = link.clone(); + links.add(newLink); + } + newDoc.setLinkedDocuments(links); + + InstanceAttributeDAO attrDAO = new InstanceAttributeDAO(em); + List attrs = new ArrayList<>(); + for (InstanceAttribute attr : beforeLastDocument.getInstanceAttributes()) { + InstanceAttribute newAttr = attr.clone(); + //Workaround for the NULL DTYPE bug + attrDAO.createAttribute(newAttr); + attrs.add(newAttr); + } + newDoc.setInstanceAttributes(attrs); + } + + return docR; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision saveTags(DocumentRevisionKey pDocRPK, String[] pTags) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, AccessRightException, UserNotFoundException, UserNotActiveException, ESServerException { + User user = checkDocumentRevisionWriteAccess(pDocRPK); + + Locale userLocale = new Locale(user.getLanguage()); + DocumentRevisionDAO docRDAO = new DocumentRevisionDAO(userLocale, em); + DocumentRevision docR = docRDAO.loadDocR(pDocRPK); + + Set tags = new HashSet<>(); + for (String label : pTags) { + tags.add(new Tag(user.getWorkspace(), label)); + } + + TagDAO tagDAO = new TagDAO(userLocale, em); + List existingTags = Arrays.asList(tagDAO.findAllTags(user.getWorkspaceId())); + + Set tagsToCreate = new HashSet<>(tags); + tagsToCreate.removeAll(existingTags); + + for (Tag t : tagsToCreate) { + try { + tagDAO.createTag(t); + } catch (CreationException | TagAlreadyExistsException ex) { + LOGGER.log(Level.SEVERE, null, ex); + } + } + + docR.setTags(tags); + + if (isCheckoutByAnotherUser(user, docR)) { + em.flush(); + em.detach(docR); + docR.removeLastIteration(); + } + + for (DocumentIteration documentIteration : docR.getDocumentIterations()) { + esIndexer.index(documentIteration); + } + return docR; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision removeTag(DocumentRevisionKey pDocRPK, String pTag) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, DocumentRevisionNotFoundException, NotAllowedException, ESServerException { + + User user = checkDocumentRevisionWriteAccess(pDocRPK); + + DocumentRevision docR = getDocumentRevision(pDocRPK); + Tag tagToRemove = new Tag(user.getWorkspace(), pTag); + docR.getTags().remove(tagToRemove); + + if (isCheckoutByAnotherUser(user, docR)) { + em.detach(docR); + docR.removeLastIteration(); + } + + for (DocumentIteration documentIteration : docR.getDocumentIterations()) { + esIndexer.index(documentIteration); + } + return docR; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision undoCheckOutDocument(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, DocumentRevisionNotFoundException, NotAllowedException, UserNotFoundException, UserNotActiveException, AccessRightException { + + User user = checkDocumentRevisionWriteAccess(pDocRPK); + Locale userLocale = new Locale(user.getLanguage()); + + DocumentRevision docR = new DocumentRevisionDAO(userLocale, em).loadDocR(pDocRPK); + if (isCheckoutByUser(user, docR)) { + if (docR.getLastIteration().getIteration() <= 1) { + throw new NotAllowedException(userLocale, "NotAllowedException27"); + } + DocumentIteration doc = docR.removeLastIteration(); + + DocumentDAO docDAO = new DocumentDAO(em); + docDAO.removeDoc(doc); + docR.setCheckOutDate(null); + docR.setCheckOutUser(null); + + for (BinaryResource file : doc.getAttachedFiles()) { + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + + return docR; + } else { + throw new NotAllowedException(userLocale, "NotAllowedException19"); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision checkInDocument(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, AccessRightException, UserNotFoundException, UserNotActiveException, ESServerException { + + User user = checkDocumentRevisionWriteAccess(pDocRPK); + Locale userLocale = new Locale(user.getLanguage()); + + DocumentRevisionDAO docRDAO = new DocumentRevisionDAO(userLocale, em); + DocumentRevision docR = docRDAO.loadDocR(pDocRPK); + + if (isCheckoutByUser(user, docR)) { + SubscriptionDAO subscriptionDAO = new SubscriptionDAO(em); + User[] subscribers = subscriptionDAO.getIterationChangeEventSubscribers(docR); + GCMAccount[] gcmAccounts = subscriptionDAO.getIterationChangeEventSubscribersGCMAccount(docR); + + docR.setCheckOutDate(null); + docR.setCheckOutUser(null); + + DocumentIteration lastIteration = docR.getLastIteration(); + lastIteration.setCheckInDate(new Date()); + + if (subscribers.length != 0) { + mailer.sendIterationNotification(subscribers, docR); + } + + if (gcmAccounts.length != 0) { + gcmNotifier.sendIterationNotification(gcmAccounts, docR); + } + + esIndexer.index(lastIteration); + + return docR; + } else { + throw new NotAllowedException(userLocale, "NotAllowedException20"); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevisionKey[] deleteFolder(String pCompletePath) throws WorkspaceNotFoundException, NotAllowedException, AccessRightException, UserNotFoundException, FolderNotFoundException, ESServerException, EntityConstraintException, UserNotActiveException, DocumentRevisionNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(Folder.parseWorkspaceId(pCompletePath)); + Locale userLocale = new Locale(user.getLanguage()); + + FolderDAO folderDAO = new FolderDAO(userLocale, em); + Folder folder = folderDAO.loadFolder(pCompletePath); + checkFoldersStructureChangeRight(user); + + if (isAnotherUserHomeFolder(user, folder) || folder.isRoot() || folder.isHome()) { + throw new NotAllowedException(userLocale, "NotAllowedException21"); + + } else { + return doFolderDeletion(folder,userLocale); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevisionKey[] deleteUserFolder(User pUser) throws WorkspaceNotFoundException, NotAllowedException, AccessRightException, UserNotFoundException, FolderNotFoundException, ESServerException, EntityConstraintException, UserNotActiveException, DocumentRevisionNotFoundException { + + User user = userManager.checkWorkspaceWriteAccess(pUser.getWorkspaceId()); + Locale userLocale = new Locale(user.getLanguage()); + String folderCompletePath = pUser.getWorkspaceId() + "/~" + pUser.getLogin(); + FolderDAO folderDAO = new FolderDAO(userLocale, em); + Folder folder = folderDAO.loadFolder(folderCompletePath); + + if (!user.isAdministrator()) { + throw new NotAllowedException(userLocale, "NotAllowedException21"); + } + + return doFolderDeletion(folder, userLocale); + } + + private DocumentRevisionKey[] doFolderDeletion(Folder folder, Locale locale) throws EntityConstraintException, NotAllowedException, WorkspaceNotFoundException, ESServerException, AccessRightException, DocumentRevisionNotFoundException, UserNotActiveException, UserNotFoundException { + FolderDAO folderDAO = new FolderDAO(locale, em); + List allDocRevision = folderDAO.findDocumentRevisionsInFolder(folder); + List allDocRevisionKey = new ArrayList<>(); + + for (DocumentRevision documentRevision : allDocRevision) { + DocumentRevisionKey documentRevisionKey = documentRevision.getKey(); + deleteDocumentRevision(documentRevisionKey); + allDocRevisionKey.add(documentRevisionKey); + } + folderDAO.removeFolder(folder); + return allDocRevisionKey.toArray(new DocumentRevisionKey[allDocRevisionKey.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevisionKey[] moveFolder(String pCompletePath, String pDestParentFolder, String pDestFolder) throws WorkspaceNotFoundException, NotAllowedException, AccessRightException, UserNotFoundException, FolderNotFoundException, CreationException, FolderAlreadyExistsException { + //TODO security check if both parameter belong to the same workspace + String workspace = Folder.parseWorkspaceId(pCompletePath); + User user = userManager.checkWorkspaceWriteAccess(workspace); + Locale userLocale = new Locale(user.getLanguage()); + + FolderDAO folderDAO = new FolderDAO(userLocale, em); + Folder folder = folderDAO.loadFolder(pCompletePath); + checkFoldersStructureChangeRight(user); + if (isAnotherUserHomeFolder(user, folder) || folder.isRoot() || folder.isHome()) { + throw new NotAllowedException(userLocale, "NotAllowedException21"); + } else if (!workspace.equals(Folder.parseWorkspaceId(pDestParentFolder))) { + throw new NotAllowedException(userLocale, "NotAllowedException23"); + } else { + Folder newFolder = createFolder(pDestParentFolder, pDestFolder); + List docRs = folderDAO.moveFolder(folder, newFolder); + DocumentRevisionKey[] pks = new DocumentRevisionKey[docRs.size()]; + int i = 0; + for (DocumentRevision docR : docRs) { + pks[i++] = docR.getKey(); + } + return pks; + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteDocumentRevision(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, AccessRightException, UserNotFoundException, UserNotActiveException, ESServerException, EntityConstraintException { + + User user = checkDocumentRevisionWriteAccess(pDocRPK); + Locale locale = new Locale(user.getLanguage()); + + DocumentMasterDAO documentMasterDAO = new DocumentMasterDAO(locale, em); + DocumentRevisionDAO docRDAO = new DocumentRevisionDAO(locale, em); + DocumentLinkDAO documentLinkDAO = new DocumentLinkDAO(locale, em); + + DocumentRevision docR = docRDAO.loadDocR(pDocRPK); + if (!user.isAdministrator() && isInAnotherUserHomeFolder(user, docR)) { + throw new NotAllowedException(locale, "NotAllowedException22"); + } + + BaselinedDocumentDAO baselinedDocumentDAO = new BaselinedDocumentDAO(locale, em); + if (baselinedDocumentDAO.hasDocumentRevision(pDocRPK)) { + throw new EntityConstraintException(locale, "EntityConstraintException6"); + } + + for (DocumentRevision documentRevision : docR.getDocumentMaster().getDocumentRevisions()) { + if (!documentLinkDAO.getInverseDocumentsLinks(documentRevision).isEmpty()) { + throw new EntityConstraintException(locale, "EntityConstraintException17"); + } + if (!documentLinkDAO.getInversePartsLinks(documentRevision).isEmpty()) { + + throw new EntityConstraintException(locale, "EntityConstraintException18"); + } + if(!documentLinkDAO.getInverseProductInstanceIteration(documentRevision).isEmpty()){ + throw new EntityConstraintException(locale, "EntityConstraintException19"); + } + if(!documentLinkDAO.getInversefindPathData(documentRevision).isEmpty()){ + throw new EntityConstraintException(locale, "EntityConstraintException20"); + } + + } + ChangeItemDAO changeItemDAO = new ChangeItemDAO(locale, em); + if (changeItemDAO.hasChangeItems(pDocRPK)) { + throw new EntityConstraintException(locale, "EntityConstraintException7"); + } + + DocumentMaster documentMaster = docR.getDocumentMaster(); + boolean isLastRevision = documentMaster.getDocumentRevisions().size() == 1; + if (isLastRevision) { + documentMasterDAO.removeDocM(documentMaster); + } else { + docRDAO.removeRevision(docR); + } + + for (DocumentIteration doc : docR.getDocumentIterations()) { + for (BinaryResource file : doc.getAttachedFiles()) { + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + + esIndexer.delete(doc); + } + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteDocumentMasterTemplate(DocumentMasterTemplateKey pKey) + throws WorkspaceNotFoundException, AccessRightException, DocumentMasterTemplateNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pKey.getWorkspaceId()); + DocumentMasterTemplateDAO templateDAO = new DocumentMasterTemplateDAO(new Locale(user.getLanguage()), em); + + DocumentMasterTemplate documentMasterTemplate = templateDAO.loadDocMTemplate(pKey); + checkDocumentTemplateWriteAccess(documentMasterTemplate, user); + + DocumentMasterTemplate template = templateDAO.removeDocMTemplate(pKey); + + for (BinaryResource file : template.getAttachedFiles()) { + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision removeFileFromDocument(String pFullName) throws WorkspaceNotFoundException, DocumentRevisionNotFoundException, NotAllowedException, AccessRightException, FileNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(pFullName)); + Locale userLocale = new Locale(user.getLanguage()); + + BinaryResourceDAO binDAO = new BinaryResourceDAO(userLocale, em); + BinaryResource file = binDAO.loadBinaryResource(pFullName); + + DocumentIteration document = binDAO.getDocumentHolder(file); + DocumentRevision docR = document.getDocumentRevision(); + + //check access rights on docR + user = checkDocumentRevisionWriteAccess(docR.getKey()); + + if (isCheckoutByUser(user, docR) && docR.getLastIteration().equals(document)) { + document.removeFile(file); + binDAO.removeBinaryResource(file); + + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + + return docR; + + } else { + throw new NotAllowedException(userLocale, "NotAllowedException24"); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public BinaryResource renameFileInDocument(String pFullName, String pNewName) throws WorkspaceNotFoundException, DocumentRevisionNotFoundException, NotAllowedException, AccessRightException, FileNotFoundException, UserNotFoundException, UserNotActiveException, FileAlreadyExistsException, CreationException, StorageException { + + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(pFullName)); + Locale userLocale = new Locale(user.getLanguage()); + checkNameFileValidity(pNewName, userLocale); + + BinaryResourceDAO binDAO = new BinaryResourceDAO(userLocale, em); + BinaryResource file = binDAO.loadBinaryResource(pFullName); + + try{ + + binDAO.loadBinaryResource(file.getNewFullName(pNewName)); + throw new FileAlreadyExistsException(userLocale, pNewName); + + }catch(FileNotFoundException e){ + + DocumentIteration document = binDAO.getDocumentHolder(file); + DocumentRevision docR = document.getDocumentRevision(); + + //check access rights on docR + user = checkDocumentRevisionWriteAccess(docR.getKey()); + + if (isCheckoutByUser(user, docR) && docR.getLastIteration().equals(document)) { + dataManager.renameFile(file, pNewName); + document.removeFile(file); + binDAO.removeBinaryResource(file); + BinaryResource newFile = new BinaryResource(file.getNewFullName(pNewName), file.getContentLength(), file.getLastModified()); + binDAO.createBinaryResource(newFile); + document.addFile(newFile); + return newFile; + } else { + throw new NotAllowedException(userLocale, "NotAllowedException29"); + } + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentMasterTemplate removeFileFromTemplate(String pFullName) throws WorkspaceNotFoundException, DocumentMasterTemplateNotFoundException, AccessRightException, FileNotFoundException, UserNotFoundException, UserNotActiveException, StorageException { + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(pFullName)); + + BinaryResourceDAO binDAO = new BinaryResourceDAO(new Locale(user.getLanguage()), em); + BinaryResource file = binDAO.loadBinaryResource(pFullName); + + DocumentMasterTemplate template = binDAO.getDocumentTemplateHolder(file); + checkDocumentTemplateWriteAccess(template, user); + + template.removeFile(file); + binDAO.removeBinaryResource(file); + + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + + return template; + } + + @Override + public BinaryResource renameFileInTemplate(String pFullName, String pNewName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, FileNotFoundException, AccessRightException, FileAlreadyExistsException, CreationException, StorageException { + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(pFullName)); + Locale userLocale = new Locale(user.getLanguage()); + checkNameFileValidity(pNewName, userLocale); + + BinaryResourceDAO binDAO = new BinaryResourceDAO(userLocale, em); + BinaryResource file = binDAO.loadBinaryResource(pFullName); + + try{ + binDAO.loadBinaryResource(file.getNewFullName(pNewName)); + throw new FileAlreadyExistsException(userLocale, pNewName); + }catch(FileNotFoundException e){ + DocumentMasterTemplate template = binDAO.getDocumentTemplateHolder(file); + + checkDocumentTemplateWriteAccess(template, user); + + dataManager.renameFile(file, pNewName); + template.removeFile(file); + binDAO.removeBinaryResource(file); + + BinaryResource newFile = new BinaryResource(file.getNewFullName(pNewName), file.getContentLength(), file.getLastModified()); + binDAO.createBinaryResource(newFile); + template.addFile(newFile); + return newFile; + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision updateDocument(DocumentIterationKey iKey, String pRevisionNote, List pAttributes, DocumentRevisionKey[] pLinkKeys, String[] documentLinkComments) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, AccessRightException, UserNotFoundException, UserNotActiveException { + DocumentRevisionKey rKey = new DocumentRevisionKey(iKey.getWorkspaceId(), iKey.getDocumentMasterId(), iKey.getDocumentRevisionVersion()); + User user = checkDocumentRevisionWriteAccess(rKey); + Locale userLocale = new Locale(user.getLanguage()); + + DocumentRevisionDAO docRDAO = new DocumentRevisionDAO(userLocale, em); + DocumentLinkDAO linkDAO = new DocumentLinkDAO(userLocale, em); + DocumentRevision docR = docRDAO.loadDocR(rKey); + //check access rights on docR ? + if (isCheckoutByUser(user, docR) && docR.getLastIteration().getKey().equals(iKey)) { + DocumentIteration doc = docR.getLastIteration(); + + if(pLinkKeys != null) { + Set currentLinks = new HashSet<>(doc.getLinkedDocuments()); + + for (DocumentLink link : currentLinks) { + doc.getLinkedDocuments().remove(link); + } + + int counter = 0; + for (DocumentRevisionKey link : pLinkKeys) { + if (!link.equals(iKey)) { + DocumentLink newLink = new DocumentLink(em.getReference(DocumentRevision.class, link)); + newLink.setComment(documentLinkComments[counter]); + linkDAO.createLink(newLink); + doc.getLinkedDocuments().add(newLink); + counter++; + } + } + } + + if (pAttributes != null) { + List currentAttrs = doc.getInstanceAttributes(); + boolean valid = AttributesConsistencyUtils.hasValidChange(pAttributes,docR.isAttributesLocked(),currentAttrs); + if(!valid) { + throw new NotAllowedException(userLocale, "NotAllowedException59"); + } + doc.setInstanceAttributes(pAttributes); + } + + doc.setRevisionNote(pRevisionNote); + Date now = new Date(); + doc.setModificationDate(now); + //doc.setLinkedDocuments(links); + return docR; + + } else { + throw new NotAllowedException(userLocale, "NotAllowedException25"); + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision[] createDocumentRevision(DocumentRevisionKey pOriginalDocRPK, String pTitle, String pDescription, String pWorkflowModelId, ACLUserEntry[] pACLUserEntries, ACLUserGroupEntry[] pACLUserGroupEntries, Map> userRoleMapping, Map> groupRoleMapping) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, NotAllowedException, DocumentRevisionAlreadyExistsException, CreationException, WorkflowModelNotFoundException, RoleNotFoundException, DocumentRevisionNotFoundException, FileAlreadyExistsException, UserGroupNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(pOriginalDocRPK.getDocumentMaster().getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + DocumentRevisionDAO docRDAO = new DocumentRevisionDAO(locale, em); + DocumentRevision originalDocR = docRDAO.loadDocR(pOriginalDocRPK); + DocumentMaster docM = originalDocR.getDocumentMaster(); + Folder folder = originalDocR.getLocation(); + checkFolderWritingRight(user, folder); + + if (originalDocR.isCheckedOut()) { + throw new NotAllowedException(locale, "NotAllowedException26"); + } + + if (originalDocR.getNumberOfIterations() == 0) { + throw new NotAllowedException(locale, "NotAllowedException27"); + } + + DocumentRevision docR = docM.createNextRevision(user); + + //create the first iteration which is a copy of the last one of the original docR + //of course we duplicate the iteration only if it exists ! + DocumentIteration lastDoc = originalDocR.getLastIteration(); + DocumentIteration firstIte = docR.createNextIteration(user); + if (lastDoc != null) { + BinaryResourceDAO binDAO = new BinaryResourceDAO(locale, em); + for (BinaryResource sourceFile : lastDoc.getAttachedFiles()) { + String fileName = sourceFile.getName(); + long length = sourceFile.getContentLength(); + Date lastModified = sourceFile.getLastModified(); + String fullName = docR.getWorkspaceId() + "/documents/" + docR.getId() + "/" + docR.getVersion() + "/1/" + fileName; + BinaryResource targetFile = new BinaryResource(fullName, length, lastModified); + binDAO.createBinaryResource(targetFile); + firstIte.addFile(targetFile); + try { + dataManager.copyData(sourceFile, targetFile); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + + Set links = new HashSet<>(); + for (DocumentLink link : lastDoc.getLinkedDocuments()) { + DocumentLink newLink = link.clone(); + links.add(newLink); + } + firstIte.setLinkedDocuments(links); + + List attrs = new ArrayList<>(); + for (InstanceAttribute attr : lastDoc.getInstanceAttributes()) { + InstanceAttribute clonedAttribute = attr.clone(); + attrs.add(clonedAttribute); + } + firstIte.setInstanceAttributes(attrs); + } + + if (pWorkflowModelId != null) { + + UserDAO userDAO = new UserDAO(locale, em); + UserGroupDAO groupDAO = new UserGroupDAO(locale, em); + RoleDAO roleDAO = new RoleDAO(locale, em); + + Map> roleUserMap = new HashMap<>(); + for (Map.Entry> pair : userRoleMapping.entrySet()) { + String roleName = pair.getKey(); + Collection userLogins = pair.getValue(); + Role role = roleDAO.loadRole(new RoleKey(pOriginalDocRPK.getDocumentMaster().getWorkspace(), roleName)); + Set users=new HashSet<>(); + roleUserMap.put(role, users); + for(String login:userLogins) { + User u = userDAO.loadUser(new UserKey(pOriginalDocRPK.getDocumentMaster().getWorkspace(), login)); + users.add(u); + } + } + + Map> roleGroupMap = new HashMap<>(); + for (Map.Entry> pair : groupRoleMapping.entrySet()) { + String roleName = pair.getKey(); + Collection groupIds = pair.getValue(); + Role role = roleDAO.loadRole(new RoleKey(pOriginalDocRPK.getDocumentMaster().getWorkspace(), roleName)); + Set groups=new HashSet<>(); + roleGroupMap.put(role, groups); + for(String groupId:groupIds) { + UserGroup g = groupDAO.loadUserGroup(new UserGroupKey(pOriginalDocRPK.getDocumentMaster().getWorkspace(), groupId)); + groups.add(g); + } + } + + WorkflowModel workflowModel = new WorkflowModelDAO(locale, em).loadWorkflowModel(new WorkflowModelKey(user.getWorkspaceId(), pWorkflowModelId)); + Workflow workflow = workflowModel.createWorkflow(roleUserMap,roleGroupMap); + docR.setWorkflow(workflow); + + for(Task task : workflow.getTasks()){ + if(!task.hasPotentialWorker()){ + throw new NotAllowedException(locale,"NotAllowedException56"); + } + } + + Collection runningTasks = workflow.getRunningTasks(); + for (Task runningTask : runningTasks) { + runningTask.start(); + } + + mailer.sendApproval(runningTasks, docR); + } + + docR.setTitle(pTitle); + docR.setDescription(pDescription); + Map userEntries = new HashMap<>(); + Map groupEntries = new HashMap<>(); + + if (pACLUserEntries != null) { + for (ACLUserEntry entry : pACLUserEntries) { + userEntries.put(entry.getPrincipalLogin(), entry.getPermission().name()); + } + } + + if (pACLUserGroupEntries != null) { + for (ACLUserGroupEntry entry : pACLUserGroupEntries) { + groupEntries.put(entry.getPrincipal().getId(), entry.getPermission().name()); + } + } + + if (!userEntries.isEmpty() || !groupEntries.isEmpty()) { + ACLFactory aclFactory = new ACLFactory(em); + ACL acl = aclFactory.createACL(docR.getWorkspaceId(), userEntries, groupEntries); + docR.setACL(acl); + } + + Date now = new Date(); + docR.setCreationDate(now); + docR.setLocation(folder); + docR.setCheckOutUser(user); + docR.setCheckOutDate(now); + firstIte.setCreationDate(now); + firstIte.setModificationDate(now); + + docRDAO.createDocR(docR); + return new DocumentRevision[]{originalDocR, docR}; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void subscribeToStateChangeEvent(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException { + User user = checkDocumentRevisionReadAccess(pDocRPK); + Locale userLocale = new Locale(user.getLanguage()); + DocumentRevision docR = new DocumentRevisionDAO(userLocale, em).loadDocR(pDocRPK); + if (isAnotherUserHomeFolder(user, docR.getLocation())) { + throw new NotAllowedException(userLocale, "NotAllowedException30"); + } + + new SubscriptionDAO(em).createStateChangeSubscription(new StateChangeSubscription(user, docR)); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void unsubscribeToStateChangeEvent(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException, DocumentRevisionNotFoundException { + User user = checkDocumentRevisionReadAccess(pDocRPK); + SubscriptionKey key = new SubscriptionKey(user.getWorkspaceId(), user.getLogin(), pDocRPK.getDocumentMaster().getWorkspace(), pDocRPK.getDocumentMaster().getId(), pDocRPK.getVersion()); + new SubscriptionDAO(em).removeStateChangeSubscription(key); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void subscribeToIterationChangeEvent(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException { + User user = checkDocumentRevisionReadAccess(pDocRPK); + Locale userLocale = new Locale(user.getLanguage()); + DocumentRevision docR = new DocumentRevisionDAO(userLocale, em).getDocRRef(pDocRPK); + if (isAnotherUserHomeFolder(user, docR.getLocation())) { + throw new NotAllowedException(userLocale, "NotAllowedException30"); + } + + new SubscriptionDAO(em).createIterationChangeSubscription(new IterationChangeSubscription(user, docR)); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void unsubscribeToIterationChangeEvent(DocumentRevisionKey pDocRPK) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException, DocumentRevisionNotFoundException { + User user = checkDocumentRevisionReadAccess(pDocRPK); + SubscriptionKey key = new SubscriptionKey(user.getWorkspaceId(), user.getLogin(), pDocRPK.getDocumentMaster().getWorkspace(), pDocRPK.getDocumentMaster().getId(), pDocRPK.getVersion()); + new SubscriptionDAO(em).removeIterationChangeSubscription(key); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public String[] getTags(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + Tag[] tags = new TagDAO(new Locale(user.getLanguage()), em).findAllTags(pWorkspaceId); + + String[] labels = new String[tags.length]; + int i = 0; + for (Tag t : tags) { + labels[i++] = t.getLabel(); + } + return labels; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public long getDiskUsageForDocumentsInWorkspace(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException { + Account account = userManager.checkAdmin(pWorkspaceId); + DocumentRevisionDAO documentRevisionDAO = new DocumentRevisionDAO(new Locale(account.getLanguage()), em); + return documentRevisionDAO.getDiskUsageForDocumentsInWorkspace(pWorkspaceId); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public long getDiskUsageForDocumentTemplatesInWorkspace(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException { + Account account = userManager.checkAdmin(pWorkspaceId); + DocumentRevisionDAO documentRevisionDAO = new DocumentRevisionDAO(new Locale(account.getLanguage()), em); + return documentRevisionDAO.getDiskUsageForDocumentTemplatesInWorkspace(pWorkspaceId); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public SharedDocument createSharedDocument(DocumentRevisionKey pDocRPK, String pPassword, Date pExpireDate) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, UserNotActiveException, NotAllowedException { + User user = userManager.checkWorkspaceWriteAccess(pDocRPK.getDocumentMaster().getWorkspace()); + SharedDocument sharedDocument = new SharedDocument(user.getWorkspace(), user, pExpireDate, pPassword, getDocumentRevision(pDocRPK)); + SharedEntityDAO sharedEntityDAO = new SharedEntityDAO(new Locale(user.getLanguage()), em); + sharedEntityDAO.createSharedDocument(sharedDocument); + return sharedDocument; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public void deleteSharedDocument(SharedEntityKey sharedEntityKey) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, SharedEntityNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(sharedEntityKey.getWorkspace()); + SharedEntityDAO sharedEntityDAO = new SharedEntityDAO(new Locale(user.getLanguage()), em); + SharedDocument sharedDocument = sharedEntityDAO.loadSharedDocument(sharedEntityKey.getUuid()); + sharedEntityDAO.deleteSharedDocument(sharedDocument); + } + + + @RolesAllowed({UserGroupMapping.GUEST_PROXY_ROLE_ID, UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public boolean canAccess(DocumentRevisionKey docRKey) throws DocumentRevisionNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + DocumentRevision documentRevision; + if (contextManager.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)) { + documentRevision = new DocumentRevisionDAO(em).loadDocR(docRKey); + return documentRevision.isPublicShared(); + } + + User user = userManager.checkWorkspaceReadAccess(docRKey.getDocumentMaster().getWorkspace()); + return canUserAccess(user, docRKey); + } + + @RolesAllowed({UserGroupMapping.GUEST_PROXY_ROLE_ID, UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public boolean canAccess(DocumentIterationKey docIKey) throws DocumentRevisionNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + DocumentRevision documentRevision; + if (contextManager.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)) { + documentRevision = new DocumentRevisionDAO(em).loadDocR(docIKey.getDocumentRevision()); + DocumentIteration lastCheckedInIteration = documentRevision.getLastCheckedInIteration(); + return documentRevision.isPublicShared() && null != lastCheckedInIteration && lastCheckedInIteration.getIteration() >= docIKey.getIteration(); + } + + User user = userManager.checkWorkspaceReadAccess(docIKey.getWorkspaceId()); + return canUserAccess(user, docIKey); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public boolean canUserAccess(User user, DocumentRevisionKey docRKey) throws DocumentRevisionNotFoundException { + DocumentRevision docRevision = new DocumentRevisionDAO(new Locale(user.getLanguage()), em).loadDocR(docRKey); + return hasDocumentRevisionReadAccess(user, docRevision); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public boolean canUserAccess(User user, DocumentIterationKey docIKey) throws DocumentRevisionNotFoundException { + DocumentRevision docRevision = new DocumentRevisionDAO(new Locale(user.getLanguage()), em).loadDocR(docIKey.getDocumentRevision()); + return hasDocumentRevisionReadAccess(user, docRevision) && + (docRevision.getLastIteration().getIteration() > docIKey.getIteration() || + !isCheckoutByAnotherUser(user, docRevision)); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public List getInverseDocumentsLink(DocumentRevisionKey docKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException { + User user = userManager.checkWorkspaceReadAccess(docKey.getWorkspaceId()); + + Locale locale = new Locale(user.getLanguage()); + + DocumentRevision documentRevision = new DocumentRevisionDAO(locale, em).loadDocR(docKey); + + DocumentLinkDAO documentLinkDAO = new DocumentLinkDAO(locale, em); + List iterations = documentLinkDAO.getInverseDocumentsLinks(documentRevision); + + ListIterator ite = iterations.listIterator(); + + while (ite.hasNext()) { + DocumentIteration next = ite.next(); + if (!canAccess(next.getKey())) { + ite.remove(); + } + } + + return iterations; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision[] getDocumentRevisionsWithAssignedTasksForGivenUser(String pWorkspaceId, String assignedUserLogin) + throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + List docRs = new DocumentRevisionDAO(new Locale(user.getLanguage()), em).findDocsWithAssignedTasksForGivenUser(pWorkspaceId, assignedUserLogin); + + ListIterator ite = docRs.listIterator(); + while (ite.hasNext()) { + DocumentRevision docR = ite.next(); + if (!hasDocumentRevisionReadAccess(user, docR)) { + ite.remove(); + } else if (isCheckoutByAnotherUser(user, docR)) { + em.detach(docR); + docR.removeLastIteration(); + } + } + + return docRs.toArray(new DocumentRevision[docRs.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision[] getDocumentRevisionsWithOpenedTasksForGivenUser(String pWorkspaceId, String assignedUserLogin) + throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + List docRs = new DocumentRevisionDAO(new Locale(user.getLanguage()), em).findDocsWithOpenedTasksForGivenUser(pWorkspaceId, assignedUserLogin); + + ListIterator ite = docRs.listIterator(); + while (ite.hasNext()) { + DocumentRevision docR = ite.next(); + if (!hasDocumentRevisionReadAccess(user, docR)) { + ite.remove(); + } else if (isCheckoutByAnotherUser(user, docR)) { + em.detach(docR); + docR.removeLastIteration(); + } + } + + return docRs.toArray(new DocumentRevision[docRs.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void createDocumentLog(DocumentLog log) { + em.persist(log); + } + + + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision releaseDocumentRevision(DocumentRevisionKey pRevisionKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, DocumentRevisionNotFoundException, AccessRightException, NotAllowedException { + User user = checkDocumentRevisionWriteAccess(pRevisionKey); // Check if the user can write the document + Locale locale = new Locale(user.getLanguage()); + + DocumentRevisionDAO documentRevisionDAO = new DocumentRevisionDAO(locale, em); + DocumentRevision documentRevision = documentRevisionDAO.loadDocR(pRevisionKey); + + if (documentRevision.isCheckedOut()) { + throw new NotAllowedException(locale, "NotAllowedException63"); + } + + if (documentRevision.getNumberOfIterations() == 0) { + throw new NotAllowedException(locale, "NotAllowedException27"); + } + + if (documentRevision.isObsolete()) { + throw new NotAllowedException(locale, "NotAllowedException64"); + } + + documentRevision.release(user); + return documentRevision; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision markDocumentRevisionAsObsolete(DocumentRevisionKey pRevisionKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, DocumentRevisionNotFoundException, AccessRightException, NotAllowedException { + User user = checkDocumentRevisionWriteAccess(pRevisionKey); // Check if the user can write the document + Locale locale = new Locale(user.getLanguage()); + + DocumentRevisionDAO documentRevisionDAO = new DocumentRevisionDAO(locale, em); + DocumentRevision documentRevision = documentRevisionDAO.loadDocR(pRevisionKey); + + if (!documentRevision.isReleased()) { + throw new NotAllowedException(locale, "NotAllowedException65"); + } + + documentRevision.markAsObsolete(user); + return documentRevision; + } + + /** + * Apply read access policy on a document revision + * + * @param user The user to test + * @param documentRevision The document revision to test + * @return The readable document revision or null if the user has no access to it. + */ + private DocumentRevision applyDocumentRevisionReadAccess(User user, DocumentRevision documentRevision) { + if (hasDocumentRevisionReadAccess(user, documentRevision)) { + if (!isCheckoutByAnotherUser(user, documentRevision)) { + return documentRevision; + } + em.detach(documentRevision); + documentRevision.removeLastIteration(); + return documentRevision; + } + return null; + } + + + /** + * Check if the current account have read access on a document revision. + * + * @param documentRevisionKey The key of the document revision. + * @return The user if he has read access to the document revision. + * @throws UserNotFoundException If there are any User matching the Workspace and the current account login. + * @throws UserNotActiveException If the user is not actif. + * @throws WorkspaceNotFoundException If the workspace doesn't exist. + * @throws AccessRightException If the user has no read access to the document revision. + * @throws DocumentRevisionNotFoundException If the document revision doesn't exist. + */ + private User checkDocumentRevisionReadAccess(DocumentRevisionKey documentRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, DocumentRevisionNotFoundException { + User user = userManager.checkWorkspaceReadAccess(documentRevisionKey.getDocumentMaster().getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + DocumentRevision documentRevision = new DocumentRevisionDAO(locale, em).loadDocR(documentRevisionKey); + + if (!hasDocumentRevisionReadAccess(user, documentRevision)) { + throw new AccessRightException(locale, user); + } + + return user; + } + + /** + * Check if the current account user have write access on a document revision. + * + * @param documentRevisionKey The key of the document revision. + * @return The user if he has write access to the document revision. + * @throws UserNotFoundException If there are any User matching the Workspace and the current account login. + * @throws UserNotActiveException If the user is not actif. + * @throws WorkspaceNotFoundException If the workspace doesn't exist. + * @throws AccessRightException If the user has no write access to the document revision. + * @throws DocumentRevisionNotFoundException If the document revision doesn't exist. + */ + private User checkDocumentRevisionWriteAccess(DocumentRevisionKey documentRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, DocumentRevisionNotFoundException, NotAllowedException { + User user = userManager.checkWorkspaceReadAccess(documentRevisionKey.getDocumentMaster().getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + if (user.isAdministrator()) { + return user; + } + DocumentRevision documentRevision = new DocumentRevisionDAO(locale, em).loadDocR(documentRevisionKey); + + if (documentRevision.getACL() == null) { + return userManager.checkWorkspaceWriteAccess(documentRevisionKey.getDocumentMaster().getWorkspace()); + } else if (hasDocumentRevisionWriteAccess(user, documentRevision)) { + return user; + } else if (isInAnotherUserHomeFolder(user, documentRevision)) { + throw new NotAllowedException(locale, "NotAllowedException5"); + } else { + throw new AccessRightException(locale, user); + } + } + + + /** + * Check if a user, which have access to the workspace, have the right of change the folder structure. + * + * @param pUser A user which have read access to the workspace. + * @throws NotAllowedException If access is deny. + */ + private void checkFoldersStructureChangeRight(User pUser) throws NotAllowedException { + Workspace wks = pUser.getWorkspace(); + if (wks.isFolderLocked() && !pUser.isAdministrator()) { + throw new NotAllowedException(new Locale(pUser.getLanguage()), "NotAllowedException7"); + } + } + + /** + * Check if a user, which have access to the workspace, have write access in a folder + * + * @param pUser A user which have read access to the workspace + * @param pFolder The folder wanted + * @return The folder access is granted. + * @throws NotAllowedException If the folder access is deny. + */ + private Folder checkFolderWritingRight(User pUser, Folder pFolder) throws NotAllowedException { + if (isAnotherUserHomeFolder(pUser, pFolder)) { + throw new NotAllowedException(new Locale(pUser.getLanguage()), "NotAllowedException33"); + } + return pFolder; + } + + + /** + * Say if a user, which have access to the workspace, have read access to a document revision + * + * @param user A user which have read access to the workspace + * @param documentRevision The document revision wanted + * @return True if access is granted, False otherwise + */ + private boolean hasDocumentRevisionReadAccess(User user, DocumentRevision documentRevision) { + return documentRevision.isPublicShared() || hasPrivateDocumentRevisionReadAccess(user, documentRevision); + } + + private boolean hasPrivateDocumentRevisionReadAccess(User user, DocumentRevision documentRevision) { + return isInSameWorkspace(user, documentRevision) && + (user.isAdministrator() || isACLGrantReadAccess(user, documentRevision)) && + !isInAnotherUserHomeFolder(user, documentRevision); + } + + private boolean hasDocumentMasterTemplateReadAccess(DocumentMasterTemplate template, User user) { + return isInSameWorkspace(user, template) && (user.isAdministrator() || isACLGrantReadAccess(user, template)); + } + + /** + * Say if a user, which have access to the workspace, have write access to a document revision + * + * @param user A user which have read access to the workspace + * @param documentRevision The document revision wanted + * @return True if access is granted, False otherwise + */ + private boolean hasDocumentRevisionWriteAccess(User user, DocumentRevision documentRevision) { + return isInSameWorkspace(user, documentRevision) && + (user.isAdministrator() || isACLGrantWriteAccess(user, documentRevision)) && + !isInAnotherUserHomeFolder(user, documentRevision); + } + + private boolean isInSameWorkspace(User user, DocumentRevision documentRevision) { + return user.getWorkspaceId().equals(documentRevision.getWorkspaceId()); + } + + private boolean isInSameWorkspace(User user, DocumentMasterTemplate template) { + return user.getWorkspaceId().equals(template.getWorkspaceId()); + } + + private boolean isAuthor(User user, DocumentRevision documentRevision) { + return documentRevision.getAuthor().getLogin().equals(user.getLogin()); + } + + private boolean isACLGrantReadAccess(User user, DocumentRevision documentRevision) { + return documentRevision.getACL() == null || documentRevision.getACL().hasReadAccess(user); + } + + private boolean isACLGrantReadAccess(User user, DocumentMasterTemplate template) { + return template.getAcl() == null || template.getAcl().hasReadAccess(user); + } + + private boolean isACLGrantWriteAccess(User user, DocumentRevision documentRevision) { + return documentRevision.getACL() == null || documentRevision.getACL().hasWriteAccess(user); + } + + private boolean isAnotherUserHomeFolder(User user, Folder folder) { + return folder.isPrivate() && !folder.getOwner().equals(user.getLogin()); + } + + private boolean isInAnotherUserHomeFolder(User user, DocumentRevision documentRevision) { + return isAnotherUserHomeFolder(user, documentRevision.getLocation()); + } + + private boolean isCheckoutByUser(User user, DocumentRevision documentRevision) { + return documentRevision.isCheckedOut() && documentRevision.getCheckOutUser().equals(user); + } + + private boolean isCheckoutByAnotherUser(User user, DocumentRevision documentRevision) { + return documentRevision.isCheckedOut() && !documentRevision.getCheckOutUser().equals(user); + } + + + private void checkNameValidity(String name, Locale locale) throws NotAllowedException { + if (!NamingConvention.correct(name)) { + throw new NotAllowedException(locale, "NotAllowedException9", name); + } + } + + private void checkNameFileValidity(String name, Locale locale) throws NotAllowedException { + if (name != null) { + name = name.trim(); + } + if (!NamingConvention.correctNameFile(name)) { + throw new NotAllowedException(locale, "NotAllowedException9", name); + } + } + + private User checkDocumentTemplateWriteAccess(DocumentMasterTemplate docTemplate, User user) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException { + if (user.isAdministrator()) { + // Check if it is the workspace's administrator + return user; + } + if (docTemplate.getAcl() == null) { + // Check if the item haven't ACL + return userManager.checkWorkspaceWriteAccess(docTemplate.getWorkspaceId()); + } else if (docTemplate.getAcl().hasWriteAccess(user)) { + // Check if there is a write access + return user; + } else { + // Else throw a AccessRightException + throw new AccessRightException(new Locale(user.getLanguage()), user); + } + + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentPostUploaderBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentPostUploaderBean.java new file mode 100644 index 0000000000..00b818118d --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentPostUploaderBean.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.services.IDocumentPostUploaderManagerLocal; +import com.docdoku.server.postuploaders.DocumentPostUploader; + +import javax.ejb.Asynchronous; +import javax.ejb.Stateless; +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + + +/** + * Document Post Uploader + */ +@Stateless(name="DocumentPostUploaderBean") +public class DocumentPostUploaderBean implements IDocumentPostUploaderManagerLocal { + + @Inject + @Any + private Instance documentPostUploaders; + + @Override + @Asynchronous + public void process(BinaryResource binaryResource){ + for (DocumentPostUploader documentPostUploader : documentPostUploaders) { + if (documentPostUploader.canProcess(binaryResource)) { + documentPostUploader.process(binaryResource); + } + } + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentResourceGetterBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentResourceGetterBean.java new file mode 100644 index 0000000000..f3745fc3d7 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentResourceGetterBean.java @@ -0,0 +1,139 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.User; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.exceptions.ConvertedResourceException; +import com.docdoku.core.exceptions.UserNotActiveException; +import com.docdoku.core.exceptions.UserNotFoundException; +import com.docdoku.core.exceptions.WorkspaceNotFoundException; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.*; +import com.docdoku.server.resourcegetters.DocumentResourceGetter; + +import javax.ejb.Stateless; +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; +import java.io.InputStream; +import java.util.Locale; + + +/** + * Resource Getter + */ +@Stateless(name="DocumentResourceGetterBean") +public class DocumentResourceGetterBean implements IDocumentResourceGetterManagerLocal { + + @Inject + @Any + private Instance documentResourceGetters; + + @Inject + private IDocumentManagerLocal documentService; + + @Inject + private IProductManagerLocal productService; + + @Inject + private IContextManagerLocal contextManager; + + @Inject + private IUserManagerLocal userManager; + + @Override + public InputStream getDocumentConvertedResource(String outputFormat, BinaryResource binaryResource) + throws WorkspaceNotFoundException, UserNotActiveException, UserNotFoundException, ConvertedResourceException { + + DocumentIteration docI; + Locale locale; + + if(contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)) { + User user = userManager.whoAmI(binaryResource.getWorkspaceId()); + locale = new Locale(user.getLanguage()); + }else{ + locale = Locale.getDefault(); + } + + docI = documentService.findDocumentIterationByBinaryResource(binaryResource); + + DocumentResourceGetter selectedDocumentResourceGetter = null; + for (DocumentResourceGetter documentResourceGetter : documentResourceGetters) { + if (documentResourceGetter.canGetConvertedResource(outputFormat, binaryResource)) { + selectedDocumentResourceGetter = documentResourceGetter; + break; + } + } + if (selectedDocumentResourceGetter != null) { + return selectedDocumentResourceGetter.getConvertedResource(outputFormat, binaryResource,docI,locale); + } + + return null; + } + + @Override + public InputStream getPartConvertedResource(String outputFormat, BinaryResource binaryResource) + throws WorkspaceNotFoundException, UserNotActiveException, UserNotFoundException, ConvertedResourceException { + + PartIteration partIteration; + Locale locale; + + if(contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)) { + User user = userManager.whoAmI(binaryResource.getWorkspaceId()); + locale = new Locale(user.getLanguage()); + }else{ + locale = Locale.getDefault(); + } + + partIteration = productService.findPartIterationByBinaryResource(binaryResource); + + DocumentResourceGetter selectedDocumentResourceGetter = null; + for (DocumentResourceGetter documentResourceGetter : documentResourceGetters) { + if (documentResourceGetter.canGetConvertedResource(outputFormat, binaryResource)) { + selectedDocumentResourceGetter = documentResourceGetter; + break; + } + } + if (selectedDocumentResourceGetter != null) { + return selectedDocumentResourceGetter.getConvertedResource(outputFormat, binaryResource,partIteration,locale); + } + + return null; + } + + @Override + public String getSubResourceVirtualPath(BinaryResource binaryResource, String subResourceUri) { + DocumentResourceGetter selectedDocumentResourceGetter = null; + for (DocumentResourceGetter documentResourceGetter : documentResourceGetters) { + if (documentResourceGetter.canGetSubResourceVirtualPath(binaryResource)) { + selectedDocumentResourceGetter = documentResourceGetter; + break; + } + } + if (selectedDocumentResourceGetter != null) { + return selectedDocumentResourceGetter.getSubResourceVirtualPath(binaryResource, subResourceUri); + } + return ""; + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentViewerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentViewerBean.java new file mode 100644 index 0000000000..ea266cacb7 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/DocumentViewerBean.java @@ -0,0 +1,102 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.services.IFileViewerManagerLocal; +import com.docdoku.server.viewers.DocumentViewer; +import com.docdoku.server.viewers.ViewerUtils; +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; + +import javax.annotation.security.DeclareRoles; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; +import java.io.IOException; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) +@Local(IFileViewerManagerLocal.class) +@Stateless(name="DocumentViewerBean") +public class DocumentViewerBean implements IFileViewerManagerLocal { + + @Inject + private IDataManagerLocal dataManager; + + @Inject + @Any + private Instance documentViewers; + + private static final Logger LOGGER = Logger.getLogger(DocumentViewerBean.class.getName()); + + @Override + public String getHtmlForViewer(BinaryResource binaryResource, String uuid) { + String template; + DocumentViewer documentViewerSelected = selectViewerForTemplate(binaryResource); + + try { + if (documentViewerSelected != null) { + template = documentViewerSelected.renderHtmlForViewer(binaryResource,uuid); + }else{ + template = getDefaultTemplate(binaryResource,uuid); + } + } catch (Exception e) { + LOGGER.log(Level.INFO, null, e); + template = new StringBuilder().append("

").append("Can't render ").append(binaryResource.getName()).append("

").toString(); + } + + return template; + } + + private DocumentViewer selectViewerForTemplate(BinaryResource binaryResource) { + DocumentViewer selectedDocumentViewer = null; + for (DocumentViewer documentViewer : documentViewers) { + if (documentViewer.canRenderViewerTemplate(binaryResource)) { + selectedDocumentViewer = documentViewer; + break; + } + } + return selectedDocumentViewer; + } + + private String getDefaultTemplate(BinaryResource binaryResource,String uuid) throws IOException { + + MustacheFactory mf = new DefaultMustacheFactory(); + Mustache mustache = mf.compile("com/docdoku/server/viewers/default_viewer.mustache"); + Map scopes = new HashMap<>(); + scopes.put("uriResource", ViewerUtils.getURI(binaryResource, uuid)); + scopes.put("fileName", binaryResource.getName()); + StringWriter templateWriter = new StringWriter(); + mustache.execute(templateWriter, scopes).flush(); + + return ViewerUtils.getViewerTemplate(dataManager, binaryResource, uuid, templateWriter.toString(),false); + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/EntityManagerProducer.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/EntityManagerProducer.java new file mode 100644 index 0000000000..4b6a6088bc --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/EntityManagerProducer.java @@ -0,0 +1,60 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Default; +import javax.enterprise.inject.Disposes; +import javax.enterprise.inject.Produces; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.PersistenceUnit; + +/** + * @author morgan on 04/09/15. + */ +@ApplicationScoped +public class EntityManagerProducer { + + @PersistenceUnit + private EntityManagerFactory entityManagerFactory; + + private EntityManager em; + + @PostConstruct + protected void postConstruct() { + em = entityManagerFactory.createEntityManager(); + } + + @Produces + @Default + @ApplicationScoped + public EntityManager create() { + return em; + } + + public void dispose(@Disposes @Default EntityManager entityManager){ + if (entityManager.isOpen()){ + entityManager.close(); + } + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/FileImport.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/FileImport.java new file mode 100644 index 0000000000..973e32c2d7 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/FileImport.java @@ -0,0 +1,36 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import javax.interceptor.InterceptorBinding; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Inherited +@InterceptorBinding +@Target({TYPE, METHOD}) +@Retention(RUNTIME) +public @interface FileImport { +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/FileImportInterceptor.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/FileImportInterceptor.java new file mode 100644 index 0000000000..70666c1f78 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/FileImportInterceptor.java @@ -0,0 +1,84 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.product.Import; +import com.docdoku.core.product.ImportResult; +import com.docdoku.core.services.IProductManagerLocal; + +import javax.ejb.EJB; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; + +@FileImport +@Interceptor +public class FileImportInterceptor { + + @Inject + private IProductManagerLocal productService; + + private static final Logger LOGGER = Logger.getLogger(FileImportInterceptor.class.getName()); + + @AroundInvoke + public Object createImport(InvocationContext ctx) throws Exception { + + Object[] parameters = ctx.getParameters(); + // TODO : check parameters before cast + String workspaceId = (String) parameters[0]; + File file = (File) parameters[1]; + String originalFileName = (String) parameters[2]; + + Import newImport = productService.createImport(workspaceId, originalFileName); + String id = newImport.getId(); + + ImportResult importResult = null; + + try{ // Run the import + + Object proceed = ctx.proceed(); + Future result = (Future) proceed; + importResult = result.get(); + return proceed; + + }catch(Exception e){ + + LOGGER.log(Level.SEVERE,"Cannot import the file", e); + List errors = new ArrayList<>(); + List warnings = new ArrayList<>(); + errors.add("Unhandled exception"); + importResult = new ImportResult(file, originalFileName, warnings, errors); + return null; + + }finally { + productService.endImport(workspaceId, id, importResult); + } + + } + + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ImporterBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ImporterBean.java new file mode 100644 index 0000000000..303ab338df --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ImporterBean.java @@ -0,0 +1,152 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server; + + +import com.docdoku.core.common.User; +import com.docdoku.core.product.ImportPreview; +import com.docdoku.core.product.ImportResult; +import com.docdoku.core.services.IImporterManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.importers.PartImporter; +import com.docdoku.server.importers.PathDataImporter; + +import javax.ejb.AsyncResult; +import javax.ejb.Asynchronous; +import javax.ejb.EJB; +import javax.ejb.Stateless; +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.concurrent.Future; +import java.util.logging.Logger; + +/** + * Attributes importer + * + * @author Elisabel Généreux + */ +@Stateless(name = "ImporterBean") +public class ImporterBean implements IImporterManagerLocal { + + + + private static final Logger LOGGER = Logger.getLogger(ImporterBean.class.getName()); + + @Inject + @Any + private Instance partImporters; + + @Inject + @Any + private Instance pathDataImporters; + + @Inject + private IUserManagerLocal userManager; + + @Override + @Asynchronous + @FileImport + public Future importIntoParts(String workspaceId, File file, String originalFileName, String revisionNote, boolean autoCheckout, boolean autoCheckin, boolean permissiveUpdate) throws Exception{ + PartImporter selectedImporter = null; + + for (PartImporter importer : partImporters) { + if (importer.canImportFile(file.getName())) { + selectedImporter = importer; + break; + } + } + + ImportResult result; + + if (selectedImporter != null) { + result = selectedImporter.importFile(workspaceId, file, revisionNote, autoCheckout, autoCheckin, permissiveUpdate); + } else { + User user = userManager.whoAmI(workspaceId); + result = getNoImporterAvailableError(file, originalFileName,new Locale(user.getLanguage())); + } + + return new AsyncResult<>(result); + } + + @Override + @Asynchronous + @FileImport + public Future importIntoPathData(String workspaceId, File file, String originalFileName, String revisionNote, boolean autoFreezeAfterUpdate, boolean permissiveUpdate) throws Exception{ + PathDataImporter selectedImporter = null; + + for (PathDataImporter importer : pathDataImporters) { + if (importer.canImportFile(file.getName())) { + selectedImporter = importer; + break; + } + } + + ImportResult result; + + if (selectedImporter != null) { + result = selectedImporter.importFile(workspaceId, file, revisionNote, autoFreezeAfterUpdate, permissiveUpdate); + } else { + + User user = userManager.whoAmI(workspaceId); + Locale locale = new Locale(user.getLanguage()); + result = getNoImporterAvailableError(file, originalFileName,locale); + } + + return new AsyncResult<>(result); + } + + @Override + public ImportPreview dryRunImportIntoParts(String workspaceId, File file, String originalFileName, boolean autoCheckout, boolean autoCheckin, boolean permissiveUpdate) throws Exception { + + PartImporter selectedImporter = null; + + for (PartImporter importer : partImporters) { + if (importer.canImportFile(file.getName())) { + selectedImporter = importer; + break; + } + } + + ImportPreview result = null; + + if (selectedImporter != null) { + result = new ImportPreview(); + result.setPartRevisions(selectedImporter.dryRunImport(workspaceId,file,originalFileName,autoCheckout, autoCheckin, permissiveUpdate)); + } + + return result; + + } + + public ImportResult getNoImporterAvailableError(File file, String fileName, Locale locale) { + List errors = new ArrayList<>(); + List warnings = new ArrayList<>(); + errors.add(ResourceBundle.getBundle("com.docdoku.core.i18n.LocalStrings", locale).getString("NoImporterAvailable")); + ImportResult result = new ImportResult(file, fileName, warnings, errors); + return result; + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/LOVManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/LOVManagerBean.java new file mode 100644 index 0000000000..ae0d6865b5 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/LOVManagerBean.java @@ -0,0 +1,176 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.document.DocumentMasterTemplate; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.meta.ListOfValues; +import com.docdoku.core.meta.ListOfValuesKey; +import com.docdoku.core.meta.NameValuePair; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartMasterTemplate; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.ILOVManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.dao.*; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.List; +import java.util.Locale; + +/** + * @author Lebeau Julien on 03/03/15. + */ +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) +@Local(ILOVManagerLocal.class) +@Stateless(name = "LOVManagerBean") +public class LOVManagerBean implements ILOVManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IUserManagerLocal userManager; + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List findLOVFromWorkspace(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(workspaceId); + LOVDAO lovDAO = new LOVDAO(new Locale(user.getLanguage()),em); + return lovDAO.loadLOVList(workspaceId); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ListOfValues findLov(ListOfValuesKey lovKey) throws ListOfValuesNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(lovKey.getWorkspaceId()); + LOVDAO lovDAO = new LOVDAO(new Locale(user.getLanguage()),em); + + return lovDAO.loadLOV(lovKey); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void createLov(String workspaceId, String name, List nameValuePairList) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, ListOfValuesAlreadyExistsException, CreationException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + userManager.checkWorkspaceWriteAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + LOVDAO lovDAO = new LOVDAO(locale, em); + + if (name == null || name.trim().isEmpty()){ + throw new CreationException("LOVNameEmptyException"); + } + + if (nameValuePairList == null || nameValuePairList.isEmpty()){ + throw new CreationException("LOVPossibleValueException"); + } + + WorkspaceDAO workspaceDAO = new WorkspaceDAO(locale, em); + Workspace workspace = workspaceDAO.loadWorkspace(workspaceId); + + ListOfValues lov = new ListOfValues(workspace, name); + lov.setValues(nameValuePairList); + + lovDAO.createLOV(lov); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteLov(ListOfValuesKey lovKey) throws ListOfValuesNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, EntityConstraintException { + User user = userManager.checkWorkspaceReadAccess(lovKey.getWorkspaceId()); + userManager.checkWorkspaceWriteAccess(lovKey.getWorkspaceId()); + Locale locale = new Locale(user.getLanguage()); + LOVDAO lovDAO = new LOVDAO(locale,em); + + if (this.isLovUsedInDocumentMasterTemplate(lovKey)){ + throw new EntityConstraintException(locale,"EntityConstraintException14"); + } + + if (this.isLovUsedInPartMasterTemplate(lovKey)){ + throw new EntityConstraintException(locale,"EntityConstraintException15"); + } + + ListOfValues lov = lovDAO.loadLOV(lovKey); + lovDAO.deleteLOV(lov); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ListOfValues updateLov(ListOfValuesKey lovKey, String name, String workspaceId, List nameValuePairList) throws ListOfValuesAlreadyExistsException, CreationException, ListOfValuesNotFoundException, UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(lovKey.getWorkspaceId()); + userManager.checkWorkspaceWriteAccess(lovKey.getWorkspaceId()); + LOVDAO lovDAO = new LOVDAO(new Locale(user.getLanguage()),em); + + ListOfValues lovToUpdate = this.findLov(lovKey); + + lovToUpdate.setValues(nameValuePairList); + + return lovDAO.updateLOV(lovToUpdate); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public boolean isLOVDeletable(ListOfValuesKey lovKey){ + return !isLovUsedInDocumentMasterTemplate(lovKey) && !isLovUsedInPartMasterTemplate(lovKey) && !isLovUsedInPartIterationInstanceAttributeTemplates(lovKey); + } + + private boolean isLovUsedInDocumentMasterTemplate(ListOfValuesKey lovKey){ + DocumentMasterTemplateDAO documentMasterTemplateDAO = new DocumentMasterTemplateDAO(em); + + List documentsUsingLOV = documentMasterTemplateDAO.findAllDocMTemplatesFromLOV(lovKey); + if (documentsUsingLOV != null && !documentsUsingLOV.isEmpty()){ + return true; + } + + return false; + } + + private boolean isLovUsedInPartMasterTemplate(ListOfValuesKey lovKey){ + PartMasterTemplateDAO partMasterTemplateDAO = new PartMasterTemplateDAO(em); + + List partsUsingLOV = partMasterTemplateDAO.findAllPartMTemplatesFromLOV(lovKey); + if (partsUsingLOV != null && !partsUsingLOV.isEmpty()){ + return true; + } + + return false; + } + + private boolean isLovUsedInPartIterationInstanceAttributeTemplates(ListOfValuesKey lovKey){ + PartIterationDAO partIterationDAO = new PartIterationDAO(em); + + List partsUsingLOV = partIterationDAO.findAllPartIterationFromLOV(lovKey); + if (partsUsingLOV != null && !partsUsingLOV.isEmpty()){ + return true; + } + + return false; + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/LogDocument.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/LogDocument.java new file mode 100644 index 0000000000..85976af05e --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/LogDocument.java @@ -0,0 +1,36 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import javax.interceptor.InterceptorBinding; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Inherited +@InterceptorBinding +@Target({TYPE, METHOD}) +@Retention(RUNTIME) +public @interface LogDocument { +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/MailerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/MailerBean.java new file mode 100644 index 0000000000..7a3a818ac5 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/MailerBean.java @@ -0,0 +1,562 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.services.IMailerLocal; +import com.docdoku.core.workflow.Task; + +import javax.annotation.Resource; +import javax.ejb.Asynchronous; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import java.io.UnsupportedEncodingException; +import java.text.MessageFormat; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Session class MailerBean + * + * @author Florent.Garin + */ +@Local(IMailerLocal.class) +@Stateless(name = "MailerBean") +public class MailerBean implements IMailerLocal { + + private static final String BASE_NAME = "com.docdoku.server.templates.MailText"; + + @Resource(name = "mail/docdokuSMTP") + private Session mailSession; + + @Resource(name = "codebase") + private String codebase; + + private static final Logger LOGGER = Logger.getLogger(MailerBean.class.getName()); + + @Asynchronous + @Override + public void sendStateNotification(User[] pSubscribers, + DocumentRevision pDocumentRevision) { + try { + for (User pSubscriber : pSubscribers) { + sendStateNotification(pSubscriber, pDocumentRevision); + } + } catch (MessagingException pMEx) { + String logMessage = "Message format error. \n\tNotifications can't be sent. \n\t" + pMEx.getMessage(); + LOGGER.severe(logMessage); + LOGGER.log(Level.FINER, logMessage, pMEx); + } + } + + @Asynchronous + @Override + public void sendIterationNotification(User[] pSubscribers, + DocumentRevision pDocumentRevision) { + try { + for (User pSubscriber : pSubscribers) { + sendIterationNotification(pSubscriber, pDocumentRevision); + } + } catch (MessagingException pMEx) { + String message = "Message format error. \n\tNotifications can't be sent. \n\t" + pMEx.getMessage(); + LOGGER.severe(message); + LOGGER.log(Level.FINER, message, pMEx); + } + } + + @Asynchronous + @Override + public void sendApproval(Collection pRunningTasks, + DocumentRevision pDocumentRevision) { + try { + for (Task task : pRunningTasks) { + sendApproval(task, pDocumentRevision); + } + } catch (MessagingException pMEx) { + String message = "Message format error. \n\t Approval can't be sent. \n\t " + pMEx.getMessage(); + LOGGER.severe(message); + LOGGER.log(Level.FINER, message, pMEx); + } + } + + + @Asynchronous + @Override + public void sendPasswordRecovery(Account account, String passwordRRUuid) { + try { + Locale locale = new Locale(account.getLanguage()); + sendMessage(new InternetAddress(account.getEmail(), account.getName()), + getPasswordRecoverySubject(locale), + getPasswordRecoveryMessage(account, passwordRRUuid, locale)); + LOGGER.info("Sending recovery message \n\tfor the user which login is " + account.getLogin()); + } catch (UnsupportedEncodingException pUEEx) { + String message = "Mail address format error. \n\t" + pUEEx.getMessage(); + LOGGER.warning(message); + LOGGER.log(Level.FINER, message, pUEEx); + } catch (MessagingException pMEx) { + String message = "Message format error. \n\tRecovery message can't be sent. \n\t" + pMEx.getMessage(); + LOGGER.severe(message); + LOGGER.log(Level.FINER, message, pMEx); + } + } + + @Asynchronous + @Override + public void sendApproval(Collection pRunningTasks, PartRevision partRevision) { + try { + for (Task task : pRunningTasks) { + sendApproval(task, partRevision); + LOGGER.info("Sending approval required emails \n\tfor the part " + partRevision.getLastIteration()); + } + } catch (MessagingException pMEx) { + String message = "Message format error. \n\tApproval can't be sent. \n\t" + pMEx.getMessage(); + LOGGER.severe(message); + LOGGER.log(Level.FINER, message, pMEx); + } + } + + @Asynchronous + @Override + public void sendWorkspaceDeletionNotification(Account admin, String workspaceId) { + try { + Locale locale = new Locale(admin.getLanguage()); + sendMessage(new InternetAddress(admin.getEmail(), admin.getName()), + getWorkspaceDeletionSubject(locale), + getWorkspaceDeletionMessage(workspaceId, locale)); + + } catch (UnsupportedEncodingException pUEEx) { + String message = "Mail address format error. \n\t" + pUEEx.getMessage(); + LOGGER.warning(message); + LOGGER.log(Level.FINER, message, pUEEx); + } catch (MessagingException pMEx) { + String message = "Message format error. \n\t" + pMEx.getMessage(); + LOGGER.severe(message); + LOGGER.log(Level.FINER, message, pMEx); + } + } + + @Asynchronous + @Override + public void sendWorkspaceDeletionErrorNotification(Account admin, String workspaceId) { + try { + Locale locale = new Locale(admin.getLanguage()); + sendMessage(new InternetAddress(admin.getEmail(), admin.getName()), + getWorkspaceDeletionSubject(locale), + getWorkspaceDeletionErrorMessage(workspaceId, locale)); + + } catch (UnsupportedEncodingException pUEEx) { + String message = "Mail address format error. \n\t" + pUEEx.getMessage(); + LOGGER.warning(message); + LOGGER.log(Level.FINER, message, pUEEx); + } catch (MessagingException pMEx) { + String message = "Message format error. \n\t" + pMEx.getMessage(); + LOGGER.severe(message); + LOGGER.log(Level.FINER, message, pMEx); + } + } + + @Asynchronous + @Override + public void sendPartRevisionWorkflowRelaunchedNotification(PartRevision partRevision) { + Workspace workspace = partRevision.getPartMaster().getWorkspace(); + Account admin = workspace.getAdmin(); + User author = partRevision.getAuthor(); + + // Mail both workspace admin and partRevision author + sendWorkflowRelaunchedNotification(admin.getName(), admin.getEmail(), admin.getLanguage(), workspace.getId(), partRevision); + + if (!admin.getLogin().equals(author.getLogin())) { + sendWorkflowRelaunchedNotification(author.getName(), author.getEmail(), author.getLanguage(), workspace.getId(), partRevision); + } + + } + + @Asynchronous + @Override + public void sendDocumentRevisionWorkflowRelaunchedNotification(DocumentRevision documentRevision) { + Workspace workspace = documentRevision.getDocumentMaster().getWorkspace(); + Account admin = workspace.getAdmin(); + User author = documentRevision.getAuthor(); + + // Mail both workspace admin and documentMaster author + sendWorkflowRelaunchedNotification(admin.getName(), admin.getEmail(), admin.getLanguage(), workspace.getId(), documentRevision); + + if (!admin.getLogin().equals(author.getLogin())) { + sendWorkflowRelaunchedNotification(author.getName(), author.getEmail(), author.getLanguage(), workspace.getId(), documentRevision); + } + } + + @Asynchronous + @Override + public void sendIndexerResult(Account account, String workspaceId, boolean hasSuccess, String pMessage) { + try { + Locale locale = new Locale(account.getLanguage()); + sendMessage(new InternetAddress(account.getEmail(), account.getName()), + getIndexerResultSubject(locale, hasSuccess), + getIndexerResultMessage(workspaceId, pMessage, hasSuccess, locale)); + } catch (UnsupportedEncodingException pUEEx) { + String message = "Mail address format error. \n\t" + pUEEx.getMessage(); + LOGGER.warning(message); + LOGGER.log(Level.FINER, message, pUEEx); + } catch (MessagingException pMEx) { + String message = "Message format error. \n\t" + pMEx.getMessage(); + LOGGER.severe(message); + LOGGER.log(Level.FINER, message, pMEx); + } + } + + @Asynchronous + @Override + public void sendCredential(Account account) { + try { + Locale locale = new Locale(account.getLanguage()); + sendMessage(new InternetAddress(account.getEmail()), + getCredentialSubject(locale), + getCredentialMessage(account, locale)); + } catch (MessagingException pMEx) { + String message = "Message format error. \n\t" + pMEx.getMessage(); + LOGGER.severe(message); + LOGGER.log(Level.FINER, message, pMEx); + } + } + + private void sendStateNotification(User pSubscriber, + DocumentRevision pDocumentRevision) throws MessagingException { + try { + Locale locale = new Locale(pSubscriber.getLanguage()); + sendMessage(new InternetAddress(pSubscriber.getEmail(), pSubscriber.getName()), + getStateNotificationSubject(locale), + getStateNotificationMessage(pDocumentRevision, locale)); + LOGGER.info("Sending state notification emails \n\tfor the document " + pDocumentRevision.getLastIteration()); + + } catch (UnsupportedEncodingException pUEEx) { + String logMessage = "Mail address format error. \n\t" + pUEEx.getMessage(); + LOGGER.warning(logMessage); + LOGGER.log(Level.FINER, logMessage, pUEEx); + } + } + + private void sendIterationNotification(User pSubscriber, + DocumentRevision pDocumentRevision) throws MessagingException { + try { + Locale locale = new Locale(pSubscriber.getLanguage()); + sendMessage(new InternetAddress(pSubscriber.getEmail(), pSubscriber.getName()), + getIterationNotificationSubject(locale), + getIterationNotificationMessage(pDocumentRevision, locale)); + LOGGER.info("Sending iteration notification emails \n\tfor the document " + pDocumentRevision.getLastIteration()); + } catch (UnsupportedEncodingException pUEEx) { + String message = "Mail address format error. \n\t" + pUEEx.getMessage(); + LOGGER.warning(message); + LOGGER.log(Level.FINER, message, pUEEx); + } + } + + private void sendApproval(Task task, DocumentRevision pDocumentRevision) throws MessagingException { + Set workers=new HashSet<>(); + workers.addAll(task.getAssignedUsers()); + task.getAssignedGroups().forEach(g->workers.addAll(g.getUsers())); + for(User worker:workers) { + try { + Locale locale = new Locale(worker.getLanguage()); + sendMessage(new InternetAddress(worker.getEmail(), worker.getName()), + getApprovalRequiredSubject(locale), + getApprovalRequiredMessage(task, pDocumentRevision, locale)); + LOGGER.info("Sending approval required emails \n\tfor the document " + pDocumentRevision.getLastIteration()); + } catch (UnsupportedEncodingException pUEEx) { + String message = "Mail address format error. \n\t" + pUEEx.getMessage(); + LOGGER.warning(message); + LOGGER.log(Level.FINER, message, pUEEx); + } + } + } + + private void sendApproval(Task task, PartRevision partRevision) throws MessagingException { + Set workers=new HashSet<>(); + workers.addAll(task.getAssignedUsers()); + task.getAssignedGroups().forEach(g->workers.addAll(g.getUsers())); + for(User worker:workers) { + try { + Locale locale = new Locale(worker.getLanguage()); + sendMessage(new InternetAddress(worker.getEmail(), worker.getName()), + getApprovalRequiredSubject(locale), + getApprovalRequiredMessage(task, partRevision, locale)); + + } catch (UnsupportedEncodingException pUEEx) { + String message = "Mail address format error. \n\t" + pUEEx.getMessage(); + LOGGER.warning(message); + LOGGER.log(Level.FINER, message, pUEEx); + } + } + } + + private String getCredentialMessage(Account account, Locale locale) { + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, locale); + Object[] args = { + account.getLogin(), + codebase + }; + + return MessageFormat.format(bundle.getString("SignUp_success_text"), args); + } + + private String getCredentialSubject(Locale pLocale) { + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return bundle.getString("SignUp_success_title"); + } + + private void sendWorkflowRelaunchedNotification(String userName, String userEmail, String userLanguage, String workspaceId, PartRevision partRevision) { + try { + Locale locale = new Locale(userLanguage); + sendMessage(new InternetAddress(userEmail, userName), + getPartRevisionWorkflowRelaunchedSubject(locale), + getPartRevisionWorkflowRelaunchedMessage(workspaceId, partRevision.getPartNumber(), + partRevision.getVersion(), partRevision.getWorkflow().getLifeCycleState(), locale)); + } catch (UnsupportedEncodingException pUEEx) { + String message = "Mail address format error. \n\t" + pUEEx.getMessage(); + LOGGER.warning(message); + LOGGER.log(Level.FINER, message, pUEEx); + } catch (MessagingException pMEx) { + String message = "Message format error. \n\tCannot send workflow relaunched notification.\n\t" + pMEx.getMessage(); + LOGGER.severe(message); + LOGGER.log(Level.FINER, message, pMEx); + } + } + + + private String getPartRevisionWorkflowRelaunchedMessage(String workspaceId, String number, String version, String lifeCycleState, Locale pLocale) { + Object[] args = { + number + "-" + version, + workspaceId, + lifeCycleState + }; + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return MessageFormat.format(bundle.getString("PartRevision_workflow_relaunched_text"), args); + } + + + private void sendWorkflowRelaunchedNotification(String userName, String userEmail, String userLanguage, String workspaceId, DocumentRevision documentRevision) { + try { + Locale locale = new Locale(userLanguage); + sendMessage(new InternetAddress(userEmail, userName), + getDocumentRevisionWorkflowRelaunchedSubject(locale), + getDocumentRevisionWorkflowRelaunchedMessage(workspaceId, documentRevision.getId(), + documentRevision.getVersion(), documentRevision.getWorkflow().getLifeCycleState(), locale)); + } catch (UnsupportedEncodingException pUEEx) { + String message = "Mail address format error. \n\t" + pUEEx.getMessage(); + LOGGER.warning(message); + LOGGER.log(Level.FINER, message, pUEEx); + } catch (MessagingException pMEx) { + String message = "Message format error. \n\tCannot send workflow relaunched notification.\n\t" + pMEx.getMessage(); + LOGGER.severe(message); + LOGGER.log(Level.FINER, message, pMEx); + } + } + + private String getDocumentRevisionWorkflowRelaunchedMessage(String workspaceId, String docMid, String version, String lifeCycleState, Locale pLocale) { + Object[] args = { + docMid + "-" + version, + workspaceId, + lifeCycleState + }; + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return MessageFormat.format(bundle.getString("DocumentRevision_workflow_relaunched_text"), args); + } + + private String getWorkspaceDeletionMessage(String workspaceId, Locale pLocale) { + Object[] args = {workspaceId}; + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return MessageFormat.format(bundle.getString("WorkspaceDeletion_text"), args); + } + + private String getWorkspaceDeletionErrorMessage(String workspaceId, Locale locale) { + Object[] args = {workspaceId}; + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, locale); + return MessageFormat.format(bundle.getString("WorkspaceDeletionError_text"), args); + } + + + private String getApprovalRequiredMessage(Task pTask, PartRevision partRevision, Locale pLocale) { + + String instructions = pTask.getInstructions() == null ? "-" : pTask.getInstructions(); + + Object[] args = { + getTaskUrl(pTask, partRevision.getWorkspaceId()), + partRevision.getWorkspaceId(), + String.valueOf(pTask.getWorkflowId()), + String.valueOf(pTask.getActivityStep()), + String.valueOf(pTask.getNum()), + pTask.getTitle(), + getPartRevisionPermalinkURL(partRevision), + partRevision, + instructions + }; + + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return MessageFormat.format(bundle.getString("Approval_part_text"), args); + } + + + private String getPasswordRecoveryMessage(Account account, String pPasswordRRUuid, Locale pLocale) { + Object[] args = { + getPasswordRecoveryUrl(pPasswordRRUuid), + account.getLogin() + }; + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return MessageFormat.format(bundle.getString("Recovery_text"), args); + } + + private String getApprovalRequiredMessage(Task pTask, + DocumentRevision pDocumentRevision, Locale pLocale) { + + Object[] args = { + getTaskUrl(pTask, pDocumentRevision.getWorkspaceId()), + pDocumentRevision.getWorkspaceId(), + String.valueOf(pTask.getWorkflowId()), + String.valueOf(pTask.getActivityStep()), + String.valueOf(pTask.getNum()), + pTask.getTitle(), + getDocumentRevisionPermalinkURL(pDocumentRevision), + pDocumentRevision, + pTask.getInstructions() == null ? "-" : pTask.getInstructions() + }; + + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return MessageFormat.format(bundle.getString("Approval_document_text"), args); + } + + private String getIterationNotificationMessage(DocumentRevision pDocumentRevision, Locale pLocale) { + + Object[] args = { + pDocumentRevision, + pDocumentRevision.getLastIteration().getCreationDate(), + pDocumentRevision.getLastIteration().getIteration(), + pDocumentRevision.getLastIteration().getAuthor(), + getDocumentRevisionPermalinkURL(pDocumentRevision) + }; + + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return MessageFormat.format(bundle.getString("IterationNotification_text"), args); + } + + private String getIndexerResultMessage(String workspaceId, String pMessage, boolean hasSuccess, Locale pLocale) { + Object[] args = { + workspaceId, + pMessage + }; + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return (hasSuccess) ? MessageFormat.format(bundle.getString("Indexer_success_text"), args) : + MessageFormat.format(bundle.getString("Indexer_failure_text"), args); + } + + private String getStateNotificationMessage(DocumentRevision pDocumentRevision, Locale pLocale) { + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + String stateName = pDocumentRevision.getLifeCycleState(); + stateName = (stateName != null && !stateName.isEmpty()) ? stateName : bundle.getString("FinalState_name"); + + Object[] args = { + pDocumentRevision, + pDocumentRevision.getLastIteration().getCreationDate(), + getDocumentRevisionPermalinkURL(pDocumentRevision), + stateName + }; + + return MessageFormat.format(bundle.getString("StateNotification_text"), args); + } + + + private String getStateNotificationSubject(Locale pLocale) { + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return bundle.getString("StateNotification_title"); + } + + private String getIterationNotificationSubject(Locale pLocale) { + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return bundle.getString("IterationNotification_title"); + } + + private String getApprovalRequiredSubject(Locale pLocale) { + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return bundle.getString("Approval_title"); + } + + private String getPasswordRecoverySubject(Locale pLocale) { + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return bundle.getString("Recovery_title"); + } + + private String getWorkspaceDeletionSubject(Locale pLocale) { + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return bundle.getString("WorkspaceDeletion_title"); + } + + private String getPartRevisionWorkflowRelaunchedSubject(Locale pLocale) { + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return bundle.getString("PartRevision_workflow_relaunched_title"); + + } + + private String getDocumentRevisionWorkflowRelaunchedSubject(Locale pLocale) { + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return bundle.getString("DocumentRevision_workflow_relaunched_title"); + + } + + private String getIndexerResultSubject(Locale pLocale, boolean hasSuccess) { + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + return (hasSuccess) ? bundle.getString("Indexer_success_title") : bundle.getString("Indexer_failure_title"); + } + + private void sendMessage(InternetAddress address, String subject, String content) throws MessagingException { + Message message = new MimeMessage(mailSession); + message.addRecipient(Message.RecipientType.TO, address); + message.setSubject(subject); + message.setSentDate(new Date()); + message.setContent(content, "text/html; charset=utf-8"); + message.setFrom(); + Transport.send(message); + } + + private String getDocumentRevisionPermalinkURL(DocumentRevision pDocR) { + return codebase + "/documents/#" + pDocR.getWorkspaceId() + "/" + pDocR.getId() + "/" + pDocR.getVersion(); + } + + private String getPartRevisionPermalinkURL(PartRevision pPartR) { + return codebase + "/parts/#" + pPartR.getWorkspaceId() + "/" + pPartR.getPartNumber() + "/" + pPartR.getVersion(); + } + + private String getTaskUrl(Task pTask, String workspaceId) { + return codebase + "/#change-management/#" + workspaceId + "/tasks/" + pTask.getWorkflowId() + "-" + pTask.getActivityStep() + "-" + pTask.getNum(); + } + + private String getPasswordRecoveryUrl(String uuid) { + return codebase + "/#recover/" + uuid; + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/OrganizationManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/OrganizationManagerBean.java new file mode 100644 index 0000000000..7ac6a1d93f --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/OrganizationManagerBean.java @@ -0,0 +1,140 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.Organization; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IAccountManagerLocal; +import com.docdoku.core.services.IOrganizationManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.dao.AccountDAO; +import com.docdoku.server.dao.OrganizationDAO; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.Locale; +import java.util.logging.Logger; + +@DeclareRoles({UserGroupMapping.GUEST_PROXY_ROLE_ID, UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) +@Local(IOrganizationManagerLocal.class) +@Stateless(name = "OrganizationManagerBean") +public class OrganizationManagerBean implements IOrganizationManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IAccountManagerLocal accountManager; + + private static final Logger LOGGER = Logger.getLogger(OrganizationManagerBean.class.getName()); + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void updateOrganization(Organization pOrganization) + throws AccountNotFoundException, OrganizationNotFoundException, AccessRightException { + + OrganizationDAO organizationDAO = new OrganizationDAO(em); + Organization oldOrganization = organizationDAO.loadOrganization(pOrganization.getName()); + + if (accountManager.checkAdmin(oldOrganization) != null) { + organizationDAO.updateOrganization(pOrganization); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public Organization getOrganizationOfAccount(String pLogin) { + OrganizationDAO organizationDAO = new OrganizationDAO(em); + return organizationDAO.findOrganizationOfAccount(pLogin); + + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public Organization createOrganization(String pName, String pDescription) throws AccountNotFoundException, OrganizationAlreadyExistsException, CreationException, NotAllowedException { + Account me = accountManager.getMyAccount(); + OrganizationDAO organizationDAO = new OrganizationDAO(new Locale(me.getLanguage()), em); + Organization ownerOrg=organizationDAO.findOrganizationOfAccount(me.getLogin()); + if (ownerOrg == null) { + Organization organization = new Organization(pName, me, pDescription); + organizationDAO.createOrganization(organization); + organization.addMember(me); + return organization; + } else { + throw new NotAllowedException(new Locale(me.getLanguage()), "NotAllowedException11"); + } + } + + @Override + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + public void deleteOrganization(String pName) throws OrganizationNotFoundException, AccessRightException, AccountNotFoundException { + OrganizationDAO organizationDAO = new OrganizationDAO(em); + Organization organization = organizationDAO.loadOrganization(pName); + + if (accountManager.checkAdmin(organization) != null) { + organizationDAO.deleteOrganization(organization); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void addAccountInOrganization(String pOrganizationName, String pLogin) throws OrganizationNotFoundException, AccountNotFoundException, NotAllowedException, AccessRightException { + OrganizationDAO organizationDAO = new OrganizationDAO(em); + Organization organization = organizationDAO.loadOrganization(pOrganizationName); + Account account = accountManager.checkAdmin(organization); + Locale locale=new Locale(account.getLanguage()); + + Account accountToAdd = new AccountDAO(locale, em).loadAccount(pLogin); + Organization accountToAddOrg=organizationDAO.findOrganizationOfAccount(pLogin); + if (accountToAddOrg != null) { + throw new NotAllowedException(locale, "NotAllowedException12"); + } else { + organization.addMember(accountToAdd); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void removeAccountsFromOrganization(String pOrganizationName, String[] pLogins) throws OrganizationNotFoundException, AccessRightException, AccountNotFoundException { + OrganizationDAO organizationDAO = new OrganizationDAO(em); + Organization organization = organizationDAO.loadOrganization(pOrganizationName); + + Account account = accountManager.checkAdmin(organization); + Locale locale=new Locale(account.getLanguage()); + + AccountDAO accountDAO = new AccountDAO(locale, em); + for (String login : pLogins) { + Account accountToRemove = accountDAO.loadAccount(login); + organization.removeMember(accountToRemove); + } + + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/PSFilterManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/PSFilterManagerBean.java new file mode 100644 index 0000000000..aab6998a53 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/PSFilterManagerBean.java @@ -0,0 +1,116 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.*; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.ConfigurationItemKey; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IPSFilterManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.configuration.filter.LatestPSFilter; +import com.docdoku.server.configuration.filter.LatestReleasedPSFilter; +import com.docdoku.server.configuration.filter.ReleasedPSFilter; +import com.docdoku.server.configuration.filter.WIPPSFilter; +import com.docdoku.server.configuration.spec.ProductBaselineConfigSpec; +import com.docdoku.server.configuration.spec.ProductInstanceConfigSpec; +import com.docdoku.server.dao.ProductBaselineDAO; +import com.docdoku.server.dao.ProductInstanceMasterDAO; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) +@Local(IPSFilterManagerLocal.class) +@Stateless(name = "PSFilterManagerBean") +public class PSFilterManagerBean implements IPSFilterManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IUserManagerLocal userManager; + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PSFilter getBaselinePSFilter(int baselineId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException { + ProductBaselineDAO productBaselineDAO = new ProductBaselineDAO(em); + ProductBaseline productBaseline = productBaselineDAO.loadBaseline(baselineId); + User user = userManager.checkWorkspaceReadAccess(productBaseline.getConfigurationItem().getWorkspaceId()); + return new ProductBaselineConfigSpec(productBaseline, user); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PSFilter getProductInstanceConfigSpec(ConfigurationItemKey ciKey, String serialNumber) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException { + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + ProductInstanceMasterKey pimk = new ProductInstanceMasterKey(serialNumber, ciKey); + ProductInstanceMaster productIM = new ProductInstanceMasterDAO(em).loadProductInstanceMaster(pimk); + ProductInstanceIteration productII = productIM.getLastIteration(); + return new ProductInstanceConfigSpec(productII, user); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PSFilter getPSFilter(ConfigurationItemKey ciKey, String filterType, boolean diverge) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, BaselineNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + + if (filterType == null) { + return new WIPPSFilter(user); + } + + PSFilter filter; + + switch (filterType) { + + case "wip": + case "undefined": + filter = new WIPPSFilter(user, diverge); + break; + case "latest": + filter = new LatestPSFilter(user, diverge); + break; + case "released": + filter = new ReleasedPSFilter(user, diverge); + break; + case "latest-released": + filter = new LatestReleasedPSFilter(user, diverge); + break; + default: + if (filterType.startsWith("pi-")) { + String serialNumber = filterType.substring(3); + filter = getProductInstanceConfigSpec(ciKey, serialNumber); + } else { + filter = getBaselinePSFilter(Integer.parseInt(filterType)); + } + break; + } + return filter; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ProductManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ProductManagerBean.java new file mode 100644 index 0000000000..5f42df7e65 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ProductManagerBean.java @@ -0,0 +1,3775 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.change.ModificationNotification; +import com.docdoku.core.common.*; +import com.docdoku.core.configuration.*; +import com.docdoku.core.document.*; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.meta.*; +import com.docdoku.core.product.*; +import com.docdoku.core.product.PartIteration.Source; +import com.docdoku.core.query.PartSearchQuery; +import com.docdoku.core.query.Query; +import com.docdoku.core.query.QueryContext; +import com.docdoku.core.query.QueryResultRow; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.ACLUserEntry; +import com.docdoku.core.security.ACLUserGroupEntry; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.*; +import com.docdoku.core.sharing.SharedEntityKey; +import com.docdoku.core.sharing.SharedPart; +import com.docdoku.core.util.NamingConvention; +import com.docdoku.core.util.Tools; +import com.docdoku.core.workflow.*; +import com.docdoku.server.configuration.PSFilterVisitor; +import com.docdoku.server.configuration.filter.*; +import com.docdoku.server.dao.*; +import com.docdoku.server.esindexer.ESIndexer; +import com.docdoku.server.esindexer.ESSearcher; +import com.docdoku.server.events.CheckedIn; +import com.docdoku.server.events.PartIterationChangeEvent; +import com.docdoku.server.events.PartRevisionChangeEvent; +import com.docdoku.server.events.Removed; +import com.docdoku.server.factory.ACLFactory; +import com.docdoku.server.validation.AttributesConsistencyUtils; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.ejb.TransactionAttribute; +import javax.ejb.TransactionAttributeType; +import javax.enterprise.event.Event; +import javax.enterprise.util.AnnotationLiteral; +import javax.inject.Inject; +import javax.jws.WebService; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.PersistenceContext; +import java.text.ParseException; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) +@Local(IProductManagerLocal.class) +@Stateless(name = "ProductManagerBean") +public class ProductManagerBean implements IProductManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IMailerLocal mailer; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IContextManagerLocal contextManager; + + @Inject + private IDataManagerLocal dataManager; + + @Inject + private ESIndexer esIndexer; + + @Inject + private ESSearcher esSearcher; + + @Inject + private IPSFilterManagerLocal psFilterManager; + + @Inject + private Event partIterationEvent; + + @Inject + private Event partRevisionEvent; + + private static final Logger LOGGER = Logger.getLogger(ProductManagerBean.class.getName()); + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List findPartUsages(ConfigurationItemKey pKey, PSFilter filter, String search) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException, ConfigurationItemNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(pKey.getWorkspace()); + + List usagePaths = new ArrayList<>(); + + ConfigurationItemDAO configurationItemDAO = new ConfigurationItemDAO(new Locale(user.getLanguage()), em); + ConfigurationItem ci = configurationItemDAO.loadConfigurationItem(pKey); + + PSFilterVisitor psFilterVisitor = new PSFilterVisitor(em, user, filter) { + @Override + public void onIndeterminateVersion(PartMaster partMaster, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onUnresolvedVersion(PartMaster partMaster) throws NotAllowedException { + // Unused here + } + + @Override + public void onIndeterminatePath(List pCurrentPath, List pCurrentPathPartIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onUnresolvedPath(List pCurrentPath, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onBranchDiscovered(List pCurrentPath, List copyPartIteration) { + // Unused here + } + + @Override + public void onOptionalPath(List path, List partIterations) { + // Unused here + } + + @Override + public boolean onPathWalk(List path, List parts) { + PartMaster pm = parts.get(parts.size() - 1); + + if (pm.getNumber().matches(search) || (pm.getName() != null && pm.getName().matches(search)) || Tools.getPathAsString(path).equals(search)) { + PartLink[] partLinks = path.toArray(new PartLink[path.size()]); + usagePaths.add(partLinks); + } + return true; + } + + }; + + psFilterVisitor.visit(ci.getDesignItem(), -1); + + return usagePaths; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List findPartMasters(String pWorkspaceId, String pPartNumber, String pPartName, int pMaxResults) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(pWorkspaceId); + PartMasterDAO partMDAO = new PartMasterDAO(new Locale(user.getLanguage()), em); + return partMDAO.findPartMasters(pWorkspaceId, pPartNumber, pPartName, pMaxResults); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ConfigurationItem createConfigurationItem(String pWorkspaceId, String pId, String pDescription, String pDesignItemNumber) throws UserNotFoundException, WorkspaceNotFoundException, AccessRightException, NotAllowedException, ConfigurationItemAlreadyExistsException, CreationException, PartMasterNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(pWorkspaceId); + Locale locale = new Locale(user.getLanguage()); + checkNameValidity(pId, locale); + + ConfigurationItem ci = new ConfigurationItem(user, user.getWorkspace(), pId, pDescription); + + try { + PartMaster designedPartMaster = new PartMasterDAO(locale, em).loadPartM(new PartMasterKey(pWorkspaceId, pDesignItemNumber)); + ci.setDesignItem(designedPartMaster); + new ConfigurationItemDAO(locale, em).createConfigurationItem(ci); + return ci; + } catch (PartMasterNotFoundException e) { + LOGGER.log(Level.FINEST, null, e); + throw new PartMasterNotFoundException(locale, pDesignItemNumber); + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartMaster createPartMaster(String pWorkspaceId, String pNumber, String pName, boolean pStandardPart, String pWorkflowModelId, String pPartRevisionDescription, String templateId, ACLUserEntry[] pACLUserEntries, ACLUserGroupEntry[] pACLUserGroupEntries, Map> userRoleMapping, Map> groupRoleMapping) throws NotAllowedException, UserNotFoundException, WorkspaceNotFoundException, AccessRightException, WorkflowModelNotFoundException, PartMasterAlreadyExistsException, CreationException, PartMasterTemplateNotFoundException, FileAlreadyExistsException, RoleNotFoundException, UserGroupNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(pWorkspaceId); + Locale locale = new Locale(user.getLanguage()); + checkNameValidity(pNumber, locale); + + PartMaster pm = new PartMaster(user.getWorkspace(), pNumber, user); + pm.setName(pName); + pm.setStandardPart(pStandardPart); + Date now = new Date(); + pm.setCreationDate(now); + PartRevision newRevision = pm.createNextRevision(user); + + if (pWorkflowModelId != null) { + + UserDAO userDAO = new UserDAO(locale, em); + UserGroupDAO groupDAO = new UserGroupDAO(locale, em); + RoleDAO roleDAO = new RoleDAO(locale, em); + + Map> roleUserMap = new HashMap<>(); + for (Map.Entry> pair : userRoleMapping.entrySet()) { + String roleName = pair.getKey(); + Collection userLogins = pair.getValue(); + Role role = roleDAO.loadRole(new RoleKey(pWorkspaceId, roleName)); + Set users=new HashSet<>(); + roleUserMap.put(role, users); + for(String login:userLogins) { + User u = userDAO.loadUser(new UserKey(pWorkspaceId, login)); + users.add(u); + } + } + + Map> roleGroupMap = new HashMap<>(); + for (Map.Entry> pair : groupRoleMapping.entrySet()) { + String roleName = pair.getKey(); + Collection groupIds = pair.getValue(); + Role role = roleDAO.loadRole(new RoleKey(pWorkspaceId, roleName)); + Set groups=new HashSet<>(); + roleGroupMap.put(role, groups); + for(String groupId:groupIds) { + UserGroup g = groupDAO.loadUserGroup(new UserGroupKey(pWorkspaceId, groupId)); + groups.add(g); + } + } + + WorkflowModel workflowModel = new WorkflowModelDAO(locale, em).loadWorkflowModel(new WorkflowModelKey(user.getWorkspaceId(), pWorkflowModelId)); + Workflow workflow = workflowModel.createWorkflow(roleUserMap, roleGroupMap); + newRevision.setWorkflow(workflow); + + for(Task task : workflow.getTasks()){ + if(!task.hasPotentialWorker()){ + throw new NotAllowedException(locale,"NotAllowedException56"); + } + } + + Collection runningTasks = workflow.getRunningTasks(); + for (Task runningTask : runningTasks) { + runningTask.start(); + } + + mailer.sendApproval(runningTasks, newRevision); + + } + newRevision.setCheckOutUser(user); + newRevision.setCheckOutDate(now); + newRevision.setCreationDate(now); + newRevision.setDescription(pPartRevisionDescription); + PartIteration ite = newRevision.createNextIteration(user); + ite.setCreationDate(now); + + if (templateId != null) { + + PartMasterTemplate partMasterTemplate = new PartMasterTemplateDAO(locale, em).loadPartMTemplate(new PartMasterTemplateKey(pWorkspaceId, templateId)); + + if (!Tools.validateMask(partMasterTemplate.getMask(), pNumber)) { + throw new NotAllowedException(locale, "NotAllowedException42"); + } + + pm.setType(partMasterTemplate.getPartType()); + pm.setAttributesLocked(partMasterTemplate.isAttributesLocked()); + + List attrs = new ArrayList<>(); + for (InstanceAttributeTemplate attrTemplate : partMasterTemplate.getAttributeTemplates()) { + InstanceAttribute attr = attrTemplate.createInstanceAttribute(); + attrs.add(attr); + } + ite.setInstanceAttributes(attrs); + + BinaryResourceDAO binDAO = new BinaryResourceDAO(locale, em); + BinaryResource sourceFile = partMasterTemplate.getAttachedFile(); + + if (sourceFile != null) { + String fileName = sourceFile.getName(); + long length = sourceFile.getContentLength(); + Date lastModified = sourceFile.getLastModified(); + String fullName = pWorkspaceId + "/parts/" + pm.getNumber() + "/A/1/nativecad/" + fileName; + BinaryResource targetFile = new BinaryResource(fullName, length, lastModified); + binDAO.createBinaryResource(targetFile); + ite.setNativeCADFile(targetFile); + try { + dataManager.copyData(sourceFile, targetFile); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + + } + //TODO refactor call ACLFactory + if ((pACLUserEntries != null && pACLUserEntries.length > 0) || (pACLUserGroupEntries != null && pACLUserGroupEntries.length > 0)) { + ACL acl = new ACL(); + if (pACLUserEntries != null) { + for (ACLUserEntry entry : pACLUserEntries) { + acl.addEntry(em.getReference(User.class, new UserKey(user.getWorkspaceId(), entry.getPrincipalLogin())), entry.getPermission()); + } + } + + if (pACLUserGroupEntries != null) { + for (ACLUserGroupEntry entry : pACLUserGroupEntries) { + acl.addEntry(em.getReference(UserGroup.class, new UserGroupKey(user.getWorkspaceId(), entry.getPrincipalId())), entry.getPermission()); + } + } + newRevision.setACL(acl); + new ACLDAO(em).createACL(acl); + } + + new PartMasterDAO(locale, em).createPartM(pm); + return pm; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision undoCheckOutPart(PartRevisionKey pPartRPK) throws NotAllowedException, PartRevisionNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pPartRPK.getPartMaster().getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + + PartRevisionDAO partRDAO = new PartRevisionDAO(locale, em); + PartRevision partR = partRDAO.loadPartR(pPartRPK); + if(partR.getACL() == null) { + userManager.checkWorkspaceWriteAccess(pPartRPK.getWorkspaceId()); + } + + //Check access rights on partR + if (!hasPartRevisionWriteAccess(user, partR)) { + throw new AccessRightException(locale, user); + } + + if (isCheckoutByUser(user, partR)) { + if (partR.getLastIteration().getIteration() <= 1) { + throw new NotAllowedException(locale, "NotAllowedException41"); + } + PartIteration partIte = partR.removeLastIteration(); + partIterationEvent.select(new AnnotationLiteral() { + }).fire(new PartIterationChangeEvent(partIte)); + + PartIterationDAO partIDAO = new PartIterationDAO(locale, em); + partIDAO.removeIteration(partIte); + partR.setCheckOutDate(null); + partR.setCheckOutUser(null); + + // Remove path to path links impacted by this change + removeObsoletePathToPathLinks(user, pPartRPK.getWorkspaceId()); + + for (Geometry file : partIte.getGeometries()) { + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + + for (BinaryResource file : partIte.getAttachedFiles()) { + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + + BinaryResource nativeCAD = partIte.getNativeCADFile(); + if (nativeCAD != null) { + try { + dataManager.deleteData(nativeCAD); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + + return partR; + } else { + throw new NotAllowedException(locale, "NotAllowedException19"); + } + } + + private void removeObsoletePathToPathLinks(User user, String workspaceId) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + Locale locale = new Locale(user.getLanguage()); + ConfigurationItemDAO configurationItemDAO = new ConfigurationItemDAO(locale, em); + List configurationItems = configurationItemDAO.findAllConfigurationItems(workspaceId); + + for(ConfigurationItem configurationItem:configurationItems){ + List pathToPathLinks = new ArrayList<>(configurationItem.getPathToPathLinks()); + for(PathToPathLink pathToPathLink:pathToPathLinks){ + try { + decodePath(configurationItem.getKey(),pathToPathLink.getSourcePath()); + decodePath(configurationItem.getKey(),pathToPathLink.getTargetPath()); + } catch (PartUsageLinkNotFoundException e) { + configurationItem.removePathToPathLink(pathToPathLink); + } catch (ConfigurationItemNotFoundException e) { + // Should not be thrown + LOGGER.log(Level.SEVERE,null,e); + } + } + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision checkOutPart(PartRevisionKey pPartRPK) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, PartRevisionNotFoundException, NotAllowedException, FileAlreadyExistsException, CreationException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pPartRPK.getPartMaster().getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + + PartRevisionDAO partRDAO = new PartRevisionDAO(locale, em); + PartRevision partR = partRDAO.loadPartR(pPartRPK); + if(partR.getACL() == null) { + userManager.checkWorkspaceWriteAccess(pPartRPK.getWorkspaceId()); + } + //Check access rights on partR + if (!hasPartRevisionWriteAccess(user, partR)) { + throw new AccessRightException(locale, user); + } + + if (partR.isCheckedOut()) { + throw new NotAllowedException(locale, "NotAllowedException37"); + } + + if (partR.isReleased() || partR.isObsolete()) { + throw new NotAllowedException(locale, "NotAllowedException47"); + } + + PartIteration beforeLastPartIteration = partR.getLastIteration(); + + PartIteration newPartIteration = partR.createNextIteration(user); + //We persist the doc as a workaround for a bug which was introduced + //since glassfish 3 that set the DTYPE to null in the instance attribute table + em.persist(newPartIteration); + Date now = new Date(); + newPartIteration.setCreationDate(now); + partR.setCheckOutUser(user); + partR.setCheckOutDate(now); + + if (beforeLastPartIteration != null) { + BinaryResourceDAO binDAO = new BinaryResourceDAO(locale, em); + for (BinaryResource sourceFile : beforeLastPartIteration.getAttachedFiles()) { + String fileName = sourceFile.getName(); + long length = sourceFile.getContentLength(); + Date lastModified = sourceFile.getLastModified(); + String fullName = partR.getWorkspaceId() + "/parts/" + partR.getPartNumber() + "/" + partR.getVersion() + "/" + newPartIteration.getIteration() + "/attachedfiles/" + fileName; + BinaryResource targetFile = new BinaryResource(fullName, length, lastModified); + binDAO.createBinaryResource(targetFile); + newPartIteration.addAttachedFile(targetFile); + } + + newPartIteration.setComponents(new ArrayList<>(beforeLastPartIteration.getComponents())); + + for (Geometry sourceFile : beforeLastPartIteration.getGeometries()) { + String fileName = sourceFile.getName(); + long length = sourceFile.getContentLength(); + int quality = sourceFile.getQuality(); + Date lastModified = sourceFile.getLastModified(); + String fullName = partR.getWorkspaceId() + "/parts/" + partR.getPartNumber() + "/" + partR.getVersion() + "/" + newPartIteration.getIteration() + "/" + fileName; + Geometry targetFile = new Geometry(quality, fullName, length, lastModified); + binDAO.createBinaryResource(targetFile); + newPartIteration.addGeometry(targetFile); + } + + BinaryResource nativeCADFile = beforeLastPartIteration.getNativeCADFile(); + if (nativeCADFile != null) { + String fileName = nativeCADFile.getName(); + long length = nativeCADFile.getContentLength(); + Date lastModified = nativeCADFile.getLastModified(); + String fullName = partR.getWorkspaceId() + "/parts/" + partR.getPartNumber() + "/" + partR.getVersion() + "/" + newPartIteration.getIteration() + "/nativecad/" + fileName; + BinaryResource targetFile = new BinaryResource(fullName, length, lastModified); + binDAO.createBinaryResource(targetFile); + newPartIteration.setNativeCADFile(targetFile); + } + + Set links = new HashSet<>(); + for (DocumentLink link : beforeLastPartIteration.getLinkedDocuments()) { + DocumentLink newLink = link.clone(); + links.add(newLink); + } + newPartIteration.setLinkedDocuments(links); + + InstanceAttributeDAO attrDAO = new InstanceAttributeDAO(em); + List attrs = new ArrayList<>(); + for (InstanceAttribute attr : beforeLastPartIteration.getInstanceAttributes()) { + InstanceAttribute newAttr = attr.clone(); + //Workaround for the NULL DTYPE bug + attrDAO.createAttribute(newAttr); + attrs.add(newAttr); + } + newPartIteration.setInstanceAttributes(attrs); + + List attrsTemplate = new ArrayList<>(); + for (InstanceAttributeTemplate attr : beforeLastPartIteration.getInstanceAttributeTemplates()) { + InstanceAttributeTemplate newAttr = attr.clone(); + attrsTemplate.add(newAttr); + } + newPartIteration.setInstanceAttributeTemplates(attrsTemplate); + + } + + return partR; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision checkInPart(PartRevisionKey pPartRPK) throws PartRevisionNotFoundException, UserNotFoundException, WorkspaceNotFoundException, AccessRightException, NotAllowedException, ESServerException, EntityConstraintException, UserNotActiveException, PartMasterNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pPartRPK.getPartMaster().getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + + PartRevisionDAO partRDAO = new PartRevisionDAO(locale, em); + PartRevision partR = partRDAO.loadPartR(pPartRPK); + if(partR.getACL() == null) { + userManager.checkWorkspaceWriteAccess(pPartRPK.getWorkspaceId()); + } + + //Check access rights on partR + if (!hasPartRevisionWriteAccess(user, partR)) { + throw new AccessRightException(locale, user); + } + + if (isCheckoutByUser(user, partR)) { + + checkCyclicAssemblyForPartIteration(partR.getLastIteration()); + + partR.setCheckOutDate(null); + partR.setCheckOutUser(null); + + PartIteration lastIteration = partR.getLastIteration(); + lastIteration.setCheckInDate(new Date()); + + esIndexer.index(lastIteration); + + partIterationEvent.select(new AnnotationLiteral() { + }).fire(new PartIterationChangeEvent(lastIteration)); + return partR; + } else { + throw new NotAllowedException(locale, "NotAllowedException20"); + } + + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) + @Override + public BinaryResource getBinaryResource(String pFullName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, FileNotFoundException, NotAllowedException, AccessRightException { + + if (contextManager.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)) { + // Don't check access right because it is do before. (Is public or isShared) + return new BinaryResourceDAO(em).loadBinaryResource(pFullName); + } + + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(pFullName)); + Locale userLocale = new Locale(user.getLanguage()); + BinaryResourceDAO binDAO = new BinaryResourceDAO(userLocale, em); + BinaryResource binaryResource = binDAO.loadBinaryResource(pFullName); + + PartIteration partIte = binDAO.getPartHolder(binaryResource); + if (partIte != null) { + PartRevision partR = partIte.getPartRevision(); + + if (isACLGrantReadAccess(user, partR)) { + if (isCheckoutByAnotherUser(user, partR) && partR.getLastIteration().equals(partIte)) { + throw new NotAllowedException(new Locale(user.getLanguage()), "NotAllowedException34"); + } else { + return binaryResource; + } + } else { + throw new AccessRightException(userLocale, user); + } + } else { + throw new FileNotFoundException(userLocale, pFullName); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public BinaryResource getTemplateBinaryResource(String pFullName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, FileNotFoundException { + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(pFullName)); + Locale userLocale = new Locale(user.getLanguage()); + BinaryResourceDAO binDAO = new BinaryResourceDAO(userLocale, em); + return binDAO.loadBinaryResource(pFullName); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public BinaryResource saveNativeCADInPartIteration(PartIterationKey pPartIPK, String pName, long pSize) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, PartRevisionNotFoundException, FileAlreadyExistsException, CreationException { + User user = userManager.checkWorkspaceReadAccess(pPartIPK.getWorkspaceId()); + Locale locale = new Locale(user.getLanguage()); + checkNameFileValidity(pName, locale); + + BinaryResourceDAO binDAO = new BinaryResourceDAO(locale, em); + PartRevisionDAO partRDAO = new PartRevisionDAO(locale, em); + PartRevision partR = partRDAO.loadPartR(pPartIPK.getPartRevision()); + PartIteration partI = partR.getIteration(pPartIPK.getIteration()); + + if (isCheckoutByUser(user, partR) && partR.getLastIteration().equals(partI)) { + String fullName = partR.getWorkspaceId() + "/parts/" + partR.getPartNumber() + "/" + partR.getVersion() + "/" + partI.getIteration() + "/nativecad/" + pName; + BinaryResource nativeCADBinaryResource = partI.getNativeCADFile(); + + if (nativeCADBinaryResource == null) { + nativeCADBinaryResource = new BinaryResource(fullName, pSize, new Date()); + binDAO.createBinaryResource(nativeCADBinaryResource); + partI.setNativeCADFile(nativeCADBinaryResource); + + } else if (nativeCADBinaryResource.getFullName().equals(fullName)) { + nativeCADBinaryResource.setContentLength(pSize); + nativeCADBinaryResource.setLastModified(new Date()); + + } else { + partI.setNativeCADFile(null); + binDAO.removeBinaryResource(nativeCADBinaryResource); + + try { + dataManager.deleteData(nativeCADBinaryResource); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + + nativeCADBinaryResource = new BinaryResource(fullName, pSize, new Date()); + binDAO.createBinaryResource(nativeCADBinaryResource); + partI.setNativeCADFile(nativeCADBinaryResource); + } + + //Delete converted files if any + List geometries = new ArrayList<>(partI.getGeometries()); + for (Geometry geometry : geometries) { + partI.removeGeometry(geometry); + binDAO.removeBinaryResource(geometry); + try { + dataManager.deleteData(geometry); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + return nativeCADBinaryResource; + } else { + throw new NotAllowedException(locale, "NotAllowedException4"); + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public BinaryResource saveGeometryInPartIteration(PartIterationKey pPartIPK, String pName, int quality, long pSize, double[] box) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, PartRevisionNotFoundException, FileAlreadyExistsException, CreationException { + User user = userManager.checkWorkspaceReadAccess(pPartIPK.getWorkspaceId()); + Locale locale = new Locale(user.getLanguage()); + checkNameFileValidity(pName, locale); + + PartRevisionDAO partRDAO = new PartRevisionDAO(locale, em); + PartRevision partR = partRDAO.loadPartR(pPartIPK.getPartRevision()); + PartIteration partI = partR.getIteration(pPartIPK.getIteration()); + if (isCheckoutByUser(user, partR) && partR.getLastIteration().equals(partI)) { + Geometry geometryBinaryResource = null; + String fullName = partR.getWorkspaceId() + "/parts/" + partR.getPartNumber() + "/" + partR.getVersion() + "/" + partI.getIteration() + "/" + pName; + + for (Geometry geo : partI.getGeometries()) { + if (geo.getFullName().equals(fullName)) { + geometryBinaryResource = geo; + break; + } + } + if (geometryBinaryResource == null) { + geometryBinaryResource = new Geometry(quality, fullName, pSize, new Date()); + new BinaryResourceDAO(locale, em).createBinaryResource(geometryBinaryResource); + partI.addGeometry(geometryBinaryResource); + } else { + geometryBinaryResource.setContentLength(pSize); + geometryBinaryResource.setQuality(quality); + geometryBinaryResource.setLastModified(new Date()); + } + + if (box != null) { + geometryBinaryResource.setBox(box[0], box[1], box[2], box[3], box[4], box[5]); + } + + return geometryBinaryResource; + } else { + throw new NotAllowedException(locale, "NotAllowedException4"); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public BinaryResource saveFileInPartIteration(PartIterationKey pPartIPK, String pName, String subType, long pSize) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, PartRevisionNotFoundException, FileAlreadyExistsException, CreationException { + User user = userManager.checkWorkspaceReadAccess(pPartIPK.getWorkspaceId()); + Locale locale = new Locale(user.getLanguage()); + checkNameFileValidity(pName, locale); + + PartRevisionDAO partRDAO = new PartRevisionDAO(locale, em); + PartRevision partR = partRDAO.loadPartR(pPartIPK.getPartRevision()); + PartIteration partI = partR.getIteration(pPartIPK.getIteration()); + if (isCheckoutByUser(user, partR) && partR.getLastIteration().equals(partI)) { + BinaryResource binaryResource = null; + String fullName = partR.getWorkspaceId() + "/parts/" + partR.getPartNumber() + "/" + partR.getVersion() + "/" + partI.getIteration() + "/" + (subType != null ? subType + "/" : "") + pName; + + for (BinaryResource bin : partI.getAttachedFiles()) { + if (bin.getFullName().equals(fullName)) { + binaryResource = bin; + break; + } + } + if (binaryResource == null) { + binaryResource = new BinaryResource(fullName, pSize, new Date()); + new BinaryResourceDAO(locale, em).createBinaryResource(binaryResource); + partI.addAttachedFile(binaryResource); + } else { + binaryResource.setContentLength(pSize); + binaryResource.setLastModified(new Date()); + } + return binaryResource; + } else { + throw new NotAllowedException(locale, "NotAllowedException4"); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public List getConfigurationItems(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + Locale locale; + if (!contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID)) { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + locale = new Locale(user.getLanguage()); + } else { + locale = Locale.getDefault(); + } + + return new ConfigurationItemDAO(locale, em).findAllConfigurationItems(pWorkspaceId); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public ConfigurationItem getConfigurationItem(ConfigurationItemKey ciKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException { + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + ConfigurationItem configurationItem = em.find(ConfigurationItem.class, ciKey); + if (configurationItem == null) { + throw new ConfigurationItemNotFoundException(locale, ciKey.getId()); + } + return configurationItem; + } + + /* + * give pAttributes null for no modification, give an empty list for removing them + * */ + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision updatePartIteration(PartIterationKey pKey, String pIterationNote, Source source, List pUsageLinks, List pAttributes, List pAttributeTemplates, DocumentRevisionKey[] pLinkKeys, String[] documentLinkComments, String[] lovNames) + throws UserNotFoundException, WorkspaceNotFoundException, AccessRightException, NotAllowedException, PartRevisionNotFoundException, PartMasterNotFoundException, EntityConstraintException, UserNotActiveException, ListOfValuesNotFoundException, PartUsageLinkNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(pKey.getWorkspaceId()); + Locale locale = new Locale(user.getLanguage()); + + PartRevisionDAO partRDAO = new PartRevisionDAO(locale, em); + PartRevision partRev = partRDAO.loadPartR(pKey.getPartRevision()); + if(partRev.getACL() == null) { + userManager.checkWorkspaceWriteAccess(pKey.getWorkspaceId()); + } + //check access rights on partRevision + if (!hasPartRevisionWriteAccess(user, partRev)) { + throw new AccessRightException(locale, user); + } + + DocumentLinkDAO linkDAO = new DocumentLinkDAO(locale, em); + PartIteration partIte = partRev.getLastIteration(); + + if (isCheckoutByUser(user, partRev) && partIte.getKey().equals(pKey)) { + + // Update linked documents + + if (pLinkKeys != null) { + Set currentLinks = new HashSet<>(partIte.getLinkedDocuments()); + + for (DocumentLink link : currentLinks) { + partIte.getLinkedDocuments().remove(link); + } + + int counter = 0; + for (DocumentRevisionKey link : pLinkKeys) { + DocumentLink newLink = new DocumentLink(em.getReference(DocumentRevision.class, link)); + newLink.setComment(documentLinkComments[counter]); + linkDAO.createLink(newLink); + partIte.getLinkedDocuments().add(newLink); + counter++; + } + + } + + // Update attributes + + + //should move that + if(pAttributes != null) { + List currentAttrs = partRev.getLastIteration().getInstanceAttributes(); + boolean valid = AttributesConsistencyUtils.hasValidChange(pAttributes,partRev.isAttributesLocked(),currentAttrs); + if(!valid) { + throw new NotAllowedException(locale, "NotAllowedException59"); + } + partRev.getLastIteration().setInstanceAttributes(pAttributes); + } + + + // Update attribute templates + + if (pAttributeTemplates != null) { + + LOVDAO lovDAO = new LOVDAO(locale, em); + + List templateAttrs = new ArrayList<>(); + for (int i = 0; i < pAttributeTemplates.size(); i++) { + templateAttrs.add(pAttributeTemplates.get(i)); + if (pAttributeTemplates.get(i) instanceof ListOfValuesAttributeTemplate) { + ListOfValuesAttributeTemplate lovAttr = (ListOfValuesAttributeTemplate) pAttributeTemplates.get(i); + ListOfValuesKey lovKey = new ListOfValuesKey(user.getWorkspaceId(), lovNames[i]); + lovAttr.setLov(lovDAO.loadLOV(lovKey)); + } + } + if(!AttributesConsistencyUtils.isTemplateAttributesValid(templateAttrs,false)) { + throw new NotAllowedException(locale, "NotAllowedException59"); + } + partIte.setInstanceAttributeTemplates(pAttributeTemplates); + } + + // Update structure + if (pUsageLinks != null) { + + List links = new ArrayList<>(); + for (PartUsageLink usageLink : pUsageLinks) { + PartUsageLink partUsageLink = findOrCreatePartLink(user, usageLink, partIte); + links.add(partUsageLink); + } + partIte.setComponents(links); + + PartUsageLinkDAO partUsageLinkDAO = new PartUsageLinkDAO(new Locale(user.getLanguage()), em); + partUsageLinkDAO.removeOrphanPartLinks(); + removeObsoletePathToPathLinks(user, pKey.getWorkspaceId()); + checkCyclicAssemblyForPartIteration(partIte); + + } + + // Set note and date + + partIte.setIterationNote(pIterationNote); + partIte.setModificationDate(new Date()); + partIte.setSource(source); + + } else { + throw new NotAllowedException(locale, "NotAllowedException25", partIte.getPartNumber()); + } + + return partRev; + + } + + private PartUsageLink findOrCreatePartLink(User user, PartUsageLink partUsageLink, PartIteration partIte) throws PartUsageLinkNotFoundException { + int id = partUsageLink.getId(); + PartUsageLink partLink; + if (id != 0) { + PartUsageLinkDAO partUsageLinkDAO = new PartUsageLinkDAO(new Locale(user.getLanguage()), em); + partLink = partUsageLinkDAO.loadPartUsageLink(id); + } else { + partLink = createNewPartLink(partUsageLink, partIte); + } + return partLink; + } + + private PartUsageLink createNewPartLink(PartUsageLink partUsageLink, PartIteration partIte) throws PartUsageLinkNotFoundException { + + PartUsageLink newLink = new PartUsageLink(); + + newLink.setAmount(partUsageLink.getAmount()); + newLink.setOptional(partUsageLink.isOptional()); + newLink.setCadInstances(partUsageLink.getCadInstances()); + newLink.setComment(partUsageLink.getComment()); + newLink.setReferenceDescription(partUsageLink.getReferenceDescription()); + newLink.setUnit(partUsageLink.getUnit()); + newLink.setComponent(partUsageLink.getComponent()); + + List substitutes = new ArrayList<>(); + + for (PartSubstituteLink partSubstituteLink : partUsageLink.getSubstitutes()) { + PartSubstituteLink newSubstituteLink = new PartSubstituteLink(); + newSubstituteLink.setAmount(partSubstituteLink.getAmount()); + newSubstituteLink.setCadInstances(partSubstituteLink.getCadInstances()); + newSubstituteLink.setComment(partSubstituteLink.getComment()); + newSubstituteLink.setReferenceDescription(partSubstituteLink.getReferenceDescription()); + newSubstituteLink.setUnit(partSubstituteLink.getUnit()); + newSubstituteLink.setSubstitute(partSubstituteLink.getSubstitute()); + substitutes.add(newSubstituteLink); + } + + newLink.setSubstitutes(substitutes); + + em.persist(newLink); + + return newLink; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) + @Override + public PartRevision getPartRevision(PartRevisionKey pPartRPK) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException { + + if (contextManager.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)) { + PartRevision partRevision = new PartRevisionDAO(em).loadPartR(pPartRPK); + if (partRevision.isCheckedOut()) { + em.detach(partRevision); + partRevision.removeLastIteration(); + } + return partRevision; + } + + User user = checkPartRevisionReadAccess(pPartRPK); + + PartRevision partR = new PartRevisionDAO(new Locale(user.getLanguage()), em).loadPartR(pPartRPK); + + if (isCheckoutByAnotherUser(user, partR)) { + em.detach(partR); + partR.removeLastIteration(); + } + return partR; + } + + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public List getModificationNotifications(PartIterationKey pPartIPK) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + PartRevisionKey partRevisionKey = pPartIPK.getPartRevision(); + checkPartRevisionReadAccess(partRevisionKey); + return new ModificationNotificationDAO(em).getModificationNotifications(pPartIPK); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public void removeModificationNotificationsOnIteration(PartIterationKey pPartIPK) { + //TODO insure access rights + new ModificationNotificationDAO(em).removeModificationNotifications(pPartIPK); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public void removeModificationNotificationsOnRevision(PartRevisionKey pPartRPK) { + //TODO insure access rights + new ModificationNotificationDAO(em).removeModificationNotifications(pPartRPK); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public void createModificationNotifications(PartIteration modifiedPartIteration) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + //TODO insure access rights + Set impactedParts = new HashSet<>(); + impactedParts.addAll(getUsedByAsComponent(modifiedPartIteration.getPartRevisionKey())); + impactedParts.addAll(getUsedByAsSubstitute(modifiedPartIteration.getPartRevisionKey())); + + ModificationNotificationDAO dao = new ModificationNotificationDAO(em); + for (PartIteration impactedPart : impactedParts) { + if (impactedPart.isLastIteration()) { + ModificationNotification notification = new ModificationNotification(); + notification.setImpactedPart(impactedPart); + notification.setModifiedPart(modifiedPartIteration); + dao.createModificationNotification(notification); + } + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public void updateModificationNotification(String pWorkspaceId, int pModificationNotificationId, String pAcknowledgementComment) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, PartRevisionNotFoundException { + + ModificationNotification modificationNotification = new ModificationNotificationDAO(em).getModificationNotification(pModificationNotificationId); + PartIterationKey partIKey = modificationNotification.getImpactedPart().getKey(); + PartRevisionKey partRKey = partIKey.getPartRevision(); + + User user = userManager.checkWorkspaceWriteAccess(partRKey.getPartMaster().getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + + PartRevisionDAO partRDAO = new PartRevisionDAO(locale, em); + PartRevision partR = partRDAO.loadPartR(partRKey); + + //Check access rights on partR + if (!hasPartRevisionWriteAccess(user, partR)) { + throw new AccessRightException(locale, user); + } + Date now = new Date(); + modificationNotification.setAcknowledgementComment(pAcknowledgementComment); + modificationNotification.setAcknowledged(true); + modificationNotification.setAcknowledgementDate(now); + modificationNotification.setAcknowledgementAuthor(user); + + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public List getUsedByAsComponent(PartRevisionKey partRevisionKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + User user = checkPartRevisionReadAccess(partRevisionKey); + Locale locale = new Locale(user.getLanguage()); + return new PartIterationDAO(locale, em).findUsedByAsComponent(partRevisionKey.getPartMaster()); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public List getUsedByAsSubstitute(PartRevisionKey partRevisionKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + User user = checkPartRevisionReadAccess(partRevisionKey); + Locale locale = new Locale(user.getLanguage()); + return new PartIterationDAO(locale, em).findUsedByAsSubstitute(partRevisionKey.getPartMaster()); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartIteration getPartIteration(PartIterationKey pPartIPK) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException, PartIterationNotFoundException, NotAllowedException { + + PartRevisionKey partRevisionKey = pPartIPK.getPartRevision(); + User user = checkPartRevisionReadAccess(partRevisionKey); + Locale locale = new Locale(user.getLanguage()); + PartIterationDAO partIterationDAO = new PartIterationDAO(locale, em); + + PartIteration partI = partIterationDAO.loadPartI(pPartIPK); + PartRevision partR = partI.getPartRevision(); + partR.getIteration(pPartIPK.getIteration()); + PartIteration lastIteration = partR.getLastIteration(); + + if (isCheckoutByAnotherUser(user, partR) && lastIteration.getKey().equals(pPartIPK)) { + throw new NotAllowedException(locale, "NotAllowedException25", partR.getPartMaster().getNumber()); + } + + return partI; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void updatePartRevisionACL(String workspaceId, PartRevisionKey revisionKey, Map pACLUserEntries, Map pACLUserGroupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException, DocumentRevisionNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + ACLFactory aclFactory = new ACLFactory(em); + + PartRevisionDAO partRevisionDAO = new PartRevisionDAO(locale, em); + PartRevision partRevision = partRevisionDAO.loadPartR(revisionKey); + + if (isAuthor(user, partRevision) || user.isAdministrator()) { + + if (partRevision.getACL() == null) { + ACL acl = aclFactory.createACL(workspaceId, pACLUserEntries, pACLUserGroupEntries); + partRevision.setACL(acl); + } else { + aclFactory.updateACL(workspaceId, partRevision.getACL(), pACLUserEntries, pACLUserGroupEntries); + } + } else { + throw new AccessRightException(locale, user); + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void removeACLFromPartRevision(PartRevisionKey revisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException { + + User user = userManager.checkWorkspaceReadAccess(revisionKey.getPartMaster().getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + + PartRevisionDAO partRevisionDAO = new PartRevisionDAO(locale, em); + PartRevision partRevision = partRevisionDAO.loadPartR(revisionKey); + + if (isAuthor(user, partRevision) || user.isAdministrator()) { + ACL acl = partRevision.getACL(); + if (acl != null) { + new ACLDAO(em).removeACLEntries(acl); + partRevision.setACL(null); + } + } else { + throw new AccessRightException(locale, user); + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List searchPartRevisions(PartSearchQuery pQuery) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ESServerException { + User user = userManager.checkWorkspaceReadAccess(pQuery.getWorkspaceId()); + List fetchedPartRs = esSearcher.search(pQuery); + // Get Search Results + + ListIterator ite = fetchedPartRs.listIterator(); + while (ite.hasNext()) { + PartRevision partR = ite.next(); + + if (isCheckoutByAnotherUser(user, partR)) { + // Remove CheckedOut PartRevision From Results + em.detach(partR); + partR.removeLastIteration(); + } + + if (!hasPartRevisionReadAccess(user, partR)) { + ite.remove(); + } + } + return new ArrayList<>(fetchedPartRs); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartMaster findPartMasterByCADFileName(String workspaceId, String cadFileName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + + BinaryResource br = new BinaryResourceDAO(locale, em).findNativeCadBinaryResourceInWorkspace(workspaceId, cadFileName); + if (br == null) { + return null; + } + String partNumber = br.getHolderId(); + PartMasterKey partMasterKey = new PartMasterKey(workspaceId, partNumber); + try { + return new PartMasterDAO(locale, em).loadPartM(partMasterKey); + } catch (PartMasterNotFoundException e) { + return null; + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Conversion getConversion(PartIterationKey partIterationKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException, PartIterationNotFoundException { + User user = checkPartRevisionReadAccess(partIterationKey.getPartRevision()); + Locale locale = new Locale(user.getLanguage()); + PartIterationDAO partIterationDAO = new PartIterationDAO(locale, em); + PartIteration partIteration = partIterationDAO.loadPartI(partIterationKey); + ConversionDAO conversionDAO = new ConversionDAO(locale, em); + return conversionDAO.findConversion(partIteration); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + @Override + public Conversion createConversion(PartIterationKey partIterationKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException, PartIterationNotFoundException, CreationException { + User user = checkPartRevisionWriteAccess(partIterationKey.getPartRevision()); + Locale locale = new Locale(user.getLanguage()); + PartIterationDAO partIterationDAO = new PartIterationDAO(locale, em); + PartIteration partIteration = partIterationDAO.loadPartI(partIterationKey); + ConversionDAO conversionDAO = new ConversionDAO(locale, em); + Conversion conversion = new Conversion(partIteration); + conversionDAO.createConversion(conversion); + return conversion; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + @Override + public void removeConversion(PartIterationKey partIterationKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException, PartIterationNotFoundException { + User user = checkPartRevisionWriteAccess(partIterationKey.getPartRevision()); + Locale locale = new Locale(user.getLanguage()); + PartIterationDAO partIterationDAO = new PartIterationDAO(locale, em); + PartIteration partIteration = partIterationDAO.loadPartI(partIterationKey); + ConversionDAO conversionDAO = new ConversionDAO(locale, em); + Conversion conversion = conversionDAO.findConversion(partIteration); + conversionDAO.deleteConversion(conversion); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + @Override + public void endConversion(PartIterationKey partIterationKey, boolean succeed) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException, PartIterationNotFoundException { + User user = checkPartRevisionWriteAccess(partIterationKey.getPartRevision()); + Locale locale = new Locale(user.getLanguage()); + PartIterationDAO partIterationDAO = new PartIterationDAO(locale, em); + PartIteration partIteration = partIterationDAO.loadPartI(partIterationKey); + ConversionDAO conversionDAO = new ConversionDAO(locale, em); + Conversion conversion = conversionDAO.findConversion(partIteration); + conversion.setPending(false); + conversion.setSucceed(succeed); + conversion.setEndDate(new Date()); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + @Override + public Import createImport(String workspaceId, String fileName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, CreationException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + Import importToCreate = new Import(user,fileName); + ImportDAO importDAO = new ImportDAO(locale, em); + importDAO.createImport(importToCreate); + return importToCreate; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getImports(String workspaceId, String filename) + throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + ImportDAO importDAO = new ImportDAO(locale, em); + return importDAO.findImports(user, filename); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Import getImport(String workspaceId, String id) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + ImportDAO importDAO = new ImportDAO(locale, em); + Import anImport = importDAO.findImport(user, id); + if(anImport.getUser().equals(user)){ + return anImport; + }else{ + throw new AccessRightException(locale, user); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + @Override + public void endImport(String workspaceId, String id, ImportResult importResult) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + ImportDAO importDAO = new ImportDAO(locale, em); + Import anImport = importDAO.findImport(user, id); + anImport.setPending(false); + anImport.setEndDate(new Date()); + if(importResult != null) { + anImport.setErrors(importResult.getErrors()); + anImport.setWarnings(importResult.getWarnings()); + anImport.setSucceed(importResult.isSucceed()); + } else{ + anImport.setSucceed(false); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void removeImport(String workspaceId, String id) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + ImportDAO importDAO = new ImportDAO(locale, em); + Import anImport = importDAO.findImport(user, id); + if(anImport.getUser().equals(user)){ + importDAO.deleteImport(anImport); + }else{ + throw new AccessRightException(locale, user); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void updateACLForPartMasterTemplate(String pWorkspaceId, String templateId, Map userEntries, Map groupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, PartMasterTemplateNotFoundException { + + ACLFactory aclFactory = new ACLFactory(em); + + // Check the read access to the workspace + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + // Load the part template + PartMasterTemplateKey pKey = new PartMasterTemplateKey(pWorkspaceId, templateId); + PartMasterTemplate partMasterTemplate = new PartMasterTemplateDAO(new Locale(user.getLanguage()), em).loadPartMTemplate(pKey); + + // Check the access to the part template + checkPartTemplateWriteAccess(partMasterTemplate, user); + + if (partMasterTemplate.getAcl() == null) { + ACL acl = aclFactory.createACL(pWorkspaceId, userEntries, groupEntries); + partMasterTemplate.setAcl(acl); + } else { + aclFactory.updateACL(pWorkspaceId, partMasterTemplate.getAcl(), userEntries, groupEntries); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void removeACLFromPartMasterTemplate(String workspaceId, String partTemplateId) throws PartMasterNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartMasterTemplateNotFoundException, AccessRightException { + + // Check the read access to the workspace + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + // Load the part template + PartMasterTemplateKey pKey = new PartMasterTemplateKey(workspaceId, partTemplateId); + PartMasterTemplate partMaster = new PartMasterTemplateDAO(locale, em).loadPartMTemplate(pKey); + + // Check the access to the part template + checkPartTemplateWriteAccess(partMaster, user); + + ACL acl = partMaster.getAcl(); + if (acl != null) { + new ACLDAO(em).removeACLEntries(acl); + partMaster.setAcl(null); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision saveTags(PartRevisionKey revisionKey, String[] pTags) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + + User user = checkPartRevisionWriteAccess(revisionKey); + + Locale userLocale = new Locale(user.getLanguage()); + PartRevisionDAO partRevDAO = new PartRevisionDAO(userLocale, em); + PartRevision partRevision = partRevDAO.loadPartR(revisionKey); + + Set tags = new HashSet<>(); + if (pTags != null) { + for (String label : pTags) { + tags.add(new Tag(user.getWorkspace(), label)); + } + + TagDAO tagDAO = new TagDAO(userLocale, em); + List existingTags = Arrays.asList(tagDAO.findAllTags(user.getWorkspaceId())); + + Set tagsToCreate = new HashSet<>(tags); + tagsToCreate.removeAll(existingTags); + + for (Tag t : tagsToCreate) { + try { + tagDAO.createTag(t); + } catch (CreationException | TagAlreadyExistsException ex) { + LOGGER.log(Level.SEVERE, null, ex); + } + } + + partRevision.setTags(tags); + + if (isCheckoutByAnotherUser(user, partRevision)) { + em.detach(partRevision); + partRevision.removeLastIteration(); + } + + for (PartIteration partIteration : partRevision.getPartIterations()) { + esIndexer.index(partIteration); + } + } else { + throw new IllegalArgumentException("pTags argument must not be null"); + } + + + return partRevision; + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision removeTag(PartRevisionKey partRevisionKey, String tagName) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + User user = checkPartRevisionWriteAccess(partRevisionKey); + + PartRevision partRevision = getPartRevision(partRevisionKey); + Tag tagToRemove = new Tag(user.getWorkspace(), tagName); + partRevision.getTags().remove(tagToRemove); + + if (isCheckoutByAnotherUser(user, partRevision)) { + em.detach(partRevision); + partRevision.removeLastIteration(); + } + + for (PartIteration partIteration : partRevision.getPartIterations()) { + esIndexer.index(partIteration); + } + return partRevision; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision[] findPartRevisionsByTag(String workspaceId, String tagId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(workspaceId); + + List partsRevision = new PartRevisionDAO(new Locale(user.getLanguage()), em).findPartByTag(new Tag(user.getWorkspace(), tagId)); + ListIterator iterator = partsRevision.listIterator(); + while (iterator.hasNext()) { + PartRevision partRevision = iterator.next(); + if (!hasPartRevisionReadAccess(user, partRevision)) { + iterator.remove(); + } else if (isCheckoutByAnotherUser(user, partRevision)) { + em.detach(partRevision); + partRevision.removeLastIteration(); + } + } + return partsRevision.toArray(new PartRevision[partsRevision.size()]); + } + + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision[] getPartRevisionsWithReferenceOrName(String pWorkspaceId, String reference, int maxResults) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + List partRs = new PartRevisionDAO(new Locale(user.getLanguage()), em).findPartsRevisionsWithReferenceOrNameLike(pWorkspaceId, reference, maxResults); + return partRs.toArray(new PartRevision[partRs.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision releasePartRevision(PartRevisionKey pRevisionKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException, NotAllowedException { + User user = checkPartRevisionWriteAccess(pRevisionKey); // Check if the user can write the part + Locale locale = new Locale(user.getLanguage()); + + PartRevisionDAO partRevisionDAO = new PartRevisionDAO(locale, em); + PartRevision partRevision = partRevisionDAO.loadPartR(pRevisionKey); + + if (partRevision.isCheckedOut()) { + throw new NotAllowedException(locale, "NotAllowedException46"); + } + + if (partRevision.getNumberOfIterations() == 0) { + throw new NotAllowedException(locale, "NotAllowedException41"); + } + + if (partRevision.isObsolete()) { + throw new NotAllowedException(locale, "NotAllowedException38"); + } + + partRevision.release(user); + return partRevision; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision markPartRevisionAsObsolete(PartRevisionKey pRevisionKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException, NotAllowedException { + User user = checkPartRevisionWriteAccess(pRevisionKey); // Check if the user can write the part + Locale locale = new Locale(user.getLanguage()); + + PartRevisionDAO partRevisionDAO = new PartRevisionDAO(locale, em); + PartRevision partRevision = partRevisionDAO.loadPartR(pRevisionKey); + + if (!partRevision.isReleased()) { + throw new NotAllowedException(locale, "NotAllowedException36"); + } + + partRevision.markAsObsolete(user); + return partRevision; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision getLastReleasePartRevision(ConfigurationItemKey ciKey) + throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, AccessRightException, PartRevisionNotFoundException { + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + ConfigurationItem ci = new ConfigurationItemDAO(locale, em).loadConfigurationItem(ciKey); + PartMaster partMaster = ci.getDesignItem(); + PartRevision lastReleasedRevision = partMaster.getLastReleasedRevision(); + if (lastReleasedRevision == null) { + throw new PartRevisionNotFoundException(locale, partMaster.getNumber(), "Released"); + } + if (!canUserAccess(user, lastReleasedRevision.getKey())) { + throw new AccessRightException(locale, user); + } + return lastReleasedRevision; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List findBaselinesWherePartRevisionHasIterations(PartRevisionKey partRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException { + User user = userManager.checkWorkspaceReadAccess(partRevisionKey.getPartMaster().getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + PartRevision partRevision = new PartRevisionDAO(locale, em).loadPartR(partRevisionKey); + return new ProductBaselineDAO(locale, em).findBaselineWherePartRevisionHasIterations(partRevision); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getComponents(PartIterationKey pPartIPK) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartIterationNotFoundException, NotAllowedException { + User user = userManager.checkWorkspaceReadAccess(pPartIPK.getWorkspaceId()); + Locale locale = new Locale(user.getLanguage()); + PartIteration partI = new PartIterationDAO(locale, em).loadPartI(pPartIPK); + PartRevision partR = partI.getPartRevision(); + + if (isCheckoutByAnotherUser(user, partR) && partR.getLastIteration().equals(partI)) { + throw new NotAllowedException(locale, "NotAllowedException34"); + } + return partI.getComponents(); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public boolean partMasterExists(PartMasterKey partMasterKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(partMasterKey.getWorkspace()); + try { + new PartMasterDAO(new Locale(user.getLanguage()), em).loadPartM(partMasterKey); + return true; + } catch (PartMasterNotFoundException e) { + LOGGER.log(Level.FINEST, null, e); + return false; + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteConfigurationItem(ConfigurationItemKey configurationItemKey) throws UserNotFoundException, WorkspaceNotFoundException, AccessRightException, NotAllowedException, UserNotActiveException, ConfigurationItemNotFoundException, LayerNotFoundException, EntityConstraintException { + User user = userManager.checkWorkspaceWriteAccess(configurationItemKey.getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + + ProductConfigurationDAO productConfigurationDAO = new ProductConfigurationDAO(locale, em); + List productConfigurations = productConfigurationDAO.getAllProductConfigurationsByConfigurationItem(configurationItemKey); + + if (!productConfigurations.isEmpty()) { + throw new EntityConstraintException(locale, "EntityConstraintException23"); + } + + ProductBaselineDAO productBaselineDAO = new ProductBaselineDAO(locale, em); + List productBaselines = productBaselineDAO.findBaselines(configurationItemKey.getId(), configurationItemKey.getWorkspace()); + + if (!productBaselines.isEmpty()) { + throw new EntityConstraintException(locale, "EntityConstraintException4"); + } + + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + List productInstanceMasters = productInstanceMasterDAO.findProductInstanceMasters(configurationItemKey.getId(), configurationItemKey.getWorkspace()); + + if (!productInstanceMasters.isEmpty()) { + throw new EntityConstraintException(locale, "EntityConstraintException13"); + } + + new ConfigurationItemDAO(locale, em).removeConfigurationItem(configurationItemKey); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteLayer(String workspaceId, int layerId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, LayerNotFoundException, AccessRightException { + Layer layer = new LayerDAO(em).loadLayer(layerId); + User user = userManager.checkWorkspaceWriteAccess(layer.getConfigurationItem().getWorkspaceId()); + new LayerDAO(new Locale(user.getLanguage()), em).deleteLayer(layer); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public BinaryResource renameFileInPartIteration(String pSubType, String pFullName, String pNewName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, FileNotFoundException, FileAlreadyExistsException, CreationException, StorageException { + + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(pFullName)); + Locale userLocale = new Locale(user.getLanguage()); + checkNameFileValidity(pNewName, userLocale); + + BinaryResourceDAO binDAO = new BinaryResourceDAO(userLocale, em); + BinaryResource file = binDAO.loadBinaryResource(pFullName); + PartIteration partIteration = binDAO.getPartHolder(file); + + PartRevision partR = partIteration.getPartRevision(); + + if (isCheckoutByUser(user, partR) && partR.getLastIteration().equals(partIteration)) { + + dataManager.renameFile(file, pNewName); + + if (pSubType != null && "nativecad".equals(pSubType)) { + partIteration.setNativeCADFile(null); + } else { + partIteration.removeAttachedFile(file); + } + binDAO.removeBinaryResource(file); + + BinaryResource newFile = new BinaryResource(file.getNewFullName(pNewName), file.getContentLength(), file.getLastModified()); + + binDAO.createBinaryResource(newFile); + + if (pSubType != null && "nativecad".equals(pSubType)) { + partIteration.setNativeCADFile(newFile); + } else { + partIteration.addAttachedFile(newFile); + } + + return newFile; + } else { + throw new NotAllowedException(userLocale, "NotAllowedException35"); + } + + } + + private void removeCADFile(PartIteration partIteration) + throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartIterationNotFoundException { + + // Delete native cad file + BinaryResource br = partIteration.getNativeCADFile(); + if (br != null) { + try { + dataManager.deleteData(br); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + partIteration.setNativeCADFile(null); + } + + // Delete generated 3D files + List geometries = new ArrayList<>(partIteration.getGeometries()); + for (Geometry geometry : geometries) { + try { + dataManager.deleteData(geometry); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + partIteration.removeGeometry(geometry); + } + } + + private void removeAttachedFiles(PartIteration partIteration) + throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartIterationNotFoundException { + + // Delete attached files + for (BinaryResource file : partIteration.getAttachedFiles()) { + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + + esIndexer.delete(partIteration); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void removeFileInPartIteration(PartIterationKey pPartIPK, String pSubType, String pName) + throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartIterationNotFoundException, FileNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(pPartIPK.getWorkspaceId()); + + PartIteration partIteration = new PartIterationDAO(new Locale(user.getLanguage()), em).loadPartI(pPartIPK); + PartRevision partR = partIteration.getPartRevision(); + + if (isCheckoutByUser(user, partR) && partR.getLastIteration().equals(partIteration)) { + BinaryResourceDAO binDAO = new BinaryResourceDAO(new Locale(user.getLanguage()), em); + BinaryResource file = binDAO.loadBinaryResource(pName); + + if (pSubType != null && "nativecad".equals(pSubType)) { + partIteration.setNativeCADFile(null); + } else { + partIteration.removeAttachedFile(file); + } + + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + + binDAO.removeBinaryResource(file); + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void setPublicSharedPart(PartRevisionKey pPartRPK, boolean isPublicShared) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + PartRevision partRevision = getPartRevision(pPartRPK); + partRevision.setPublicShared(isPublicShared); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartMaster getPartMaster(PartMasterKey pPartMPK) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartMasterNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pPartMPK.getWorkspace()); + PartMaster partM = new PartMasterDAO(new Locale(user.getLanguage()), em).loadPartM(pPartMPK); + + for (PartRevision partR : partM.getPartRevisions()) { + if (isCheckoutByAnotherUser(user, partR)) { + em.detach(partR); + partR.removeLastIteration(); + } + } + return partM; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getLayers(ConfigurationItemKey pKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pKey.getWorkspace()); + return new LayerDAO(new Locale(user.getLanguage()), em).findAllLayers(pKey); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Layer getLayer(int pId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, LayerNotFoundException { + Layer layer = new LayerDAO(em).loadLayer(pId); + userManager.checkWorkspaceReadAccess(layer.getConfigurationItem().getWorkspaceId()); + return layer; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Layer createLayer(ConfigurationItemKey pKey, String pName, String color) throws UserNotFoundException, WorkspaceNotFoundException, AccessRightException, ConfigurationItemNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(pKey.getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + + ConfigurationItem ci = new ConfigurationItemDAO(locale, em).loadConfigurationItem(pKey); + Layer layer = new Layer(pName, user, ci, color); + Date now = new Date(); + layer.setCreationDate(now); + + new LayerDAO(locale, em).createLayer(layer); + return layer; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Layer updateLayer(ConfigurationItemKey pKey, int pId, String pName, String color) throws UserNotFoundException, WorkspaceNotFoundException, AccessRightException, ConfigurationItemNotFoundException, LayerNotFoundException, UserNotActiveException { + Layer layer = getLayer(pId); + userManager.checkWorkspaceWriteAccess(layer.getConfigurationItem().getWorkspaceId()); + layer.setName(pName); + layer.setColor(color); + return layer; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Marker createMarker(int pLayerId, String pTitle, String pDescription, double pX, double pY, double pZ) throws LayerNotFoundException, UserNotFoundException, WorkspaceNotFoundException, AccessRightException { + Layer layer = new LayerDAO(em).loadLayer(pLayerId); + User user = userManager.checkWorkspaceWriteAccess(layer.getConfigurationItem().getWorkspaceId()); + Marker marker = new Marker(pTitle, user, pDescription, pX, pY, pZ); + Date now = new Date(); + marker.setCreationDate(now); + + new MarkerDAO(new Locale(user.getLanguage()), em).createMarker(marker); + layer.addMarker(marker); + return marker; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteMarker(int pLayerId, int pMarkerId) throws WorkspaceNotFoundException, UserNotActiveException, LayerNotFoundException, UserNotFoundException, AccessRightException, MarkerNotFoundException { + Layer layer = new LayerDAO(em).loadLayer(pLayerId); + User user = userManager.checkWorkspaceWriteAccess(layer.getConfigurationItem().getWorkspaceId()); + Locale locale = new Locale(user.getLanguage()); + Marker marker = new MarkerDAO(locale, em).loadMarker(pMarkerId); + + if (layer.getMarkers().contains(marker)) { + layer.removeMarker(marker); + em.flush(); + new MarkerDAO(locale, em).removeMarker(pMarkerId); + } else { + throw new MarkerNotFoundException(locale, pMarkerId); + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartMasterTemplate[] getPartMasterTemplates(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + List templates = new PartMasterTemplateDAO(new Locale(user.getLanguage()), em).findAllPartMTemplates(pWorkspaceId); + + ListIterator ite = templates.listIterator(); + while (ite.hasNext()) { + PartMasterTemplate template = ite.next(); + if (!hasPartTemplateReadAccess(user, template)) { + ite.remove(); + } + } + + return templates.toArray(new PartMasterTemplate[templates.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartMasterTemplate getPartMasterTemplate(PartMasterTemplateKey pKey) throws WorkspaceNotFoundException, PartMasterTemplateNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pKey.getWorkspaceId()); + return new PartMasterTemplateDAO(new Locale(user.getLanguage()), em).loadPartMTemplate(pKey); + } + + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartMasterTemplate createPartMasterTemplate(String pWorkspaceId, String pId, String pPartType, String pWorkflowModelId, String pMask, List pAttributeTemplates, String[] lovNames, List pAttributeInstanceTemplates, String[] instanceLovNames, boolean idGenerated, boolean attributesLocked) throws WorkspaceNotFoundException, AccessRightException, PartMasterTemplateAlreadyExistsException, UserNotFoundException, NotAllowedException, CreationException, WorkflowModelNotFoundException, ListOfValuesNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(pWorkspaceId); + Locale locale = new Locale(user.getLanguage()); + checkNameValidity(pId, locale); + + //Check pMask + if (pMask != null && !pMask.isEmpty() && !NamingConvention.correctNameMask(pMask)) { + throw new NotAllowedException(locale, "MaskCreationException"); + } + + PartMasterTemplate template = new PartMasterTemplate(user.getWorkspace(), pId, user, pPartType, pMask, attributesLocked); + Date now = new Date(); + template.setCreationDate(now); + template.setIdGenerated(idGenerated); + LOVDAO lovDAO = new LOVDAO(locale, em); + + List attrs = new ArrayList<>(); + for (int i = 0; i < pAttributeTemplates.size(); i++) { + if(attributesLocked) { + pAttributeTemplates.get(i).setLocked(attributesLocked); + } + attrs.add(pAttributeTemplates.get(i)); + if (pAttributeTemplates.get(i) instanceof ListOfValuesAttributeTemplate) { + ListOfValuesAttributeTemplate lovAttr = (ListOfValuesAttributeTemplate) pAttributeTemplates.get(i); + ListOfValuesKey lovKey = new ListOfValuesKey(user.getWorkspaceId(), lovNames[i]); + lovAttr.setLov(lovDAO.loadLOV(lovKey)); + } + } + if(!AttributesConsistencyUtils.isTemplateAttributesValid(attrs,attributesLocked)) { + throw new NotAllowedException(locale, "NotAllowedException59"); + } + template.setAttributeTemplates(attrs); + + List instanceAttrs = new ArrayList<>(); + for (int i = 0; i < pAttributeInstanceTemplates.size(); i++) { + instanceAttrs.add(pAttributeInstanceTemplates.get(i)); + if (pAttributeInstanceTemplates.get(i) instanceof ListOfValuesAttributeTemplate) { + ListOfValuesAttributeTemplate lovAttr = (ListOfValuesAttributeTemplate) pAttributeInstanceTemplates.get(i); + ListOfValuesKey lovKey = new ListOfValuesKey(user.getWorkspaceId(), instanceLovNames[i]); + lovAttr.setLov(lovDAO.loadLOV(lovKey)); + } + } + if(!AttributesConsistencyUtils.isTemplateAttributesValid(instanceAttrs,false)) { + throw new NotAllowedException(locale, "NotAllowedException59"); + } + template.setAttributeInstanceTemplates(instanceAttrs); + + if (pWorkflowModelId != null) { + WorkflowModel workflowModel = new WorkflowModelDAO(locale, em).loadWorkflowModel(new WorkflowModelKey(user.getWorkspaceId(), pWorkflowModelId)); + template.setWorkflowModel(workflowModel); + } + + new PartMasterTemplateDAO(locale, em).createPartMTemplate(template); + return template; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartMasterTemplate updatePartMasterTemplate(PartMasterTemplateKey pKey, String pPartType, String pWorkflowModelId, String pMask, List pAttributeTemplates, String[] lovNames, List pAttributeInstanceTemplates, String[] instanceLovNames, boolean idGenerated, boolean attributesLocked) throws WorkspaceNotFoundException, AccessRightException, PartMasterTemplateNotFoundException, UserNotFoundException, WorkflowModelNotFoundException, UserNotActiveException, ListOfValuesNotFoundException, NotAllowedException { + User user = userManager.checkWorkspaceReadAccess(pKey.getWorkspaceId()); + Locale locale = new Locale(user.getLanguage()); + + PartMasterTemplateDAO templateDAO = new PartMasterTemplateDAO(new Locale(user.getLanguage()), em); + PartMasterTemplate template = templateDAO.loadPartMTemplate(pKey); + + checkPartTemplateWriteAccess(template, user); + + Date now = new Date(); + template.setModificationDate(now); + template.setAuthor(user); + template.setPartType(pPartType); + template.setMask(pMask); + template.setIdGenerated(idGenerated); + template.setAttributesLocked(attributesLocked); + LOVDAO lovDAO = new LOVDAO(locale, em); + + List attrs = new ArrayList<>(); + for (int i = 0; i < pAttributeTemplates.size(); i++) { + if(attributesLocked) { + pAttributeTemplates.get(i).setLocked(attributesLocked); + } + attrs.add(pAttributeTemplates.get(i)); + if (pAttributeTemplates.get(i) instanceof ListOfValuesAttributeTemplate) { + ListOfValuesAttributeTemplate lovAttr = (ListOfValuesAttributeTemplate) pAttributeTemplates.get(i); + ListOfValuesKey lovKey = new ListOfValuesKey(user.getWorkspaceId(), lovNames[i]); + lovAttr.setLov(lovDAO.loadLOV(lovKey)); + } + } + if(!AttributesConsistencyUtils.isTemplateAttributesValid(attrs,attributesLocked)) { + throw new NotAllowedException(locale, "NotAllowedException59"); + } + template.setAttributeTemplates(attrs); + + List instanceAttrs = new ArrayList<>(); + for (int i = 0; i < pAttributeInstanceTemplates.size(); i++) { + instanceAttrs.add(pAttributeInstanceTemplates.get(i)); + if (pAttributeInstanceTemplates.get(i) instanceof ListOfValuesAttributeTemplate) { + ListOfValuesAttributeTemplate lovAttr = (ListOfValuesAttributeTemplate) pAttributeInstanceTemplates.get(i); + ListOfValuesKey lovKey = new ListOfValuesKey(user.getWorkspaceId(), instanceLovNames[i]); + lovAttr.setLov(lovDAO.loadLOV(lovKey)); + } + } + if(!AttributesConsistencyUtils.isTemplateAttributesValid(instanceAttrs,false)) { + throw new NotAllowedException(locale, "NotAllowedException59"); + } + template.setAttributeInstanceTemplates(instanceAttrs); + + WorkflowModel workflowModel = null; + if (pWorkflowModelId != null) { + workflowModel = new WorkflowModelDAO(locale, em).loadWorkflowModel(new WorkflowModelKey(user.getWorkspaceId(), pWorkflowModelId)); + } + template.setWorkflowModel(workflowModel); + + return template; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deletePartMasterTemplate(PartMasterTemplateKey pKey) throws WorkspaceNotFoundException, AccessRightException, PartMasterTemplateNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pKey.getWorkspaceId()); + PartMasterTemplateDAO templateDAO = new PartMasterTemplateDAO(new Locale(user.getLanguage()), em); + + PartMasterTemplate partMasterTemplate = templateDAO.loadPartMTemplate(pKey); + checkPartTemplateWriteAccess(partMasterTemplate, user); + + PartMasterTemplate template = templateDAO.removePartMTemplate(pKey); + BinaryResource file = template.getAttachedFile(); + if (file != null) { + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public BinaryResource saveFileInTemplate(PartMasterTemplateKey pPartMTemplateKey, String pName, long pSize) throws WorkspaceNotFoundException, NotAllowedException, PartMasterTemplateNotFoundException, FileAlreadyExistsException, UserNotFoundException, UserNotActiveException, CreationException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(pPartMTemplateKey.getWorkspaceId()); + Locale locale = new Locale(user.getLanguage()); + checkNameFileValidity(pName, locale); + + PartMasterTemplateDAO templateDAO = new PartMasterTemplateDAO(locale, em); + PartMasterTemplate template = templateDAO.loadPartMTemplate(pPartMTemplateKey); + + checkPartTemplateWriteAccess(template, user); + + BinaryResource binaryResource = null; + String fullName = template.getWorkspaceId() + "/part-templates/" + template.getId() + "/" + pName; + + BinaryResource bin = template.getAttachedFile(); + if (bin != null && bin.getFullName().equals(fullName)) { + binaryResource = bin; + } else if(bin != null && !bin.getFullName().equals(fullName)) { + try { + dataManager.deleteData(bin); + } catch (StorageException e) { + LOGGER.log(Level.WARNING, "Could not delete attached file", e); + } + } + + if (binaryResource == null) { + binaryResource = new BinaryResource(fullName, pSize, new Date()); + new BinaryResourceDAO(locale, em).createBinaryResource(binaryResource); + template.setAttachedFile(binaryResource); + } else { + binaryResource.setContentLength(pSize); + binaryResource.setLastModified(new Date()); + } + return binaryResource; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartMasterTemplate removeFileFromTemplate(String pFullName) throws WorkspaceNotFoundException, PartMasterTemplateNotFoundException, AccessRightException, FileNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(pFullName)); + BinaryResourceDAO binDAO = new BinaryResourceDAO(new Locale(user.getLanguage()), em); + BinaryResource file = binDAO.loadBinaryResource(pFullName); + + PartMasterTemplate template = binDAO.getPartTemplateHolder(file); + checkPartTemplateWriteAccess(template, user); + + template.setAttachedFile(null); + binDAO.removeBinaryResource(file); + + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + return template; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public BinaryResource renameFileInTemplate(String pFullName, String pNewName) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, FileNotFoundException, UserNotActiveException, FileAlreadyExistsException, CreationException, StorageException, NotAllowedException { + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(pFullName)); + Locale userLocale = new Locale(user.getLanguage()); + checkNameFileValidity(pNewName, userLocale); + + BinaryResourceDAO binDAO = new BinaryResourceDAO(new Locale(user.getLanguage()), em); + BinaryResource file = binDAO.loadBinaryResource(pFullName); + + PartMasterTemplate template = binDAO.getPartTemplateHolder(file); + + checkPartTemplateWriteAccess(template, user); + + dataManager.renameFile(file, pNewName); + + template.setAttachedFile(null); + binDAO.removeBinaryResource(file); + + BinaryResource newFile = new BinaryResource(file.getNewFullName(pNewName), file.getContentLength(), file.getLastModified()); + + binDAO.createBinaryResource(newFile); + template.setAttachedFile(newFile); + + return newFile; + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getPartMasters(String pWorkspaceId, int start, int pMaxResults) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + return new PartMasterDAO(new Locale(user.getLanguage()), em).getPartMasters(pWorkspaceId, start, pMaxResults); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getPartRevisions(String pWorkspaceId, int start, int pMaxResults) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + List partRevisions; + + if (pMaxResults == 0) { + partRevisions = new PartRevisionDAO(new Locale(user.getLanguage()), em).getAllPartRevisions(pWorkspaceId); + } else { + partRevisions = new PartRevisionDAO(new Locale(user.getLanguage()), em).getPartRevisions(pWorkspaceId, start, pMaxResults); + } + + List filteredPartRevisions = new ArrayList<>(); + + for (PartRevision partRevision : partRevisions) { + try { + checkPartRevisionReadAccess(partRevision.getKey()); + + if (isCheckoutByAnotherUser(user, partRevision)) { + em.detach(partRevision); + partRevision.removeLastIteration(); + } + + filteredPartRevisions.add(partRevision); + + } catch (AccessRightException | PartRevisionNotFoundException e) { + LOGGER.log(Level.FINER, null, e); + } + } + return filteredPartRevisions; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public int getPartsInWorkspaceCount(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + return new PartRevisionDAO(new Locale(user.getLanguage()), em).getPartRevisionCountFiltered(user, pWorkspaceId); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public int getTotalNumberOfParts(String pWorkspaceId) throws AccessRightException, WorkspaceNotFoundException, AccountNotFoundException, UserNotFoundException, UserNotActiveException { + Locale locale; + if (!contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID)) { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + locale = new Locale(user.getLanguage()); + } else { + locale = Locale.getDefault(); + } + //TODO: count only part you can see + return new PartRevisionDAO(locale, em).getTotalNumberOfParts(pWorkspaceId); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deletePartRevision(PartRevisionKey partRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, EntityConstraintException, ESServerException, AccessRightException { + + User user = userManager.checkWorkspaceReadAccess(partRevisionKey.getPartMaster().getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + + PartMasterDAO partMasterDAO = new PartMasterDAO(locale, em); + PartRevisionDAO partRevisionDAO = new PartRevisionDAO(locale, em); + PartUsageLinkDAO partUsageLinkDAO = new PartUsageLinkDAO(locale, em); + ProductBaselineDAO productBaselineDAO = new ProductBaselineDAO(locale, em); + + ConfigurationItemDAO configurationItemDAO = new ConfigurationItemDAO(locale, em); + + PartRevision partR = partRevisionDAO.loadPartR(partRevisionKey); + if(!hasPartOrWorkspaceWriteAccess(user,partR)) { + throw new AccessRightException(locale, user); + } + PartMaster partMaster = partR.getPartMaster(); + boolean isLastRevision = partMaster.getPartRevisions().size() == 1; + + //TODO all the 3 removal restrictions may be performed + //more precisely on PartRevision rather on PartMaster + // check if part is linked to a product + if (configurationItemDAO.isPartMasterLinkedToConfigurationItem(partMaster)) { + throw new EntityConstraintException(locale, "EntityConstraintException1"); + } + // check if this part is in a partUsage + if (partUsageLinkDAO.hasPartUsages(partMaster.getWorkspaceId(), partMaster.getNumber())) { + throw new EntityConstraintException(locale, "EntityConstraintException2"); + } + + // check if this part is in a partSubstitute + if (partUsageLinkDAO.hasPartSubstitutes(partMaster.getWorkspaceId(), partMaster.getNumber())) { + throw new EntityConstraintException(locale, "EntityConstraintException22"); + } + + // check if part is baselined + if (productBaselineDAO.existBaselinedPart(partMaster.getWorkspaceId(), partMaster.getNumber())) { + throw new EntityConstraintException(locale, "EntityConstraintException5"); + } + + ChangeItemDAO changeItemDAO = new ChangeItemDAO(locale, em); + if (changeItemDAO.hasChangeItems(partRevisionKey)) { + throw new EntityConstraintException(locale, "EntityConstraintException21"); + } + + // delete ElasticSearch Index for this revision iteration + for (PartIteration partIteration : partR.getPartIterations()) { + esIndexer.delete(partIteration); + // Remove ElasticSearch Index for this PartIteration + } + + partRevisionEvent.select(new AnnotationLiteral() { + }).fire(new PartRevisionChangeEvent(partR)); + + if (isLastRevision) { + partMasterDAO.removePartM(partMaster); + } else { + partMaster.removeRevision(partR); + partRevisionDAO.removeRevision(partR); + } + + // delete CAD and other files attached with this partMaster + for (PartIteration partIteration : partR.getPartIterations()) { + try { + removeCADFile(partIteration); + removeAttachedFiles(partIteration); + } catch (PartIterationNotFoundException e) { + LOGGER.log(Level.INFO, null, e); + } + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public int getNumberOfIteration(PartRevisionKey partRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException { + User user = userManager.checkWorkspaceReadAccess(partRevisionKey.getPartMaster().getWorkspace()); + return new PartRevisionDAO(new Locale(user.getLanguage()), em).loadPartR(partRevisionKey).getLastIterationNumber(); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision createPartRevision(PartRevisionKey revisionKey, String pDescription, String pWorkflowModelId, ACLUserEntry[] pACLUserEntries, ACLUserGroupEntry[] pACLUserGroupEntries, Map> userRoleMapping, Map> groupRoleMapping) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, PartRevisionNotFoundException, NotAllowedException, FileAlreadyExistsException, CreationException, RoleNotFoundException, WorkflowModelNotFoundException, PartRevisionAlreadyExistsException, UserGroupNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(revisionKey.getPartMaster().getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + + PartRevisionDAO partRevisionDAO = new PartRevisionDAO(locale, em); + + PartRevision originalPartR = partRevisionDAO.loadPartR(revisionKey); + + if (originalPartR.isCheckedOut()) { + throw new NotAllowedException(locale, "NotAllowedException40"); + } + + if (originalPartR.getNumberOfIterations() == 0) { + throw new NotAllowedException(locale, "NotAllowedException41"); + } + + PartRevision partR = originalPartR.getPartMaster().createNextRevision(user); + + PartIteration lastPartI = originalPartR.getLastIteration(); + PartIteration firstPartI = partR.createNextIteration(user); + + + if (lastPartI != null) { + + BinaryResourceDAO binDAO = new BinaryResourceDAO(locale, em); + for (BinaryResource sourceFile : lastPartI.getAttachedFiles()) { + String fileName = sourceFile.getName(); + long length = sourceFile.getContentLength(); + Date lastModified = sourceFile.getLastModified(); + String fullName = partR.getWorkspaceId() + "/parts/" + partR.getPartNumber() + "/" + partR.getVersion() + "/1/" + fileName; + BinaryResource targetFile = new BinaryResource(fullName, length, lastModified); + binDAO.createBinaryResource(targetFile); + firstPartI.addAttachedFile(targetFile); + try { + dataManager.copyData(sourceFile, targetFile); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + + // Copy usage links + // Create new p2p links + + List newComponents = new LinkedList<>(); + List oldComponents = lastPartI.getComponents(); + for (PartUsageLink usage : lastPartI.getComponents()) { + PartUsageLink newUsage = usage.clone(); + //PartUsageLink is shared among PartIteration hence there is no cascade persist + //so we need to persist them explicitly + em.persist(newUsage); + newComponents.add(newUsage); + } + firstPartI.setComponents(newComponents); + //flush to ensure the new PartUsageLinks have their id generated + em.flush(); + PathToPathLinkDAO pathToPathLinkDAO = new PathToPathLinkDAO(locale, em); + pathToPathLinkDAO.cloneAndUpgradePathToPathLinks(oldComponents, newComponents); + + // copy geometries + for (Geometry sourceFile : lastPartI.getGeometries()) { + String fileName = sourceFile.getName(); + long length = sourceFile.getContentLength(); + int quality = sourceFile.getQuality(); + Date lastModified = sourceFile.getLastModified(); + String fullName = partR.getWorkspaceId() + "/parts/" + partR.getPartNumber() + "/" + partR.getVersion() + "/1/" + fileName; + Geometry targetFile = new Geometry(quality, fullName, length, lastModified); + binDAO.createBinaryResource(targetFile); + firstPartI.addGeometry(targetFile); + try { + dataManager.copyData(sourceFile, targetFile); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + + BinaryResource nativeCADFile = lastPartI.getNativeCADFile(); + if (nativeCADFile != null) { + String fileName = nativeCADFile.getName(); + long length = nativeCADFile.getContentLength(); + Date lastModified = nativeCADFile.getLastModified(); + String fullName = partR.getWorkspaceId() + "/parts/" + partR.getPartNumber() + "/" + partR.getVersion() + "/1/nativecad/" + fileName; + BinaryResource targetFile = new BinaryResource(fullName, length, lastModified); + binDAO.createBinaryResource(targetFile); + firstPartI.setNativeCADFile(targetFile); + try { + dataManager.copyData(nativeCADFile, targetFile); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + + + Set links = new HashSet<>(); + for (DocumentLink link : lastPartI.getLinkedDocuments()) { + DocumentLink newLink = link.clone(); + links.add(newLink); + } + firstPartI.setLinkedDocuments(links); + + List attrs = new ArrayList<>(); + for (InstanceAttribute attr : lastPartI.getInstanceAttributes()) { + InstanceAttribute clonedAttribute = attr.clone(); + attrs.add(clonedAttribute); + } + firstPartI.setInstanceAttributes(attrs); + + } + + + if (pWorkflowModelId != null) { + + UserDAO userDAO = new UserDAO(locale, em); + UserGroupDAO groupDAO = new UserGroupDAO(locale, em); + RoleDAO roleDAO = new RoleDAO(locale, em); + + Map> roleUserMap = new HashMap<>(); + for (Map.Entry> pair : userRoleMapping.entrySet()) { + String roleName = pair.getKey(); + Collection userLogins = pair.getValue(); + Role role = roleDAO.loadRole(new RoleKey(originalPartR.getWorkspaceId(), roleName)); + Set users=new HashSet<>(); + roleUserMap.put(role, users); + for(String login:userLogins) { + User u = userDAO.loadUser(new UserKey(originalPartR.getWorkspaceId(), login)); + users.add(u); + } + } + + Map> roleGroupMap = new HashMap<>(); + for (Map.Entry> pair : groupRoleMapping.entrySet()) { + String roleName = pair.getKey(); + Collection groupIds = pair.getValue(); + Role role = roleDAO.loadRole(new RoleKey(originalPartR.getWorkspaceId(), roleName)); + Set groups=new HashSet<>(); + roleGroupMap.put(role, groups); + for(String groupId:groupIds) { + UserGroup g = groupDAO.loadUserGroup(new UserGroupKey(originalPartR.getWorkspaceId(), groupId)); + groups.add(g); + } + } + + WorkflowModel workflowModel = new WorkflowModelDAO(locale, em).loadWorkflowModel(new WorkflowModelKey(user.getWorkspaceId(), pWorkflowModelId)); + Workflow workflow = workflowModel.createWorkflow(roleUserMap, roleGroupMap); + partR.setWorkflow(workflow); + + for(Task task : workflow.getTasks()){ + if(!task.hasPotentialWorker()){ + throw new NotAllowedException(locale,"NotAllowedException56"); + } + } + + Collection runningTasks = workflow.getRunningTasks(); + + for (Task runningTask : runningTasks) { + runningTask.start(); + } + + mailer.sendApproval(runningTasks, partR); + } + + partR.setDescription(pDescription); + + if ((pACLUserEntries != null && pACLUserEntries.length > 0) || (pACLUserGroupEntries != null && pACLUserGroupEntries.length > 0)) { + ACL acl = new ACL(); + if (pACLUserEntries != null) { + for (ACLUserEntry entry : pACLUserEntries) { + acl.addEntry(em.getReference(User.class, new UserKey(user.getWorkspaceId(), entry.getPrincipalLogin())), entry.getPermission()); + } + } + + if (pACLUserGroupEntries != null) { + for (ACLUserGroupEntry entry : pACLUserGroupEntries) { + acl.addEntry(em.getReference(UserGroup.class, new UserGroupKey(user.getWorkspaceId(), entry.getPrincipalId())), entry.getPermission()); + } + } + partR.setACL(acl); + } + Date now = new Date(); + partR.setCreationDate(now); + partR.setCheckOutUser(user); + partR.setCheckOutDate(now); + firstPartI.setCreationDate(now); + firstPartI.setModificationDate(now); + + partRevisionDAO.createPartR(partR); + + return partR; + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public String generateId(String pWorkspaceId, String pPartMTemplateId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, PartMasterTemplateNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + Locale locale = new Locale(user.getLanguage()); + PartMasterTemplateKey partMasterTemplateKey = new PartMasterTemplateKey(user.getWorkspaceId(), pPartMTemplateId); + PartMasterTemplate template = new PartMasterTemplateDAO(locale, em).loadPartMTemplate(partMasterTemplateKey); + + String newId = null; + try { + String latestId = new PartMasterDAO(locale, em).findLatestPartMId(pWorkspaceId, template.getPartType()); + String inputMask = template.getMask(); + String convertedMask = Tools.convertMask(inputMask); + newId = Tools.increaseId(latestId, convertedMask); + } catch (ParseException ex) { + LOGGER.log(Level.WARNING, "Different mask has been used for the same document type", ex); + } catch (NoResultException ex) { + LOGGER.log(Level.FINE, "No document of the specified type has been created", ex); + } + return newId; + + } + + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public long getDiskUsageForPartsInWorkspace(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException { + Account account = userManager.checkAdmin(pWorkspaceId); + return new PartMasterDAO(new Locale(account.getLanguage()), em).getDiskUsageForPartsInWorkspace(pWorkspaceId); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public long getDiskUsageForPartTemplatesInWorkspace(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException { + Account account = userManager.checkAdmin(pWorkspaceId); + return new PartMasterDAO(new Locale(account.getLanguage()), em).getDiskUsageForPartTemplatesInWorkspace(pWorkspaceId); + } + + @Override + public PartRevision[] getCheckedOutPartRevisions(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + List partRevisions = new PartRevisionDAO(new Locale(user.getLanguage()), em).findCheckedOutPartRevisionsForUser(pWorkspaceId, user.getLogin()); + return partRevisions.toArray(new PartRevision[partRevisions.size()]); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public PartRevision[] getAllCheckedOutPartRevisions(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException { + Account account = userManager.checkAdmin(pWorkspaceId); + List partRevisions = new PartRevisionDAO(new Locale(account.getLanguage()), em).findAllCheckedOutPartRevisions(pWorkspaceId); + return partRevisions.toArray(new PartRevision[partRevisions.size()]); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public SharedPart createSharedPart(PartRevisionKey pPartRevisionKey, String pPassword, Date pExpireDate) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, PartRevisionNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceWriteAccess(pPartRevisionKey.getPartMaster().getWorkspace()); + SharedPart sharedPart = new SharedPart(user.getWorkspace(), user, pExpireDate, pPassword, getPartRevision(pPartRevisionKey)); + SharedEntityDAO sharedEntityDAO = new SharedEntityDAO(new Locale(user.getLanguage()), em); + sharedEntityDAO.createSharedPart(sharedPart); + return sharedPart; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public void deleteSharedPart(SharedEntityKey pSharedEntityKey) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, SharedEntityNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(pSharedEntityKey.getWorkspace()); + SharedEntityDAO sharedEntityDAO = new SharedEntityDAO(new Locale(user.getLanguage()), em); + SharedPart sharedPart = sharedEntityDAO.loadSharedPart(pSharedEntityKey.getUuid()); + sharedEntityDAO.deleteSharedPart(sharedPart); + } + + + @RolesAllowed({UserGroupMapping.GUEST_PROXY_ROLE_ID, UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public boolean canAccess(PartRevisionKey partRKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException { + PartRevision partRevision; + if (contextManager.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)) { + partRevision = new PartRevisionDAO(em).loadPartR(partRKey); + return partRevision.isPublicShared(); + } + + User user = userManager.checkWorkspaceReadAccess(partRKey.getPartMaster().getWorkspace()); + return canUserAccess(user, partRKey); + } + + @RolesAllowed({UserGroupMapping.GUEST_PROXY_ROLE_ID, UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public boolean canAccess(PartIterationKey partIKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, PartIterationNotFoundException { + PartRevision partRevision; + if (contextManager.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)) { + partRevision = new PartRevisionDAO(em).loadPartR(partIKey.getPartRevision()); + return partRevision.isPublicShared() && partRevision.getLastCheckedInIteration().getIteration() >= partIKey.getIteration(); + } + + User user = userManager.checkWorkspaceReadAccess(partIKey.getWorkspaceId()); + return canUserAccess(user, partIKey); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public boolean canUserAccess(User user, PartRevisionKey partRKey) throws PartRevisionNotFoundException { + PartRevision partRevision = new PartRevisionDAO(new Locale(user.getLanguage()), em).loadPartR(partRKey); + return hasPartRevisionReadAccess(user, partRevision); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public boolean canUserAccess(User user, PartIterationKey partIKey) throws PartRevisionNotFoundException, PartIterationNotFoundException { + PartRevisionDAO partRevisionDAO = new PartRevisionDAO(new Locale(user.getLanguage()), em); + PartRevision partR = partRevisionDAO.loadPartR(partIKey.getPartRevision()); + return hasPartRevisionReadAccess(user, partR) && + (!partRevisionDAO.isCheckedOutIteration(partIKey) || + user.equals(partR.getCheckOutUser())); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public User checkPartRevisionReadAccess(PartRevisionKey partRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(partRevisionKey.getPartMaster().getWorkspace()); + if (!canUserAccess(user, partRevisionKey)) { + throw new AccessRightException(new Locale(user.getLanguage()), user); + } + return user; + } + + @RolesAllowed({UserGroupMapping.GUEST_PROXY_ROLE_ID, UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public boolean canWrite(PartRevisionKey partRKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException { + + String workspace = partRKey.getPartMaster().getWorkspace(); + + User user = userManager.checkWorkspaceReadAccess(workspace); + + if(user.isAdministrator()){ + return true; + } + + PartRevision partRevision; + + try { + partRevision = getPartRevision(partRKey); + }catch(AccessRightException e){ + return false; + } + + if(partRevision.getACL() != null){ + if(partRevision.getACL().hasWriteAccess(user)){ + return true; + } + return false; + } + + try{ + userManager.checkWorkspaceWriteAccess(workspace); + return true; + } catch(AccessRightException e){ + return false; + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Component filterProductStructure(ConfigurationItemKey ciKey, PSFilter filter, List path, Integer pDepth) throws ConfigurationItemNotFoundException, WorkspaceNotFoundException, NotAllowedException, UserNotFoundException, UserNotActiveException, PartUsageLinkNotFoundException, AccessRightException, PartMasterNotFoundException, EntityConstraintException { + + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + + + PSFilterVisitor psFilterVisitor = new PSFilterVisitor(em, user, filter) { + @Override + public void onIndeterminateVersion(PartMaster partMaster, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onIndeterminatePath(List pCurrentPath, List pCurrentPathPartIterations) { + // Unused here + } + + @Override + public void onUnresolvedPath(List pCurrentPath, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onBranchDiscovered(List pCurrentPath, List copyPartIteration) { + // Unused here + } + + @Override + public void onOptionalPath(List path, List partIterations) { + // Unused here + } + + @Override + public boolean onPathWalk(List path, List parts) { + // Unused here + return true; + } + + @Override + public void onUnresolvedVersion(PartMaster partMaster) { + // Unused here + } + }; + + if (path == null) { + ConfigurationItem ci = new ConfigurationItemDAO(locale, em).loadConfigurationItem(ciKey); + psFilterVisitor.visit(ci.getDesignItem(), pDepth); + }else{ + psFilterVisitor.visit(path, pDepth); + } + + return psFilterVisitor.getComponent(); + + } + + + @Override + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + public Set getWritablePartRevisionsFromPath(ConfigurationItemKey configurationItemKey, String path) throws EntityConstraintException, PartMasterNotFoundException, NotAllowedException, UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException { + User user = userManager.checkWorkspaceReadAccess(configurationItemKey.getWorkspace()); + Set partRevisions = new HashSet<>(); + PSFilterVisitor psFilterVisitor = new PSFilterVisitor(em,user,new WIPPSFilter(user)) { + @Override + public void onIndeterminateVersion(PartMaster partMaster, List partIterations) throws NotAllowedException { + + } + + @Override + public void onUnresolvedVersion(PartMaster partMaster) throws NotAllowedException { + + } + + @Override + public void onIndeterminatePath(List pCurrentPath, List pCurrentPathPartIterations) throws NotAllowedException { + + } + + @Override + public void onUnresolvedPath(List pCurrentPath, List partIterations) throws NotAllowedException { + + } + + @Override + public void onBranchDiscovered(List pCurrentPath, List copyPartIteration) { + + } + + @Override + public void onOptionalPath(List path, List partIterations) { + + } + + @Override + public boolean onPathWalk(List path, List parts) { + PartMaster pm = parts.get(parts.size() -1); + try { + if(!hasPartRevisionReadAccess(user,pm.getLastRevision())) { + //Don't visit this branch anymore + return false; + } + if(!hasPartOrWorkspaceWriteAccess(user,pm.getLastRevision())) { + return true; + } + partRevisions.add(pm.getLastRevision()); + + } catch (WorkspaceNotFoundException e) { + LOGGER.log(Level.SEVERE, "Could not check access to part revision",e); + return false; + } + return true; + } + }; + + psFilterVisitor.visit(decodePath(configurationItemKey,path),null); + return partRevisions; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Component filterProductStructureOnLinkType(ConfigurationItemKey ciKey, PSFilter filter, String configSpecType, String path, String linkType) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException, ProductInstanceMasterNotFoundException, BaselineNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + ConfigurationItem ci = new ConfigurationItemDAO(locale, em).loadConfigurationItem(ciKey); + + PathToPathLinkDAO pathToPathLinkDAO = new PathToPathLinkDAO(locale, em); + + Component component = new Component(); + component.setUser(user); + component.setComponents(new ArrayList<>()); + + Set links = new HashSet<>(); + + if (path == null) { // If path is null => get Root links embedded as subComponents of a virtual component + List rootPathToPathLinks; + + if (configSpecType.startsWith("pi-")) { + String serialNumber = configSpecType.substring(3); + ProductInstanceMasterKey productInstanceMasterKey = new ProductInstanceMasterKey(serialNumber, ciKey.getWorkspace(), ciKey.getId()); + ProductInstanceIteration pii = new ProductInstanceMasterDAO(locale, em).loadProductInstanceMaster(productInstanceMasterKey).getLastIteration(); + rootPathToPathLinks = pathToPathLinkDAO.findRootPathToPathLinks(pii, linkType); + + } else if (!"wip".equals(configSpecType) && !"latest".equals(configSpecType) && !"released".equals(configSpecType)) { + int baselineId = 0; + try { + baselineId = Integer.parseInt(configSpecType); + } catch(NumberFormatException e) { + LOGGER.log(Level.FINEST, null, e); + } + ProductBaseline pb = new ProductBaselineDAO(locale, em).loadBaseline(baselineId); + rootPathToPathLinks = pathToPathLinkDAO.findRootPathToPathLinks(pb, linkType); + + } else { + rootPathToPathLinks = pathToPathLinkDAO.findRootPathToPathLinks(ci, linkType); + } + + if (rootPathToPathLinks.size() > 0) { + for (PathToPathLink link : rootPathToPathLinks) { + links.add(link.getSourcePath()); + } + + PartMaster virtualPartMaster = new PartMaster(ci.getWorkspace(), linkType, user); + PartRevision virtualPartRevision = new PartRevision(virtualPartMaster, user); + virtualPartMaster.getPartRevisions().add(virtualPartRevision); + PartIteration virtualPartIteration = new PartIteration(virtualPartRevision, user); + virtualPartRevision.getPartIterations().add(virtualPartIteration); + + component.setPartMaster(virtualPartMaster); + component.setRetainedIteration(virtualPartIteration); + component.setPath(new ArrayList<>()); + component.setVirtual(true); + + component.getPath().add(new PartLink() { + @Override + public int getId() { + return 0; + } + + @Override + public Character getCode() { + return null; + } + + @Override + public String getFullId() { + return null; + } + + @Override + public double getAmount() { + return 1; + } + + @Override + public String getUnit() { + return null; + } + + @Override + public String getComment() { + return null; + } + + @Override + public boolean isOptional() { + return false; + } + + @Override + public PartMaster getComponent() { + return virtualPartMaster; + } + + @Override + public List getSubstitutes() { + return null; + } + + @Override + public String getReferenceDescription() { + return linkType; + } + + @Override + public List getCadInstances() { + return null; + } + }); + } + + } else { // If path is not null => get next path to path links + + if ("wip".equals(configSpecType) || "latest".equals(configSpecType) || "released".equals(configSpecType)) { + List sourcesPathToPathLinksInProduct = pathToPathLinkDAO.getSourcesPathToPathLinksInProduct(ci, linkType, path); + for (PathToPathLink link : sourcesPathToPathLinksInProduct) { + links.add(link.getTargetPath()); + } + List decodedSourcePath = decodePath(ciKey, path); + PartLink link = decodedSourcePath.get(decodedSourcePath.size() - 1); + List partIterations = filter.filter(link.getComponent()); + PartIteration retainedIteration = partIterations.get(partIterations.size() - 1); + + component.setPath(decodedSourcePath); + component.setPartMaster(link.getComponent()); + component.setRetainedIteration(retainedIteration); + + } else { + ProductBaseline productBaseline = null; + ProductBaselineDAO productBaselineDAO = new ProductBaselineDAO(locale, em); + + if (configSpecType.startsWith("pi-")) { + String serialNumber = configSpecType.substring(3); + productBaseline = productBaselineDAO.findLastBaselineWithSerialNumber(ciKey, serialNumber); + + } else { + int baselineId = 0; + try { + baselineId = Integer.parseInt(configSpecType); + } catch(NumberFormatException e) { + LOGGER.log(Level.FINEST, null, e); + } + productBaseline = productBaselineDAO.loadBaseline(baselineId); + } + + List sourcesPathToPathLinksInProduct = pathToPathLinkDAO.getSourcesPathToPathLinksInBaseline(productBaseline, linkType, path); + for (PathToPathLink link : sourcesPathToPathLinksInProduct) { + links.add(link.getTargetPath()); + } + List decodedSourcePath = decodePath(ciKey, path); + PartLink link = decodedSourcePath.get(decodedSourcePath.size() - 1); + List partIterations = filter.filter(link.getComponent()); + PartIteration retainedIteration = partIterations.get(partIterations.size() - 1); + + component.setPath(decodedSourcePath); + component.setPartMaster(link.getComponent()); + component.setRetainedIteration(retainedIteration); + } + } + + // Iterate the list and populate sub components + + for (String link : links) { + Component subComponent = new Component(); + + List decodedPath = decodePath(ciKey, link); + subComponent.setPath(decodedPath); + + PartLink partLink = decodedPath.get(decodedPath.size() - 1); + subComponent.setPartMaster(partLink.getComponent()); + + // TODO: determine if we do not need to add the subcomponent if there is no retainedIteration + List partIterations = filter.filter(partLink.getComponent()); + if (partIterations.size() > 0) { + PartIteration retainedIteration = partIterations.get(partIterations.size() - 1); + subComponent.setRetainedIteration(retainedIteration); + } + + subComponent.setUser(user); + subComponent.setComponents(new ArrayList<>()); + component.addComponent(subComponent); + } + + return component; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List decodePath(ConfigurationItemKey ciKey, String path) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartUsageLinkNotFoundException, ConfigurationItemNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + + if (path == null) { + throw new IllegalArgumentException("Path cannot be null"); + } + List decodedPath = new ArrayList<>(); + + PartLink rootPartUsageLink = getRootPartUsageLink(ciKey); + decodedPath.add(rootPartUsageLink); + + if ("-1".equals(path)) { + return decodedPath; + } + + // Remove the -1- in front of string + String[] split = path.substring(3).split("-"); + + for (String codeAndId : split) { + + int id = Integer.valueOf(codeAndId.substring(1)); + + if (codeAndId.startsWith("u")) { + decodedPath.add(getPartUsageLink(user, id)); + } else if (codeAndId.startsWith("s")) { + decodedPath.add(getPartSubstituteLink(user, id)); + } else { + throw new IllegalArgumentException("Missing code"); + } + + } + + return decodedPath; + } + + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List searchPartRevisions(String workspaceId, Query query) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + + WorkspaceDAO workspaceDAO = new WorkspaceDAO(locale, em); + Workspace workspace = workspaceDAO.loadWorkspace(workspaceId); + + QueryDAO queryDAO = new QueryDAO(locale, em); + List parts = queryDAO.runQuery(workspace, query); + + ListIterator ite = parts.listIterator(); + + while (ite.hasNext()) { + PartRevision partR = ite.next(); + + if (isCheckoutByAnotherUser(user, partR)) { + em.detach(partR); + partR.removeLastIteration(); + } + + if (partR.getLastIteration() != null && !hasPartRevisionReadAccess(user, partR)) { + ite.remove(); + } + } + + return parts; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Query getQuery(String workspaceId, int queryId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(workspaceId); + + QueryDAO queryDAO = new QueryDAO(new Locale(user.getLanguage()), em); + Query query = queryDAO.loadQuery(queryId); + + if (!query.getAuthor().getWorkspace().getId().equals(workspaceId)) { + userManager.checkWorkspaceReadAccess(query.getAuthor().getWorkspace().getId()); + } + + return query; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void createQuery(String workspaceId, Query query) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, QueryAlreadyExistsException, CreationException { + User user = userManager.checkWorkspaceWriteAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + QueryDAO queryDAO = new QueryDAO(locale, em); + + Query existingQuery = queryDAO.findQueryByName(workspaceId, query.getName()); + if (existingQuery != null) { + deleteQuery(workspaceId, existingQuery.getId()); + } + + query.setAuthor(user); + query.setCreationDate(new Date()); + queryDAO.createQuery(query); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteQuery(String workspaceId, int queryId) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + QueryDAO queryDAO = new QueryDAO(locale, em); + Query query = queryDAO.loadQuery(queryId); + + Workspace workspace = query.getAuthor().getWorkspace(); + User userInQueryWorkspace = userManager.checkWorkspaceWriteAccess(workspace.getId()); + + if (query.getAuthor().equals(userInQueryWorkspace) || userInQueryWorkspace.isAdministrator()) { + queryDAO.removeQuery(query); + } else { + throw new AccessRightException(locale, userInQueryWorkspace); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getQueries(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + QueryDAO queryDAO = new QueryDAO(new Locale(user.getLanguage()), em); + return queryDAO.loadQueries(workspaceId); + } + + private PartUsageLink getPartUsageLink(User user, int id) throws PartUsageLinkNotFoundException { + PartUsageLinkDAO partUsageLinkDAO = new PartUsageLinkDAO(new Locale(user.getLanguage()), em); + return partUsageLinkDAO.loadPartUsageLink(id); + } + + private PartSubstituteLink getPartSubstituteLink(User user, int id) throws PartUsageLinkNotFoundException { + PartUsageLinkDAO partUsageLinkDAO = new PartUsageLinkDAO(new Locale(user.getLanguage()), em); + return partUsageLinkDAO.loadPartSubstituteLink(id); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartLink getRootPartUsageLink(ConfigurationItemKey pKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pKey.getWorkspace()); + ConfigurationItem ci = new ConfigurationItemDAO(new Locale(user.getLanguage()), em).loadConfigurationItem(pKey); + + return new PartLink() { + @Override + public int getId() { + return 1; + } + + @Override + public Character getCode() { + return '-'; + } + + @Override + public String getFullId() { + return "-1"; + } + + @Override + public double getAmount() { + return 1; + } + + @Override + public String getUnit() { + return null; + } + + @Override + public String getComment() { + return null; + } + + @Override + public boolean isOptional() { + return false; + } + + @Override + public PartMaster getComponent() { + return ci.getDesignItem(); + } + + @Override + public List getSubstitutes() { + return null; + } + + @Override + public String getReferenceDescription() { + return null; + } + + @Override + public List getCadInstances() { + List cads = new ArrayList<>(); + CADInstance cad = new CADInstance(0d, 0d, 0d, 0d, 0d, 0d); + cad.setId(0); + cads.add(cad); + return cads; + } + }; + + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void checkCyclicAssemblyForPartIteration(PartIteration partIteration) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException { + + PartMaster partMaster = partIteration.getPartRevision().getPartMaster(); + Workspace workspace = partMaster.getWorkspace(); + + User user = userManager.checkWorkspaceReadAccess(workspace.getId()); + + // Navigate the WIP + PSFilterVisitor psFilterVisitor = new PSFilterVisitor(em, user, new UpdatePartIterationPSFilter(user, partIteration)) { + @Override + public void onIndeterminateVersion(PartMaster partMaster, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onIndeterminatePath(List pCurrentPath, List pCurrentPathPartIterations) { + // Unused here + } + + @Override + public void onUnresolvedPath(List pCurrentPath, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onBranchDiscovered(List pCurrentPath, List copyPartIteration) { + // Unused here + } + + @Override + public void onOptionalPath(List path, List partIterations) { + // Unused here + } + + @Override + public boolean onPathWalk(List path, List parts) { + // Unused here + return true; + } + + @Override + public void onUnresolvedVersion(PartMaster partMaster) { + // Unused here + } + }; + + psFilterVisitor.visit(partMaster, -1); + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PSFilter getLatestCheckedInPSFilter(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + return new LatestPSFilter(user); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public boolean hasModificationNotification(ConfigurationItemKey ciKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException { + + + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + ConfigurationItemDAO configurationItemDAO = new ConfigurationItemDAO(locale, em); + ConfigurationItem configurationItem = configurationItemDAO.loadConfigurationItem(ciKey); + + ModificationNotificationDAO modificationNotificationDAO = new ModificationNotificationDAO(em); + + List visitedPartNumbers = new ArrayList<>(); + LatestPSFilter latestPSFilter = new LatestPSFilter(user, true); + final boolean[] hasModificationNotification = {false}; + + PSFilterVisitor psFilterVisitor = new PSFilterVisitor(em, user, latestPSFilter) { + @Override + public void onIndeterminateVersion(PartMaster partMaster, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onIndeterminatePath(List pCurrentPath, List pCurrentPathPartIterations) { + // Unused here + } + + @Override + public void onUnresolvedPath(List pCurrentPath, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onBranchDiscovered(List pCurrentPath, List copyPartIteration) { + // Unused here + } + + @Override + public void onOptionalPath(List path, List partIterations) { + // Unused here + } + + @Override + public boolean onPathWalk(List path, List parts) { + PartMaster partMaster = parts.get(parts.size() - 1); + if (!visitedPartNumbers.contains(partMaster.getNumber()) && !hasModificationNotification[0]) { + visitedPartNumbers.add(partMaster.getNumber()); + List partIterations = latestPSFilter.filter(partMaster); + // As we use a latest checked-in filter, partIterations array can be empty + if (!partIterations.isEmpty()) { + PartIteration partIteration = partIterations.get(partIterations.size() - 1); + if (modificationNotificationDAO.hasModificationNotifications(partIteration.getKey())) { + hasModificationNotification[0] = true; + } + } + } + return true; + } + + @Override + public void onUnresolvedVersion(PartMaster partMaster) { + // Unused here + } + }; + + psFilterVisitor.visit(configurationItem.getDesignItem(), -1); + + return hasModificationNotification[0]; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getPartIterationsInstanceAttributesInWorkspace(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + userManager.checkWorkspaceReadAccess(workspaceId); + InstanceAttributeDAO instanceAttributeDAO = new InstanceAttributeDAO(em); + return instanceAttributeDAO.getPartIterationsInstanceAttributesInWorkspace(workspaceId); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getPathDataInstanceAttributesInWorkspace(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + userManager.checkWorkspaceReadAccess(workspaceId); + InstanceAttributeDAO instanceAttributeDAO = new InstanceAttributeDAO(em); + return instanceAttributeDAO.getPathDataInstanceAttributesInWorkspace(workspaceId); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List filterProductBreakdownStructure(String workspaceId, Query query) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException, ProductInstanceMasterNotFoundException, ConfigurationItemNotFoundException, NotAllowedException, PartMasterNotFoundException, EntityConstraintException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + List rows = new ArrayList<>(); + for (QueryContext queryContext : query.getContexts()) { + rows.addAll(filterPBS(workspaceId, queryContext, user)); + } + return rows; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getBinaryResourceFromBaseline(int baselineId) { + List binaryResources = new ArrayList<>(); + + ProductBaselineDAO productBaselineDAO = new ProductBaselineDAO(em); + for (Map.Entry doc : productBaselineDAO.findBaselineById(baselineId).getBaselinedDocuments().entrySet()) { + for (BinaryResource binary : doc.getValue().getTargetDocument().getAttachedFiles()) { + if (!binaryResources.contains(binary)) { + binaryResources.add(binary); + } + } + } + return binaryResources; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Map> getBinariesInTree(Integer baselineId, String workspaceId, ConfigurationItemKey ciKey, PSFilter psFilter, boolean exportNativeCADFiles, boolean exportDocumentLinks) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Map> result = new HashMap<>(); + + Locale locale = new Locale(user.getLanguage()); + + ConfigurationItem ci = new ConfigurationItemDAO(locale, em).loadConfigurationItem(ciKey); + PartMaster root = ci.getDesignItem(); + + + PSFilterVisitor psFilterVisitor = new PSFilterVisitor(em, user, psFilter) { + @Override + public void onIndeterminateVersion(PartMaster partMaster, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onIndeterminatePath(List pCurrentPath, List pCurrentPathPartIterations) { + // Unused here + } + + @Override + public void onUnresolvedPath(List pCurrentPath, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onBranchDiscovered(List pCurrentPath, List copyPartIteration) { + // Unused here + } + + @Override + public void onOptionalPath(List path, List partIterations) { + // Unused here + } + + @Override + public boolean onPathWalk(List path, List parts) { + PartMaster part = parts.get(parts.size() - 1); + List partIterations = psFilter.filter(part); + + if (!partIterations.isEmpty()) { + + PartIteration partIteration = partIterations.get(0); + String partFolderName = partIteration.toString(); + Set binaryResources = result.get(partFolderName); + + if (binaryResources == null) { + binaryResources = new HashSet<>(); + result.put(partFolderName, binaryResources); + } + + if (exportNativeCADFiles) { + BinaryResource nativeCADFile = partIteration.getNativeCADFile(); + if (nativeCADFile != null) { + binaryResources.add(nativeCADFile); + } + + if (exportDocumentLinks) { + for (BinaryResource attachedFile : partIteration.getAttachedFiles()) { + if (attachedFile != null) { + binaryResources.add(attachedFile); + } + } + } + } + + if (exportDocumentLinks && baselineId == null) { + Set linkedDocuments = partIteration.getLinkedDocuments(); + + for (DocumentLink documentLink : linkedDocuments) { + + DocumentIteration lastCheckedInIteration = documentLink.getTargetDocument().getLastCheckedInIteration(); + + if(null != lastCheckedInIteration){ + + String linkedDocumentFolderName = "links/" + lastCheckedInIteration.toString(); + + Set linkedBinaryResources = result.get(linkedDocumentFolderName); + + if (linkedBinaryResources == null) { + linkedBinaryResources = new HashSet<>(); + result.put(linkedDocumentFolderName, linkedBinaryResources); + } + + Set attachedFiles = lastCheckedInIteration.getAttachedFiles(); + + for (BinaryResource binary : attachedFiles) { + if (!linkedBinaryResources.contains(binary)) { + linkedBinaryResources.add(binary); + } + } + + } + } + } + + } + return true; + } + + @Override + public void onUnresolvedVersion(PartMaster partMaster) { + // Unused here + } + }; + + psFilterVisitor.visit(root, -1); + + return result; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ProductBaseline loadProductBaselineForProductInstanceMaster(ConfigurationItemKey ciKey, String serialNumber) throws ProductInstanceMasterNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + return new ProductBaselineDAO(new Locale(user.getLanguage()), em).findLastBaselineWithSerialNumber(ciKey, serialNumber); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Query loadQuery(String workspaceId, int queryId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + QueryDAO queryDAO = new QueryDAO(new Locale(user.getLanguage()), em); + return queryDAO.loadQuery(queryId); + } + + private List filterPBS(String workspaceId, QueryContext queryContext, User user) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, BaselineNotFoundException, ProductInstanceMasterNotFoundException, ConfigurationItemNotFoundException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException { + + String configurationItemId = queryContext.getConfigurationItemId(); + String serialNumber = queryContext.getSerialNumber(); + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, configurationItemId); + Locale locale = new Locale(user.getLanguage()); + + List rows = new ArrayList<>(); + + PSFilter filter = serialNumber != null ? psFilterManager.getPSFilter(ciKey, "pi-" + serialNumber, false) : psFilterManager.getPSFilter(ciKey, "latest", false); + + ProductInstanceIteration productInstanceIteration = null; + if (serialNumber != null) { + ProductInstanceMaster productIM = new ProductInstanceMasterDAO(em).loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, ciKey)); + productInstanceIteration = productIM.getLastIteration(); + } + + ConfigurationItem ci = new ConfigurationItemDAO(locale, em).loadConfigurationItem(ciKey); + PartMaster root = ci.getDesignItem(); + + List pathToPathLinks = ci.getPathToPathLinks(); + PathDataIterationDAO pathDataIterationDAO = new PathDataIterationDAO(em); + + List lastPathDataIterations = pathDataIterationDAO.getLastPathDataIterations(productInstanceIteration); + Map lastPathDataIterationsMap = new HashMap<>(); + + for(PathDataIteration iteration: lastPathDataIterations){ + lastPathDataIterationsMap.put(iteration.getPathDataMaster().getPath(),iteration); + } + + final ProductInstanceIteration finalProductInstanceIteration = productInstanceIteration; + + PSFilterVisitor psFilterVisitor = new PSFilterVisitor(em, user, filter) { + @Override + public void onIndeterminateVersion(PartMaster partMaster, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onIndeterminatePath(List pCurrentPath, List pCurrentPathPartIterations) { + // Unused here + } + + @Override + public void onUnresolvedPath(List pCurrentPath, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onBranchDiscovered(List pCurrentPath, List copyPartIteration) { + // Unused here + } + + @Override + public void onOptionalPath(List path, List partIterations) { + // Unused here + } + + @Override + public boolean onPathWalk(List path, List parts) { + QueryResultRow row = new QueryResultRow(); + double totalAmount = 1; + for (PartLink pl : path) { + if (pl.getUnit() == null) { + totalAmount *= pl.getAmount(); + } + } + String pathAsString = Tools.getPathAsString(path); + row.setPath(pathAsString); + int depth = parts.size() -1; + PartMaster part = parts.get(parts.size() - 1); + List partIterations = filter.filter(part); + if (!partIterations.isEmpty()) { + PartRevision partRevision = partIterations.get(0).getPartRevision(); + row.setPartRevision(partRevision); + row.setDepth(depth); + row.setContext(queryContext); + row.setAmount(totalAmount); + + // try block and decodePath method are time consuming (db access) May need refactor + for(PathToPathLink pathToPathLink:pathToPathLinks){ + try{ + if(pathToPathLink.getSourcePath().equals(pathAsString)){ + row.addSource(pathToPathLink.getType(), decodePath(ciKey, pathToPathLink.getTargetPath())); + } + if(pathToPathLink.getTargetPath().equals(pathAsString)){ + row.addTarget(pathToPathLink.getType(), decodePath(ciKey, pathToPathLink.getTargetPath())); + } + } catch (WorkspaceNotFoundException |UserNotFoundException | ConfigurationItemNotFoundException | PartUsageLinkNotFoundException | UserNotActiveException e) { + LOGGER.log(Level.SEVERE,null,e); + } + } + + if(finalProductInstanceIteration != null) { + row.setPathDataIteration(lastPathDataIterationsMap.get(pathAsString)); + } + rows.add(row); + } + return true; + } + + @Override + public void onUnresolvedVersion(PartMaster partMaster) { + // Unused here + } + }; + + psFilterVisitor.visit(root, -1); + return rows; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public List getInversePartsLink(DocumentRevisionKey docKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, PartIterationNotFoundException, PartRevisionNotFoundException { + User user = userManager.checkWorkspaceReadAccess(docKey.getWorkspaceId()); + + Locale locale = new Locale(user.getLanguage()); + + DocumentRevision documentRevision = new DocumentRevisionDAO(locale, em).loadDocR(docKey); + + DocumentLinkDAO documentLinkDAO = new DocumentLinkDAO(locale, em); + List iterations = documentLinkDAO.getInversePartsLinks(documentRevision); + ListIterator ite = iterations.listIterator(); + + while (ite.hasNext()) { + PartIteration next = ite.next(); + if (!canAccess(next.getKey())) { + ite.remove(); + } + } + return iterations; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public Set getInverseProductInstancesLink(DocumentRevisionKey docKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, PartIterationNotFoundException, PartRevisionNotFoundException { + User user = userManager.checkWorkspaceReadAccess(docKey.getWorkspaceId()); + + Locale locale = new Locale(user.getLanguage()); + + DocumentRevision documentRevision = new DocumentRevisionDAO(locale, em).loadDocR(docKey); + + DocumentLinkDAO documentLinkDAO = new DocumentLinkDAO(locale, em); + List iterations = documentLinkDAO.getInverseProductInstanceIteration(documentRevision); + Set productInstanceMasterSet = new HashSet<>(); + for (ProductInstanceIteration productInstanceIteration : iterations) { + productInstanceMasterSet.add(productInstanceIteration.getProductInstanceMaster()); + + } + return productInstanceMasterSet; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public Set getInversePathDataLink(DocumentRevisionKey docKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, PartIterationNotFoundException, PartRevisionNotFoundException { + User user = userManager.checkWorkspaceReadAccess(docKey.getWorkspaceId()); + + Locale locale = new Locale(user.getLanguage()); + + DocumentRevision documentRevision = new DocumentRevisionDAO(locale, em).loadDocR(docKey); + + DocumentLinkDAO documentLinkDAO = new DocumentLinkDAO(locale, em); + List pathDataIterations = documentLinkDAO.getInversefindPathData(documentRevision); + Set productInstanceMasterSet = new HashSet<>(); + for (PathDataIteration pathDataIteration : pathDataIterations) { + productInstanceMasterSet.add(pathDataIteration.getPathDataMaster()); + + } + return productInstanceMasterSet; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public PathToPathLink createPathToPathLink(String workspaceId, String configurationItemId, String type, String pathFrom, String pathTo, String description) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, PathToPathLinkAlreadyExistsException, CreationException, PathToPathCyclicException, PartUsageLinkNotFoundException, UserNotActiveException, NotAllowedException { + User user = userManager.checkWorkspaceWriteAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + + // Load the product + ConfigurationItem ci = new ConfigurationItemDAO(locale, em).loadConfigurationItem(new ConfigurationItemKey(workspaceId, configurationItemId)); + + if (type == null || type.isEmpty()) { + throw new NotAllowedException(locale, "NotAllowedException54"); + } + + if(pathFrom != null && pathTo != null && pathFrom.equals(pathTo)){ + throw new NotAllowedException(locale, "NotAllowedException57"); + } + + // Decode the paths to insure path validity + List sourcePath = decodePath(ci.getKey(), pathFrom); + List targetPath = decodePath(ci.getKey(), pathTo); + + // Check for substitute linking + PartLink sourceLink = sourcePath.get(sourcePath.size() - 1); + PartLink targetLink = targetPath.get(targetPath.size() - 1); + + if(sourceLink.getSubstitutes() != null && sourceLink.getSubstitutes().contains(targetLink) || targetLink.getSubstitutes() != null && targetLink.getSubstitutes().contains(sourceLink)){ + throw new NotAllowedException(locale, "NotAllowedException58"); + } + + PathToPathLink pathToPathLink = new PathToPathLink(type, pathFrom, pathTo, description); + PathToPathLinkDAO pathToPathLinkDAO = new PathToPathLinkDAO(locale, em); + PathToPathLink samePathToPathLink = pathToPathLinkDAO.getSamePathToPathLink(ci, pathToPathLink); + + if (samePathToPathLink != null) { + throw new PathToPathLinkAlreadyExistsException(locale, pathToPathLink); + } + + pathToPathLinkDAO.createPathToPathLink(pathToPathLink); + + ci.addPathToPathLink(pathToPathLink); + + checkCyclicPathToPathLink(ci, pathToPathLink, user, new ArrayList<>()); + + return pathToPathLink; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public PathToPathLink updatePathToPathLink(String workspaceId, String configurationItemId, int pathToPathLinkId, String description) + throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException, UserNotActiveException, NotAllowedException, PathToPathLinkNotFoundException { + + User user = userManager.checkWorkspaceWriteAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + + // Load the product + ConfigurationItem ci = new ConfigurationItemDAO(locale, em).loadConfigurationItem(new ConfigurationItemKey(workspaceId, configurationItemId)); + + PathToPathLinkDAO pathToPathLinkDAO = new PathToPathLinkDAO(locale, em); + PathToPathLink pathToPathLink = pathToPathLinkDAO.loadPathToPathLink(pathToPathLinkId); + + if(!ci.getPathToPathLinks().contains(pathToPathLink)){ + throw new NotAllowedException(locale, "NotAllowedException60"); + } + + pathToPathLink.setDescription(description); + + return pathToPathLink; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public List getPathToPathLinkTypes(String workspaceId, String configurationItemId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + ConfigurationItem configurationItem = new ConfigurationItemDAO(locale, em).loadConfigurationItem(new ConfigurationItemKey(workspaceId, configurationItemId)); + return new PathToPathLinkDAO(locale, em).getDistinctPathToPathLinkTypes(configurationItem); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public List getPathToPathLinkFromSourceAndTarget(String workspaceId, String configurationItemId, String sourcePath, String targetPath) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + ConfigurationItem configurationItem = new ConfigurationItemDAO(locale, em).loadConfigurationItem(new ConfigurationItemKey(workspaceId, configurationItemId)); + return new PathToPathLinkDAO(locale, em).getPathToPathLinkFromSourceAndTarget(configurationItem, sourcePath, targetPath); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public ProductInstanceMaster findProductByPathMaster(String workspaceId, PathDataMaster pathDataMaster) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + PathDataMasterDAO pathDataMasterDAO = new PathDataMasterDAO(new Locale(user.getLanguage()), em); + return pathDataMasterDAO.findByPathData(pathDataMaster); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public PartMaster getPartMasterFromPath(String workspaceId, String configurationItemId, String partPath) throws ConfigurationItemNotFoundException, UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartUsageLinkNotFoundException { + ConfigurationItem configurationItem = new ConfigurationItemDAO(em).loadConfigurationItem(new ConfigurationItemKey(workspaceId, configurationItemId)); + List path = decodePath(configurationItem.getKey(), partPath); + return path.get(path.size() - 1).getComponent(); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public void deletePathToPathLink(String workspaceId, String configurationItemId, int pathToPathLinkId) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, PathToPathLinkNotFoundException { + + User user = userManager.checkWorkspaceWriteAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + + // Load the product + ConfigurationItem ci = new ConfigurationItemDAO(locale, em).loadConfigurationItem(new ConfigurationItemKey(workspaceId, configurationItemId)); + + PathToPathLinkDAO pathToPathLinkDAO = new PathToPathLinkDAO(locale, em); + PathToPathLink pathToPathLink = pathToPathLinkDAO.loadPathToPathLink(pathToPathLinkId); + ci.removePathToPathLink(pathToPathLink); + pathToPathLinkDAO.removePathToPathLink(pathToPathLink); + + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public List getDocumentLinksAsDocumentIterations(String workspaceId, String configurationItemId, String configSpec, PartIterationKey partIterationKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException, PartIterationNotFoundException, ProductInstanceMasterNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + + PartIterationDAO partIterationDAO = new PartIterationDAO(locale,em); + PartIteration partIteration = partIterationDAO.loadPartI(partIterationKey); + + if(null == configSpec){ + throw new IllegalArgumentException("Config spec cannot be null"); + } + + ProductBaseline baseline; + + if (configSpec.startsWith("pi-")) { + String serialNumber = configSpec.substring(3); + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale,em); + ProductInstanceMaster productInstanceMaster = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + baseline = productInstanceMaster.getLastIteration().getBasedOn(); + } else { + int baselineId = Integer.parseInt(configSpec); + ProductBaselineDAO productBaselineDAO = new ProductBaselineDAO(locale, em); + baseline = productBaselineDAO.loadBaseline(baselineId); + } + + DocumentCollection documentCollection = baseline.getDocumentCollection(); + Map baselinedDocuments = documentCollection.getBaselinedDocuments(); + List documentIterationLinks = new ArrayList<>(); + + for(Map.Entry map : baselinedDocuments.entrySet()){ + BaselinedDocument baselinedDocument = map.getValue(); + DocumentIteration targetDocument = baselinedDocument.getTargetDocument(); + for(DocumentLink documentLink : partIteration.getLinkedDocuments() ){ + if(documentLink.getTargetDocument().getKey().equals(targetDocument.getDocumentRevisionKey())){ + documentIterationLinks.add(new DocumentIterationLink(documentLink,targetDocument)); + } + } + } + + return documentIterationLinks; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) + @Override + public PartIteration findPartIterationByBinaryResource(BinaryResource binaryResource) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + BinaryResourceDAO binaryResourceDAO; + + if (contextManager.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)) { + binaryResourceDAO = new BinaryResourceDAO(em); + } else { + User user = userManager.checkWorkspaceReadAccess(binaryResource.getWorkspaceId()); + binaryResourceDAO = new BinaryResourceDAO(new Locale(user.getLanguage()),em); + } + return binaryResourceDAO.getPartHolder(binaryResource); + } + + private void checkCyclicPathToPathLink(ConfigurationItem ci, PathToPathLink startLink, User user, List visitedLinks) throws PathToPathCyclicException { + Locale locale = new Locale(user.getLanguage()); + PathToPathLinkDAO pathToPathLinkDAO = new PathToPathLinkDAO(locale, em); + + List nextPathToPathLinks = pathToPathLinkDAO.getNextPathToPathLinkInProduct(ci, startLink); + for (PathToPathLink nextPathToPathLink : nextPathToPathLinks) { + if (visitedLinks.contains(nextPathToPathLink)) { + throw new PathToPathCyclicException(locale); + } + visitedLinks.add(nextPathToPathLink); + checkCyclicPathToPathLink(ci, startLink, user, visitedLinks); + } + } + + private User checkPartRevisionWriteAccess(PartRevisionKey partRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException { + String workspaceId = partRevisionKey.getPartMaster().getWorkspace(); + User user = userManager.checkWorkspaceReadAccess(workspaceId); + if (user.isAdministrator()) { // Check if it is the workspace's administrator + return user; + } + PartRevision partRevision = new PartRevisionDAO(em).loadPartR(partRevisionKey); + if (partRevision.getACL() == null) { // Check if the part haven't ACL + return userManager.checkWorkspaceWriteAccess(workspaceId); + } + if (partRevision.getACL().hasWriteAccess(user)) { // Check if the ACL grant write access + return user; + } + throw new AccessRightException(new Locale(user.getLanguage()), user); // Else throw a AccessRightException + } + + private User checkPartTemplateWriteAccess(PartMasterTemplate template, User user) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException { + + if (user.isAdministrator()) { + // Check if it is the workspace's administrator + return user; + } + if (template.getAcl() == null) { + // Check if the item haven't ACL + return userManager.checkWorkspaceWriteAccess(template.getWorkspaceId()); + } else if (template.getAcl().hasWriteAccess(user)) { + // Check if there is a write access + return user; + } else { + // Else throw a AccessRightException + throw new AccessRightException(new Locale(user.getLanguage()), user); + } + + + } + + /** + * Say if a user, which have access to the workspace, have read access to a part revision + * + * @param user A user which have read access to the workspace + * @param partRevision The part revision wanted + * @return True if access is granted, False otherwise + */ + private boolean hasPartRevisionReadAccess(User user, PartRevision partRevision) { + return user.isAdministrator() || isACLGrantReadAccess(user, partRevision); + } + + private boolean hasPartTemplateReadAccess(User user, PartMasterTemplate template) { + return user.isAdministrator() || isACLGrantReadAccess(user, template); + } + + /** + * Say if a user, which have access to the workspace, have write access to a part revision + * + * @param user A user which have read access to the workspace + * @param partRevision The part revision wanted + * @return True if access is granted, False otherwise + */ + private boolean hasPartRevisionWriteAccess(User user, PartRevision partRevision) { + return user.isAdministrator() || isACLGrantWriteAccess(user, partRevision); + } + + /** + * Say if a user can write on a part Revision through ACL or Workspace Access + * @param user A user with at least Read access to the workspace + * @param partRevision + * @return True if access is granted, False otherwise + * @throws WorkspaceNotFoundException + */ + private boolean hasPartOrWorkspaceWriteAccess(User user, PartRevision partRevision) throws WorkspaceNotFoundException { + return partRevision.getACL() == null ? + userManager.hasWorkspaceWriteAccess(user,partRevision.getWorkspaceId()) : partRevision.getACL().hasWriteAccess(user); + } + + private boolean isAuthor(User user, PartRevision partRevision) { + return partRevision.getAuthor().getLogin().equals(user.getLogin()); + } + + private boolean isACLGrantReadAccess(User user, PartRevision partRevision) { + return partRevision.getACL() == null || partRevision.getACL().hasReadAccess(user); + } + + private boolean isACLGrantReadAccess(User user, PartMasterTemplate template) { + return template.getAcl() == null || template.getAcl().hasReadAccess(user); + } + + private boolean isACLGrantWriteAccess(User user, PartRevision partRevision) { + return partRevision.getACL() == null || partRevision.getACL().hasWriteAccess(user); + } + + private boolean isCheckoutByUser(User user, PartRevision partRevision) { + return partRevision.isCheckedOut() && partRevision.getCheckOutUser().equals(user); + } + + private boolean isCheckoutByAnotherUser(User user, PartRevision partRevision) { + return partRevision.isCheckedOut() && !partRevision.getCheckOutUser().equals(user); + } + + private void checkNameValidity(String name, Locale locale) throws NotAllowedException { + if (!NamingConvention.correct(name)) { + throw new NotAllowedException(locale, "NotAllowedException9", name); + } + } + + private void checkNameFileValidity(String name, Locale locale) throws NotAllowedException { + if (name != null) { + name = name.trim(); + } + if (!NamingConvention.correctNameFile(name)) { + throw new NotAllowedException(locale, "NotAllowedException9", name); + } + } +} +//TODO when using layers and markers, check for concordance +//TODO add a method to update a marker +//TODO use dozer diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/SessionContextProducer.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/SessionContextProducer.java new file mode 100644 index 0000000000..474027c13d --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/SessionContextProducer.java @@ -0,0 +1,83 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server; + +import javax.annotation.PostConstruct; +import javax.ejb.SessionContext; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Default; +import javax.enterprise.inject.Produces; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author morgan on 04/09/15. + */ + +@ApplicationScoped +public class SessionContextProducer { + + private static final Logger LOGGER = Logger.getLogger(SessionContextProducer.class.getName()); + + private Context context; + + @PostConstruct + private void init(){ + try{ + context = new InitialContext(); + } + catch (NamingException e) { + LOGGER.log(Level.SEVERE,e.getMessage(),e); + } + } + + @Produces + @Default + @RequestScoped + public SessionContext create() throws NamingException { + return (SessionContext) context.lookup("java:comp/EJBContext"); + } + + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ShareManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ShareManagerBean.java new file mode 100644 index 0000000000..d765304c28 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/ShareManagerBean.java @@ -0,0 +1,63 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.exceptions.SharedEntityNotFoundException; +import com.docdoku.core.services.IShareManagerLocal; +import com.docdoku.core.sharing.SharedEntity; +import com.docdoku.server.dao.SharedEntityDAO; + +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.Date; + +/** + * @author Morgan Guimard + */ + +@Local(IShareManagerLocal.class) +@Stateless(name = "ShareManagerBean") +public class ShareManagerBean implements IShareManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Override + public SharedEntity findSharedEntityForGivenUUID(String pUuid) throws SharedEntityNotFoundException { + SharedEntityDAO sharedEntityDAO = new SharedEntityDAO(em); + return sharedEntityDAO.loadSharedEntity(pUuid); + } + + @Override + public void deleteSharedEntityIfExpired(SharedEntity pSharedEntity) { + + // insure the entity is really expired + if(pSharedEntity.getExpireDate() != null){ + Date now = new Date(); + if(pSharedEntity.getExpireDate().getTime() < now.getTime()){ + SharedEntityDAO sharedEntityDAO = new SharedEntityDAO(em); + sharedEntityDAO.deleteSharedEntity(pSharedEntity); + } + + } + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/TaskManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/TaskManagerBean.java new file mode 100644 index 0000000000..4044dad29c --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/TaskManagerBean.java @@ -0,0 +1,193 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.common.User; +import com.docdoku.core.workflow.WorkspaceWorkflow; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.*; +import com.docdoku.core.workflow.Task; +import com.docdoku.core.workflow.TaskKey; +import com.docdoku.core.workflow.TaskWrapper; +import com.docdoku.server.dao.DocumentRevisionDAO; +import com.docdoku.server.dao.PartRevisionDAO; +import com.docdoku.server.dao.TaskDAO; +import com.docdoku.server.dao.WorkflowDAO; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * + * @author Morgan Guimard + */ +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID}) +@Local(ITaskManagerLocal.class) +@Stateless(name = "TaskManagerBean") +public class TaskManagerBean implements ITaskManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IDocumentWorkflowManagerLocal documentWorkflowService; + + @Inject + private IPartWorkflowManagerLocal partWorkflowService; + + @Inject + private IWorkflowManagerLocal workflowService; + + @Override + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + public TaskWrapper[] getAssignedTasksForGivenUser(String workspaceId, String userLogin) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + TaskDAO taskDAO = new TaskDAO(new Locale(user.getLanguage()), em); + Task[] assignedTasks = taskDAO.findAssignedTasks(workspaceId, userLogin); + List taskWrappers = new ArrayList<>(); + for(Task task : assignedTasks){ + TaskWrapper taskWrapper = wrapTask(task, workspaceId); + if(taskWrapper != null) { + taskWrappers.add(taskWrapper); + } + } + return taskWrappers.toArray(new TaskWrapper[taskWrappers.size()]); + } + + @Override + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + public TaskWrapper[] getInProgressTasksForGivenUser(String workspaceId, String userLogin) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + TaskDAO taskDAO = new TaskDAO(new Locale(user.getLanguage()), em); + Task[] inProgressTasks = taskDAO.findInProgressTasks(workspaceId, userLogin); + List taskWrappers = new ArrayList<>(); + for(Task task : inProgressTasks){ + TaskWrapper taskWrapper = wrapTask(task, workspaceId); + if(taskWrapper != null) { + taskWrappers.add(taskWrapper); + } + } + return taskWrappers.toArray(new TaskWrapper[taskWrappers.size()]); + } + + @Override + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + public TaskWrapper getTask(String workspaceId, TaskKey taskKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, TaskNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + TaskDAO taskDAO = new TaskDAO(new Locale(user.getLanguage()), em); + Task task = taskDAO.loadTask(taskKey); + TaskWrapper taskWrapper = wrapTask(task, workspaceId); + if(taskWrapper == null){ + throw new AccessRightException(new Locale(user.getLanguage()),user); + } + return taskWrapper; + } + + @Override + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + public void processTask(String workspaceId, TaskKey taskKey, String action, String comment, String signature) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, TaskNotFoundException, NotAllowedException, WorkflowNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + TaskDAO taskDAO = new TaskDAO(new Locale(user.getLanguage()), em); + Task task = taskDAO.loadTask(taskKey); + TaskWrapper taskWrapper = wrapTask(task, workspaceId); + if(taskWrapper == null){ + throw new AccessRightException(new Locale(user.getLanguage()),user); + } + switch (taskWrapper.getHolderType()){ + case "documents": + if("APPROVE".equals(action)){ + documentWorkflowService.approveTaskOnDocument(workspaceId,taskKey,comment,signature); + } + else if("REJECT".equals(action)){ + documentWorkflowService.rejectTaskOnDocument(workspaceId, taskKey, comment, signature); + } + break; + case "parts": + if("APPROVE".equals(action)){ + partWorkflowService.approveTaskOnPart(workspaceId,taskKey,comment,signature); + } + else if("reject".equals(action)){ + partWorkflowService.rejectTaskOnPart(workspaceId, taskKey, comment, signature); + } + break; + case "workspace-workflows": + if("APPROVE".equals(action)){ + workflowService.approveTaskOnWorkspaceWorkflow(workspaceId, taskKey, comment, signature); + } + else if("REJECT".equals(action)){ + workflowService.rejectTaskOnWorkspaceWorkflow(workspaceId, taskKey, comment, signature); + } + break; + default: + // should throw + break; + } + + } + + + + private TaskWrapper wrapTask(Task task, String workspaceId){ + TaskWrapper taskWrapper = new TaskWrapper(task, workspaceId); + + DocumentRevisionDAO documentRevisionDAO = new DocumentRevisionDAO(em); + DocumentRevision documentRevision = documentRevisionDAO.getWorkflowHolder(task.getActivity().getWorkflow()); + + if(documentRevision!=null){ + taskWrapper.setHolderType("documents"); + taskWrapper.setHolderReference(documentRevision.getDocumentMasterId()); + taskWrapper.setHolderVersion(documentRevision.getVersion()); + return taskWrapper; + } + PartRevisionDAO PartRevisionDAO = new PartRevisionDAO(em); + PartRevision partRevision = PartRevisionDAO.getWorkflowHolder(task.getActivity().getWorkflow()); + + if(partRevision!=null){ + taskWrapper.setHolderType("parts"); + taskWrapper.setHolderReference(partRevision.getPartNumber()); + taskWrapper.setHolderVersion(partRevision.getVersion()); + return taskWrapper; + } + + WorkspaceWorkflow workspaceWorkflowTarget = new WorkflowDAO(em).getWorkspaceWorkflowTarget(workspaceId,task.getActivity().getWorkflow()); + if(workspaceWorkflowTarget!=null){ + taskWrapper.setHolderType("workspace-workflows"); + taskWrapper.setHolderReference(workspaceWorkflowTarget.getId()); + return taskWrapper; + } + + return null; + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/UserManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/UserManagerBean.java new file mode 100644 index 0000000000..7697992394 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/UserManagerBean.java @@ -0,0 +1,642 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.common.*; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.security.*; +import com.docdoku.core.services.IContextManagerLocal; +import com.docdoku.core.services.IMailerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.core.util.NamingConvention; +import com.docdoku.server.dao.*; +import com.docdoku.server.esindexer.ESIndexer; +import com.docdoku.server.events.*; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.enterprise.event.Event; +import javax.enterprise.util.AnnotationLiteral; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.List; +import java.util.Locale; + +@DeclareRoles({UserGroupMapping.GUEST_PROXY_ROLE_ID, UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) +@Local(IUserManagerLocal.class) +@Stateless(name = "UserManagerBean") +public class UserManagerBean implements IUserManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private ESIndexer esIndexer; + + @Inject + private Event workspaceAccessEvent; + + @Inject + private Event userRemovedEvent; + + @Inject + private IContextManagerLocal contextManager; + + @Inject + private IMailerLocal mailer; + + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void addUserInGroup(UserGroupKey pGroupKey, String pLogin) throws AccessRightException, UserGroupNotFoundException, AccountNotFoundException, WorkspaceNotFoundException, UserAlreadyExistsException, FolderAlreadyExistsException, CreationException { + Account account = checkAdmin(pGroupKey.getWorkspaceId()); + UserDAO userDAO = new UserDAO(new Locale(account.getLanguage()), em); + User userToAdd = em.find(User.class, new UserKey(pGroupKey.getWorkspaceId(), pLogin)); + if (userToAdd == null) { + Account userAccount = new AccountDAO(em).loadAccount(pLogin); + Workspace workspace = em.getReference(Workspace.class, pGroupKey.getWorkspaceId()); + userToAdd = new User(workspace, userAccount); + userDAO.createUser(userToAdd); + } + UserGroupDAO groupDAO = new UserGroupDAO(new Locale(account.getLanguage()), em); + UserGroup group = groupDAO.loadUserGroup(pGroupKey); + + userDAO.removeUserMembership(new WorkspaceUserMembershipKey(pGroupKey.getWorkspaceId(), pGroupKey.getWorkspaceId(), pLogin)); + group.addUser(userToAdd); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void addUserInWorkspace(String pWorkspaceId, String pLogin) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, UserAlreadyExistsException, FolderAlreadyExistsException, CreationException { + Account account = checkAdmin(pWorkspaceId); + UserDAO userDAO = new UserDAO(new Locale(account.getLanguage()), em); + User userToAdd = em.find(User.class, new UserKey(pWorkspaceId, pLogin)); + Workspace workspace = em.getReference(Workspace.class, pWorkspaceId); + if (userToAdd == null) { + Account userAccount = new AccountDAO(em).loadAccount(pLogin); + userToAdd = new User(workspace, userAccount); + userDAO.createUser(userToAdd); + } + userDAO.addUserMembership(workspace, userToAdd); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public Workspace removeUser(String pWorkspaceId, String login) throws UserNotFoundException, NotAllowedException, AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, FolderNotFoundException, ESServerException, EntityConstraintException, UserNotActiveException, DocumentRevisionNotFoundException { + Account account = new AccountDAO(em).loadAccount(contextManager.getCallerPrincipalLogin()); + Workspace workspace = new WorkspaceDAO(new Locale(account.getLanguage()), em).loadWorkspace(pWorkspaceId); + checkAdmin(workspace,account); + + Locale locale = new Locale(account.getLanguage()); + UserDAO userDAO = new UserDAO(locale, em); + User user = userDAO.loadUser(new UserKey(pWorkspaceId, login)); + + userRemovedEvent.select(new AnnotationLiteral() { + }).fire(new UserRemovedEvent(user)); + userDAO.removeUser(user); + + return workspace; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void removeUserFromGroup(UserGroupKey pGroupKey, String[] pLogins) throws AccessRightException, UserGroupNotFoundException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = checkAdmin(pGroupKey.getWorkspaceId()); + UserGroup group = new UserGroupDAO(new Locale(account.getLanguage()), em).loadUserGroup(pGroupKey); + for (String login : pLogins) { + User userToRemove = em.getReference(User.class, new UserKey(pGroupKey.getWorkspaceId(), login)); + group.removeUser(userToRemove); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public UserGroup removeUserFromGroup(UserGroupKey pGroupKey, String login) throws AccessRightException, UserGroupNotFoundException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = checkAdmin(pGroupKey.getWorkspaceId()); + UserGroup group = new UserGroupDAO(new Locale(account.getLanguage()), em).loadUserGroup(pGroupKey); + User userToRemove = em.getReference(User.class, new UserKey(pGroupKey.getWorkspaceId(), login)); + group.removeUser(userToRemove); + return group; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public UserGroup createUserGroup(String pId, Workspace pWorkspace) throws UserGroupAlreadyExistsException, AccessRightException, AccountNotFoundException, CreationException { + Account account = checkAdmin(pWorkspace); + UserGroup groupToCreate = new UserGroup(pWorkspace, pId); + UserGroupDAO groupDAO = new UserGroupDAO(new Locale(account.getLanguage()), em); + groupDAO.createUserGroup(groupToCreate); + groupDAO.addUserGroupMembership(pWorkspace, groupToCreate); + return groupToCreate; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public UserGroup createUserGroup(String pId, String workspaceId) throws UserGroupAlreadyExistsException, AccessRightException, AccountNotFoundException, CreationException, WorkspaceNotFoundException { + Workspace workspace = new WorkspaceDAO(em).loadWorkspace(workspaceId); + return createUserGroup(pId,workspace); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public Workspace createWorkspace(String pID, Account pAdmin, String pDescription, boolean pFolderLocked) throws WorkspaceAlreadyExistsException, FolderAlreadyExistsException, UserAlreadyExistsException, CreationException, ESIndexNamingException, NotAllowedException { + if (!NamingConvention.correct(pID)) { + throw new NotAllowedException(new Locale(pAdmin.getLanguage()), "NotAllowedException9", pID); + } + Workspace workspace = new Workspace(pID, pAdmin, pDescription, pFolderLocked); + new WorkspaceDAO(em).createWorkspace(workspace); + User userToCreate = new User(workspace, pAdmin); + UserDAO userDAO = new UserDAO(new Locale(pAdmin.getLanguage()), em); + userDAO.createUser(userToCreate); + userDAO.addUserMembership(workspace, userToCreate); + + try { + esIndexer.createIndex(pID); + } catch (ESServerException e) { + // When ElasticSearch have not start + } catch (ESIndexAlreadyExistsException e) { + throw new WorkspaceAlreadyExistsException(new Locale(pAdmin.getLanguage()), workspace); // Send if the workspace have the same index name that another + } + + return workspace; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public Workspace[] getAdministratedWorkspaces() throws AccountNotFoundException { + if (contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID)) { + return new AccountDAO(em).getAllWorkspaces(); + } else { + Account account = new AccountDAO(em).loadAccount(contextManager.getCallerPrincipalLogin()); + return new AccountDAO(em).getAdministratedWorkspaces(account); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public Workspace getWorkspace(String workspaceId) throws WorkspaceNotFoundException, AccountNotFoundException { + + if (contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID)) { + return new WorkspaceDAO(em).loadWorkspace(workspaceId); + } + + String login = contextManager.getCallerPrincipalLogin(); + + User[] users = new UserDAO(em).getUsers(login); + Account account = new AccountDAO(em).loadAccount(login); + Locale locale = new Locale(account.getLanguage()); + + Workspace workspace = null; + for (User user : users) { + if (user.getWorkspace().getId().equals(workspaceId)) { + workspace = user.getWorkspace(); + break; + } + } + + if (workspace == null) { + throw new WorkspaceNotFoundException(locale, workspaceId); + } + + return workspace; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public UserGroup[] getUserGroups(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccountNotFoundException { + if (contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID)) { + Account account = new AccountDAO(em).loadAccount(contextManager.getCallerPrincipalLogin()); + return new UserGroupDAO(new Locale(account.getLanguage()), em).findAllUserGroups(pWorkspaceId); + } else { + User user = checkWorkspaceReadAccess(pWorkspaceId); + return new UserGroupDAO(new Locale(user.getLanguage()), em).findAllUserGroups(pWorkspaceId); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public UserGroup getUserGroup(UserGroupKey pKey) throws WorkspaceNotFoundException, UserGroupNotFoundException, UserNotFoundException, UserNotActiveException, AccountNotFoundException { + if (contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID)) { + Account account = new AccountDAO(em).loadAccount(contextManager.getCallerPrincipalLogin()); + return new UserGroupDAO(new Locale(account.getLanguage()), em).loadUserGroup(pKey); + } else { + User user = checkWorkspaceReadAccess(pKey.getWorkspaceId()); + return new UserGroupDAO(new Locale(user.getLanguage()), em).loadUserGroup(pKey); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public WorkspaceUserMembership getWorkspaceSpecificUserMemberships(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = checkWorkspaceReadAccess(pWorkspaceId); + return new UserDAO(new Locale(user.getLanguage()), em).loadUserMembership(new WorkspaceUserMembershipKey(pWorkspaceId, pWorkspaceId, user.getLogin())); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public WorkspaceUserMembership[] getWorkspaceUserMemberships(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccountNotFoundException { + if (contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID)) { + Account account = new AccountDAO(em).loadAccount(contextManager.getCallerPrincipalLogin()); + return new UserDAO(new Locale(account.getLanguage()), em).findAllWorkspaceUserMemberships(pWorkspaceId); + } else { + User user = checkWorkspaceReadAccess(pWorkspaceId); + return new UserDAO(new Locale(user.getLanguage()), em).findAllWorkspaceUserMemberships(pWorkspaceId); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public WorkspaceUserGroupMembership[] getWorkspaceSpecificUserGroupMemberships(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, UserGroupNotFoundException { + User user = checkWorkspaceReadAccess(pWorkspaceId); + UserGroupDAO userGroupDAO = new UserGroupDAO(new Locale(user.getLanguage()), em); + List userGroups = userGroupDAO.getUserGroups(pWorkspaceId, user); + WorkspaceUserGroupMembership[] workspaceUserGroupMembership = new WorkspaceUserGroupMembership[userGroups.size()]; + for (int i = 0; i < userGroups.size(); i++) { + workspaceUserGroupMembership[i] = userGroupDAO.loadUserGroupMembership(new WorkspaceUserGroupMembershipKey(pWorkspaceId, pWorkspaceId, userGroups.get(i).getId())); + } + return workspaceUserGroupMembership; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public WorkspaceUserGroupMembership[] getWorkspaceUserGroupMemberships(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccountNotFoundException { + if (contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID)) { + Account account = new AccountDAO(em).loadAccount(contextManager.getCallerPrincipalLogin()); + return new UserGroupDAO(new Locale(account.getLanguage()), em).findAllWorkspaceUserGroupMemberships(pWorkspaceId); + } else { + User user = checkWorkspaceReadAccess(pWorkspaceId); + return new UserGroupDAO(new Locale(user.getLanguage()), em).findAllWorkspaceUserGroupMemberships(pWorkspaceId); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void grantUserAccess(String pWorkspaceId, String[] pLogins, boolean pReadOnly) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = checkAdmin(pWorkspaceId); + UserDAO userDAO = new UserDAO(new Locale(account.getLanguage()), em); + for (String login : pLogins) { + WorkspaceUserMembership ms = userDAO.loadUserMembership(new WorkspaceUserMembershipKey(pWorkspaceId, pWorkspaceId, login)); + if (ms != null) { + ms.setReadOnly(pReadOnly); + } + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public WorkspaceUserMembership grantUserAccess(String pWorkspaceId, String login, boolean pReadOnly) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = checkAdmin(pWorkspaceId); + UserDAO userDAO = new UserDAO(new Locale(account.getLanguage()), em); + WorkspaceUserMembership ms = userDAO.loadUserMembership(new WorkspaceUserMembershipKey(pWorkspaceId, pWorkspaceId, login)); + if (ms != null) { + ms.setReadOnly(pReadOnly); + } + return ms; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public WorkspaceUserGroupMembership grantGroupAccess(String pWorkspaceId, String groupId, boolean pReadOnly) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, UserGroupNotFoundException { + Account account = checkAdmin(pWorkspaceId); + UserGroupDAO groupDAO = new UserGroupDAO(new Locale(account.getLanguage()), em); + WorkspaceUserGroupMembership ms = groupDAO.loadUserGroupMembership(new WorkspaceUserGroupMembershipKey(pWorkspaceId, pWorkspaceId, groupId)); + if (ms != null) { + ms.setReadOnly(pReadOnly); + } + + return ms; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void grantGroupAccess(String pWorkspaceId, String[] pGroupIds, boolean pReadOnly) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, UserGroupNotFoundException { + Account account = checkAdmin(pWorkspaceId); + UserGroupDAO groupDAO = new UserGroupDAO(new Locale(account.getLanguage()), em); + for (String id : pGroupIds) { + WorkspaceUserGroupMembership ms = groupDAO.loadUserGroupMembership(new WorkspaceUserGroupMembershipKey(pWorkspaceId, pWorkspaceId, id)); + if (ms != null) { + ms.setReadOnly(pReadOnly); + } + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void activateUsers(String pWorkspaceId, String[] pLogins) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = checkAdmin(pWorkspaceId); + Workspace workspace = em.getReference(Workspace.class, pWorkspaceId); + UserDAO userDAO = new UserDAO(new Locale(account.getLanguage()), em); + for (String login : pLogins) { + User member = em.getReference(User.class, new UserKey(pWorkspaceId, login)); + userDAO.addUserMembership(workspace, member); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void activateUserGroups(String pWorkspaceId, String[] pGroupIds) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = checkAdmin(pWorkspaceId); + Workspace workspace = em.getReference(Workspace.class, pWorkspaceId); + UserGroupDAO groupDAO = new UserGroupDAO(new Locale(account.getLanguage()), em); + for (String id : pGroupIds) { + UserGroup member = em.getReference(UserGroup.class, new UserGroupKey(pWorkspaceId, id)); + groupDAO.addUserGroupMembership(workspace, member); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void activateUser(String pWorkspaceId, String login) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = checkAdmin(pWorkspaceId); + Workspace workspace = em.getReference(Workspace.class, pWorkspaceId); + UserDAO userDAO = new UserDAO(new Locale(account.getLanguage()), em); + User member = em.getReference(User.class, new UserKey(pWorkspaceId, login)); + userDAO.addUserMembership(workspace, member); + + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void activateUserGroup(String pWorkspaceId, String groupId) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = checkAdmin(pWorkspaceId); + Workspace workspace = em.getReference(Workspace.class, pWorkspaceId); + UserGroupDAO groupDAO = new UserGroupDAO(new Locale(account.getLanguage()), em); + UserGroup member = em.getReference(UserGroup.class, new UserGroupKey(pWorkspaceId, groupId)); + groupDAO.addUserGroupMembership(workspace, member); + + } + + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void passivateUserGroups(String pWorkspaceId, String[] pGroupIds) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = checkAdmin(pWorkspaceId); + UserGroupDAO groupDAO = new UserGroupDAO(new Locale(account.getLanguage()), em); + for (String id : pGroupIds) { + groupDAO.removeUserGroupMembership(new WorkspaceUserGroupMembershipKey(pWorkspaceId, pWorkspaceId, id)); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void passivateUsers(String pWorkspaceId, String[] pLogins) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = checkAdmin(pWorkspaceId); + UserDAO userDAO = new UserDAO(new Locale(account.getLanguage()), em); + for (String login : pLogins) { + userDAO.removeUserMembership(new WorkspaceUserMembershipKey(pWorkspaceId, pWorkspaceId, login)); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void passivateUserGroup(String pWorkspaceId, String groupId) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = checkAdmin(pWorkspaceId); + UserGroupDAO groupDAO = new UserGroupDAO(new Locale(account.getLanguage()), em); + groupDAO.removeUserGroupMembership(new WorkspaceUserGroupMembershipKey(pWorkspaceId, pWorkspaceId, groupId)); + + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void removeUsers(String pWorkspaceId, String[] pLogins) throws UserNotFoundException, NotAllowedException, AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, FolderNotFoundException, ESServerException, EntityConstraintException, UserNotActiveException, DocumentRevisionNotFoundException { + Account account = checkAdmin(pWorkspaceId); + Locale locale = new Locale(account.getLanguage()); + UserDAO userDAO = new UserDAO(locale, em); + + for (String login : pLogins) { + User user = userDAO.loadUser(new UserKey(pWorkspaceId, login)); + userRemovedEvent.select(new AnnotationLiteral() { + }).fire(new UserRemovedEvent(user)); + userDAO.removeUser(user); + } + + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void passivateUser(String pWorkspaceId, String login) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = checkAdmin(pWorkspaceId); + UserDAO userDAO = new UserDAO(new Locale(account.getLanguage()), em); + userDAO.removeUserMembership(new WorkspaceUserMembershipKey(pWorkspaceId, pWorkspaceId, login)); + + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void removeUserGroups(String pWorkspaceId, String[] pIds) throws UserGroupNotFoundException, AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, EntityConstraintException { + Account account = checkAdmin(pWorkspaceId); + Locale locale = new Locale(account.getLanguage()); + UserGroupDAO groupDAO = new UserGroupDAO(locale, em); + for (String id : pIds) { + UserGroupKey userGroupKey = new UserGroupKey(pWorkspaceId, id); + if (groupDAO.hasACLConstraint(userGroupKey)) { + throw new EntityConstraintException(locale, "EntityConstraintException11"); + } + groupDAO.removeUserGroup(userGroupKey); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public void updateWorkspace(Workspace pWorkspace) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = checkAdmin(pWorkspace); + new WorkspaceDAO(new Locale(account.getLanguage()), em).updateWorkspace(pWorkspace); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + public Workspace updateWorkspace(String workspaceId, String description, boolean isFolderLocked) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = new AccountDAO(em).loadAccount(contextManager.getCallerPrincipalLogin()); + Locale locale = new Locale(account.getLanguage()); + WorkspaceDAO workspaceDAO = new WorkspaceDAO(locale, em); + Workspace workspace = workspaceDAO.loadWorkspace(workspaceId); + checkAdmin(workspace,account); + workspace.setDescription(description); + workspace.setFolderLocked(isFolderLocked); + + return workspace; + } + + + @Override + public void recoverPassword(String pPasswdRRUuid, String pPassword) throws PasswordRecoveryRequestNotFoundException { + PasswordRecoveryRequestDAO passwdRRequestDAO = new PasswordRecoveryRequestDAO(em); + PasswordRecoveryRequest passwdRR = passwdRRequestDAO.loadPasswordRecoveryRequest(pPasswdRRUuid); + AccountDAO accountDAO = new AccountDAO(em); + accountDAO.updateCredential(passwdRR.getLogin(), pPassword); + passwdRRequestDAO.removePasswordRecoveryRequest(passwdRR); + } + + @Override + public PasswordRecoveryRequest createPasswordRecoveryRequest(Account account) { + PasswordRecoveryRequest passwdRR = PasswordRecoveryRequest.createPasswordRecoveryRequest(account.getLogin()); + em.persist(passwdRR); + mailer.sendPasswordRecovery(account, passwdRR.getUuid()); + return passwdRR; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public User checkWorkspaceReadAccess(String pWorkspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + String login = contextManager.getCallerPrincipalLogin(); + User user; + UserDAO userDAO = new UserDAO(em); + WorkspaceUserMembership userMS = userDAO.loadUserMembership(new WorkspaceUserMembershipKey(pWorkspaceId, pWorkspaceId, login)); + if (userMS != null) { + user = userMS.getMember(); + } else { + Workspace wks = new WorkspaceDAO(em).loadWorkspace(pWorkspaceId); + user = userDAO.loadUser(new UserKey(pWorkspaceId, login)); + if (!wks.getAdmin().getLogin().equals(login)) { + WorkspaceUserGroupMembership[] groupMS = new UserGroupDAO(em).getUserGroupMemberships(pWorkspaceId, user); + if (groupMS.length == 0) { + throw new UserNotActiveException(new Locale(user.getLanguage()), login); + } + } + } + workspaceAccessEvent.select(new AnnotationLiteral() { + }).fire(new WorkspaceAccessEvent(user)); + + return user; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public User checkWorkspaceWriteAccess(String pWorkspaceId) throws UserNotFoundException, WorkspaceNotFoundException, AccessRightException { + String login = contextManager.getCallerPrincipalLogin(); + + UserDAO userDAO = new UserDAO(em); + User user = userDAO.loadUser(new UserKey(pWorkspaceId, login)); + if(!hasWorkspaceWriteAccess(user,pWorkspaceId)) { + throw new AccessRightException(new Locale(user.getLanguage()), user); + } + workspaceAccessEvent.select(new AnnotationLiteral() { + }).fire(new WorkspaceAccessEvent(user)); + + return user; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public boolean hasWorkspaceWriteAccess(User user, String pWorkspaceId) throws WorkspaceNotFoundException { + String login = contextManager.getCallerPrincipalLogin(); + + UserDAO userDAO = new UserDAO(em); + + Workspace wks = new WorkspaceDAO(em).loadWorkspace(pWorkspaceId); + if (!wks.getAdmin().getLogin().equals(login)) { + WorkspaceUserMembership userMS = userDAO.loadUserMembership(new WorkspaceUserMembershipKey(pWorkspaceId, pWorkspaceId, login)); + if (userMS != null) { + if (userMS.isReadOnly()) { + return false; + } + } else { + WorkspaceUserGroupMembership[] groupMS = new UserGroupDAO(em).getUserGroupMemberships(pWorkspaceId, user); + boolean readOnly = true; + for (WorkspaceUserGroupMembership ms : groupMS) { + if (!ms.isReadOnly()) { + readOnly = false; + break; + } + } + if (readOnly) { + return false; + } + } + } + return true; + } + + + /* + * Don't expose this method on remote. + * Method returns true if given users have a common workspace, false otherwise. + */ + @Override + public boolean hasCommonWorkspace(String userLogin1, String userLogin2) { + return userLogin1 != null && userLogin2 != null && new UserDAO(em).hasCommonWorkspace(userLogin1, userLogin2); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public UserGroup[] getUserGroupsForUser(UserKey userKey) throws UserNotFoundException { + User user = new UserDAO(em).loadUser(userKey); + List userGroups = new UserGroupDAO(em).getUserGroups(userKey.getWorkspace(), user); + return userGroups.toArray(new UserGroup[userGroups.size()]); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public Workspace[] getWorkspacesWhereCallerIsActive() { + String callerLogin = contextManager.getCallerPrincipalLogin(); + List workspaces = new WorkspaceDAO(em).findWorkspacesWhereUserIsActive(callerLogin); + return workspaces.toArray(new Workspace[workspaces.size()]); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public Account checkAdmin(Workspace pWorkspace) throws AccessRightException, AccountNotFoundException { + Account account = new AccountDAO(em).loadAccount(contextManager.getCallerPrincipalLogin()); + checkAdmin(pWorkspace,account); + return account; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public Account checkAdmin(String pWorkspaceId) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + Account account = new AccountDAO(em).loadAccount(contextManager.getCallerPrincipalLogin()); + Workspace wks = new WorkspaceDAO(new Locale(account.getLanguage()), em).loadWorkspace(pWorkspaceId); + checkAdmin(wks,account); + return account; + } + + private void checkAdmin(Workspace workspace, Account account) throws AccessRightException { + if (!contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID) && !workspace.getAdmin().equals(account)) { + throw new AccessRightException(new Locale(account.getLanguage()), account); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public User whoAmI(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + return checkWorkspaceReadAccess(pWorkspaceId); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public User[] getReachableUsers() throws AccountNotFoundException { + String callerLogin = contextManager.getCallerPrincipalLogin(); + Account account = new AccountDAO(em).loadAccount(callerLogin); + return new UserDAO(new Locale(account.getLanguage()), em).findReachableUsersForCaller(callerLogin); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) + @Override + public User[] getUsers(String pWorkspaceId) throws WorkspaceNotFoundException, AccessRightException, AccountNotFoundException, UserNotFoundException, UserNotActiveException { + if (contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID)) { + Account account = new AccountDAO(em).loadAccount(contextManager.getCallerPrincipalLogin()); + return new UserDAO(new Locale(account.getLanguage()), em).findAllUsers(pWorkspaceId); + } + User user = checkWorkspaceReadAccess(pWorkspaceId); + return new UserDAO(new Locale(user.getLanguage()), em).findAllUsers(pWorkspaceId); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/WorkflowManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/WorkflowManagerBean.java new file mode 100644 index 0000000000..7d9875f3b8 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/WorkflowManagerBean.java @@ -0,0 +1,603 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + +import com.docdoku.core.common.*; +import com.docdoku.core.workflow.WorkspaceWorkflow; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.core.services.IWorkflowManagerLocal; +import com.docdoku.core.util.Tools; +import com.docdoku.core.workflow.*; +import com.docdoku.server.dao.*; +import com.docdoku.server.factory.ACLFactory; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.*; + +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@Local(IWorkflowManagerLocal.class) +@Stateless(name = "WorkflowManagerBean") +public class WorkflowManagerBean implements IWorkflowManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IUserManagerLocal userManager; + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteWorkflowModel(WorkflowModelKey pKey) throws WorkspaceNotFoundException, AccessRightException, WorkflowModelNotFoundException, UserNotFoundException, UserNotActiveException, EntityConstraintException { + User user = userManager.checkWorkspaceReadAccess(pKey.getWorkspaceId()); + Locale locale = new Locale(user.getLanguage()); + WorkflowModelDAO workflowModelDAO = new WorkflowModelDAO(locale, em); + + WorkflowModel workflowModel = workflowModelDAO.loadWorkflowModel(pKey); + + if(workflowModelDAO.isInUseInDocumentMasterTemplate(workflowModel)){ + throw new EntityConstraintException(locale,"EntityConstraintException24"); + } + + if(workflowModelDAO.isInUseInPartMasterTemplate(workflowModel)){ + throw new EntityConstraintException(locale,"EntityConstraintException25"); + } + + checkWorkflowWriteAccess(workflowModel,user); + + workflowModelDAO.removeWorkflowModel(pKey); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public WorkflowModel createWorkflowModel(String pWorkspaceId, String pId, String pFinalLifeCycleState, ActivityModel[] pActivityModels) throws WorkspaceNotFoundException, AccessRightException, UserNotFoundException, WorkflowModelAlreadyExistsException, CreationException, NotAllowedException { + User user = userManager.checkWorkspaceWriteAccess(pWorkspaceId); + Locale userLocale = new Locale(user.getLanguage()); + + checkWorkflowValidity(pWorkspaceId, pId, userLocale, pActivityModels); + + WorkflowModelDAO modelDAO = new WorkflowModelDAO(userLocale, em); + WorkflowModel model = new WorkflowModel(user.getWorkspace(), pId, user, pFinalLifeCycleState, pActivityModels); + Tools.resetParentReferences(model); + Date now = new Date(); + model.setCreationDate(now); + modelDAO.createWorkflowModel(model); + return model; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public WorkflowModel updateWorkflowModel(WorkflowModelKey workflowModelKey, String pFinalLifeCycleState, ActivityModel[] pActivityModels) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, WorkflowModelNotFoundException, NotAllowedException, WorkflowModelAlreadyExistsException, CreationException { + //remove all activities from workflow model + //but do not remove it to maintain associated links + //for instance document or part templates + //and also ACL, author, creation date... + User user = userManager.checkWorkspaceReadAccess(workflowModelKey.getWorkspaceId()); + Locale userLocale = new Locale(user.getLanguage()); + WorkflowModelDAO workflowModelDAO = new WorkflowModelDAO(userLocale, em); + + workflowModelDAO.removeAllActivityModels(workflowModelKey); + + WorkflowModel workflowModel = workflowModelDAO.loadWorkflowModel(workflowModelKey); + checkWorkflowWriteAccess(workflowModel,user); + + checkWorkflowValidity(workflowModelKey.getWorkspaceId(), workflowModelKey.getId(), userLocale, pActivityModels); + workflowModel.setFinalLifeCycleState(pFinalLifeCycleState); + List activityModels = new LinkedList<>(); + Collections.addAll(activityModels, pActivityModels); + workflowModel.setActivityModels(activityModels); + Tools.resetParentReferences(workflowModel); + return workflowModel; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public WorkflowModel[] getWorkflowModels(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + + List allWorkflowModels = new WorkflowModelDAO(new Locale(user.getLanguage()), em).findAllWorkflowModels(pWorkspaceId); + + ListIterator ite = allWorkflowModels.listIterator(); + while(ite.hasNext()){ + WorkflowModel workflowModel = ite.next(); + if(!hasWorkflowModelReadAccess(workflowModel, user)){ + ite.remove(); + } + } + return allWorkflowModels.toArray(new WorkflowModel[allWorkflowModels.size()]); + } + + private boolean hasWorkflowModelReadAccess(WorkflowModel workflowModel, User user) { + return user.isAdministrator() || isACLGrantReadAccess(user,workflowModel); + } + + private boolean isACLGrantReadAccess(User user, WorkflowModel workflowModel) { + return workflowModel.getAcl()==null || workflowModel.getAcl().hasReadAccess(user); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public WorkflowModel getWorkflowModel(WorkflowModelKey pKey) throws WorkspaceNotFoundException, WorkflowModelNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pKey.getWorkspaceId()); + return new WorkflowModelDAO(new Locale(user.getLanguage()), em).loadWorkflowModel(pKey); + } + + @Override + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + public Role[] getRoles(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + RoleDAO roleDAO = new RoleDAO(new Locale(user.getLanguage()), em); + List roles = roleDAO.findRolesInWorkspace(pWorkspaceId); + return roles.toArray(new Role[roles.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Role[] getRolesInUse(String pWorkspaceId) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + RoleDAO roleDAO = new RoleDAO(new Locale(user.getLanguage()), em); + List roles = roleDAO.findRolesInUseWorkspace(pWorkspaceId); + return roles.toArray(new Role[roles.size()]); + } + + @Override + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + public Role createRole(String roleName, String workspaceId, List userLogins, List userGroupIds) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException, RoleAlreadyExistsException, CreationException, UserGroupNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + Workspace wks = new WorkspaceDAO(locale, em).loadWorkspace(workspaceId); + + Set users = new HashSet<>(); + Set groups = new HashSet<>(); + + if (userLogins != null) { + for(String userLogin :userLogins) { + users.add(new UserDAO(locale, em).loadUser(new UserKey(workspaceId, userLogin))); + } + } + + if (userGroupIds != null) { + for(String id :userGroupIds) { + groups.add(new UserGroupDAO(locale, em).loadUserGroup(new UserGroupKey(workspaceId, id))); + } + } + + Role role = new Role(roleName, wks, users, groups); + new RoleDAO(locale, em).createRole(role); + + return role; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Role updateRole(RoleKey roleKey, List userLogins, List userGroupIds) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException, RoleNotFoundException, UserGroupNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(roleKey.getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + + RoleDAO roleDAO = new RoleDAO(new Locale(user.getLanguage()), em); + Role role = roleDAO.loadRole(roleKey); + + Set users = new HashSet<>(); + Set groups = new HashSet<>(); + + if (userLogins != null) { + for(String userLogin :userLogins) { + users.add(new UserDAO(locale, em).loadUser(new UserKey(roleKey.getWorkspace(), userLogin))); + } + role.setDefaultAssignedUsers(users); + } + + if (userGroupIds != null) { + for(String id :userGroupIds) { + groups.add(new UserGroupDAO(locale, em).loadUserGroup(new UserGroupKey(roleKey.getWorkspace(), id))); + } + role.setDefaultAssignedGroups(groups); + } + + return role; + + } + + @Override + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + public void deleteRole(RoleKey roleKey) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException, RoleNotFoundException, EntityConstraintException { + User user = userManager.checkWorkspaceWriteAccess(roleKey.getWorkspace()); + RoleDAO roleDAO = new RoleDAO(new Locale(user.getLanguage()), em); + Role role = roleDAO.loadRole(roleKey); + + if (roleDAO.isRoleInUseInWorkflowModel(role)) { + throw new EntityConstraintException(new Locale(user.getLanguage()), "EntityConstraintException3"); + } + + roleDAO.deleteRole(role); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void removeACLFromWorkflow(String pWorkspaceId, String workflowModelId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, WorkflowModelNotFoundException, AccessRightException { + // Check the read access to the workspace + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + // Load the workflowModel + WorkflowModelKey workflowModelKey = new WorkflowModelKey(pWorkspaceId, workflowModelId); + WorkflowModel workflowModel = new WorkflowModelDAO(new Locale(user.getLanguage()), em).loadWorkflowModel(workflowModelKey); + // Check the access to the workflow + checkWorkflowWriteAccess(workflowModel, user); + + ACL acl = workflowModel.getAcl(); + if (acl != null) { + new ACLDAO(em).removeACLEntries(acl); + workflowModel.setAcl(null); + } + } + + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public WorkflowModel updateACLForWorkflow(String pWorkspaceId, String workflowModelId, Map userEntries, Map groupEntries) throws WorkflowNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, WorkflowModelNotFoundException, AccessRightException { + // Check the read access to the workspace + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + // Load the workflowModel + WorkflowModelKey workflowModelKey = new WorkflowModelKey(pWorkspaceId, workflowModelId); + WorkflowModel workflowModel = new WorkflowModelDAO(new Locale(user.getLanguage()), em).loadWorkflowModel(workflowModelKey); + // Check the access to the workflow + checkWorkflowWriteAccess(workflowModel, user); + ACLFactory aclFactory = new ACLFactory(em); + + if(workflowModel.getAcl() == null){ + ACL acl = aclFactory.createACL(pWorkspaceId, userEntries, groupEntries); + workflowModel.setAcl(acl); + }else{ + aclFactory.updateACL(pWorkspaceId,workflowModel.getAcl(),userEntries, groupEntries); + } + + return workflowModel; + + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public WorkspaceWorkflow instantiateWorkflow(String workspaceId, String id, String workflowModelId, Map> userRoleMapping, Map> groupRoleMapping) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, RoleNotFoundException, WorkflowModelNotFoundException, NotAllowedException, UserGroupNotFoundException { + + User user = userManager.checkWorkspaceWriteAccess(workspaceId); + + Locale locale = new Locale(user.getLanguage()); + + UserDAO userDAO = new UserDAO(locale, em); + UserGroupDAO groupDAO = new UserGroupDAO(locale, em); + RoleDAO roleDAO = new RoleDAO(locale, em); + WorkflowDAO workflowDAO = new WorkflowDAO(em); + + Map> roleUserMap = new HashMap<>(); + for (Map.Entry> pair : userRoleMapping.entrySet()) { + String roleName = pair.getKey(); + Collection userLogins = pair.getValue(); + Role role = roleDAO.loadRole(new RoleKey(workspaceId, roleName)); + Set users=new HashSet<>(); + roleUserMap.put(role, users); + for(String login:userLogins) { + User u = userDAO.loadUser(new UserKey(workspaceId, login)); + users.add(u); + } + } + + Map> roleGroupMap = new HashMap<>(); + for (Map.Entry> pair : groupRoleMapping.entrySet()) { + String roleName = pair.getKey(); + Collection groupIds = pair.getValue(); + Role role = roleDAO.loadRole(new RoleKey(workspaceId, roleName)); + Set groups=new HashSet<>(); + roleGroupMap.put(role, groups); + for(String groupId:groupIds) { + UserGroup g = groupDAO.loadUserGroup(new UserGroupKey(workspaceId, groupId)); + groups.add(g); + } + } + + WorkflowModel workflowModel = new WorkflowModelDAO(locale, em).loadWorkflowModel(new WorkflowModelKey(user.getWorkspaceId(), workflowModelId)); + Workflow workflow = workflowModel.createWorkflow(roleUserMap, roleGroupMap); + + for(Task task : workflow.getTasks()){ + if(!task.hasPotentialWorker()){ + throw new NotAllowedException(locale,"NotAllowedException56"); + } + } + + Collection runningTasks = workflow.getRunningTasks(); + + for (Task runningTask : runningTasks) { + runningTask.start(); + } + + WorkspaceWorkflow workspaceWorkflow = new WorkspaceWorkflow(user.getWorkspace(), id, workflow); + workflowDAO.createWorkflow(workspaceWorkflow.getWorkflow()); + workflowDAO.createWorkspaceWorkflow(workspaceWorkflow); + + //mailer.sendApproval(runningTasks, docR); + + return workspaceWorkflow; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Workflow getWorkflow(String workspaceId, int workflowId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Workflow workflow = new WorkflowDAO(em).getWorkflow(workflowId); + checkWorkflowBelongToWorkspace(user, workspaceId, workflow); + return workflow; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public WorkspaceWorkflow getWorkspaceWorkflow(String workspaceId, String workspaceWorkflowId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, WorkflowNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + WorkspaceWorkflow workspaceWorkflow = new WorkflowDAO(em).getWorkspaceWorkflow(workspaceId,workspaceWorkflowId); + if(workspaceWorkflow != null){ + return workspaceWorkflow; + }else{ + throw new WorkflowNotFoundException(new Locale(user.getLanguage()), 0); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Workflow[] getWorkflowAbortedWorkflows(String workspaceId, int workflowId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + WorkflowDAO workflowDAO = new WorkflowDAO(em); + Workflow workflow = workflowDAO.getWorkflow(workflowId); + DocumentRevision documentTarget = workflowDAO.getDocumentTarget(workflow); + + if(documentTarget!= null){ + if( documentTarget.getWorkspaceId().equals(workspaceId)){ + List abortedWorkflows = documentTarget.getAbortedWorkflows(); + return abortedWorkflows.toArray(new Workflow[abortedWorkflows.size()]); + }else{ + throw new AccessRightException(new Locale(user.getLanguage()),user); + } + } + + PartRevision partTarget = workflowDAO.getPartTarget(workflow); + if(partTarget!= null){ + if( partTarget.getWorkspaceId().equals(workspaceId)){ + List abortedWorkflows = partTarget.getAbortedWorkflows(); + return abortedWorkflows.toArray(new Workflow[abortedWorkflows.size()]); + }else{ + throw new AccessRightException(new Locale(user.getLanguage()),user); + } + } + + WorkspaceWorkflow workspaceWorkflowTarget = workflowDAO.getWorkspaceWorkflowTarget(workspaceId,workflow); + if(workspaceWorkflowTarget!=null){ + List abortedWorkflows = workspaceWorkflowTarget.getAbortedWorkflows(); + return abortedWorkflows.toArray(new Workflow[abortedWorkflows.size()]); + }else{ + throw new AccessRightException(new Locale(user.getLanguage()),user); + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void approveTaskOnWorkspaceWorkflow(String workspaceId, TaskKey taskKey, String comment, String signature) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, TaskNotFoundException, AccessRightException, WorkflowNotFoundException, NotAllowedException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + + Task task = new TaskDAO(new Locale(user.getLanguage()), em).loadTask(taskKey); + Workflow workflow = task.getActivity().getWorkflow(); + + checkTaskAccess(user,task); + + task.approve(user,comment, 0, signature); + + Collection runningTasks = workflow.getRunningTasks(); + for (Task runningTask : runningTasks) { + runningTask.start(); + } + // TODO mails + // mailer.sendApproval(runningTasks, partRevision); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void rejectTaskOnWorkspaceWorkflow(String workspaceId, TaskKey taskKey, String comment, String signature) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, TaskNotFoundException, AccessRightException, WorkflowNotFoundException, NotAllowedException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + + Task task = new TaskDAO(new Locale(user.getLanguage()), em).loadTask(taskKey); + + WorkspaceWorkflow workspaceWorkflow = checkTaskAccess(user, task); + + task.reject(user, comment, 0, signature); + + // Relaunch Workflow ? + Activity currentActivity = task.getActivity(); + Activity relaunchActivity = currentActivity.getRelaunchActivity(); + + if(currentActivity.isStopped() && relaunchActivity != null){ + relaunchWorkflow(workspaceWorkflow,relaunchActivity.getStep()); + // TODO Send mails for running tasks + //mailer.sendApproval(workspaceWorkflowTarget); + // TODO Send notification for relaunch + //mailer.sendWorkspaceWorkflowRelaunchedNotification(workspaceWorkflowTarget); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public WorkspaceWorkflow[] getWorkspaceWorkflowList(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + userManager.checkWorkspaceReadAccess(workspaceId); + List workspaceWorkflows = new WorkflowDAO(em).getWorkspaceWorkflowList(workspaceId); + return workspaceWorkflows.toArray(new WorkspaceWorkflow[workspaceWorkflows.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteWorkspaceWorkflow(String workspaceId, String workspaceWorkflowId) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(workspaceId); + WorkflowDAO workflowDAO = new WorkflowDAO(em); + WorkspaceWorkflow workspaceWorkflow = workflowDAO.getWorkspaceWorkflow(workspaceId,workspaceWorkflowId); + if(workspaceWorkflow != null){ + workflowDAO.deleteWorkspaceWorkflow(workspaceWorkflow); + }else{ + throw new AccessRightException(new Locale(user.getLanguage()),user); + } + } + + + + /** + * Check if a user can approve or reject a task + * @param user The specific user + * @param task The specific task + * @return The part concern by the task + * @throws WorkflowNotFoundException If no workflow was find for this task + * @throws NotAllowedException If you can not make this task + */ + private WorkspaceWorkflow checkTaskAccess(User user, Task task) throws WorkflowNotFoundException, NotAllowedException { + + Workflow workflow = task.getActivity().getWorkflow(); + WorkspaceWorkflow workspaceWorkflowTarget = new WorkflowDAO(em).getWorkspaceWorkflowTarget(user.getWorkspaceId(), workflow); + Locale locale = new Locale(user.getLanguage()); + + if(workspaceWorkflowTarget == null){ + throw new WorkflowNotFoundException(locale,workflow.getId()); + } + if(!task.isInProgress()){ + throw new NotAllowedException(locale,"NotAllowedException15"); + } + if (!task.isPotentialWorker(user)) { + throw new NotAllowedException(locale, "NotAllowedException14"); + } + if (!workflow.getRunningTasks().contains(task)) { + throw new NotAllowedException(locale, "NotAllowedException15"); + } + + return workspaceWorkflowTarget; + } + + private void relaunchWorkflow(WorkspaceWorkflow workspaceWorkflow, int activityStep){ + Workflow workflow = workspaceWorkflow.getWorkflow(); + // Clone new workflow + Workflow relaunchedWorkflow = new WorkflowDAO(em).duplicateWorkflow(workflow); + + // Move aborted workflow in docR list + workflow.abort(); + workspaceWorkflow.addAbortedWorkflows(workflow); + // Set new workflow on document + workspaceWorkflow.setWorkflow(relaunchedWorkflow); + // Reset some properties + relaunchedWorkflow.relaunch(activityStep); + } + + private void checkWorkflowBelongToWorkspace(User user, String workspaceId, Workflow workflow) throws AccessRightException { + WorkflowDAO workflowDAO = new WorkflowDAO(em); + + DocumentRevisionDAO documentRevisionDAO = new DocumentRevisionDAO(em); + DocumentRevision documentTarget = documentRevisionDAO.getWorkflowHolder(workflow); + + if(documentTarget!=null){ + if(workspaceId.equals(documentTarget.getWorkspaceId())){ + return; + }else{ + throw new AccessRightException(new Locale(user.getLanguage()),user); + } + } + + PartRevisionDAO partRevisionDAO = new PartRevisionDAO(em); + PartRevision partTarget = partRevisionDAO.getWorkflowHolder(workflow); + if(partTarget !=null){ + if(workspaceId.equals(partTarget.getWorkspaceId())){ + return; + }else{ + throw new AccessRightException(new Locale(user.getLanguage()),user); + } + } + + WorkspaceWorkflow workspaceWorkflowTarget = workflowDAO.getWorkspaceWorkflowTarget(workspaceId,workflow); + if(workspaceWorkflowTarget!=null){ + return; + }else{ + throw new AccessRightException(new Locale(user.getLanguage()),user); + } + + } + + + private void checkWorkflowValidity(String workspaceId, String pId, Locale userLocale, ActivityModel[] pActivityModels) throws NotAllowedException { + + List roles = new RoleDAO(userLocale, em).findRolesInWorkspace(workspaceId); + + if (pId == null || " ".equals(pId)) { + throw new NotAllowedException(userLocale, "WorkflowNameEmptyException"); + } + + if (pActivityModels.length == 0) { + throw new NotAllowedException(userLocale, "NotAllowedException2"); + } + + for (ActivityModel activity : pActivityModels) { + if (activity.getLifeCycleState() == null || "".equals(activity.getLifeCycleState()) || activity.getTaskModels().isEmpty()) { + throw new NotAllowedException(userLocale, "NotAllowedException3"); + } + for (TaskModel taskModel : activity.getTaskModels()) { + + Role modelRole = taskModel.getRole(); + if (modelRole == null) { + throw new NotAllowedException(userLocale, "NotAllowedException13"); + } + String roleName = modelRole.getName(); + for (Role role : roles) { + if (role.getName().equals(roleName)) { + taskModel.setRole(role); + break; + } + } + } + } + } + + + + private User checkWorkflowWriteAccess(WorkflowModel workflow, User user) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException { + if (user.isAdministrator()) { + // Check if it is the workspace's administrator + return user; + } + if (workflow.getAcl() == null) { + // Check if the item haven't ACL + return userManager.checkWorkspaceWriteAccess(workflow.getWorkspaceId()); + } else if (workflow.getAcl().hasWriteAccess(user)) { + // Check if there is a write access + return user; + } else { + // Else throw a AccessRightException + throw new AccessRightException(new Locale(user.getLanguage()), user); + } + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/WorkspaceManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/WorkspaceManagerBean.java new file mode 100644 index 0000000000..20be3664a4 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/WorkspaceManagerBean.java @@ -0,0 +1,142 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server; + + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.*; +import com.docdoku.server.dao.AccountDAO; +import com.docdoku.server.dao.WorkspaceDAO; +import com.docdoku.server.esindexer.ESIndexer; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Asynchronous; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.io.IOException; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID,UserGroupMapping.ADMIN_ROLE_ID}) +@Local(IWorkspaceManagerLocal.class) +@Stateless(name = "WorkspaceManagerBean") +public class WorkspaceManagerBean implements IWorkspaceManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IDataManagerLocal dataManager; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IContextManagerLocal contextManager; + + @Inject + private IMailerLocal mailerManager; + + @Inject + private ESIndexer esIndexer; + + private static final Logger LOGGER = Logger.getLogger(WorkspaceManagerBean.class.getName()); + + @RolesAllowed(UserGroupMapping.ADMIN_ROLE_ID) + @Override + public long getDiskUsageInWorkspace(String workspaceId) throws AccountNotFoundException { + Account account = new AccountDAO(em).loadAccount(contextManager.getCallerPrincipalLogin()); + return new WorkspaceDAO(new Locale(account.getLanguage()),em).getDiskUsageForWorkspace(workspaceId); + } + + @Override + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID,UserGroupMapping.ADMIN_ROLE_ID}) + @Asynchronous + public void deleteWorkspace(String workspaceId) { + try{ + if(contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID)){ + Workspace workspace = new WorkspaceDAO(em, dataManager).loadWorkspace(workspaceId); + doWorkspaceDeletion(workspace); + esIndexer.deleteWorkspace(workspaceId); + }else{ + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Workspace workspace = new WorkspaceDAO(em, dataManager).loadWorkspace(workspaceId); + if(workspace.getAdmin().getLogin().equals(contextManager.getCallerPrincipalLogin())){ + doWorkspaceDeletion(workspace); + esIndexer.deleteWorkspace(workspaceId); + }else{ + throw new AccessRightException(new Locale(user.getLanguage()),user); + } + } + } catch (UserNotFoundException | UserNotActiveException | AccessRightException e) { + LOGGER.log(Level.SEVERE,"Attempt to delete a unauthorized workspace : ("+workspaceId+")",e); + } catch (WorkspaceNotFoundException e) { + LOGGER.log(Level.WARNING,"Attempt to delete a workspace which does not exist : ("+workspaceId+")",e); + } catch (Exception e) { + LOGGER.log(Level.SEVERE,"Exception deleting workspace "+workspaceId,e); + } + + } + + @Override + @RolesAllowed({UserGroupMapping.ADMIN_ROLE_ID}) + public void synchronizeIndexer(String workspaceId) { + esIndexer.indexWorkspace(workspaceId); + } + + @Override + @RolesAllowed({UserGroupMapping.ADMIN_ROLE_ID}) + public Workspace changeAdmin(String workspaceId, String login) throws WorkspaceNotFoundException, AccountNotFoundException { + Account account = new AccountDAO(em).loadAccount(login); + Workspace workspace = new WorkspaceDAO(em).loadWorkspace(workspaceId); + + workspace.setAdmin(account); + return workspace; + } + + private void doWorkspaceDeletion(Workspace workspace) throws Exception { + Account admin = workspace.getAdmin(); + String workspaceId = workspace.getId(); + try { + new WorkspaceDAO(em, dataManager).removeWorkspace(workspace); + } catch (IOException e) { + LOGGER.log(Level.SEVERE,"IOException while deleting the workspace : "+workspaceId,e); + } catch (StorageException e) { + LOGGER.log(Level.SEVERE,"StorageException while deleting the workspace : "+workspaceId,e); + } catch (Exception e) { + LOGGER.log(Level.SEVERE,"Exception while deleting the workspace : "+workspaceId,e); + //TODO : create own exception + mailerManager.sendWorkspaceDeletionErrorNotification(admin, workspaceId); + throw new Exception("Runtime exception while deleting the workspace : "+workspaceId); + + } + + mailerManager.sendWorkspaceDeletionNotification(admin,workspaceId); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/PSFilterVisitor.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/PSFilterVisitor.java new file mode 100644 index 0000000000..0ccb79a56b --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/PSFilterVisitor.java @@ -0,0 +1,292 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.configuration; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.PSFilter; +import com.docdoku.core.exceptions.EntityConstraintException; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.exceptions.PartMasterNotFoundException; +import com.docdoku.core.product.*; +import com.docdoku.server.dao.PartMasterDAO; + +import javax.persistence.EntityManager; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.logging.Logger; + + +public abstract class PSFilterVisitor { + + private static final Logger LOGGER = Logger.getLogger(PSFilterVisitor.class.getName()); + + private User user; + private Locale locale; + private String workspaceId; + private PSFilter filter; + private EntityManager em; + private PartMasterDAO partMasterDAO; + private Component component; + private int stopAtDepth = -1; + private boolean stopped = false; + + public PSFilterVisitor(EntityManager pEm, User pUser, PSFilter pFilter) + throws PartMasterNotFoundException, NotAllowedException, EntityConstraintException { + + filter = pFilter; + em = pEm; + user = pUser; + workspaceId = user.getWorkspaceId(); + locale = new Locale(user.getLanguage()); + partMasterDAO = new PartMasterDAO(locale, em); + } + + /** + * Start the visitor with given part master + * */ + public void visit(PartMaster pNodeFrom, Integer pStopAtDepth) throws PartMasterNotFoundException, EntityConstraintException, NotAllowedException { + + setDepth(pStopAtDepth); + List currentPath = new ArrayList<>(); + List currentPathParts = new ArrayList<>(); + List currentPathPartIterations = new ArrayList<>(); + + PartLink virtualLink = createVirtualRootLink(pNodeFrom); + currentPathParts.add(pNodeFrom); + currentPath.add(virtualLink); + + component = new Component(pNodeFrom.getAuthor(),pNodeFrom,currentPath,null); + List result = getComponentsRecursively(component, currentPathPartIterations, currentPathParts, currentPath); + component.setComponents(result); + + } + + /** + * Start the visitor with given path + * */ + public void visit(List pStartingPath, Integer pStopAtDepth) throws PartMasterNotFoundException, EntityConstraintException, NotAllowedException { + + setDepth(pStopAtDepth); + List currentPath = pStartingPath; + List currentPathParts = new ArrayList<>(); + List currentPathPartIterations = new ArrayList<>(); + + PartMaster rootNode = currentPath.get(currentPath.size() - 1).getComponent(); + currentPathParts.add(rootNode); + + component = new Component(rootNode.getAuthor(),rootNode,currentPath,null); + List result = getComponentsRecursively(component, currentPathPartIterations, currentPathParts, currentPath); + component.setComponents(result); + } + + public void stop(){ + stopped = true; + } + + private void setDepth(Integer pDepth){ + stopAtDepth = pDepth == null ? -1 : pDepth; + } + + private List getComponentsRecursively(Component currentComponent, List pCurrentPathPartIterations, List pCurrentPathParts, List pCurrentPath) throws PartMasterNotFoundException, NotAllowedException, EntityConstraintException { + List components = new ArrayList<>(); + + if(stopped){ + return components; + } + + if(!onPathWalk(new ArrayList<>(pCurrentPath), new ArrayList<>(pCurrentPathParts))) { + return components; + } + + // Current depth + int currentDepth = pCurrentPathParts.size(); + + // Current part master is the last from pCurrentPathParts + PartMaster currentUsagePartMaster = pCurrentPathParts.get(pCurrentPathParts.size()-1); + + // Find filtered iterations to visit + List partIterations = filter.filter(currentUsagePartMaster); + + if(partIterations.isEmpty()){ + onUnresolvedVersion(currentUsagePartMaster); + } + + if(partIterations.size() > 1){ + onIndeterminateVersion(currentUsagePartMaster, new ArrayList<>(partIterations)); + } + + if(partIterations.size()==1){ + currentComponent.setRetainedIteration(partIterations.get(0)); + } + + // Visit them all, potentially diverging branches + for (PartIteration partIteration : partIterations) { + + // We know which iteration of current partMaster, add it to list + List copyPartIteration = new ArrayList<>(pCurrentPathPartIterations); + copyPartIteration.add(partIteration); + + // Is branch over ? + if(partIteration.getComponents().isEmpty()){ + onBranchDiscovered(new ArrayList<>(pCurrentPath),new ArrayList<>(copyPartIteration)); + } + + // Navigate links + for (PartUsageLink usageLink : partIteration.getComponents()) { + + List currentPath = new ArrayList<>(pCurrentPath); + currentPath.add(usageLink); + + // Filter the current path, potentially diverging branches + List eligiblePath = filter.filter(currentPath); + + if(eligiblePath.isEmpty() && !usageLink.isOptional()){ + onUnresolvedPath(new ArrayList<>(currentPath), new ArrayList<>(copyPartIteration)); + } + + if(eligiblePath.size() > 1 ){ + onIndeterminatePath(new ArrayList<>(currentPath), new ArrayList<>(copyPartIteration)); + } + + if (eligiblePath.size() == 1 && eligiblePath.get(0).isOptional()){ + onOptionalPath(new ArrayList<>(currentPath), new ArrayList<>(copyPartIteration)); + } + + for(PartLink link : eligiblePath){ + List nextPath = new ArrayList<>(pCurrentPath); + nextPath.add(link); + + if (stopAtDepth == -1 || stopAtDepth >= currentDepth) { + + // Going on a new path + PartMaster pm = loadPartMaster(link.getComponent().getNumber()); + + // Run cyclic integrity check here + if(pCurrentPathParts.contains(pm)){ + throw new EntityConstraintException(locale,"EntityConstraintException12"); + } + + // Continue tree walking on pm + List copyPathParts = new ArrayList<>(pCurrentPathParts); + List copyPath = new ArrayList<>(nextPath); + List copyPartIterations = new ArrayList<>(copyPartIteration); + copyPathParts.add(pm); + + // Recursive + Component subComponent= new Component(pm.getAuthor(), pm, copyPath, null); + subComponent.setComponents(getComponentsRecursively(subComponent, copyPartIterations, copyPathParts, copyPath)); + components.add(subComponent); + } + + } + + } + } + + return components; + } + + + + private PartMaster loadPartMaster(String partNumber) throws PartMasterNotFoundException { + return partMasterDAO.loadPartM(new PartMasterKey(workspaceId, partNumber)); + } + + private PartLink createVirtualRootLink(PartMaster pNodeFrom) { + + return new PartLink() { + @Override + public int getId() { + return 1; + } + + @Override + public double getAmount() { + return 1; + } + + @Override + public String getUnit() { + return null; + } + + @Override + public String getComment() { + return ""; + } + + @Override + public boolean isOptional() { + return false; + } + + @Override + public PartMaster getComponent() { + return pNodeFrom; + } + + @Override + public List getSubstitutes() { + return null; + } + + @Override + public String getReferenceDescription() { + return null; + } + + @Override + public Character getCode() { + return '-'; + } + + @Override + public String getFullId() { + return "-1"; + } + + @Override + public List getCadInstances() { + return null; + } + }; + } + + /** + * Getters + */ + public Component getComponent() { + return component; + } + + /** + * Abstracts + * */ + + public abstract void onIndeterminateVersion(PartMaster partMaster, List partIterations) throws NotAllowedException; + public abstract void onUnresolvedVersion(PartMaster partMaster) throws NotAllowedException; + public abstract void onIndeterminatePath(List pCurrentPath, List pCurrentPathPartIterations) throws NotAllowedException; + public abstract void onUnresolvedPath(List pCurrentPath, List partIterations) throws NotAllowedException; + public abstract void onBranchDiscovered(List pCurrentPath, List copyPartIteration); + public abstract void onOptionalPath(List path, List partIterations); + public abstract boolean onPathWalk(List path, List parts); +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/LatestPSFilter.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/LatestPSFilter.java new file mode 100644 index 0000000000..ed2b1d206f --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/LatestPSFilter.java @@ -0,0 +1,97 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + + +package com.docdoku.server.configuration.filter; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.PSFilter; +import com.docdoku.core.product.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link com.docdoku.core.configuration.ProductConfigSpec} which selects the latest checked in iteration. + * + * Filters the usage link to nominal, filters the iteration to the latest checked in. + * This filter is strict, and will return only one result for the iteration. + * + * @author Florent Garin + * @version 1.1, 30/10/11 + * @since V1.1 + * + */ + +public class LatestPSFilter extends PSFilter { + + private User user; + private boolean diverge = false; + + public LatestPSFilter() { + } + + public LatestPSFilter(User user) { + this.user = user; + } + + public LatestPSFilter(User user, boolean diverge) { + this.user = user; + this.diverge = diverge; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + @Override + public List filter(PartMaster partMaster) { + List partIterations = new ArrayList<>(); + PartIteration partIteration = partMaster.getLastRevision().getLastCheckedInIteration(); + + if (partIteration != null) { + partIterations.add(partIteration); + } + + return partIterations; + } + + @Override + public List filter(List path) { + + List links = new ArrayList<>(); + + PartLink link = path.get(path.size()-1); + links.add(link); + + if(diverge){ + for(PartSubstituteLink substituteLink: link.getSubstitutes()){ + links.add(substituteLink); + } + } + + return links; + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/LatestReleasedPSFilter.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/LatestReleasedPSFilter.java new file mode 100644 index 0000000000..84877b1361 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/LatestReleasedPSFilter.java @@ -0,0 +1,89 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + + +package com.docdoku.server.configuration.filter; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.PSFilter; +import com.docdoku.core.product.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A {@link com.docdoku.server.configuration.spec} which selects all released iterations. + * + * @author Taylor LABEJOF + * @version 2.0, 29/08/14 + * @since V2.0 + */ + +public class LatestReleasedPSFilter extends PSFilter { + + private User user; + private boolean diverge = false; + + public LatestReleasedPSFilter() { + } + + public LatestReleasedPSFilter(User user) { + this.user = user; + } + public LatestReleasedPSFilter(User user, boolean diverge) { + this.user = user; + this.diverge = diverge; + } + + public User getUser() { + return user; + } + public void setUser(User user) { + this.user = user; + } + + @Override + public List filter(PartMaster part) { + PartRevision partRevision = part.getLastReleasedRevision(); + if(partRevision != null){ + return Arrays.asList(partRevision.getLastIteration()); + } + return new ArrayList<>(); + } + + @Override + public List filter(List path) { + + List links = new ArrayList<>(); + + PartLink link = path.get(path.size()-1); + links.add(link); + + if(diverge){ + for(PartSubstituteLink substituteLink: link.getSubstitutes()){ + links.add(substituteLink); + } + } + + return links; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/ReleasedPSFilter.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/ReleasedPSFilter.java new file mode 100644 index 0000000000..6d9fa57bd1 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/ReleasedPSFilter.java @@ -0,0 +1,90 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + + +package com.docdoku.server.configuration.filter; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.PSFilter; +import com.docdoku.core.product.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link com.docdoku.server.configuration.spec} which selects all released iterations. + * + * @author Taylor LABEJOF + * @version 2.0, 29/08/14 + * @since V2.0 + */ + +public class ReleasedPSFilter extends PSFilter { + + private User user; + private boolean diverge = false; + + public ReleasedPSFilter() { + } + + public ReleasedPSFilter(User user) { + this.user = user; + } + public ReleasedPSFilter(User user, boolean diverge) { + this.user = user; + this.diverge = diverge; + } + + public User getUser() { + return user; + } + public void setUser(User user) { + this.user = user; + } + + @Override + public List filter(PartMaster part) { + List partIterations = new ArrayList<>(); + List partRevisions = part.getAllReleasedRevisions(); + for(PartRevision partRevision: partRevisions){ + // Taking last iteration, can't be checked out if released. + partIterations.add(partRevision.getLastIteration()); + } + return partIterations; + } + + @Override + public List filter(List path) { + + List links = new ArrayList<>(); + + PartLink link = path.get(path.size()-1); + links.add(link); + + if(diverge){ + for(PartSubstituteLink substituteLink: link.getSubstitutes()){ + links.add(substituteLink); + } + } + + return links; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/UpdatePartIterationPSFilter.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/UpdatePartIterationPSFilter.java new file mode 100644 index 0000000000..bd0dc02796 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/UpdatePartIterationPSFilter.java @@ -0,0 +1,96 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + + +package com.docdoku.server.configuration.filter; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.PSFilter; +import com.docdoku.core.product.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * + * @author Morgan Guimard + * + * Check for cyclic assembly after part iteration update : must check on the wip and on the latest. + * We also need to walk every substitute branches. + * + */ +public class UpdatePartIterationPSFilter extends PSFilter { + + private User user; + private PartMasterKey rootKey; + private PartIteration partIteration; + + public UpdatePartIterationPSFilter(User user, PartIteration partIteration) { + this.user = user; + this.partIteration = partIteration; + rootKey = partIteration.getKey().getPartRevision().getPartMaster(); + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + @Override + public List filter(PartMaster part) { + + // Return wip on updated part iteration + if(part.getKey().equals(rootKey)){ + return Arrays.asList(partIteration); + } + + // Return wip and last + List partIterations = new ArrayList<>(); + PartRevision partRevision = part.getLastRevision(); + PartIteration lastIteration = partRevision.getLastIteration(); + PartIteration lastCheckedInIteration = partRevision.getLastCheckedInIteration(); + + if(partRevision.isCheckedOut() && lastCheckedInIteration != null){ + partIterations.add(lastCheckedInIteration); + } + + partIterations.add(lastIteration); + return partIterations; + } + + @Override + public List filter(List path) { + + List links = new ArrayList<>(); + PartLink link = path.get(path.size()-1); + links.add(link); + + for(PartSubstituteLink substituteLink: link.getSubstitutes()){ + links.add(substituteLink); + } + + return links; + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/WIPPSFilter.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/WIPPSFilter.java new file mode 100644 index 0000000000..aa2a1ea902 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/filter/WIPPSFilter.java @@ -0,0 +1,96 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + + +package com.docdoku.server.configuration.filter; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.PSFilter; +import com.docdoku.core.product.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link com.docdoku.core.configuration.ProductConfigSpec} which selects the latest iteration (checkin or not) + * + * @author Morgan Guimard + */ +public class WIPPSFilter extends PSFilter { + + private User user; + private boolean diverge = false; + + public WIPPSFilter() { + } + + public WIPPSFilter(User user) { + this.user = user; + } + public WIPPSFilter(User user, boolean diverge) { + this.user = user; + this.diverge = diverge; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + @Override + public List filter(PartMaster part) { + List partIterations = new ArrayList<>(); + PartIteration partIteration = getLastAccessibleIteration(part); + if(partIteration != null) { + partIterations.add(partIteration); + } + return partIterations; + } + + @Override + public List filter(List path) { + + List links = new ArrayList<>(); + + PartLink link = path.get(path.size()-1); + links.add(link); + + if(diverge){ + for(PartSubstituteLink substituteLink: link.getSubstitutes()){ + links.add(substituteLink); + } + } + + return links; + } + + private PartIteration getLastAccessibleIteration(PartMaster partMaster) { + PartIteration iteration = null; + for(int i = partMaster.getPartRevisions().size()-1; i >= 0 && iteration == null; i--) { + iteration = partMaster.getPartRevisions().get(i).getLastAccessibleIteration(user); + } + return iteration; + } + + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/BaselineDocumentConfigSpec.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/BaselineDocumentConfigSpec.java new file mode 100644 index 0000000000..17fe4b89e6 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/BaselineDocumentConfigSpec.java @@ -0,0 +1,78 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.configuration.spec; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.DocumentBaseline; +import com.docdoku.core.configuration.DocumentConfigSpec; +import com.docdoku.core.configuration.FolderCollection; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevision; + + +/** + * @author Morgan Guimard + */ +public class BaselineDocumentConfigSpec extends DocumentConfigSpec { + + private DocumentBaseline documentBaseline; + private User user; + + public BaselineDocumentConfigSpec(){ + } + + public BaselineDocumentConfigSpec(DocumentBaseline documentBaseline, User user) { + this.documentBaseline = documentBaseline; + this.user = user; + } + + public int getFolderCollectionId(){ + return documentBaseline.getFolderCollection().getId(); + } + + @Override + public DocumentIteration filter(DocumentRevision documentRevision) { + FolderCollection folderCollection = documentBaseline==null ? null : documentBaseline.getFolderCollection(); // Prevent NullPointerException + if(folderCollection != null){ + DocumentIteration docI = folderCollection.getDocumentIteration(documentRevision.getKey()); + if(docI!=null){ + return docI; + } + } + + return null; + } + + public DocumentBaseline getDocumentBaseline() { + return documentBaseline; + } + + public void setDocumentBaseline(DocumentBaseline documentBaseline) { + this.documentBaseline = documentBaseline; + } + + public User getUser() { + return user; + } + public void setUser(User user) { + this.user = user; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/DateBasedEffectivityConfigSpec.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/DateBasedEffectivityConfigSpec.java new file mode 100644 index 0000000000..26bb107ca6 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/DateBasedEffectivityConfigSpec.java @@ -0,0 +1,68 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + + +package com.docdoku.server.configuration.spec; + +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartLink; +import com.docdoku.core.product.PartMaster; + +import java.util.Date; +import java.util.List; + +/** + * A kind of {@link EffectivityConfigSpec} expressed by date and time. + * + * @author Florent Garin + * @version 1.1, 30/10/11 + * @since V1.1 + */ + +public class DateBasedEffectivityConfigSpec extends EffectivityConfigSpec { + /** + * The date and/or time of the context. + */ + + private Date date; + + public DateBasedEffectivityConfigSpec() { + } + + @Override + public PartIteration filterPartIteration(PartMaster partMaster) { + // TODO : implement filter + return null; + } + + @Override + public PartLink filterPartLink(List path) { + // TODO : implement filter + return null; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/EffectivityConfigSpec.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/EffectivityConfigSpec.java new file mode 100644 index 0000000000..29d92f82a5 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/EffectivityConfigSpec.java @@ -0,0 +1,50 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.configuration.spec; + +import com.docdoku.core.configuration.ProductConfigSpec; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.product.ConfigurationItem; +import com.docdoku.core.product.PartMaster; + +/** + * A configuration specification used to filter {@link PartMaster}s and {@link DocumentRevision}s + * according to its effectivities. + * + * @author Florent Garin + * @version 1.1, 30/10/11 + * @since V1.1 + */ +public abstract class EffectivityConfigSpec extends ProductConfigSpec { + + protected ConfigurationItem configurationItem; + + public EffectivityConfigSpec() { + } + + public void setConfigurationItem(ConfigurationItem configurationItem) { + this.configurationItem = configurationItem; + } + + public ConfigurationItem getConfigurationItem() { + return configurationItem; + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/LatestDocumentConfigSpec.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/LatestDocumentConfigSpec.java new file mode 100644 index 0000000000..7c171bcf1f --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/LatestDocumentConfigSpec.java @@ -0,0 +1,57 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + + +package com.docdoku.server.configuration.spec; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.DocumentConfigSpec; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevision; + +/** + * @author Morgan Guimard + * + */ + +public class LatestDocumentConfigSpec extends DocumentConfigSpec { + + private User user; + public LatestDocumentConfigSpec() { + } + + public LatestDocumentConfigSpec(User user) { + this.user = user; + } + + @Override + public DocumentIteration filter(DocumentRevision documentRevision) { + return documentRevision.getLastIteration(); + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/LotBasedEffectivityConfigSpec.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/LotBasedEffectivityConfigSpec.java new file mode 100644 index 0000000000..8dd974f542 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/LotBasedEffectivityConfigSpec.java @@ -0,0 +1,68 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + + +package com.docdoku.server.configuration.spec; + +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartLink; +import com.docdoku.core.product.PartMaster; + +import java.util.List; + +/** + * A kind of {@link EffectivityConfigSpec} based on a specific lot. + * + * @author Florent Garin + * @version 1.1, 30/10/11 + * @since V1.1 + */ +public class LotBasedEffectivityConfigSpec extends EffectivityConfigSpec { + + /** + * The lot id of the particular batch of items specified by the context. + */ + private String lotId; + + public LotBasedEffectivityConfigSpec() { + } + + @Override + public PartIteration filterPartIteration(PartMaster partMaster) { + // TODO : implement filter + return null; + } + + @Override + public PartLink filterPartLink(List path) { + // TODO : implement filter + return null; + } + + + public String getLotId() { + return lotId; + } + + public void setLotId(String lotId) { + this.lotId = lotId; + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/ProductBaselineConfigSpec.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/ProductBaselineConfigSpec.java new file mode 100644 index 0000000000..0a4a1f52df --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/ProductBaselineConfigSpec.java @@ -0,0 +1,117 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.configuration.spec; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.*; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartLink; +import com.docdoku.core.product.PartMaster; +import com.docdoku.core.product.PartSubstituteLink; +import com.docdoku.core.util.Tools; + +import java.util.ArrayList; +import java.util.List; + + +/** + * A {@link com.docdoku.core.configuration.ProductConfigSpec} which returns the {@link PartIteration} and {@link DocumentIteration} + * which belong to the given baseline. + * + * As a baseline should have no ambiguities, if a filter returns null the spec is considered as invalid. + * + * @author Florent Garin + * @version 1.1, 30/10/11 + * @since V1.1 + */ +public class ProductBaselineConfigSpec extends ProductConfigSpec { + + private ProductBaseline productBaseline; + private User user; + + public ProductBaselineConfigSpec(ProductBaseline productBaseline, User user) { + // Prevent NullPointerException + if(productBaseline == null){ + throw new IllegalArgumentException("Cannot instantiate a BaselineProductConfigSpec without a baseline"); + } + this.productBaseline = productBaseline; + this.user = user; + } + + public ProductBaseline getProductBaseline() { + return productBaseline; + } + public void setProductBaseline(ProductBaseline productBaseline) { + this.productBaseline = productBaseline; + } + + public User getUser() { + return user; + } + public void setUser(User user) { + this.user = user; + } + + public int getPartCollectionId(){ + return productBaseline.getPartCollection().getId(); + } + + @Override + public PartIteration filterPartIteration(PartMaster partMaster) { + + PartCollection partCollection = productBaseline.getPartCollection(); + BaselinedPartKey baselinedRootPartKey = new BaselinedPartKey(partCollection.getId(), partMaster.getWorkspaceId(), partMaster.getNumber()); + BaselinedPart baselinedRootPart = partCollection.getBaselinedPart(baselinedRootPartKey); + + if (baselinedRootPart != null) { + return baselinedRootPart.getTargetPart(); + } + + return null; + } + + @Override + public PartLink filterPartLink(List path) { + + // No ambiguities here, must return 1 value + // Check if optional or substitute, nominal link else + + PartLink nominalLink = path.get(path.size()-1); + + if(nominalLink.isOptional() && productBaseline.isLinkOptional(Tools.getPathAsString(path))){ + return null; + } + + for(PartSubstituteLink substituteLink:nominalLink.getSubstitutes()){ + + List substitutePath = new ArrayList<>(path); + substitutePath.set(substitutePath.size()-1,substituteLink); + + if(productBaseline.hasSubstituteLink(Tools.getPathAsString(substitutePath))){ + return substituteLink; + } + + } + + return nominalLink; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/ProductBaselineCreationConfigSpec.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/ProductBaselineCreationConfigSpec.java new file mode 100644 index 0000000000..e89daeb780 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/ProductBaselineCreationConfigSpec.java @@ -0,0 +1,140 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.configuration.spec; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.ProductBaseline; +import com.docdoku.core.configuration.ProductConfigSpec; +import com.docdoku.core.product.*; +import com.docdoku.core.util.Tools; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author Morgan Guimard + */ +public class ProductBaselineCreationConfigSpec extends ProductConfigSpec { + + private List partIterations; + private List substituteLinks; + private List optionalUsageLinks; + + private Set retainedPartIterations = new HashSet<>(); + private Set retainedSubstituteLinks = new HashSet<>(); + private Set retainedOptionalUsageLinks = new HashSet<>(); + + private ProductBaseline.BaselineType type; + + private User user; + + public ProductBaselineCreationConfigSpec(){ + } + + public ProductBaselineCreationConfigSpec(User user, ProductBaseline.BaselineType type, List partIterations, List substituteLinks, List optionalUsageLinks) { + this.user = user; + this.partIterations = partIterations; + this.substituteLinks = substituteLinks; + this.optionalUsageLinks = optionalUsageLinks; + this.type = type; + } + + public User getUser() { + return user; + } + public void setUser(User user) { + this.user = user; + } + + @Override + public PartIteration filterPartIteration(PartMaster partMaster) { + + if(type.equals(ProductBaseline.BaselineType.RELEASED)){ + + for(PartIteration pi : partIterations){ + if(pi.getPartRevision().getPartMaster().getKey().equals(partMaster.getKey())){ + retainedPartIterations.add(pi); + return pi; + } + } + // Else, take the latest released + PartRevision lastReleasedRevision = partMaster.getLastReleasedRevision(); + if(lastReleasedRevision != null){ + PartIteration pi = lastReleasedRevision.getLastIteration(); + retainedPartIterations.add(pi); + return pi; + } + + }else if(type.equals(ProductBaseline.BaselineType.LATEST)){ + + PartIteration pi = partMaster.getLastRevision().getLastCheckedInIteration(); + + if(pi!=null){ + retainedPartIterations.add(pi); + return pi; + } + } + + return null; + } + + @Override + public PartLink filterPartLink(List path) { + + // No ambiguities here, must return 1 value + // Check if optional or substitute, nominal link else + + PartLink nominalLink = path.get(path.size()-1); + + if(nominalLink.isOptional() && optionalUsageLinks.contains(Tools.getPathAsString(path))){ + retainedOptionalUsageLinks.add(Tools.getPathAsString(path)); + return null; + } + + for(PartSubstituteLink substituteLink:nominalLink.getSubstitutes()){ + + List substitutePath = new ArrayList<>(path); + substitutePath.set(substitutePath.size()-1,substituteLink); + + if(substituteLinks.contains(Tools.getPathAsString(substitutePath))){ + retainedSubstituteLinks.add(Tools.getPathAsString(substitutePath)); + return substituteLink; + } + + } + + return nominalLink; + } + + + public Set getRetainedPartIterations() { + return retainedPartIterations; + } + + public Set getRetainedSubstituteLinks() { + return retainedSubstituteLinks; + } + + public Set getRetainedOptionalUsageLinks() { + return retainedOptionalUsageLinks; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/ProductInstanceConfigSpec.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/ProductInstanceConfigSpec.java new file mode 100644 index 0000000000..cb67cc128c --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/ProductInstanceConfigSpec.java @@ -0,0 +1,110 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.configuration.spec; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.*; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartLink; +import com.docdoku.core.product.PartMaster; +import com.docdoku.core.product.PartSubstituteLink; +import com.docdoku.core.util.Tools; + +import java.util.ArrayList; +import java.util.List; + + +/** + * A {@link com.docdoku.core.configuration.ProductConfigSpec} which returns the {@link com.docdoku.core.product.PartIteration} and {@link com.docdoku.core.document.DocumentIteration} + * which belong to the given baseline. + * + * @author Florent Garin + * @version 1.1, 30/10/11 + * @since V1.1 + */ +public class ProductInstanceConfigSpec extends ProductConfigSpec { + + private ProductInstanceIteration productInstanceIteration; + private User user; + + public ProductInstanceConfigSpec(){ + } + public ProductInstanceConfigSpec(ProductInstanceIteration productInstanceIteration, User user) { + this.productInstanceIteration = productInstanceIteration; + this.user = user; + } + + public ProductInstanceIteration getProductInstanceIteration() { + return productInstanceIteration; + } + public void setProductInstanceIteration(ProductInstanceIteration productInstanceIteration) { + this.productInstanceIteration = productInstanceIteration; + } + + public User getUser() { + return user; + } + public void setUser(User user) { + this.user = user; + } + + public int getPartCollectionId(){ + return productInstanceIteration.getPartCollection().getId(); + } + + @Override + public PartIteration filterPartIteration(PartMaster part) { + PartCollection partCollection = productInstanceIteration==null ? null : productInstanceIteration.getPartCollection();// Prevent NullPointerException + if(partCollection != null) { + BaselinedPartKey baselinedRootPartKey = new BaselinedPartKey(partCollection.getId(), part.getWorkspaceId(), part.getNumber()); + BaselinedPart baselinedRootPart = productInstanceIteration.getBaselinedPart(baselinedRootPartKey); + if (baselinedRootPart != null) { + return baselinedRootPart.getTargetPart(); + } + } + return null; + } + + @Override + public PartLink filterPartLink(List path) { + // No ambiguities here, must return 1 value + // Check if optional or substitute, nominal link else + PartLink nominalLink = path.get(path.size()-1); + + if(nominalLink.isOptional() && productInstanceIteration.isLinkOptional(Tools.getPathAsString(path))){ + return null; + } + + for(PartSubstituteLink substituteLink:nominalLink.getSubstitutes()){ + + List substitutePath = new ArrayList<>(path); + substitutePath.set(substitutePath.size()-1,substituteLink); + + if(productInstanceIteration.hasSubstituteLink(Tools.getPathAsString(substitutePath))){ + return substituteLink; + } + + } + + return nominalLink; + + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/SerialNumberBasedEffectivityConfigSpec.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/SerialNumberBasedEffectivityConfigSpec.java new file mode 100644 index 0000000000..849124ead2 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/configuration/spec/SerialNumberBasedEffectivityConfigSpec.java @@ -0,0 +1,66 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + + +package com.docdoku.server.configuration.spec; + +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartLink; +import com.docdoku.core.product.PartMaster; + +import java.util.List; + +/** + * A kind of {@link EffectivityConfigSpec} based on serial number. + * + * @author Florent Garin + * @version 1.1, 30/10/11 + * @since V1.1 + */ +public class SerialNumberBasedEffectivityConfigSpec extends EffectivityConfigSpec { + + /** + * The serial number of the particular item specified by the context. + */ + private String number; + public SerialNumberBasedEffectivityConfigSpec() { + } + + @Override + public PartIteration filterPartIteration(PartMaster partMaster) { + // TODO : implement filter + return null; + } + + @Override + public PartLink filterPartLink(List path) { + // TODO : implement filter + return null; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getNumber() { + return number; + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ACLDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ACLDAO.java new file mode 100644 index 0000000000..7b2f8493e1 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ACLDAO.java @@ -0,0 +1,64 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.ACLUserEntry; +import com.docdoku.core.security.ACLUserGroupEntry; + +import javax.persistence.EntityManager; +import javax.persistence.Query; +import java.util.Map; + +public class ACLDAO { + + private final EntityManager em; + + public ACLDAO(EntityManager pEM) { + em = pEM; + } + + public void createACL(ACL acl) { + //Hack to prevent a bug inside the JPA implementation (Eclipse Link) + Map groupEntries = acl.getGroupEntries(); + Map userEntries = acl.getUserEntries(); + acl.setGroupEntries(null); + acl.setUserEntries(null); + em.persist(acl); + em.flush(); + acl.setGroupEntries(groupEntries); + acl.setUserEntries(userEntries); + } + + public void removeACLEntries(ACL acl){ + em.createNamedQuery("ACL.removeUserEntries").setParameter("aclId",acl.getId()).executeUpdate(); + em.createNamedQuery("ACL.removeUserGroupEntries").setParameter("aclId",acl.getId()).executeUpdate(); + em.flush(); + } + + public void removeAclUserEntries(User pUser) { + Query query = em.createQuery("DELETE FROM ACLUserEntry a WHERE a.principal = :user"); + query.setParameter("user", pUser).executeUpdate(); + + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/AccountDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/AccountDAO.java new file mode 100644 index 0000000000..ab90c129a8 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/AccountDAO.java @@ -0,0 +1,116 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.exceptions.AccountAlreadyExistsException; +import com.docdoku.core.exceptions.AccountNotFoundException; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.security.Credential; +import com.docdoku.core.security.UserGroupMapping; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import javax.persistence.TypedQuery; +import java.util.List; +import java.util.Locale; + +public class AccountDAO { + + private EntityManager em; + private Locale mLocale; + + public AccountDAO(Locale pLocale, EntityManager pEM) { + mLocale=pLocale; + em=pEM; + } + + public AccountDAO(EntityManager pEM) { + mLocale=Locale.getDefault(); + em=pEM; + } + + public void createAccount(Account pAccount, String pPassword) throws AccountAlreadyExistsException, CreationException { + try{ + //the EntityExistsException is thrown only when flush occurs + em.persist(pAccount); + em.flush(); + Credential credential = Credential.createCredential(pAccount.getLogin(),pPassword); + em.persist(credential); + em.persist(new UserGroupMapping(pAccount.getLogin())); + }catch(EntityExistsException pEEEx){ + throw new AccountAlreadyExistsException(mLocale, pAccount.getLogin()); + }catch(PersistenceException pPEx){ + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } + + public void updateAccount(Account pAccount, String pPassword){ + em.merge(pAccount); + if(pPassword!=null){ + updateCredential(pAccount.getLogin(),pPassword); + } + } + + public void updateCredential(String pLogin, String pPassword){ + Credential credential = Credential.createCredential(pLogin,pPassword); + em.merge(credential); + } + + public Account loadAccount(String pLogin) throws AccountNotFoundException { + Account account = em.find(Account.class,pLogin); + if (account == null) { + throw new AccountNotFoundException(mLocale, pLogin); + } else { + return account; + } + } + + public Workspace[] getAdministratedWorkspaces(Account pAdmin) { + Workspace[] workspaces; + TypedQuery query = em.createQuery("SELECT DISTINCT w FROM Workspace w WHERE w.admin = :admin", Workspace.class); + List listWorkspaces = query.setParameter("admin",pAdmin).getResultList(); + workspaces = new Workspace[listWorkspaces.size()]; + for(int i=0;i query = em.createQuery("SELECT DISTINCT w FROM Workspace w", Workspace.class); + List listWorkspaces = query.getResultList(); + workspaces = new Workspace[listWorkspaces.size()]; + for(int i=0;i. + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.configuration.BaselinedDocument; +import com.docdoku.core.configuration.BaselinedDocumentKey; +import com.docdoku.core.configuration.BaselinedFolderKey; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.DocumentRevisionNotFoundException; + +import javax.persistence.EntityManager; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +public class BaselinedDocumentDAO { + + private EntityManager em; + private Locale mLocale; + + public BaselinedDocumentDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale=pLocale; + } + + public BaselinedDocumentDAO(EntityManager pEM) { + em = pEM; + mLocale=Locale.getDefault(); + } + + public BaselinedDocument loadBaselineDocument(BaselinedDocumentKey baselinedDocumentKey) throws DocumentRevisionNotFoundException { + BaselinedDocument baselinedDocument = em.find(BaselinedDocument.class,baselinedDocumentKey); + if (baselinedDocument == null) { + DocumentRevisionKey key = new DocumentRevisionKey(baselinedDocumentKey.getTargetDocumentWorkspaceId(),baselinedDocumentKey.getTargetDocumentId(),""); + throw new DocumentRevisionNotFoundException(mLocale, key); + } else { + return baselinedDocument; + } + } + + public List findDocRsByFolder(BaselinedFolderKey baselinedFolderKey){ + List list = em.createQuery("" + + "SELECT d.documentIterations " + + "FROM BaselinedFolder d " + + "WHERE d.baselinedFolderKey = :pk ", DocumentIteration.class) + .setParameter("pk", baselinedFolderKey) + .getResultList(); + // TODO : find out why the list can contains a null value and remove this workaround : + list.removeAll(Collections.singleton(null)); + return list; + } + + public List findDocumentRevision(DocumentRevisionKey pDocumentRevisionKey) { + return em.createQuery("" + + "SELECT d " + + "FROM BaselinedFolder d, DocumentIteration i " + + "WHERE i member of d.documentIterations " + + "AND i.documentRevision.documentMaster.workspace.id = :workspaceId " + + "AND i.documentRevision.documentMasterId = :documentMasterId " + + "AND i.documentRevision.version = :version",DocumentIteration.class) + .setParameter("documentMasterId", pDocumentRevisionKey.getDocumentMaster().getId()) + .setParameter("version", pDocumentRevisionKey.getVersion()) + .setParameter("workspaceId", pDocumentRevisionKey.getDocumentMaster().getWorkspace()) + .getResultList(); + } + + public boolean hasDocumentRevision(DocumentRevisionKey pDocumentRevisionKey) { + return !findDocumentRevision(pDocumentRevisionKey).isEmpty(); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/BaselinedFolderDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/BaselinedFolderDAO.java new file mode 100644 index 0000000000..8efbc791b2 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/BaselinedFolderDAO.java @@ -0,0 +1,65 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.configuration.BaselinedFolder; +import com.docdoku.core.configuration.BaselinedFolderKey; +import com.docdoku.core.exceptions.FolderNotFoundException; + +import javax.persistence.EntityManager; +import java.util.List; +import java.util.Locale; + +public class BaselinedFolderDAO { + + private EntityManager em; + private Locale mLocale; + + public BaselinedFolderDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale=pLocale; + } + + public BaselinedFolderDAO(EntityManager pEM) { + em = pEM; + mLocale=Locale.getDefault(); + } + + public BaselinedFolder loadBaselineFolder(BaselinedFolderKey baselinedFolderKey) throws FolderNotFoundException { + BaselinedFolder baselinedFolder = em.find(BaselinedFolder.class,baselinedFolderKey); + if (baselinedFolder == null) { + throw new FolderNotFoundException(mLocale, baselinedFolderKey.getCompletePath()); + } else { + return baselinedFolder; + } + } + + public List getSubFolders(BaselinedFolderKey baselinedFolderKey){ + return em.createQuery("" + + "SELECT DISTINCT f " + + "FROM BaselinedFolder f " + + "WHERE f.parentFolder.completePath = :completePath " + + "AND f.folderCollection.id = :collectionId", BaselinedFolder.class) + .setParameter("completePath",baselinedFolderKey.getCompletePath()) + .setParameter("collectionId", baselinedFolderKey.getFolderCollection()) + .getResultList(); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/BinaryResourceDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/BinaryResourceDAO.java new file mode 100644 index 0000000000..971cc9b6b0 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/BinaryResourceDAO.java @@ -0,0 +1,181 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.configuration.PathDataIteration; +import com.docdoku.core.configuration.ProductInstanceIteration; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentMasterTemplate; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.FileAlreadyExistsException; +import com.docdoku.core.exceptions.FileNotFoundException; +import com.docdoku.core.product.Geometry; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartMasterTemplate; + +import javax.persistence.*; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class BinaryResourceDAO { + private static final Logger LOGGER = Logger.getLogger(BinaryResourceDAO.class.getName()); + + private final EntityManager em; + private final Locale mLocale; + + public BinaryResourceDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public BinaryResourceDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + public void createBinaryResource(BinaryResource pBinaryResource) throws FileAlreadyExistsException, CreationException { + try { + //the EntityExistsException is thrown only when flush occurs + em.persist(pBinaryResource); + em.flush(); + } catch (EntityExistsException pEEEx) { + LOGGER.log(Level.FINER,null,pEEEx); + throw new FileAlreadyExistsException(mLocale, pBinaryResource); + } catch (PersistenceException pPEx) { + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + LOGGER.log(Level.FINER,null,pPEx); + throw new CreationException(mLocale); + } + } + + public void removeBinaryResource(String pFullName) throws FileNotFoundException { + BinaryResource file = loadBinaryResource(pFullName); + em.remove(file); + } + + public void removeBinaryResource(BinaryResource pBinaryResource) { + em.remove(pBinaryResource); + em.flush(); + } + + public BinaryResource loadBinaryResource(String pFullName) throws FileNotFoundException { + BinaryResource file = em.find(BinaryResource.class, pFullName); + if(null == file){ + throw new FileNotFoundException(mLocale,pFullName); + } + return file; + + } + + public PartIteration getPartHolder(BinaryResource pBinaryResource) { + TypedQuery query; + String fileType = pBinaryResource.getFileType(); + if(pBinaryResource instanceof Geometry){ + query = em.createQuery("SELECT p FROM PartIteration p WHERE :binaryResource MEMBER OF p.geometries", PartIteration.class); + }else if("nativecad".equals(fileType)){ + query = em.createQuery("SELECT p FROM PartIteration p WHERE p.nativeCADFile = :binaryResource", PartIteration.class); + }else{ + query = em.createQuery("SELECT p FROM PartIteration p WHERE :binaryResource MEMBER OF p.attachedFiles", PartIteration.class); + } + try { + return query.setParameter("binaryResource", pBinaryResource).getSingleResult(); + } catch (NoResultException pNREx) { + LOGGER.log(Level.FINER,null,pNREx); + return null; + } + } + + public DocumentIteration getDocumentHolder(BinaryResource pBinaryResource) { + TypedQuery query = em.createQuery("SELECT d FROM DocumentIteration d WHERE :binaryResource MEMBER OF d.attachedFiles", DocumentIteration.class); + try { + return query.setParameter("binaryResource", pBinaryResource).getSingleResult(); + } catch (NoResultException pNREx) { + LOGGER.log(Level.FINER,null,pNREx); + return null; + } + } + public ProductInstanceIteration getProductInstanceIterationHolder(BinaryResource pBinaryResource) { + TypedQuery query = em.createQuery("SELECT d FROM ProductInstanceIteration d WHERE :binaryResource MEMBER OF d.attachedFiles", ProductInstanceIteration.class); + try { + return query.setParameter("binaryResource", pBinaryResource).getSingleResult(); + } catch (NoResultException pNREx) { + LOGGER.log(Level.FINER,null,pNREx); + return null; + } + } + + public PathDataIteration getPathDataHolder(BinaryResource pBinaryResource) { + TypedQuery query = em.createQuery("SELECT p FROM PathDataIteration p WHERE :binaryResource MEMBER OF p.attachedFiles", PathDataIteration.class); + try { + return query.setParameter("binaryResource", pBinaryResource).getSingleResult(); + } catch (NoResultException pNREx) { + LOGGER.log(Level.FINER,null,pNREx); + return null; + } + } + + + public DocumentMasterTemplate getDocumentTemplateHolder(BinaryResource pBinaryResource) { + TypedQuery query = em.createQuery("SELECT t FROM DocumentMasterTemplate t WHERE :binaryResource MEMBER OF t.attachedFiles", DocumentMasterTemplate.class); + try { + return query.setParameter("binaryResource", pBinaryResource).getSingleResult(); + } catch (NoResultException pNREx) { + LOGGER.log(Level.FINER,null,pNREx); + return null; + } + } + + public PartMasterTemplate getPartTemplateHolder(BinaryResource pBinaryResource) { + TypedQuery query = em.createQuery("SELECT t FROM PartMasterTemplate t WHERE t.attachedFile = :binaryResource", PartMasterTemplate.class); + try { + return query.setParameter("binaryResource", pBinaryResource).getSingleResult(); + } catch (NoResultException pNREx) { + LOGGER.log(Level.FINER,null,pNREx); + return null; + } + } + + public BinaryResource findNativeCadBinaryResourceInWorkspace(String workspaceId, String cadFileName) { + TypedQuery query = em.createQuery("SELECT br FROM BinaryResource br WHERE br.fullName like :name", BinaryResource.class); + try { + return query.setParameter("name", workspaceId + "/parts/%/nativecad/" + cadFileName).getSingleResult(); + } catch (NoResultException pNREx) { + LOGGER.log(Level.FINER,null,pNREx); + return null; + } + } + + + + private boolean isNativeCADFile(String pFullName){ + String[] parts = pFullName.split("/"); + return parts.length==7 && "nativecad".equals(parts[5]); + } + + private boolean isAttachedFile(String pFullName) { + String[] parts = pFullName.split("/"); + return parts.length==7 && "attachedfiles".equals(parts[5]); + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ChangeItemDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ChangeItemDAO.java new file mode 100644 index 0000000000..2354eef78b --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ChangeItemDAO.java @@ -0,0 +1,250 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.change.ChangeIssue; +import com.docdoku.core.change.ChangeItem; +import com.docdoku.core.change.ChangeOrder; +import com.docdoku.core.change.ChangeRequest; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.document.Folder; +import com.docdoku.core.exceptions.ChangeIssueNotFoundException; +import com.docdoku.core.exceptions.ChangeOrderNotFoundException; +import com.docdoku.core.exceptions.ChangeRequestNotFoundException; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.product.PartRevisionKey; + +import javax.persistence.EntityManager; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class ChangeItemDAO { + + private EntityManager em; + private Locale mLocale; + private static final String WORKSPACEID = "workspaceId"; + + public ChangeItemDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public ChangeItemDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + + public List findAllChangeIssues(String pWorkspaceId) { + return em.createNamedQuery("ChangeIssue.findChangeIssuesByWorkspace", ChangeIssue.class) + .setParameter(WORKSPACEID, pWorkspaceId) + .getResultList(); + } + public List findAllChangeRequests(String pWorkspaceId) { + return em.createNamedQuery("ChangeRequest.findChangeRequestsByWorkspace", ChangeRequest.class) + .setParameter(WORKSPACEID, pWorkspaceId) + .getResultList(); + } + public List findAllChangeOrders(String pWorkspaceId) { + return em.createNamedQuery("ChangeOrder.findChangeOrdersByWorkspace", ChangeOrder.class) + .setParameter(WORKSPACEID, pWorkspaceId) + .getResultList(); + } + + public ChangeIssue loadChangeIssue(int pId) throws ChangeIssueNotFoundException { + ChangeIssue change = em.find(ChangeIssue.class, pId); + if (change == null) { + throw new ChangeIssueNotFoundException(mLocale, pId); + } else { + return change; + } + } + public ChangeOrder loadChangeOrder(int pId) throws ChangeOrderNotFoundException { + ChangeOrder change = em.find(ChangeOrder.class, pId); + if (change == null) { + throw new ChangeOrderNotFoundException(mLocale, pId); + } else { + return change; + } + } + public ChangeRequest loadChangeRequest(int pId) throws ChangeRequestNotFoundException { + ChangeRequest change = em.find(ChangeRequest.class, pId); + if (change == null) { + throw new ChangeRequestNotFoundException(mLocale, pId); + } else { + return change; + } + } + + public void createChangeItem(ChangeItem pChange) { + if(pChange.getACL()!=null){ + ACLDAO aclDAO = new ACLDAO(em); + aclDAO.createACL(pChange.getACL()); + } + + em.persist(pChange); + em.flush(); + } + public void deleteChangeItem(ChangeItem pChange) { + em.remove(pChange); + em.flush(); + } + public ChangeItem removeTag(ChangeItem pChange, String tagName){ + Tag tagToRemove = new Tag(pChange.getWorkspace(), tagName); + pChange.getTags().remove(tagToRemove); + return pChange; + } + + public List findAllChangeIssuesWithReferenceLike(String pWorkspaceId, String reference, int maxResults) { + return em.createNamedQuery("ChangeIssue.findByReference",ChangeIssue.class) + .setParameter(WORKSPACEID, pWorkspaceId).setParameter("name", "%" + reference + "%").setMaxResults(maxResults).getResultList(); + } + + public List findAllChangeRequestsWithReferenceLike(String pWorkspaceId, String reference, int maxResults) { + return em.createNamedQuery("ChangeRequest.findByReference",ChangeRequest.class) + .setParameter(WORKSPACEID, pWorkspaceId).setParameter("name", "%" + reference + "%").setMaxResults(maxResults).getResultList(); + } + + public List findChangeItemByTag(String pWorkspaceId, Tag tag){ + List changeItems = new ArrayList<>(); + changeItems.addAll(em.createQuery("SELECT c FROM ChangeIssue c WHERE :tag MEMBER OF c.tags AND c.workspace.id = :workspaceId ", ChangeIssue.class) + .setParameter("tag", tag) + .setParameter(WORKSPACEID, pWorkspaceId) + .getResultList()); + changeItems.addAll(em.createQuery("SELECT c FROM ChangeRequest c WHERE :tag MEMBER OF c.tags AND c.workspace.id = :workspaceId ", ChangeRequest.class) + .setParameter("tag", tag) + .setParameter(WORKSPACEID, pWorkspaceId) + .getResultList()); + changeItems.addAll(em.createQuery("SELECT c FROM ChangeOrder c WHERE :tag MEMBER OF c.tags AND c.workspace.id = :workspaceId ", ChangeOrder.class) + .setParameter("tag", tag) + .setParameter(WORKSPACEID, pWorkspaceId) + .getResultList()); + return changeItems; + } + + public List findChangeItemByDoc(DocumentRevisionKey documentRevisionKey){ + String workspaceId = documentRevisionKey.getDocumentMaster().getWorkspace(); + String id = documentRevisionKey.getDocumentMaster().getId(); + List changeItems = new ArrayList<>(); + changeItems.addAll(em.createQuery("SELECT c FROM ChangeIssue c , DocumentIteration i WHERE i member of c.affectedDocuments AND i.documentRevision.documentMaster.workspace.id = :workspaceId AND i.documentRevision.version = :version AND i.documentRevision.documentMasterId = :documentMasterId", ChangeIssue.class) + .setParameter("workspaceId", workspaceId) + .setParameter("documentMasterId", id) + .setParameter("version", documentRevisionKey.getVersion()) + .getResultList()); + changeItems.addAll(em.createQuery("SELECT c FROM ChangeRequest c , DocumentIteration i WHERE i member of c.affectedDocuments AND i.documentRevision.documentMaster.workspace.id = :workspaceId AND i.documentRevision.version = :version AND i.documentRevision.documentMasterId = :documentMasterId", ChangeRequest.class) + .setParameter("workspaceId", workspaceId) + .setParameter("documentMasterId", id) + .setParameter("version", documentRevisionKey.getVersion()) + .getResultList()); + changeItems.addAll(em.createQuery("SELECT c FROM ChangeOrder c , DocumentIteration i WHERE i member of c.affectedDocuments AND i.documentRevision.documentMaster.workspace.id = :workspaceId AND i.documentRevision.version = :version AND i.documentRevision.documentMasterId = :documentMasterId", ChangeOrder.class) + .setParameter("workspaceId", workspaceId) + .setParameter("documentMasterId", id) + .setParameter("version", documentRevisionKey.getVersion()) + .getResultList()); + return changeItems; + } + + public List findChangeItemByPart(PartRevisionKey partRevisionKey){ + String workspaceId = partRevisionKey.getPartMaster().getWorkspace(); + String id = partRevisionKey.getPartMasterNumber(); + List changeItems = new ArrayList<>(); + changeItems.addAll(em.createQuery("SELECT c FROM ChangeIssue c , PartIteration i WHERE i member of c.affectedParts AND i.partRevision.partMaster.workspace.id = :workspaceId AND i.partRevision.version = :version AND i.partRevision.partMasterNumber = :partMasterNumber", ChangeIssue.class) + .setParameter("workspaceId", workspaceId) + .setParameter("partMasterNumber", id) + .setParameter("version", partRevisionKey.getVersion()) + .getResultList()); + changeItems.addAll(em.createQuery("SELECT c FROM ChangeRequest c , PartIteration i WHERE i member of c.affectedParts AND i.partRevision.partMaster.workspace.id = :workspaceId AND i.partRevision.version = :version AND i.partRevision.partMasterNumber = :partMasterNumber", ChangeRequest.class) + .setParameter("workspaceId", workspaceId) + .setParameter("partMasterNumber", id) + .setParameter("version", partRevisionKey.getVersion()) + .getResultList()); + changeItems.addAll(em.createQuery("SELECT c FROM ChangeOrder c , PartIteration i WHERE i member of c.affectedParts AND i.partRevision.partMaster.workspace.id = :workspaceId AND i.partRevision.version = :version AND i.partRevision.partMasterNumber = :partMasterNumber", ChangeOrder.class) + .setParameter("workspaceId", workspaceId) + .setParameter("partMasterNumber", id) + .setParameter("version", partRevisionKey.getVersion()) + .getResultList()); + return changeItems; + } + + public boolean hasChangeItems(DocumentRevisionKey documentRevisionKey){ + return !findChangeItemByDoc(documentRevisionKey).isEmpty(); + } + + public boolean hasChangeItems(PartRevisionKey partRevisionKey){ + return !findChangeItemByPart(partRevisionKey).isEmpty(); + } + + + public List findChangeItemByFolder(Folder folder){ + List changeItems = new ArrayList<>(); + changeItems.addAll(em.createQuery("SELECT c FROM ChangeIssue c , DocumentIteration i WHERE i member of c.affectedDocuments AND i.documentRevision.location = :folder", ChangeIssue.class) + .setParameter("folder", folder) + .getResultList()); + changeItems.addAll(em.createQuery("SELECT c FROM ChangeRequest c , DocumentIteration i WHERE i member of c.affectedDocuments AND i.documentRevision.location = :folder", ChangeRequest.class) + .setParameter("folder", folder) + .getResultList()); + changeItems.addAll(em.createQuery("SELECT c FROM ChangeOrder c , DocumentIteration i WHERE i member of c.affectedDocuments AND i.documentRevision.location = :folder", ChangeOrder.class) + .setParameter("folder", folder) + .getResultList()); + return changeItems; + } + + + public boolean hasConstraintsInFolderHierarchy(Folder folder) { + boolean hasConstraints = false; + + FolderDAO folderDAO = new FolderDAO(mLocale,em); + + for(Folder subFolder : folderDAO.getSubFolders(folder)){ + hasConstraints |= hasConstraintsInFolderHierarchy(subFolder); + } + + return hasConstraints || hasChangeItems(folder); + + } + + public boolean hasChangeItems(Folder folder) { + return !findChangeItemByFolder(folder).isEmpty(); + } + + public boolean hasChangeRequestsLinked(ChangeIssue changeIssue) { + return !findAllChangeRequestsByChangeIssue(changeIssue).isEmpty(); + } + + public boolean hasChangeOrdersLinked(ChangeRequest changeRequest) { + return !findAllChangeOrdersByChangeRequest(changeRequest).isEmpty(); + } + + private List findAllChangeRequestsByChangeIssue(ChangeIssue changeIssue) { + return em.createNamedQuery("ChangeRequest.findByChangeIssue", ChangeRequest.class) + .setParameter("workspaceId", changeIssue.getWorkspaceId()) + .setParameter("changeIssue", changeIssue) + .getResultList(); + } + + private List findAllChangeOrdersByChangeRequest(ChangeRequest changeRequest) { + return em.createNamedQuery("ChangeOrder.findByChangeRequest", ChangeOrder.class) + .setParameter("workspaceId", changeRequest.getWorkspaceId()) + .setParameter("changeRequest", changeRequest) + .getResultList(); + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ConfigurationItemDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ConfigurationItemDAO.java new file mode 100644 index 0000000000..f6a6235a0f --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ConfigurationItemDAO.java @@ -0,0 +1,120 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + + +import com.docdoku.core.exceptions.ConfigurationItemAlreadyExistsException; +import com.docdoku.core.exceptions.ConfigurationItemNotFoundException; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.LayerNotFoundException; +import com.docdoku.core.product.*; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import javax.persistence.TypedQuery; +import java.util.List; +import java.util.Locale; + +public class ConfigurationItemDAO { + + private EntityManager em; + private Locale mLocale; + + public ConfigurationItemDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public ConfigurationItemDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + public void updateConfigurationItem(ConfigurationItem pCI) { + em.merge(pCI); + } + + public ConfigurationItem removeConfigurationItem(ConfigurationItemKey pKey) throws ConfigurationItemNotFoundException, LayerNotFoundException { + ConfigurationItem ci = loadConfigurationItem(pKey); + + removeLayersFromConfigurationItem(pKey); + removeEffectivitiesFromConfigurationItem(pKey); + + em.remove(ci); + return ci; + } + + public void removeLayersFromConfigurationItem(ConfigurationItemKey pKey){ + TypedQuery query = em.createNamedQuery("Layer.removeLayersFromConfigurationItem", Layer.class); + query.setParameter("workspaceId", pKey.getWorkspace()); + query.setParameter("configurationItemId", pKey.getId()); + query.executeUpdate(); + } + + public void removeEffectivitiesFromConfigurationItem(ConfigurationItemKey pKey){ + TypedQuery query = em.createNamedQuery("Effectivity.removeEffectivitiesFromConfigurationItem", Effectivity.class); + query.setParameter("workspaceId", pKey.getWorkspace()); + query.setParameter("configurationItemId", pKey.getId()); + query.executeUpdate(); + } + + public List findAllConfigurationItems(String pWorkspaceId) { + TypedQuery query = em.createNamedQuery("ConfigurationItem.getConfigurationItemsInWorkspace", ConfigurationItem.class); + query.setParameter("workspaceId", pWorkspaceId); + return query.getResultList(); + } + + public ConfigurationItem loadConfigurationItem(ConfigurationItemKey pKey) + throws ConfigurationItemNotFoundException { + ConfigurationItem ci = em.find(ConfigurationItem.class, pKey); + if (ci == null) { + throw new ConfigurationItemNotFoundException(mLocale, pKey.getId()); + } else { + return ci; + } + } + + public void createConfigurationItem(ConfigurationItem pCI) throws ConfigurationItemAlreadyExistsException, CreationException { + try { + //the EntityExistsException is thrown only when flush occurs + em.persist(pCI); + em.flush(); + } catch (EntityExistsException pEEEx) { + throw new ConfigurationItemAlreadyExistsException(mLocale, pCI); + } catch (PersistenceException pPEx) { + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } + + public List findConfigurationItemsByDesignItem(PartMaster partMaster) { + + TypedQuery query = em.createNamedQuery("ConfigurationItem.findByDesignItem", ConfigurationItem.class); + return query.setParameter("designItem", partMaster).getResultList(); + + } + + public boolean isPartMasterLinkedToConfigurationItem(PartMaster partMaster){ + return !findConfigurationItemsByDesignItem(partMaster).isEmpty(); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ConversionDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ConversionDAO.java new file mode 100644 index 0000000000..321b489222 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ConversionDAO.java @@ -0,0 +1,87 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.product.Conversion; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartRevision; + +import javax.persistence.*; +import java.util.Locale; + +public class ConversionDAO { + + private EntityManager em; + private Locale mLocale; + + public ConversionDAO(Locale pLocale, EntityManager pEM) { + mLocale=pLocale; + em=pEM; + } + + public ConversionDAO(EntityManager pEM) { + mLocale=Locale.getDefault(); + em=pEM; + } + + public void createConversion(Conversion conversion) throws CreationException { + try{ + //the EntityExistsException is thrown only when flush occurs + em.persist(conversion); + em.flush(); + }catch(EntityExistsException pEEEx){ + throw new CreationException(mLocale); + }catch(PersistenceException pPEx){ + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } + + public Conversion findConversion(PartIteration partIteration) { + TypedQuery query = em.createQuery("SELECT DISTINCT c FROM Conversion c WHERE c.partIteration = :partIteration", Conversion.class); + query.setParameter("partIteration", partIteration); + try{ + return query.getSingleResult(); + }catch(NoResultException e){ + return null; + } + } + + public void deleteConversion(Conversion conversion) { + em.remove(conversion); + em.flush(); + } + + public void removePartRevisionConversions(PartRevision pPartR) { + em.createQuery("DELETE FROM Conversion c WHERE c.partIteration.partRevision = :partRevision", Conversion.class) + .setParameter("partRevision", pPartR) + .executeUpdate(); + } + + public void removePartIterationConversion(PartIteration pPartI) { + em.createQuery("DELETE FROM Conversion c WHERE c.partIteration = :partIteration", Conversion.class) + .setParameter("partIteration", pPartI) + .executeUpdate(); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentBaselineDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentBaselineDAO.java new file mode 100644 index 0000000000..5508fef4eb --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentBaselineDAO.java @@ -0,0 +1,77 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.configuration.DocumentBaseline; +import com.docdoku.core.exceptions.BaselineNotFoundException; + +import javax.persistence.EntityManager; +import java.util.List; +import java.util.Locale; + +/** + * Data access object for DocumentBaseline + * + * @author Taylor LABEJOF + * @version 2.0, 28/08/14 + * @since V2.0 + */ +public class DocumentBaselineDAO { + private EntityManager em; + private Locale mLocale; + + public DocumentBaselineDAO(EntityManager em) { + this.em = em; + this.mLocale = Locale.getDefault(); + } + + public DocumentBaselineDAO(EntityManager em, Locale mLocale) { + this.em = em; + this.mLocale = mLocale; + } + + public void createBaseline(DocumentBaseline documentBaseline) { + em.persist(documentBaseline); + em.flush(); + } + + public List findBaselines(String workspaceId) { + return em.createQuery("SELECT b " + + "FROM DocumentBaseline b " + + "WHERE b.workspace.id = :workspaceId ",DocumentBaseline.class) + .setParameter("workspaceId",workspaceId) + .getResultList(); + } + + public DocumentBaseline loadBaseline(int baselineId) throws BaselineNotFoundException { + DocumentBaseline documentBaseline = em.find(DocumentBaseline.class,baselineId); + if(documentBaseline == null){ + throw new BaselineNotFoundException(mLocale,baselineId); + }else{ + return documentBaseline; + } + } + + public void deleteBaseline(DocumentBaseline documentBaseline) { + em.remove(documentBaseline); + em.flush(); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentCollectionDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentCollectionDAO.java new file mode 100644 index 0000000000..47d036e2d0 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentCollectionDAO.java @@ -0,0 +1,47 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.configuration.DocumentCollection; + +import javax.persistence.EntityManager; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class DocumentCollectionDAO { + + private EntityManager em; + + private static final Logger LOGGER = Logger.getLogger(DocumentCollectionDAO.class.getName()); + + public DocumentCollectionDAO(EntityManager pEM) { + em = pEM; + } + + public void createDocumentCollection(DocumentCollection documentCollection){ + try { + em.persist(documentCollection); + em.flush(); + }catch (Exception e){ + LOGGER.log(Level.SEVERE,"Fail to create a collection of documents",e); + } + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentDAO.java new file mode 100644 index 0000000000..17c6c102a9 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentDAO.java @@ -0,0 +1,43 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.document.DocumentIteration; + +import javax.persistence.EntityManager; + + +public class DocumentDAO { + + private final EntityManager em; + + public DocumentDAO(EntityManager pEM) { + em=pEM; + } + + public void updateDoc(DocumentIteration pDoc){ + em.merge(pDoc); + } + + public void removeDoc(DocumentIteration pDoc){ + em.remove(pDoc); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentLinkDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentLinkDAO.java new file mode 100644 index 0000000000..5e9c2d656d --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentLinkDAO.java @@ -0,0 +1,81 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.configuration.PathDataIteration; +import com.docdoku.core.configuration.ProductInstanceIteration; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentLink; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.product.PartIteration; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import java.util.List; +import java.util.Locale; + + +public class DocumentLinkDAO { + + private EntityManager em; + + public DocumentLinkDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + } + + public void removeLink(DocumentLink pLink){ + em.remove(pLink); + } + + public void createLink(DocumentLink pLink){ + try{ + //the EntityExistsException is thrown only when flush occurs + em.persist(pLink); + em.flush(); + }catch(EntityExistsException pEEEx){ + //already created + } + } + + public List getInverseDocumentsLinks(DocumentRevision documentRevision){ + return em.createNamedQuery("DocumentLink.findInverseDocumentLinks",DocumentIteration.class) + .setParameter("documentRevision",documentRevision) + .getResultList(); + } + + public List getInversePartsLinks(DocumentRevision documentRevision){ + return em.createNamedQuery("DocumentLink.findInversePartLinks",PartIteration.class) + .setParameter("documentRevision",documentRevision) + .getResultList(); + } + public List getInverseProductInstanceIteration(DocumentRevision documentRevision){ + return em.createNamedQuery("DocumentLink.findProductInstanceIteration", ProductInstanceIteration.class) + .setParameter("documentRevision",documentRevision) + .getResultList(); + } + public List getInversefindPathData(DocumentRevision documentRevision){ + return em.createNamedQuery("DocumentLink.findPathData", PathDataIteration.class) + .setParameter("documentRevision",documentRevision) + .getResultList(); + } + + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentMasterDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentMasterDAO.java new file mode 100644 index 0000000000..10a85ee586 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentMasterDAO.java @@ -0,0 +1,84 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.document.DocumentMaster; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.DocumentMasterAlreadyExistsException; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class DocumentMasterDAO { + private static final Logger LOGGER = Logger.getLogger(DocumentMasterDAO.class.getName()); + + private EntityManager em; + private Locale mLocale; + + public DocumentMasterDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public DocumentMasterDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + public void createDocM(DocumentMaster pDocumentMaster) throws DocumentMasterAlreadyExistsException, CreationException { + try { + //the EntityExistsException is thrown only when flush occurs + em.persist(pDocumentMaster); + em.flush(); + } catch (EntityExistsException pEEEx) { + LOGGER.log(Level.FINER,null,pEEEx); + throw new DocumentMasterAlreadyExistsException(mLocale, pDocumentMaster); + } catch (PersistenceException pPEx) { + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + LOGGER.log(Level.FINER,null,pPEx); + throw new CreationException(mLocale); + } + } + + public void removeDocM(DocumentMaster pDocM) { + DocumentRevisionDAO documentRevisionDAO = new DocumentRevisionDAO(mLocale, em); + List docRs = new ArrayList<>(pDocM.getDocumentRevisions()); + for(DocumentRevision documentRevision:docRs){ + documentRevisionDAO.removeRevision(documentRevision); + } + em.remove(pDocM); + } + + + public List getAllByWorkspace(String workspaceId) { + return em.createNamedQuery("DocumentMaster.findByWorkspace",DocumentMaster.class) + .setParameter("workspaceId",workspaceId) + .getResultList(); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentMasterTemplateDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentMasterTemplateDAO.java new file mode 100644 index 0000000000..9196f29daf --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentMasterTemplateDAO.java @@ -0,0 +1,97 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.document.DocumentMasterTemplate; +import com.docdoku.core.document.DocumentMasterTemplateKey; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.DocumentMasterTemplateAlreadyExistsException; +import com.docdoku.core.exceptions.DocumentMasterTemplateNotFoundException; +import com.docdoku.core.meta.ListOfValuesKey; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import javax.persistence.TypedQuery; +import java.util.List; +import java.util.Locale; + +public class DocumentMasterTemplateDAO { + + private EntityManager em; + private Locale mLocale; + + public DocumentMasterTemplateDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public DocumentMasterTemplateDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + public void updateDocMTemplate(DocumentMasterTemplate pTemplate) { + em.merge(pTemplate); + } + + public DocumentMasterTemplate removeDocMTemplate(DocumentMasterTemplateKey pKey) throws DocumentMasterTemplateNotFoundException { + DocumentMasterTemplate template = loadDocMTemplate(pKey); + em.remove(template); + return template; + } + + public List findAllDocMTemplates(String pWorkspaceId) { + TypedQuery query = em.createQuery("SELECT DISTINCT t FROM DocumentMasterTemplate t WHERE t.workspaceId = :workspaceId", DocumentMasterTemplate.class); + return query.setParameter("workspaceId", pWorkspaceId).getResultList(); + } + + public DocumentMasterTemplate loadDocMTemplate(DocumentMasterTemplateKey pKey) + throws DocumentMasterTemplateNotFoundException { + DocumentMasterTemplate template = em.find(DocumentMasterTemplate.class, pKey); + if (template == null) { + throw new DocumentMasterTemplateNotFoundException(mLocale, pKey.getId()); + } else { + return template; + } + } + + public void createDocMTemplate(DocumentMasterTemplate pTemplate) throws DocumentMasterTemplateAlreadyExistsException, CreationException { + try { + //the EntityExistsException is thrown only when flush occurs + em.persist(pTemplate); + em.flush(); + } catch (EntityExistsException pEEEx) { + throw new DocumentMasterTemplateAlreadyExistsException(mLocale, pTemplate); + } catch (PersistenceException pPEx) { + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } + + public List findAllDocMTemplatesFromLOV(ListOfValuesKey lovKey){ + return em.createNamedQuery("DocumentMasterTemplate.findWhereLOV", DocumentMasterTemplate.class) + .setParameter("lovName", lovKey.getName()) + .setParameter("workspace_id", lovKey.getWorkspaceId()) + .getResultList(); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentRevisionDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentRevisionDAO.java new file mode 100644 index 0000000000..abc52dc08e --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/DocumentRevisionDAO.java @@ -0,0 +1,271 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserKey; +import com.docdoku.core.document.*; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.DocumentIterationNotFoundException; +import com.docdoku.core.exceptions.DocumentRevisionAlreadyExistsException; +import com.docdoku.core.exceptions.DocumentRevisionNotFoundException; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.workflow.Task; +import com.docdoku.core.workflow.Workflow; + +import javax.persistence.*; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class DocumentRevisionDAO { + + private EntityManager em; + private Locale mLocale; + private static final int MAX_RESULTS = 500; + private static final Logger LOGGER = Logger.getLogger("DocumentRevisionDAO"); + + public DocumentRevisionDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public DocumentRevisionDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + public String findLatestDocMId(String pWorkspaceId, String pType) { + String docMId; + Query query = em.createQuery("SELECT m.id FROM DocumentMaster m " + + "WHERE m.workspace.id = :workspaceId " + + "AND m.type = :type " + + "AND m.creationDate = (" + + "SELECT MAX(m2.creationDate) FROM DocumentMaster m2 " + + "WHERE m2.workspace.id = :workspaceId " + + "AND m2.type = :type" + + ")"); + query.setParameter("workspaceId", pWorkspaceId); + query.setParameter("type", pType); + docMId = (String) query.getSingleResult(); + return docMId; + } + + public List findDocRsByFolder(String pCompletePath) { + TypedQuery query = em.createQuery("SELECT DISTINCT d FROM DocumentRevision d WHERE d.location.completePath = :completePath", DocumentRevision.class); + query.setParameter("completePath", pCompletePath); + return query.getResultList(); + } + + public List findDocRsByTag(Tag pTag) { + TypedQuery query = em.createQuery("SELECT DISTINCT d FROM DocumentRevision d WHERE :tag MEMBER OF d.tags", DocumentRevision.class); + query.setParameter("tag", pTag); + return query.getResultList(); + } + + public List findCheckedOutDocRs(User pUser) { + TypedQuery query = em.createQuery("SELECT DISTINCT d FROM DocumentRevision d WHERE d.checkOutUser = :user", DocumentRevision.class); + query.setParameter("user", pUser); + return query.getResultList(); + } + + public DocumentRevision loadDocR(DocumentRevisionKey pKey) throws DocumentRevisionNotFoundException { + DocumentRevision docR = em.find(DocumentRevision.class, pKey); + if (docR == null) { + throw new DocumentRevisionNotFoundException(mLocale, pKey); + } else { + return docR; + } + } + + public DocumentIteration loadDocI(DocumentIterationKey pKey) throws DocumentIterationNotFoundException { + DocumentIteration docI = em.find(DocumentIteration.class, pKey); + if (docI == null) { + throw new DocumentIterationNotFoundException(mLocale, pKey); + } else { + return docI; + } + } + + public DocumentRevision getDocRRef(DocumentRevisionKey pKey) throws DocumentRevisionNotFoundException { + try { + return em.getReference(DocumentRevision.class, pKey); + } catch (EntityNotFoundException pENFEx) { + LOGGER.log(Level.FINEST,null,pENFEx); + throw new DocumentRevisionNotFoundException(mLocale, pKey); + } + } + + public void createDocR(DocumentRevision pDocumentRevision) throws DocumentRevisionAlreadyExistsException, CreationException { + try { + if(pDocumentRevision.getWorkflow()!=null){ + WorkflowDAO workflowDAO = new WorkflowDAO(em); + workflowDAO.createWorkflow(pDocumentRevision.getWorkflow()); + } + + if(pDocumentRevision.getACL()!=null){ + ACLDAO aclDAO = new ACLDAO(em); + aclDAO.createACL(pDocumentRevision.getACL()); + } + + //the EntityExistsException is thrown only when flush occurs + em.persist(pDocumentRevision); + em.flush(); + } catch (EntityExistsException pEEEx) { + LOGGER.log(Level.FINEST,null,pEEEx); + throw new DocumentRevisionAlreadyExistsException(mLocale, pDocumentRevision); + } catch (PersistenceException pPEx) { + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + LOGGER.log(Level.FINEST,null,pPEx); + throw new CreationException(mLocale); + } + } + + public void removeRevision(DocumentRevision pDocR) { + SubscriptionDAO subscriptionDAO = new SubscriptionDAO(em); + DocumentDAO docDAO = new DocumentDAO(em); + subscriptionDAO.removeAllSubscriptions(pDocR); + + WorkflowDAO workflowDAO = new WorkflowDAO(em); + workflowDAO.removeWorkflowConstraints(pDocR); + + for(DocumentIteration doc:pDocR.getDocumentIterations()) { + docDAO.removeDoc(doc); + } + + SharedEntityDAO sharedEntityDAO = new SharedEntityDAO(em); + sharedEntityDAO.deleteSharesForDocument(pDocR); + + DocumentMaster docM = pDocR.getDocumentMaster(); + docM.removeRevision(pDocR); + + em.remove(pDocR); + em.flush(); + } + + public List findDocsWithAssignedTasksForGivenUser(String pWorkspaceId, String assignedUserLogin) { + return em.createNamedQuery("DocumentRevision.findWithAssignedTasksForUser", DocumentRevision.class) + .setParameter("workspaceId", pWorkspaceId) + .setParameter("login", assignedUserLogin) + .getResultList(); + } + + public List findDocsWithOpenedTasksForGivenUser(String pWorkspaceId, String assignedUserLogin) { + return em.createNamedQuery("DocumentRevision.findWithOpenedTasksForUser",DocumentRevision.class) + .setParameter("workspaceId", pWorkspaceId) + .setParameter("login", assignedUserLogin) + .getResultList(); + } + + public List findDocsRevisionsWithReferenceOrTitleLike(String pWorkspaceId, String search, int maxResults) { + return em.createNamedQuery("DocumentRevision.findByReferenceOrTitle",DocumentRevision.class). + setParameter("workspaceId", pWorkspaceId) + .setParameter("id", "%" + search + "%") + .setParameter("title", "%" + search + "%") + .setMaxResults(maxResults).getResultList(); + } + + public int getTotalNumberOfDocuments(String pWorkspaceId) { + return ((Number)em.createNamedQuery("DocumentRevision.countByWorkspace") + .setParameter("workspaceId", pWorkspaceId) + .getSingleResult()).intValue(); + } + + public long getDiskUsageForDocumentsInWorkspace(String pWorkspaceId) { + Number result = (Number)em.createNamedQuery("BinaryResource.diskUsageInPath") + .setParameter("path", pWorkspaceId+"/documents/%") + .getSingleResult(); + + return result != null ? result.longValue() : 0L; + + } + + public long getDiskUsageForDocumentTemplatesInWorkspace(String pWorkspaceId) { + Number result = (Number)em.createNamedQuery("BinaryResource.diskUsageInPath") + .setParameter("path", pWorkspaceId+"/document-templates/%") + .getSingleResult(); + + return result != null ? result.longValue() : 0L; + + } + + public List findAllCheckedOutDocRevisions(String pWorkspaceId) { + TypedQuery query = em.createQuery("SELECT DISTINCT d FROM DocumentRevision d WHERE d.checkOutUser is not null and d.documentMaster.workspace.id = :workspaceId", DocumentRevision.class); + query.setParameter("workspaceId", pWorkspaceId); + return query.getResultList(); + } + + public DocumentIteration findDocumentIterationByBinaryResource(BinaryResource pBinaryResource) { + TypedQuery query = em.createNamedQuery("DocumentIteration.findByBinaryResource", DocumentIteration.class); + query.setParameter("binaryResource", pBinaryResource); + try{ + return query.getSingleResult(); + }catch(NoResultException ex){ + LOGGER.log(Level.FINEST,null,ex); + return null; + } + } + + public List getDocumentRevisionsFiltered(User user, String workspaceId) { + return getDocumentRevisionsFiltered(user,workspaceId, -1, -1); + } + + public List getDocumentRevisionsFiltered(User user, String workspaceId, int start, int pMaxResults) { + // Todo check group access right + + int maxResults = (pMaxResults<1) ? pMaxResults : MAX_RESULTS; + + String excludedFolders = workspaceId + "/~%"; + + Query query =em.createNamedQuery("DocumentRevision.findByWorkspace.filterUserACLEntry", DocumentRevision.class) + .setParameter("workspaceId", workspaceId) + .setParameter("user", user) + .setParameter("excludedFolders", excludedFolders); + if(start>-1 && maxResults >-1){ + query.setFirstResult(start) + .setMaxResults(maxResults); + } + return query.getResultList(); + } + + public int getDocumentRevisionsCountFiltered(User user, String workspaceId) { + + String excludedFolders = workspaceId + "/~%"; + + return ((Number) em.createNamedQuery("DocumentRevision.countByWorkspace.filterUserACLEntry") + .setParameter("workspaceId", workspaceId) + .setParameter("user", user) + .setParameter("excludedFolders", excludedFolders) + .getSingleResult()).intValue(); + } + + public DocumentRevision getWorkflowHolder(Workflow workflow) { + try { + return em.createNamedQuery("DocumentRevision.findByWorkflow", DocumentRevision.class). + setParameter("workflow", workflow).getSingleResult(); + }catch(NoResultException e){ + return null; + } + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/FolderDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/FolderDAO.java new file mode 100644 index 0000000000..feb1e00ca8 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/FolderDAO.java @@ -0,0 +1,158 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.document.Folder; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.EntityConstraintException; +import com.docdoku.core.exceptions.FolderAlreadyExistsException; +import com.docdoku.core.exceptions.FolderNotFoundException; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import javax.persistence.TypedQuery; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FolderDAO { + + private EntityManager em; + private Locale mLocale; + private static final Logger LOGGER = Logger.getLogger(FolderDAO.class.getName()); + + public FolderDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale=pLocale; + } + + public FolderDAO(EntityManager pEM) { + em = pEM; + mLocale=Locale.getDefault(); + } + + public Folder loadFolder(String pCompletePath) throws FolderNotFoundException { + Folder folder = em.find(Folder.class,pCompletePath); + if (folder == null) { + throw new FolderNotFoundException(mLocale, pCompletePath); + } else { + return folder; + } + } + + public void createFolder(Folder pFolder) throws FolderAlreadyExistsException, CreationException{ + try{ + //the EntityExistsException is thrown only when flush occurs + em.persist(pFolder); + em.flush(); + }catch(EntityExistsException pEEEx){ + LOGGER.log(Level.FINEST,null,pEEEx); + throw new FolderAlreadyExistsException(mLocale, pFolder); + }catch(PersistenceException pPEx){ + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + LOGGER.log(Level.FINEST,null,pPEx); + throw new CreationException(mLocale); + } + } + + public Folder[] getSubFolders(String pCompletePath){ + Folder[] folders; + TypedQuery query = em.createQuery("SELECT DISTINCT f FROM Folder f WHERE f.parentFolder.completePath = :completePath", Folder.class); + query.setParameter("completePath",pCompletePath); + List listFolders = query.getResultList(); + folders = new Folder[listFolders.size()]; + for(int i=0;i moveFolder(Folder pFolder, Folder pNewFolder) throws FolderAlreadyExistsException, CreationException{ + DocumentRevisionDAO docRDAO=new DocumentRevisionDAO(mLocale,em); + List allDocRs = new LinkedList<>(); + List docRs = docRDAO.findDocRsByFolder(pFolder.getCompletePath()); + allDocRs.addAll(docRs); + + for(DocumentRevision docR:allDocRs){ + docR.setLocation(pNewFolder); + } + + Folder[] subFolders = getSubFolders(pFolder); + for(Folder subFolder:subFolders){ + Folder newSubFolder = new Folder(pNewFolder.getCompletePath(),subFolder.getShortName()); + createFolder(newSubFolder); + allDocRs.addAll(moveFolder(subFolder, newSubFolder)); + } + em.remove(pFolder); + //flush to insure the right delete order to avoid integrity constraint + //violation on folder. + em.flush(); + + return allDocRs; + } + + public List findDocumentRevisionsInFolder(Folder pFolder) { + DocumentRevisionDAO docRDAO = new DocumentRevisionDAO(mLocale, em); + List allDocRs = new LinkedList<>(); + List docRs = docRDAO.findDocRsByFolder(pFolder.getCompletePath()); + allDocRs.addAll(docRs); + + Folder[] subFolders = getSubFolders(pFolder); + for (Folder subFolder : subFolders) { + allDocRs.addAll(findDocumentRevisionsInFolder(subFolder)); + } + + return allDocRs; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/GCMAccountDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/GCMAccountDAO.java new file mode 100644 index 0000000000..5a8ed3a4f1 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/GCMAccountDAO.java @@ -0,0 +1,77 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.common.Account; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.GCMAccountAlreadyExistsException; +import com.docdoku.core.exceptions.GCMAccountNotFoundException; +import com.docdoku.core.gcm.GCMAccount; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import java.util.Locale; + +public class GCMAccountDAO { + + private EntityManager em; + private Locale mLocale; + + public GCMAccountDAO(Locale pLocale, EntityManager pEM) { + mLocale=pLocale; + em=pEM; + } + + public GCMAccountDAO(EntityManager pEM) { + mLocale=Locale.getDefault(); + em=pEM; + } + + public GCMAccount loadGCMAccount(Account account) throws GCMAccountNotFoundException { + GCMAccount gcmAccount = em.find(GCMAccount.class, account.getLogin()); + if(gcmAccount == null){ + throw new GCMAccountNotFoundException(mLocale,account.getLogin()); + } + return gcmAccount; + } + + public void createGCMAccount(GCMAccount gcmAccount) throws GCMAccountAlreadyExistsException, CreationException { + try{ + //the EntityExistsException is thrown only when flush occurs + em.persist(gcmAccount); + em.flush(); + }catch(EntityExistsException pEEEx){ + throw new GCMAccountAlreadyExistsException(mLocale, gcmAccount.getAccount().getLogin()); + }catch(PersistenceException pPEx){ + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } + + public void deleteGCMAccount(GCMAccount gcmAccount){ + em.remove(gcmAccount); + em.flush(); + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ImportDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ImportDAO.java new file mode 100644 index 0000000000..4582a95e12 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ImportDAO.java @@ -0,0 +1,84 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.common.User; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.product.Import; + +import javax.persistence.*; +import java.util.List; +import java.util.Locale; + +public class ImportDAO { + + private EntityManager em; + private Locale mLocale; + + public ImportDAO(Locale pLocale, EntityManager pEM) { + mLocale=pLocale; + em=pEM; + } + + public ImportDAO(EntityManager pEM) { + mLocale=Locale.getDefault(); + em=pEM; + } + + public void createImport(Import importToPersist) throws CreationException { + try{ + //the EntityExistsException is thrown only when flush occurs + em.persist(importToPersist); + em.flush(); + }catch(EntityExistsException pEEEx){ + throw new CreationException(mLocale); + }catch(PersistenceException pPEx){ + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } + + public Import findImport(User user, String id) { + TypedQuery query = em.createQuery("SELECT DISTINCT i FROM Import i WHERE i.id = :id AND i.user = :user", Import.class); + query.setParameter("user", user); + query.setParameter("id", id); + try{ + return query.getSingleResult(); + }catch(NoResultException e){ + return null; + } + } + + public List findImports(User user, String filename) { + TypedQuery query = em.createQuery("SELECT DISTINCT i FROM Import i WHERE i.user = :user AND i.fileName = :filename", Import.class); + query.setParameter("user", user); + query.setParameter("filename", filename); + return query.getResultList(); + } + + public void deleteImport(Import importToDelete) { + em.remove(importToDelete); + em.flush(); + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/InstanceAttributeDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/InstanceAttributeDAO.java new file mode 100644 index 0000000000..32cdfb5d9d --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/InstanceAttributeDAO.java @@ -0,0 +1,88 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.InstanceAttributeDescriptor; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class InstanceAttributeDAO { + private static final Logger LOGGER = Logger.getLogger(InstanceAttributeDAO.class.getName()); + + private final EntityManager em; + + public InstanceAttributeDAO(EntityManager pEM) { + em=pEM; + } + + public void removeAttribute(InstanceAttribute pAttr){ + em.remove(pAttr); + } + + public void createAttribute(InstanceAttribute pAttr){ + try{ + //the EntityExistsException is thrown only when flush occurs + em.persist(pAttr); + em.flush(); + }catch(EntityExistsException pEEEx){ + //already created + LOGGER.log(Level.FINER,null,pEEEx); + } + } + + public List getPartIterationsInstanceAttributesInWorkspace(String workspaceId){ + + List partsAttributesInWorkspace = em.createNamedQuery("PartIteration.findDistinctInstanceAttributes", InstanceAttribute.class) + .setParameter("workspaceId", workspaceId) + .getResultList(); + + Set descriptors = new HashSet<>(); + + for(InstanceAttribute attribute: partsAttributesInWorkspace){ + descriptors.add(new InstanceAttributeDescriptor(attribute)); + } + + return new ArrayList<>(descriptors); + } + + public List getPathDataInstanceAttributesInWorkspace(String workspaceId){ + + List partsAttributesInWorkspace = em.createNamedQuery("PathDataIteration.findDistinctInstanceAttributes", InstanceAttribute.class) + .setParameter("workspaceId", workspaceId) + .getResultList(); + + Set descriptors = new HashSet<>(); + + for(InstanceAttribute attribute: partsAttributesInWorkspace){ + descriptors.add(new InstanceAttributeDescriptor(attribute)); + } + + return new ArrayList<>(descriptors); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/InstanceAttributeTemplateDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/InstanceAttributeTemplateDAO.java new file mode 100644 index 0000000000..7072a7f51a --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/InstanceAttributeTemplateDAO.java @@ -0,0 +1,41 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.meta.InstanceAttributeTemplate; + +import javax.persistence.EntityManager; + + + +public class InstanceAttributeTemplateDAO { + + private EntityManager em; + + public InstanceAttributeTemplateDAO(EntityManager pEM) { + em=pEM; + } + + + public void removeAttribute(InstanceAttributeTemplate pAttr){ + em.remove(pAttr); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/LOVDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/LOVDAO.java new file mode 100644 index 0000000000..f81089c910 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/LOVDAO.java @@ -0,0 +1,88 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.ListOfValuesAlreadyExistsException; +import com.docdoku.core.exceptions.ListOfValuesNotFoundException; +import com.docdoku.core.meta.ListOfValues; +import com.docdoku.core.meta.ListOfValuesKey; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import javax.persistence.TypedQuery; +import java.util.List; +import java.util.Locale; + +public class LOVDAO { + + private EntityManager em; + private Locale mLocale; + + public LOVDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public LOVDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + public ListOfValues loadLOV(ListOfValuesKey lovKey) throws ListOfValuesNotFoundException { + ListOfValues lov=em.find(ListOfValues.class,lovKey); + if (lov == null) { + throw new ListOfValuesNotFoundException(mLocale, lovKey.getName()); + } else { + return lov; + } + } + + public List loadLOVList(String pWorkspaceId){ + TypedQuery query = em.createQuery("SELECT DISTINCT l FROM ListOfValues l WHERE l.workspaceId = :workspaceId", ListOfValues.class); + query.setParameter("workspaceId", pWorkspaceId); + return query.getResultList(); + } + + public void createLOV(ListOfValues pLov) throws CreationException, ListOfValuesAlreadyExistsException { + try{ + //the EntityExistsException is thrown only when flush occurs + em.persist(pLov); + em.flush(); + }catch(EntityExistsException pEEEx){ + throw new ListOfValuesAlreadyExistsException(mLocale, pLov); + }catch(PersistenceException pPEx){ + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } + + public void deleteLOV(ListOfValues pLov) { + em.remove(pLov); + em.flush(); + } + + public ListOfValues updateLOV(ListOfValues pLov){ + return em.merge(pLov); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/LayerDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/LayerDAO.java new file mode 100644 index 0000000000..2140f439b4 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/LayerDAO.java @@ -0,0 +1,71 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.exceptions.LayerNotFoundException; +import com.docdoku.core.product.ConfigurationItemKey; +import com.docdoku.core.product.Layer; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.List; +import java.util.Locale; + +public class LayerDAO { + + private EntityManager em; + private Locale mLocale; + + public LayerDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public LayerDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + public List findAllLayers(ConfigurationItemKey pKey) { + TypedQuery query = em.createNamedQuery("Layer.findLayersByConfigurationItem", Layer.class); + query.setParameter("workspaceId", pKey.getWorkspace()); + query.setParameter("configurationItemId", pKey.getId()); + return query.getResultList(); + } + + public Layer loadLayer(int pId) throws LayerNotFoundException { + Layer layer = em.find(Layer.class, pId); + if (layer == null) { + throw new LayerNotFoundException(mLocale, pId); + } else { + return layer; + } + } + + public void createLayer(Layer pLayer) { + em.persist(pLayer); + em.flush(); + } + + public void deleteLayer(Layer layer) { + em.remove(layer); + em.flush(); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/MarkerDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/MarkerDAO.java new file mode 100644 index 0000000000..3d5865210d --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/MarkerDAO.java @@ -0,0 +1,57 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.exceptions.MarkerNotFoundException; +import com.docdoku.core.product.Marker; + +import javax.persistence.EntityManager; +import java.util.Locale; + +public class MarkerDAO { + + private EntityManager em; + private Locale mLocale; + + public MarkerDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public void createMarker(Marker pMarker) { + em.persist(pMarker); + em.flush(); + } + + public Marker loadMarker(int pId) throws MarkerNotFoundException { + Marker marker = em.find(Marker.class, pId); + if (marker == null) { + throw new MarkerNotFoundException(mLocale, pId); + } else { + return marker; + } + } + + public void removeMarker(int pId) throws MarkerNotFoundException { + Marker marker = loadMarker(pId); + em.remove(marker); + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/MilestoneDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/MilestoneDAO.java new file mode 100644 index 0000000000..6b2912db8b --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/MilestoneDAO.java @@ -0,0 +1,149 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.change.ChangeOrder; +import com.docdoku.core.change.ChangeRequest; +import com.docdoku.core.change.Milestone; +import com.docdoku.core.exceptions.MilestoneAlreadyExistsException; +import com.docdoku.core.exceptions.MilestoneNotFoundException; + +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import java.util.List; +import java.util.Locale; + +public class MilestoneDAO { + + private EntityManager em; + private Locale mLocale; + + public MilestoneDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public MilestoneDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + + public List findAllMilestone(String pWorkspaceId) { + List milestones = em.createNamedQuery("Milestone.findMilestonesByWorkspace", Milestone.class) + .setParameter("workspaceId", pWorkspaceId) + .getResultList(); + return milestones; + } + + public Milestone loadMilestone(int pId) throws MilestoneNotFoundException { + Milestone milestone = em.find(Milestone.class, pId); + if (milestone == null) { + throw new MilestoneNotFoundException(mLocale, pId); + } else { + return milestone; + } + } + + public Milestone loadMilestone(String pTitle, String pWorkspace) throws MilestoneNotFoundException { + Milestone milestone = em.createNamedQuery("Milestone.findMilestonesByTitleAndWorkspace", Milestone.class) + .setParameter("title", pTitle) + .setParameter("workspaceId", pWorkspace) + .getSingleResult(); + if (milestone == null) { + throw new MilestoneNotFoundException(mLocale, pTitle); + } else { + return milestone; + } + } + + public void createMilestone(Milestone pMilestone) throws MilestoneAlreadyExistsException { + if(!this.checkTitleUniqueness(pMilestone.getTitle(),pMilestone.getWorkspace().getId())) + throw new MilestoneAlreadyExistsException(mLocale,pMilestone.getTitle()); + + if(pMilestone.getACL()!=null){ + ACLDAO aclDAO = new ACLDAO(em); + aclDAO.createACL(pMilestone.getACL()); + } + + em.persist(pMilestone); + em.flush(); + } + + public void deleteMilestone(Milestone pMilestone) { + em.remove(pMilestone); + em.flush(); + } + + public List getAllRequests(int pId,String pWorkspace){ + try{ + return em.createNamedQuery("ChangeRequest.getRequestByMilestonesAndWorkspace",ChangeRequest.class) + .setParameter("milestoneId", pId) + .setParameter("workspaceId", pWorkspace) + .getResultList(); + }catch(Exception e){ + return null; + } + } + + public List getAllOrders(int pId,String pWorkspace){ + try{ + return em.createNamedQuery("ChangeOrder.getOrderByMilestonesAndWorkspace",ChangeOrder.class) + .setParameter("milestoneId", pId) + .setParameter("workspaceId", pWorkspace) + .getResultList(); + }catch(Exception e){ + return null; + } + } + + public int getNumberOfRequests(int pId,String pWorkspace){ + try{ + return ((Number)em.createNamedQuery("ChangeRequest.countRequestByMilestonesAndWorkspace") + .setParameter("milestoneId", pId) + .setParameter("workspaceId", pWorkspace) + .getSingleResult()).intValue(); + }catch(Exception e){ + return 0; + } + } + + public int getNumberOfOrders(int pId,String pWorkspace){ + try{ + return ((Number)em.createNamedQuery("ChangeOrder.countOrderByMilestonesAndWorkspace") + .setParameter("milestoneId", pId) + .setParameter("workspaceId", pWorkspace) + .getSingleResult()).intValue(); + }catch(Exception e){ + return 0; + } + } + + private boolean checkTitleUniqueness(String pTitle,String pWorkspace){ + try{ + return em.createNamedQuery("Milestone.findMilestonesByTitleAndWorkspace") + .setParameter("title", pTitle) + .setParameter("workspaceId", pWorkspace) + .getResultList().isEmpty(); + }catch (NoResultException e){ + return true; + } + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ModificationNotificationDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ModificationNotificationDAO.java new file mode 100644 index 0000000000..b22e5bacbf --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ModificationNotificationDAO.java @@ -0,0 +1,72 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.change.ModificationNotification; +import com.docdoku.core.product.PartIterationKey; +import com.docdoku.core.product.PartRevisionKey; + +import javax.persistence.EntityManager; +import java.util.List; + +public class ModificationNotificationDAO { + + private EntityManager em; + + public ModificationNotificationDAO(EntityManager pEM) { + em = pEM; + } + + public void removeModificationNotifications(PartIterationKey pPartIPK){ + em.createNamedQuery("ModificationNotification.removeAllOnPartIteration") + .setParameter("workspaceId", pPartIPK.getWorkspaceId()) + .setParameter("partNumber", pPartIPK.getPartMasterNumber()) + .setParameter("version", pPartIPK.getPartRevisionVersion()) + .setParameter("iteration", pPartIPK.getIteration()).executeUpdate(); + } + + public void removeModificationNotifications(PartRevisionKey pPartRPK){ + em.createNamedQuery("ModificationNotification.removeAllOnPartRevision") + .setParameter("workspaceId", pPartRPK.getWorkspaceId()) + .setParameter("partNumber", pPartRPK.getPartMasterNumber()) + .setParameter("version", pPartRPK.getVersion()).executeUpdate(); + } + + public void createModificationNotification(ModificationNotification pNotification) { + em.persist(pNotification); + } + + public ModificationNotification getModificationNotification(int pId) { + return em.find(ModificationNotification.class, pId); + } + + public List getModificationNotifications(PartIterationKey pPartIPK) { + return em.createNamedQuery("ModificationNotification.findByImpactedPartIteration", ModificationNotification.class) + .setParameter("workspaceId", pPartIPK.getWorkspaceId()) + .setParameter("partNumber", pPartIPK.getPartMasterNumber()) + .setParameter("version", pPartIPK.getPartRevisionVersion()) + .setParameter("iteration", pPartIPK.getIteration()).getResultList(); + } + + public boolean hasModificationNotifications(PartIterationKey pPartIPK){ + return !getModificationNotifications(pPartIPK).isEmpty(); + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/OrganizationDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/OrganizationDAO.java new file mode 100644 index 0000000000..43a0ac9397 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/OrganizationDAO.java @@ -0,0 +1,98 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.Organization; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.OrganizationAlreadyExistsException; +import com.docdoku.core.exceptions.OrganizationNotFoundException; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.PersistenceException; +import java.util.Locale; + +public class OrganizationDAO { + + + private EntityManager em; + private Locale mLocale; + + public OrganizationDAO(Locale pLocale, EntityManager pEM) { + em=pEM; + mLocale=pLocale; + } + + public OrganizationDAO(EntityManager pEM) { + em=pEM; + mLocale=Locale.getDefault(); + } + + + public Organization findOrganizationOfAccount(String login) { + Account account = em.getReference(Account.class, login); + Organization organization = null; + try { + organization = em.createNamedQuery("Organization.ofAccount", Organization.class) + .setParameter("account", account).getSingleResult(); + }catch (NoResultException ex){ + //null will be returned + } + return organization; + } + + public void updateOrganization(Organization pOrganization){ + em.merge(pOrganization); + } + + public void createOrganization(Organization pOrganization) throws OrganizationAlreadyExistsException, CreationException { + try{ + //the EntityExistsException is thrown only when flush occurs + em.persist(pOrganization); + em.flush(); + }catch(EntityExistsException pEEEx){ + throw new OrganizationAlreadyExistsException(mLocale, pOrganization); + }catch(PersistenceException pPEx){ + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } + + public void deleteOrganization(Organization pOrganization) { + em.remove(pOrganization); + em.flush(); + } + + public Organization loadOrganization(String pName) throws OrganizationNotFoundException { + Organization organization=em.find(Organization.class,pName); + if (organization == null) { + throw new OrganizationNotFoundException(mLocale, pName); + } else { + return organization; + } + } + + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartCollectionDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartCollectionDAO.java new file mode 100644 index 0000000000..ae432c6c1a --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartCollectionDAO.java @@ -0,0 +1,47 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.configuration.PartCollection; + +import javax.persistence.EntityManager; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class PartCollectionDAO { + + private EntityManager em; + + private static final Logger LOGGER = Logger.getLogger(PartCollectionDAO.class.getName()); + + public PartCollectionDAO(EntityManager pEM) { + em = pEM; + } + + public void createPartCollection(PartCollection partCollection){ + try { + em.persist(partCollection); + em.flush(); + }catch (Exception e){ + LOGGER.log(Level.SEVERE,"Fail to create a collection of parts",e); + } + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartIterationDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartIterationDAO.java new file mode 100644 index 0000000000..9d642166e5 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartIterationDAO.java @@ -0,0 +1,110 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.exceptions.PartIterationNotFoundException; +import com.docdoku.core.meta.ListOfValuesKey; +import com.docdoku.core.product.*; + +import javax.persistence.EntityManager; +import java.util.List; +import java.util.Locale; + + + +public class PartIterationDAO { + + private EntityManager em; + private Locale mLocale; + + public PartIterationDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public PartIterationDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + + + public PartIteration loadPartI(PartIterationKey pKey) throws PartIterationNotFoundException { + PartIteration partI = em.find(PartIteration.class, pKey); + if (partI == null) { + throw new PartIterationNotFoundException(mLocale, pKey); + } else { + return partI; + } + } + + + public void updateIteration(PartIteration pPartI){ + em.merge(pPartI); + } + + public void removeIteration(PartIteration pPartI){ + new ConversionDAO(em).removePartIterationConversion(pPartI); + for(PartUsageLink partUsageLink:pPartI.getComponents()){ + if(!partLinkIsUsedInPreviousIteration(partUsageLink,pPartI)){ + em.remove(partUsageLink); + } + } + em.remove(pPartI); + } + + public boolean partLinkIsUsedInPreviousIteration(PartUsageLink partUsageLink, PartIteration partIte) { + int iteration = partIte.getIteration(); + if(iteration == 1){ + return false; + } + PartIteration previousIteration = partIte.getPartRevision().getIteration(iteration-1); + return previousIteration.getComponents().contains(partUsageLink); + } + + public List findUsedByAsComponent(PartMasterKey pPart) { + return findUsedByAsComponent(em.getReference(PartMaster.class,pPart)); + } + + public List findUsedByAsComponent(PartMaster pPart) { + List usedByParts = em.createNamedQuery("PartIteration.findUsedByAsComponent", PartIteration.class) + .setParameter("partMaster", pPart).getResultList(); + return usedByParts; + } + + public List findUsedByAsSubstitute(PartMasterKey pPart) { + return findUsedByAsSubstitute(em.getReference(PartMaster.class,pPart)); + } + + public List findUsedByAsSubstitute(PartMaster pPart) { + List usedByParts = em.createNamedQuery("PartIteration.findUsedByAsSubstitute", PartIteration.class) + .setParameter("partMaster", pPart).getResultList(); + return usedByParts; + } + + + public List findAllPartIterationFromLOV(ListOfValuesKey lovKey) { + return em.createNamedQuery("PartIteration.findWhereLOV", PartIteration.class) + .setParameter("lovName", lovKey.getName()) + .setParameter("workspace_id", lovKey.getWorkspaceId()) + .getResultList(); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartMasterDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartMasterDAO.java new file mode 100644 index 0000000000..3e591e0662 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartMasterDAO.java @@ -0,0 +1,155 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + + +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.PartMasterAlreadyExistsException; +import com.docdoku.core.exceptions.PartMasterNotFoundException; +import com.docdoku.core.product.PartMaster; +import com.docdoku.core.product.PartMasterKey; +import com.docdoku.core.product.PartRevision; + +import javax.persistence.*; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class PartMasterDAO { + + private EntityManager em; + private Locale mLocale; + private static final Logger LOGGER = Logger.getLogger(PartMasterDAO.class.getName()); + + public PartMasterDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public PartMasterDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + + public PartMaster loadPartM(PartMasterKey pKey) throws PartMasterNotFoundException { + PartMaster partM = em.find(PartMaster.class, pKey); + if (partM == null) { + throw new PartMasterNotFoundException(mLocale, pKey.getNumber()); + } else { + return partM; + } + } + + public PartMaster getPartMRef(PartMasterKey pKey) throws PartMasterNotFoundException { + try { + return em.getReference(PartMaster.class, pKey); + } catch (EntityNotFoundException pENFEx) { + LOGGER.log(Level.FINEST,null,pENFEx); + throw new PartMasterNotFoundException(mLocale, pKey.getNumber()); + } + } + + public void createPartM(PartMaster pPartM) throws PartMasterAlreadyExistsException, CreationException { + try { + PartRevision firstRev = pPartM.getLastRevision(); + if(firstRev!=null && firstRev.getWorkflow()!=null){ + WorkflowDAO workflowDAO = new WorkflowDAO(em); + workflowDAO.createWorkflow(firstRev.getWorkflow()); + } + //the EntityExistsException is thrown only when flush occurs + em.persist(pPartM); + em.flush(); + } catch (EntityExistsException pEEEx) { + LOGGER.log(Level.FINEST,null,pEEEx); + throw new PartMasterAlreadyExistsException(mLocale, pPartM); + } catch (PersistenceException pPEx) { + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + LOGGER.log(Level.FINEST,null,pPEx); + throw new CreationException(mLocale); + } + } + + public void removePartM(PartMaster pPartM) { + PartRevisionDAO partRevisionDAO = new PartRevisionDAO(mLocale, em); + for(PartRevision partRevision:pPartM.getPartRevisions()){ + partRevisionDAO.removeRevision(partRevision); + } + em.remove(pPartM); + } + + public List findPartMasters(String workspaceId, String partNumber, String partName, int maxResults){ + return em.createNamedQuery("PartMaster.findByNameOrNumber", PartMaster.class) + .setParameter("partNumber", partNumber) + .setParameter("partName", partName) + .setParameter("workspaceId", workspaceId) + .setMaxResults(maxResults) + .getResultList(); + } + + public String findLatestPartMId(String pWorkspaceId, String pType) { + String partMId; + TypedQuery query = em.createQuery("SELECT m.number FROM PartMaster m " + + "WHERE m.workspace.id = :workspaceId " + + "AND m.type = :type " + + "AND m.creationDate = (" + + "SELECT MAX(m2.creationDate) FROM PartMaster m2 " + + "WHERE m2.workspace.id = :workspaceId " + + "AND m2.type = :type " + + ")", String.class); + query.setParameter("workspaceId", pWorkspaceId); + query.setParameter("type", pType); + partMId = query.getSingleResult(); + return partMId; + } + + public List getPartMasters(String pWorkspaceId, int pStart, int pMaxResults) { + return em.createNamedQuery("PartMaster.findByWorkspace", PartMaster.class) + .setParameter("workspaceId", pWorkspaceId) + .setFirstResult(pStart) + .setMaxResults(pMaxResults) + .getResultList(); + } + + public long getDiskUsageForPartsInWorkspace(String pWorkspaceId) { + Number result = (Number)em.createNamedQuery("BinaryResource.diskUsageInPath") + .setParameter("path", pWorkspaceId+"/parts/%") + .getSingleResult(); + + return result != null ? result.longValue() : 0L; + } + + public long getDiskUsageForPartTemplatesInWorkspace(String pWorkspaceId) { + Number result = (Number)em.createNamedQuery("BinaryResource.diskUsageInPath") + .setParameter("path", pWorkspaceId+"/part-templates/%") + .getSingleResult(); + + return result != null ? result.longValue() : 0L; + } + + public List getAllByWorkspace(String workspaceId) { + return em.createNamedQuery("PartMaster.findByWorkspace",PartMaster.class) + .setParameter("workspaceId",workspaceId) + .getResultList(); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartMasterTemplateDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartMasterTemplateDAO.java new file mode 100644 index 0000000000..f623c83daf --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartMasterTemplateDAO.java @@ -0,0 +1,97 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.PartMasterTemplateAlreadyExistsException; +import com.docdoku.core.exceptions.PartMasterTemplateNotFoundException; +import com.docdoku.core.meta.ListOfValuesKey; +import com.docdoku.core.product.PartMasterTemplate; +import com.docdoku.core.product.PartMasterTemplateKey; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import javax.persistence.Query; +import java.util.List; +import java.util.Locale; + +public class PartMasterTemplateDAO { + + private EntityManager em; + private Locale mLocale; + + public PartMasterTemplateDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public PartMasterTemplateDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + public void updatePartMTemplate(PartMasterTemplate pTemplate) { + em.merge(pTemplate); + } + + public PartMasterTemplate removePartMTemplate(PartMasterTemplateKey pKey) throws PartMasterTemplateNotFoundException { + PartMasterTemplate template = loadPartMTemplate(pKey); + em.remove(template); + return template; + } + + public List findAllPartMTemplates(String pWorkspaceId) { + Query query = em.createQuery("SELECT DISTINCT t FROM PartMasterTemplate t WHERE t.workspaceId = :workspaceId"); + return query.setParameter("workspaceId", pWorkspaceId).getResultList(); + } + + public PartMasterTemplate loadPartMTemplate(PartMasterTemplateKey pKey) + throws PartMasterTemplateNotFoundException { + PartMasterTemplate template = em.find(PartMasterTemplate.class, pKey); + if (template == null) { + throw new PartMasterTemplateNotFoundException(mLocale, pKey.getId()); + } else { + return template; + } + } + + public void createPartMTemplate(PartMasterTemplate pTemplate) throws PartMasterTemplateAlreadyExistsException, CreationException { + try { + //the EntityExistsException is thrown only when flush occurs + em.persist(pTemplate); + em.flush(); + } catch (EntityExistsException pEEEx) { + throw new PartMasterTemplateAlreadyExistsException(mLocale, pTemplate); + } catch (PersistenceException pPEx) { + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } + + public List findAllPartMTemplatesFromLOV(ListOfValuesKey lovKey){ + return em.createNamedQuery("PartMasterTemplate.findWhereLOV", PartMasterTemplate.class) + .setParameter("lovName", lovKey.getName()) + .setParameter("workspace_id", lovKey.getWorkspaceId()) + .getResultList(); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartRevisionDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartRevisionDAO.java new file mode 100644 index 0000000000..62856af820 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartRevisionDAO.java @@ -0,0 +1,182 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.common.User; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.PartRevisionAlreadyExistsException; +import com.docdoku.core.exceptions.PartRevisionNotFoundException; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.product.*; +import com.docdoku.core.workflow.Task; +import com.docdoku.core.workflow.Workflow; + +import javax.persistence.*; +import java.util.List; +import java.util.Locale; + +public class PartRevisionDAO { + + private EntityManager em; + private Locale mLocale; + + public PartRevisionDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + public PartRevisionDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + + public PartRevision loadPartR(PartRevisionKey pKey) throws PartRevisionNotFoundException { + PartRevision partR = em.find(PartRevision.class, pKey); + if (partR == null) { + throw new PartRevisionNotFoundException(mLocale, pKey); + } else { + return partR; + } + } + + public void updateRevision(PartRevision pPartR) { + em.merge(pPartR); + } + + public void removeRevision(PartRevision pPartR) { + new SharedEntityDAO(em).deleteSharesForPart(pPartR); + new WorkflowDAO(em).removeWorkflowConstraints(pPartR); + new ConversionDAO(em).removePartRevisionConversions(pPartR); + for(PartIteration partIteration:pPartR.getPartIterations()){ + for(PartUsageLink partUsageLink:partIteration.getComponents()){ + em.remove(partUsageLink); + } + } + em.remove(pPartR); + } + + public List findAllCheckedOutPartRevisions(String pWorkspaceId) { + TypedQuery query = em.createQuery("SELECT DISTINCT p FROM PartRevision p WHERE p.checkOutUser is not null and p.partMaster.workspace.id = :workspaceId", PartRevision.class); + query.setParameter("workspaceId", pWorkspaceId); + return query.getResultList(); + } + + public List findCheckedOutPartRevisionsForUser(String pWorkspaceId, String pUserLogin) { + TypedQuery query = em.createQuery("SELECT DISTINCT p FROM PartRevision p WHERE p.checkOutUser is not null and p.partMaster.workspace.id = :workspaceId and p.checkOutUser.login = :userLogin", PartRevision.class); + query.setParameter("workspaceId", pWorkspaceId); + query.setParameter("userLogin", pUserLogin); + return query.getResultList(); + } + + public List getPartRevisions(String pWorkspaceId, int pStart, int pMaxResults) { + return em.createNamedQuery("PartRevision.findByWorkspace", PartRevision.class) + .setParameter("workspaceId", pWorkspaceId) + .setFirstResult(pStart) + .setMaxResults(pMaxResults) + .getResultList(); + } + + public List getAllPartRevisions(String pWorkspaceId) { + return em.createNamedQuery("PartRevision.findByWorkspace", PartRevision.class) + .setParameter("workspaceId", pWorkspaceId) + .getResultList(); + } + + public int getTotalNumberOfParts(String pWorkspaceId) { + return ((Number)em.createNamedQuery("PartRevision.countByWorkspace") + .setParameter("workspaceId", pWorkspaceId) + .getSingleResult()).intValue(); + } + + public int getPartRevisionCountFiltered(User caller, String workspaceId) { + return ((Number) em.createNamedQuery("PartRevision.countByWorkspace.filterUserACLEntry") + .setParameter("workspaceId", workspaceId) + .setParameter("user", caller) + .getSingleResult()).intValue(); + } + + public List getPartRevisionsFiltered(User caller, String pWorkspaceId, int pStart, int pMaxResults) { + return em.createNamedQuery("PartRevision.findByWorkspace.filterUserACLEntry", PartRevision.class) + .setParameter("workspaceId", pWorkspaceId) + .setParameter("user", caller) + .setFirstResult(pStart) + .setMaxResults(pMaxResults) + .getResultList(); + } + + public void createPartR(PartRevision partR) throws PartRevisionAlreadyExistsException, CreationException { + + try { + if (partR.getWorkflow() != null) { + WorkflowDAO workflowDAO = new WorkflowDAO(em); + workflowDAO.createWorkflow(partR.getWorkflow()); + } + + if (partR.getACL() != null) { + ACLDAO aclDAO = new ACLDAO(em); + aclDAO.createACL(partR.getACL()); + } + + //the EntityExistsException is thrown only when flush occurs + em.persist(partR); + em.flush(); + } catch (EntityExistsException pEEEx) { + throw new PartRevisionAlreadyExistsException(mLocale, partR); + } catch (PersistenceException pPEx) { + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + + } + + + public List findPartsRevisionsWithReferenceOrNameLike(String pWorkspaceId, String reference, int maxResults) { + return em.createNamedQuery("PartRevision.findByReferenceOrName",PartRevision.class) + .setParameter("workspaceId", pWorkspaceId) + .setParameter("partNumber", "%" + reference + "%") + .setParameter("partName", "%" + reference + "%") + .setMaxResults(maxResults) + .getResultList(); + } + + public boolean isCheckedOutIteration(PartIterationKey partIKey) throws PartRevisionNotFoundException { + PartRevision partR = loadPartR(partIKey.getPartRevision()); + return partR.isCheckedOut() && (partIKey.getIteration() == partR.getLastIterationNumber()); + } + + public List findPartByTag(Tag tag) { + TypedQuery query = em.createQuery("SELECT DISTINCT d FROM PartRevision d WHERE :tag MEMBER OF d.tags", PartRevision.class); + query.setParameter("tag", tag); + return query.getResultList(); + } + + public PartRevision getWorkflowHolder(Workflow workflow) { + try { + return em.createNamedQuery("PartRevision.findByWorkflow", PartRevision.class). + setParameter("workflow", workflow).getSingleResult(); + }catch(NoResultException e){ + return null; + } + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartUsageLinkDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartUsageLinkDAO.java new file mode 100644 index 0000000000..a01dc3cad3 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PartUsageLinkDAO.java @@ -0,0 +1,132 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.exceptions.PartUsageLinkNotFoundException; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartMasterKey; +import com.docdoku.core.product.PartSubstituteLink; +import com.docdoku.core.product.PartUsageLink; + +import javax.persistence.EntityManager; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class PartUsageLinkDAO { + + private EntityManager em; + private Locale mLocale; + + public PartUsageLinkDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public PartUsageLinkDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + + public List findPartUsagePaths(PartMasterKey pPartMKey){ + List usages= findPartUsages(pPartMKey.getWorkspace(),pPartMKey.getNumber()); + List usagePaths = new ArrayList<>(); + for(PartUsageLink usage:usages){ + List path=new ArrayList<>(); + path.add(usage); + createPath(usage,path,usagePaths); + } + + return usagePaths; + } + + private void createPath(PartUsageLink currentUsage, List currentPath, List usagePaths){ + + PartIteration owner = em.createNamedQuery("PartUsageLink.getPartOwner",PartIteration.class) + .setParameter("usage", currentUsage) + .getSingleResult(); + List parentUsages = findPartUsages(owner.getWorkspaceId(), owner.getPartNumber()); + + for(PartUsageLink parentUsage:parentUsages){ + List newPath=new ArrayList<>(currentPath); + newPath.add(0,parentUsage); + createPath(parentUsage, newPath, usagePaths); + } + if(parentUsages.isEmpty()) { + usagePaths.add(currentPath.toArray(new PartUsageLink[currentPath.size()])); + } + + } + + public List findPartUsages(String workspaceId, String partNumber){ + return em.createNamedQuery("PartUsageLink.findByComponent",PartUsageLink.class) + .setParameter("partNumber", partNumber) + .setParameter("workspaceId", workspaceId) + .getResultList(); + } + + public boolean hasPartUsages(String workspaceId, String partNumber){ + return !findPartUsages(workspaceId,partNumber).isEmpty(); + } + + public List findPartSubstitutes(String workspaceId, String partNumber) { + return em.createNamedQuery("PartSubstituteLink.findBySubstitute", PartSubstituteLink.class) + .setParameter("partNumber", partNumber) + .setParameter("workspaceId", workspaceId) + .getResultList(); + } + + public boolean hasPartSubstitutes(String workspaceId, String partNumber) { + return !findPartSubstitutes(workspaceId,partNumber).isEmpty(); + } + + public PartUsageLink loadPartUsageLink(int pId) throws PartUsageLinkNotFoundException { + PartUsageLink link = em.find(PartUsageLink.class, pId); + if (link == null) { + throw new PartUsageLinkNotFoundException(mLocale, pId); + } else { + return link; + } + } + + public PartSubstituteLink loadPartSubstituteLink(int pId) throws PartUsageLinkNotFoundException { + PartSubstituteLink link = em.find(PartSubstituteLink.class, pId); + if (link == null) { + throw new PartUsageLinkNotFoundException(mLocale, pId); + } else { + return link; + } + } + + public void removeOrphanPartLinks() { + List partUsageLinks = em.createNamedQuery("PartUsageLink.findOrphans", PartUsageLink.class).getResultList(); + + PathToPathLinkDAO pathToPathLinkDAO = new PathToPathLinkDAO(mLocale, em); + + for(PartUsageLink partUsageLink:partUsageLinks){ + pathToPathLinkDAO.removePathToPathLinks(partUsageLink.getFullId()); + for (PartSubstituteLink partSubstituteLink : partUsageLink.getSubstitutes()) { + pathToPathLinkDAO.removePathToPathLinks(partSubstituteLink.getFullId()); + } + em.remove(partUsageLink); + } + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PasswordRecoveryRequestDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PasswordRecoveryRequestDAO.java new file mode 100644 index 0000000000..ff3f9fef8f --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PasswordRecoveryRequestDAO.java @@ -0,0 +1,58 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.exceptions.PasswordRecoveryRequestNotFoundException; +import com.docdoku.core.security.PasswordRecoveryRequest; + +import javax.persistence.EntityManager; +import java.util.Locale; + +public class PasswordRecoveryRequestDAO { + + private EntityManager em; + private Locale mLocale; + + public PasswordRecoveryRequestDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale =pLocale; + } + + public PasswordRecoveryRequestDAO(EntityManager pEM) { + em=pEM; + mLocale=Locale.getDefault(); + } + + public PasswordRecoveryRequest loadPasswordRecoveryRequest(String pPasswordRRUuid) throws PasswordRecoveryRequestNotFoundException { + PasswordRecoveryRequest passwdRR = em.find(PasswordRecoveryRequest.class,pPasswordRRUuid); + if (passwdRR == null) { + throw new PasswordRecoveryRequestNotFoundException(mLocale, pPasswordRRUuid); + } else { + return passwdRR; + } + } + + + public void removePasswordRecoveryRequest(PasswordRecoveryRequest pPasswdRRUuid){ + em.remove(pPasswdRRUuid); + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PathDataIterationDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PathDataIterationDAO.java new file mode 100644 index 0000000000..374b018928 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PathDataIterationDAO.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.configuration.PathDataIteration; +import com.docdoku.core.configuration.ProductInstanceIteration; + +import javax.persistence.EntityManager; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class PathDataIterationDAO { + + private EntityManager em; + + private static final Logger LOGGER = Logger.getLogger(PathDataIterationDAO.class.getName()); + + public PathDataIterationDAO(EntityManager pEM) { + em = pEM; + } + + public void createPathDataIteration(PathDataIteration pathDataIteration){ + try { + em.persist(pathDataIteration); + em.flush(); + }catch (Exception e){ + LOGGER.log(Level.SEVERE,"Fail to create path data",e); + } + } + + public List getLastPathDataIterations(ProductInstanceIteration productInstanceIteration){ + return em.createNamedQuery("PathDataIteration.findLastIterationFromProductInstanceIteration",PathDataIteration.class) + .setParameter("productInstanceIteration", productInstanceIteration) + .getResultList(); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PathDataMasterDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PathDataMasterDAO.java new file mode 100644 index 0000000000..d97ea6f261 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PathDataMasterDAO.java @@ -0,0 +1,96 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.configuration.PathDataMaster; +import com.docdoku.core.configuration.ProductInstanceIteration; +import com.docdoku.core.configuration.ProductInstanceMaster; +import com.docdoku.core.exceptions.PathDataMasterNotFoundException; + +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class PathDataMasterDAO { + + private EntityManager em; + private Locale mLocale; + + private static final Logger LOGGER = Logger.getLogger(PathDataMasterDAO.class.getName()); + + public PathDataMasterDAO(EntityManager pEM) { + em = pEM; + } + + public PathDataMasterDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public void createPathData(PathDataMaster pathDataMaster){ + try { + em.persist(pathDataMaster); + em.flush(); + }catch (Exception e){ + LOGGER.log(Level.SEVERE,"Fail to create path data",e); + } + } + + + public PathDataMaster findByPathAndProductInstanceIteration(String pathAsString, ProductInstanceIteration productInstanceIteration) throws PathDataMasterNotFoundException { + try { + return em.createNamedQuery("PathDataMaster.findByPathAndProductInstanceIteration", PathDataMaster.class) + .setParameter("path", pathAsString) + .setParameter("productInstanceIteration", productInstanceIteration) + .getSingleResult(); + } catch (NoResultException e) { + throw new PathDataMasterNotFoundException(mLocale,pathAsString); + } + } + + public PathDataMaster findByPathIdAndProductInstanceIteration(int pathId, ProductInstanceIteration productInstanceIteration) throws PathDataMasterNotFoundException { + try { + return em.createNamedQuery("PathDataMaster.findByPathIdAndProductInstanceIteration", PathDataMaster.class) + .setParameter("pathId", pathId) + .setParameter("productInstanceIteration", productInstanceIteration) + .getSingleResult(); + } catch (NoResultException e) { + throw new PathDataMasterNotFoundException(mLocale,pathId); + } + } + + public ProductInstanceMaster findByPathData(PathDataMaster pathDataMaster){ + try { + return em.createNamedQuery("ProductInstanceMaster.findByPathData", ProductInstanceMaster.class) + .setParameter("pathDataMasterList", pathDataMaster) + .getSingleResult(); + } catch(NoResultException e) { + return null; + } + } + + public void removePathData(PathDataMaster pathDataMaster) { + em.remove(pathDataMaster); + em.flush(); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PathToPathLinkDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PathToPathLinkDAO.java new file mode 100644 index 0000000000..a6b888e880 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/PathToPathLinkDAO.java @@ -0,0 +1,347 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.configuration.ProductBaseline; +import com.docdoku.core.configuration.ProductInstanceIteration; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.PathToPathLinkAlreadyExistsException; +import com.docdoku.core.exceptions.PathToPathLinkNotFoundException; +import com.docdoku.core.product.ConfigurationItem; +import com.docdoku.core.product.PartLink; +import com.docdoku.core.product.PartUsageLink; +import com.docdoku.core.product.PathToPathLink; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.PersistenceException; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author morgan on 29/04/15. + */ +public class PathToPathLinkDAO { + + private static final Logger LOGGER = Logger.getLogger(PathToPathLinkDAO.class.getName()); + + private EntityManager em; + private Locale mLocale; + + public PathToPathLinkDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public PathToPathLinkDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + public void createPathToPathLink(PathToPathLink pathToPathLink) throws CreationException, PathToPathLinkAlreadyExistsException { + + try { + //the EntityExistsException is thrown only when flush occurs + em.persist(pathToPathLink); + em.flush(); + } catch (EntityExistsException pEEEx) { + LOGGER.log(Level.FINEST,null,pEEEx); + throw new PathToPathLinkAlreadyExistsException(mLocale, pathToPathLink); + } catch (PersistenceException pPEx) { + LOGGER.log(Level.FINEST,null,pPEx); + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } + + public PathToPathLink loadPathToPathLink(int pathToPathLinkId) throws PathToPathLinkNotFoundException { + PathToPathLink pathToPathLink = em.find(PathToPathLink.class,pathToPathLinkId); + if(pathToPathLink != null){ + return pathToPathLink; + } + throw new PathToPathLinkNotFoundException(mLocale,pathToPathLinkId); + } + + public void removePathToPathLink(PathToPathLink pathToPathLink) { + em.remove(pathToPathLink); + em.flush(); + } + + public List getDistinctPathToPathLinkTypes(ProductInstanceIteration productInstanceIteration) { + return em.createNamedQuery("PathToPathLink.findPathToPathLinkTypesByProductInstanceIteration",String.class) + .setParameter("productInstanceIteration",productInstanceIteration) + .getResultList(); + } + public List getDistinctPathToPathLink(ProductInstanceIteration productInstanceIteration) { + return em.createNamedQuery("PathToPathLink.findPathToPathLinkByProductInstanceIteration",PathToPathLink.class) + .setParameter("productInstanceIteration",productInstanceIteration) + .getResultList(); + } + + public List getDistinctPathToPathLinkTypes(ProductBaseline productBaseline) { + return em.createNamedQuery("PathToPathLink.findPathToPathLinkTypesByProductBaseline",String.class) + .setParameter("productBaseline",productBaseline) + .getResultList(); + } + + public List getDistinctPathToPathLinkTypes(ConfigurationItem configurationItem) { + return em.createNamedQuery("PathToPathLink.findPathToPathLinkTypesByProduct",String.class) + .setParameter("configurationItem",configurationItem) + .getResultList(); + } + + public PathToPathLink getSamePathToPathLink(ConfigurationItem configurationItem, PathToPathLink pathToPathLink){ + try { + return em.createNamedQuery("PathToPathLink.findSamePathToPathLinkInProduct", PathToPathLink.class) + .setParameter("configurationItem", configurationItem) + .setParameter("targetPath", pathToPathLink.getTargetPath()) + .setParameter("sourcePath", pathToPathLink.getSourcePath()) + .setParameter("type", pathToPathLink.getType()) + .getSingleResult(); + }catch(NoResultException e){ + return null; + } + } + + public List getNextPathToPathLinkInProduct(ConfigurationItem configurationItem, PathToPathLink pathToPathLink){ + return em.createNamedQuery("PathToPathLink.findNextPathToPathLinkInProduct", PathToPathLink.class) + .setParameter("configurationItem", configurationItem) + .setParameter("targetPath", pathToPathLink.getTargetPath()) + .setParameter("type", pathToPathLink.getType()) + .getResultList(); + } + + public List getPathToPathLinkFromSourceAndTarget(ProductBaseline baseline, String source, String target) { + return em.createNamedQuery("PathToPathLink.findPathToPathLinkBySourceAndTargetInBaseline", PathToPathLink.class) + .setParameter("baseline", baseline) + .setParameter("source",source) + .setParameter("target", target) + .getResultList(); + } + + public List getPathToPathLinkFromSourceAndTarget(ProductInstanceIteration productInstanceIteration, String source, String target) { + return em.createNamedQuery("PathToPathLink.findPathToPathLinkBySourceAndTargetInProductInstance", PathToPathLink.class) + .setParameter("productInstanceIteration", productInstanceIteration) + .setParameter("source",source) + .setParameter("target", target) + .getResultList(); + } + + public List getPathToPathLinkFromSourceAndTarget(ConfigurationItem configurationItem, String source, String target) { + return em.createNamedQuery("PathToPathLink.findPathToPathLinkBySourceAndTargetInProduct", PathToPathLink.class) + .setParameter("configurationItem", configurationItem) + .setParameter("source",source) + .setParameter("target", target) + .getResultList(); + } + + public List findRootPathToPathLinks(ConfigurationItem configurationItem, String type) { + return em.createNamedQuery("PathToPathLink.findRootPathToPathLinkForGivenProductAndType", PathToPathLink.class) + .setParameter("configurationItem", configurationItem) + .setParameter("type", type) + .getResultList(); + } + + public List findRootPathToPathLinks(ProductBaseline productBaseline, String type) { + return em.createNamedQuery("PathToPathLink.findRootPathToPathLinkForGivenProductBaselineAndType", PathToPathLink.class) + .setParameter("productBaseline", productBaseline) + .setParameter("type", type) + .getResultList(); + } + + public List findRootPathToPathLinks(ProductInstanceIteration productInstanceIteration, String type) { + return em.createNamedQuery("PathToPathLink.findRootPathToPathLinkForGivenProductInstanceIterationAndType", PathToPathLink.class) + .setParameter("productInstanceIteration", productInstanceIteration) + .setParameter("type", type) + .getResultList(); + } + + public List getPathToPathLinkFromPathList(ConfigurationItem configurationItem, List paths) { + return em.createNamedQuery("PathToPathLink.findPathToPathLinkByPathListInProduct", PathToPathLink.class) + .setParameter("configurationItem", configurationItem) + .setParameter("paths",paths) + .getResultList(); + } + + public List getSourcesPathToPathLinksInProduct(ConfigurationItem configurationItem, String type, String source) { + return em.createNamedQuery("PathToPathLink.findSourcesPathToPathLinkInProduct", PathToPathLink.class) + .setParameter("configurationItem", configurationItem) + .setParameter("type", type) + .setParameter("source", source) + .getResultList(); + } + + public List getSourcesPathToPathLinksInBaseline(ProductBaseline productBaseline, String type, String source) { + return em.createNamedQuery("PathToPathLink.findSourcesPathToPathLinkInProductBaseline", PathToPathLink.class) + .setParameter("productBaseline", productBaseline) + .setParameter("type", type) + .setParameter("source", source) + .getResultList(); + } + + public List getPathToPathLinksFromPartialPath(String usageLinkId){ + return em.createNamedQuery("PathToPathLink.findLinksWherePartialPathIsPresent", PathToPathLink.class) + .setParameter("inChain", "%" + usageLinkId + "-%") + .setParameter("endOfChain", "%" + usageLinkId) + .getResultList(); + } + + public void removePathToPathLinks(String usageLinkId) { + + List pathToPathLinks = getPathToPathLinksFromPartialPath(usageLinkId); + + for(PathToPathLink pathToPathLink:pathToPathLinks){ + try{ + ConfigurationItem configurationItem = em.createNamedQuery("ConfigurationItem.findByPathToPathLink", ConfigurationItem.class) + .setParameter("pathToPathLink", pathToPathLink) + .getSingleResult(); + + configurationItem.removePathToPathLink(pathToPathLink); + } catch (NoResultException e){ + // Nothing to remove, continue loop + } + + } + + } + + private void upgradePathToPathLink(PartLink oldLink, PartLink newLink){ + + List oldP2PLinks = getPathToPathLinksFromPartialPath(oldLink.getFullId()); + String oldFullId = oldLink.getFullId(); + String newFullId = newLink.getFullId(); + + for(PathToPathLink pathToPathLink:oldP2PLinks){ + pathToPathLink.setSourcePath(upgradePath(pathToPathLink.getSourcePath(), oldFullId, newFullId)); + pathToPathLink.setTargetPath(upgradePath(pathToPathLink.getTargetPath(), oldFullId, newFullId)); + } + + if(oldLink.getSubstitutes() != null){ + int size = oldLink.getSubstitutes().size(); + for(int i = 0; i < size; i++){ + PartLink oldSubstituteLink = oldLink.getSubstitutes().get(i); + PartLink newSubstituteLink = newLink.getSubstitutes().get(i); + upgradePathToPathLink(oldSubstituteLink,newSubstituteLink); + } + } + + } + + public void cloneAndUpgradePathToPathLinks(List oldComponents, List newComponents) { + int size = oldComponents.size(); + //keep a track of p2p to create and add it to the configuration item only once + //at the end of the process in order to not create them twice + //Map> is oldP2P => newP2P => the configuration item into + //which it has be added + Map> modifiedP2P=new HashMap<>(); + for(int i = 0; i < size; i++){ + PartLink oldLink = oldComponents.get(i); + PartLink newLink = newComponents.get(i); + cloneAndUpgradePathToPathLink(oldLink, newLink, modifiedP2P); + } + //to the new p2p to their configuration item + for(Map p2pAndConfigItem:modifiedP2P.values()){ + Map.Entry pair = p2pAndConfigItem.entrySet().iterator().next(); + ConfigurationItem ci = pair.getValue(); + ci.addPathToPathLink(pair.getKey()); + } + } + + private void cloneAndUpgradePathToPathLink(PartLink oldLink, PartLink newLink, Map> modifiedP2P){ + + List oldP2PLinks = getPathToPathLinksFromPartialPath(oldLink.getFullId()); + + String oldFullId = oldLink.getFullId(); + String newFullId = newLink.getFullId(); + PathToPathLink clone; + for(PathToPathLink pathToPathLink:oldP2PLinks){ + if(modifiedP2P.get(pathToPathLink) !=null){ + clone = modifiedP2P.get(pathToPathLink).keySet().iterator().next(); + }else{ + clone = new PathToPathLink(pathToPathLink.getType(),pathToPathLink.getSourcePath(),pathToPathLink.getTargetPath(),pathToPathLink.getDescription()); + } + clone.setSourcePath(upgradePath(clone.getSourcePath(), oldFullId, newFullId)); + clone.setTargetPath(upgradePath(clone.getTargetPath(), oldFullId, newFullId)); + + // Add in configuration item list + try { + ConfigurationItem configurationItem = em.createNamedQuery("ConfigurationItem.findByPathToPathLink", ConfigurationItem.class) + .setParameter("pathToPathLink", pathToPathLink) + .getSingleResult(); + Map p2pToAdd = new HashMap<>(); + p2pToAdd.put(clone,configurationItem); + modifiedP2P.put(pathToPathLink,p2pToAdd); + }catch(NoResultException e){ + LOGGER.log(Level.FINEST,null,e); + } + } + + if(oldLink.getSubstitutes() != null){ + int size = oldLink.getSubstitutes().size(); + for(int i = 0; i < size; i++){ + PartLink oldSubstituteLink = oldLink.getSubstitutes().get(i); + PartLink newSubstituteLink = newLink.getSubstitutes().get(i); + cloneAndUpgradePathToPathLink(oldSubstituteLink, newSubstituteLink, modifiedP2P); + } + } + + } + + public String upgradePath(String path, String oldFullId, String newFullId) { + return path.replaceAll("("+oldFullId+")(-|$)", newFullId + "$2"); + } + + public List getPathToPathLinkSourceInContext(ConfigurationItem configurationItem, ProductInstanceIteration productInstanceIteration, String path){ + if(productInstanceIteration != null){ + return em.createNamedQuery("PathToPathLink.isSourceInProductInstanceContext", PathToPathLink.class) + .setParameter("productInstanceIteration", productInstanceIteration) + .setParameter("path", path) + .getResultList(); + }else{ + return em.createNamedQuery("PathToPathLink.isSourceInConfigurationItemContext", PathToPathLink.class) + .setParameter("configurationItem", configurationItem) + .setParameter("path", path) + .getResultList(); + } + } + + public List getPathToPathLinkTargetInContext(ConfigurationItem configurationItem, ProductInstanceIteration productInstanceIteration, String path){ + if(productInstanceIteration != null){ + return em.createNamedQuery("PathToPathLink.isTargetInProductInstanceContext", PathToPathLink.class) + .setParameter("productInstanceIteration", productInstanceIteration) + .setParameter("path", path) + .getResultList(); + }else{ + return em.createNamedQuery("PathToPathLink.isTargetInConfigurationItemContext", PathToPathLink.class) + .setParameter("configurationItem", configurationItem) + .setParameter("path", path) + .getResultList(); + } + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ProductBaselineDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ProductBaselineDAO.java new file mode 100644 index 0000000000..d55510c77d --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ProductBaselineDAO.java @@ -0,0 +1,140 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.configuration.*; +import com.docdoku.core.exceptions.BaselineNotFoundException; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.ProductInstanceMasterNotFoundException; +import com.docdoku.core.product.ConfigurationItemKey; +import com.docdoku.core.product.PartRevision; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ProductBaselineDAO { + + private final EntityManager em; + private final Locale mLocale; + private static final Logger LOGGER = Logger.getLogger(ProductBaselineDAO.class.getName()); + + public ProductBaselineDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + public ProductBaselineDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public List findBaselines(String workspaceId) { + return em.createQuery("SELECT b FROM ProductBaseline b WHERE b.configurationItem.workspace.id = :workspaceId", ProductBaseline.class) + .setParameter("workspaceId",workspaceId) + .getResultList(); + } + + public List findBaselines(String ciId, String workspaceId){ + return em.createNamedQuery("ProductBaseline.findByConfigurationItemId", ProductBaseline.class) + .setParameter("ciId", ciId) + .setParameter("workspaceId",workspaceId) + .getResultList(); + } + + public void createBaseline(ProductBaseline productBaseline) throws CreationException { + try { + em.persist(productBaseline); + em.flush(); + } catch (PersistenceException pPEx) { + LOGGER.log(Level.FINEST,null,pPEx); + throw new CreationException(mLocale); + } + } + + public ProductBaseline loadBaseline(int pId) throws BaselineNotFoundException { + ProductBaseline productBaseline = em.find(ProductBaseline.class, pId); + if (productBaseline == null) { + throw new BaselineNotFoundException(mLocale, pId); + } else { + return productBaseline; + } + } + + public void deleteBaseline(ProductBaseline productBaseline) { + flushBaselinedParts(productBaseline); + em.remove(productBaseline); + em.flush(); + } + + public boolean existBaselinedPart(String workspaceId, String partNumber) { + return em.createNamedQuery("BaselinedPart.existBaselinedPart", Long.class) + .setParameter("partNumber", partNumber) + .setParameter("workspaceId", workspaceId) + .getSingleResult() > 0; + } + + public void flushBaselinedParts(ProductBaseline productBaseline) { + productBaseline.removeAllBaselinedParts(); + em.flush(); + } + + public List findBaselineWherePartRevisionHasIterations(PartRevision partRevision) { + return em.createNamedQuery("ProductBaseline.getBaselinesForPartRevision", ProductBaseline.class) + .setParameter("partRevision", partRevision) + .getResultList(); + } + + public List findObsoletePartsInBaseline(String workspaceId, ProductBaseline productBaseline) { + return em.createNamedQuery("ProductBaseline.findObsoletePartRevisions", PartRevision.class) + .setParameter("productBaseline", productBaseline) + .setParameter("workspaceId", workspaceId) + .getResultList(); + } + + public ProductBaseline findBaselineById(int baselineId) { + return em.find(ProductBaseline.class, baselineId); + } + + public List findBaselinedPartWithReferenceLike(int collectionId, String q, int maxResults) throws BaselineNotFoundException { + return em.createNamedQuery("BaselinedPart.findByReference",BaselinedPart.class) + .setParameter("id", "%" + q + "%") + .setParameter("partCollection",collectionId) + .setMaxResults(maxResults) + .getResultList(); + } + + public ProductBaseline findLastBaselineWithSerialNumber(ConfigurationItemKey ciKey, String serialNumber) throws ProductInstanceMasterNotFoundException { + ProductInstanceMasterKey pimk = new ProductInstanceMasterKey(serialNumber, ciKey); + ProductInstanceMaster productIM = new ProductInstanceMasterDAO(em).loadProductInstanceMaster(pimk); + ProductInstanceIteration productII = productIM.getLastIteration(); + + if (productII != null) { + return productII.getBasedOn(); + } + + return null; + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ProductConfigurationDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ProductConfigurationDAO.java new file mode 100644 index 0000000000..bc00c873ec --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ProductConfigurationDAO.java @@ -0,0 +1,87 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.configuration.ProductConfiguration; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.ProductConfigurationNotFoundException; +import com.docdoku.core.product.ConfigurationItemKey; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ProductConfigurationDAO { + + private final EntityManager em; + private final Locale mLocale; + private static final Logger LOGGER = Logger.getLogger(ProductConfigurationDAO.class.getName()); + + public ProductConfigurationDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + public ProductConfigurationDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public void createProductConfiguration(ProductConfiguration productConfiguration) throws CreationException { + try { + em.persist(productConfiguration); + em.flush(); + } catch (PersistenceException pPEx) { + LOGGER.log(Level.FINEST,null,pPEx); + throw new CreationException(mLocale); + } + } + + public ProductConfiguration getProductConfiguration(int productConfigurationId) throws ProductConfigurationNotFoundException { + ProductConfiguration productConfiguration = em.find(ProductConfiguration.class, productConfigurationId); + if(productConfiguration != null){ + return productConfiguration; + }else{ + throw new ProductConfigurationNotFoundException(mLocale,productConfigurationId); + } + } + + public List getAllProductConfigurations(String workspaceId) { + return em.createNamedQuery("ProductConfiguration.findByWorkspace", ProductConfiguration.class) + .setParameter("workspaceId", workspaceId) + .getResultList(); + } + + public List getAllProductConfigurationsByConfigurationItem(ConfigurationItemKey ciKey) { + return em.createNamedQuery("ProductConfiguration.findByConfigurationItem", ProductConfiguration.class) + .setParameter("workspaceId", ciKey.getWorkspace()) + .setParameter("configurationItemId", ciKey.getId()) + .getResultList(); + } + + public void deleteProductConfiguration(ProductConfiguration productConfiguration) { + em.remove(productConfiguration); + em.flush(); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ProductInstanceIterationDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ProductInstanceIterationDAO.java new file mode 100644 index 0000000000..44ba7eb1d8 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ProductInstanceIterationDAO.java @@ -0,0 +1,85 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.configuration.BaselinedPart; +import com.docdoku.core.configuration.ProductBaseline; +import com.docdoku.core.configuration.ProductInstanceIteration; +import com.docdoku.core.configuration.ProductInstanceIterationKey; +import com.docdoku.core.exceptions.ProductInstanceIterationNotFoundException; +import com.docdoku.core.exceptions.ProductInstanceMasterNotFoundException; + +import javax.persistence.EntityManager; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ProductInstanceIterationDAO { + + private EntityManager em; + private Locale mLocale; + + private static final Logger LOGGER = Logger.getLogger(ProductInstanceIterationDAO.class.getName()); + + public ProductInstanceIterationDAO(EntityManager pEM) { + em = pEM; + } + + public ProductInstanceIterationDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public void createProductInstanceIteration(ProductInstanceIteration productInstanceIteration){ + try { + em.persist(productInstanceIteration); + em.flush(); + }catch (Exception e){ + LOGGER.log(Level.SEVERE,"Fail to create product instance iteration",e); + } + } + + + public ProductInstanceIteration loadProductInstanceIteration(ProductInstanceIterationKey pId) throws ProductInstanceMasterNotFoundException, ProductInstanceIterationNotFoundException { + ProductInstanceIteration productInstanceIteration = em.find(ProductInstanceIteration.class, pId); + if (productInstanceIteration == null) { + throw new ProductInstanceIterationNotFoundException(mLocale, pId); + } else { + return productInstanceIteration; + } + } + + public List findBaselinedPartWithReferenceLike(int collectionId, String q, int maxResults) { + return em.createNamedQuery("BaselinedPart.findByReference",BaselinedPart.class) + .setParameter("id", "%" + q + "%") + .setParameter("partCollection",collectionId) + .setMaxResults(maxResults) + .getResultList(); + + } + + public boolean isBaselinedUsed(ProductBaseline productBaseline) { + return !em.createNamedQuery("ProductInstanceIteration.findByProductBaseline",ProductInstanceIteration.class) + .setParameter("productBaseline",productBaseline) + .getResultList().isEmpty(); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ProductInstanceMasterDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ProductInstanceMasterDAO.java new file mode 100644 index 0000000000..2992da7ede --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/ProductInstanceMasterDAO.java @@ -0,0 +1,106 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.configuration.BaselinedPart; +import com.docdoku.core.configuration.ProductInstanceIteration; +import com.docdoku.core.configuration.ProductInstanceMaster; +import com.docdoku.core.configuration.ProductInstanceMasterKey; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.ProductInstanceAlreadyExistsException; +import com.docdoku.core.exceptions.ProductInstanceMasterNotFoundException; +import com.docdoku.core.product.PartRevision; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import java.util.List; +import java.util.Locale; + +public class ProductInstanceMasterDAO { + + private EntityManager em; + private Locale mLocale; + + public ProductInstanceMasterDAO(EntityManager pEM) { + em = pEM; + } + + public ProductInstanceMasterDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public List findProductInstanceMasters(String workspaceId) { + return em.createQuery("SELECT pim FROM ProductInstanceMaster pim WHERE pim.instanceOf.workspace.id = :workspaceId", ProductInstanceMaster.class) + .setParameter("workspaceId",workspaceId) + .getResultList(); + } + + public List findProductInstanceMasters(String ciId, String workspaceId) { + return em.createNamedQuery("ProductInstanceMaster.findByConfigurationItemId", ProductInstanceMaster.class) + .setParameter("ciId", ciId) + .setParameter("workspaceId",workspaceId) + .getResultList(); + } + + public List findProductInstanceMasters(PartRevision partRevision) { + return em.createNamedQuery("ProductInstanceMaster.findByPart", ProductInstanceMaster.class) + .setParameter("partRevision", partRevision) + .getResultList(); + } + + public void createProductInstanceMaster(ProductInstanceMaster productInstanceMaster) throws ProductInstanceAlreadyExistsException, CreationException { + try{ + em.persist(productInstanceMaster); + em.flush(); + }catch (EntityExistsException e){ + throw new ProductInstanceAlreadyExistsException(mLocale, productInstanceMaster); + }catch (PersistenceException e){ + throw new CreationException(mLocale); + } + + } + + public ProductInstanceMaster loadProductInstanceMaster(ProductInstanceMasterKey pId) throws ProductInstanceMasterNotFoundException { + ProductInstanceMaster productInstanceMaster = em.find(ProductInstanceMaster.class, pId); + if (productInstanceMaster == null) { + throw new ProductInstanceMasterNotFoundException(mLocale, pId); + } else { + return productInstanceMaster; + } + } + + public void deleteProductInstanceMaster(ProductInstanceMaster productInstanceMaster) { + for(ProductInstanceIteration productInstanceIteration : productInstanceMaster.getProductInstanceIterations()){ + for(BaselinedPart baselinedPart : productInstanceIteration.getBaselinedParts().values()){ + em.remove(baselinedPart); + } + + em.refresh(productInstanceIteration.getPartCollection()); + em.remove(productInstanceIteration.getPartCollection()); + em.remove(productInstanceIteration); + } + + em.remove(productInstanceMaster); + em.flush(); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/QueryDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/QueryDAO.java new file mode 100644 index 0000000000..16dae9ce95 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/QueryDAO.java @@ -0,0 +1,490 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.common.Workspace; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.QueryAlreadyExistsException; +import com.docdoku.core.meta.*; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartMaster; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.query.Query; +import com.docdoku.core.query.QueryContext; +import com.docdoku.core.query.QueryRule; + +import javax.persistence.*; +import javax.persistence.criteria.*; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Morgan Guimard on 09/04/15. + */ +public class QueryDAO { + + private EntityManager em; + private Locale mLocale; + + private CriteriaBuilder cb; + private CriteriaQuery cq; + private Root pm; + private Root pr; + private Root pi; + + + private static final Logger LOGGER = Logger.getLogger(QueryDAO.class.getName()); + + public QueryDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + cb = em.getCriteriaBuilder(); + + cq = cb.createQuery(PartRevision.class); + pm = cq.from(PartMaster.class); + pr = cq.from(PartRevision.class); + pi = cq.from(PartIteration.class); + } + + public void createQuery(Query query) throws CreationException, QueryAlreadyExistsException { + try { + persistQueryRules(query.getQueryRule()); + em.persist(query); + em.flush(); + persistContexts(query,query.getContexts()); + }catch (EntityExistsException pEEEx) { + LOGGER.log(Level.FINEST,null,pEEEx); + throw new QueryAlreadyExistsException(mLocale, query); + } catch (PersistenceException pPEx) { + LOGGER.log(Level.FINEST,null,pPEx); + throw new CreationException(mLocale); + } + } + + private void persistContexts(Query query, List contexts) { + for(QueryContext context:contexts){ + context.setParentQuery(query); + em.persist(context); + } + em.flush(); + } + + private void persistQueryRules(QueryRule queryRule) { + + em.persist(queryRule); + em.flush(); + + if(!queryRule.hasSubRules()){ + return; + } + + for(QueryRule subRule : queryRule.getSubQueryRules()){ + subRule.setParentQueryRule(queryRule); + persistQueryRules(subRule); + } + + } + + public List loadQueries(String workspaceId) { + return em.createNamedQuery("Query.findByWorkspace", Query.class) + .setParameter("workspaceId", workspaceId) + .getResultList(); + } + + public Query findQueryByName(String workspaceId, String name) { + try { + return em.createNamedQuery("Query.findByWorkspaceAndWorkspace", Query.class) + .setParameter("workspaceId", workspaceId) + .setParameter("name", name) + .getSingleResult(); + }catch (NoResultException e){ + return null; + } + } + + public Query loadQuery(int id) { + Query query = em.find(Query.class, id); + return query; + } + + public void removeQuery(Query query){ + em.remove(query); + em.flush(); + } + + public List runQuery(Workspace workspace, Query query) { + + // Simple select + cq.select(pr); + + // Restrict search to workspace + Expression workspaceExp = pm.get("workspace"); + Predicate rulesPredicate = null; + String firstCondition = query.getQueryRule().getCondition(); + if ( firstCondition != null) { + rulesPredicate = getPredicate(query.getQueryRule()); + } + Predicate workspacePredicate = cb.and(cb.equal(workspaceExp, workspace)); + + // Join PartMaster + Predicate prJoinPredicate = cb.and(cb.equal(pm.get("number"), pr.get("partMasterNumber")), cb.equal(pm.get("workspace"), workspace)); + + // Join PartIteration + Join piJoin = pi.join("partRevision"); + Predicate piJoinPredicate = piJoin.on(cb.and(cb.equal(pi.get("partRevision").get("partMasterNumber"), pr.get("partMasterNumber")), cb.equal(pr.get("partMaster").get("workspace"), workspace))).getOn(); + + + if ( firstCondition != null) { + cq.where(cb.and( + rulesPredicate, + workspacePredicate, + prJoinPredicate, + piJoinPredicate + )); + }else{ + cq.where(cb.and( + workspacePredicate, + prJoinPredicate, + piJoinPredicate + )); + } + + + TypedQuery tp = em.createQuery(cq); + Set revisions = new HashSet<>(); + + for(PartRevision part : tp.getResultList()){ + if(part.getLastCheckedInIteration() != null) { + revisions.add(part); + } + } + + return new ArrayList<>(revisions); + } + + private Predicate getPredicate(QueryRule queryRule){ + + String condition = queryRule.getCondition(); + + List subQueryRules = queryRule.getSubQueryRules(); + + if(subQueryRules != null && !subQueryRules.isEmpty()){ + + Predicate[] predicates = new Predicate[subQueryRules.size()]; + + for(int i = 0; i < predicates.length; i++){ + Predicate predicate = getPredicate(subQueryRules.get(i)); + predicates[i] = predicate; + } + + if("OR".equals(condition)){ + return cb.or(predicates); + } + else if("AND".equals(condition)){ + return cb.and(predicates); + } + + throw new IllegalArgumentException(); + + }else{ + return getRulePredicate(queryRule); + } + } + + private Predicate getRulePredicate(QueryRule queryRule){ + + String field = queryRule.getField(); + String operator = queryRule.getOperator(); + List values = queryRule.getValues(); + String type = queryRule.getType(); + + if(field.startsWith("pm.")){ + return getPartMasterPredicate(field.substring(3), operator, values , type); + } + + if(field.startsWith("pr.")){ + return getPartRevisionPredicate(field.substring(3), operator, values, type); + } + + if(field.startsWith("author.")){ + return getAuthorPredicate(field.substring(7), operator, values, type); + } + + if(field.startsWith("attr-TEXT.")){ + return getInstanceTextAttributePredicate(field.substring(10), operator, values, type); + } + + if(field.startsWith("attr-LONG_TEXT.")){ + return getInstanceLongTextAttributePredicate(field.substring(15), operator, values, type); + } + + if(field.startsWith("attr-DATE.")){ + return getInstanceDateAttributePredicate(field.substring(10), operator, values, type); + } + + if(field.startsWith("attr-BOOLEAN.")){ + return getInstanceBooleanAttributePredicate(field.substring(13), operator, values, type); + } + + if(field.startsWith("attr-URL.")){ + return getInstanceURLAttributePredicate(field.substring(9), operator, values, type); + } + + if(field.startsWith("attr-NUMBER.")){ + return getInstanceNumberAttributePredicate(field.substring(12), operator, values, type); + } + + if(field.startsWith("attr-LOV.")){ + return getInstanceLovAttributePredicate(field.substring(9), operator, values, type); + } + + throw new IllegalArgumentException(); + } + + private Predicate getAuthorPredicate(String field, String operator, List values, String type) { + return getPredicate(pr.get("author").get(field),operator,values,type); + } + + private Predicate getPartRevisionPredicate(String field, String operator, List values, String type) { + if("checkInDate".equals(field)){ + Predicate lastIterationPredicate = cb.equal(cb.size(pr.get("partIterations")), pi.get("iteration")); + return cb.and(lastIterationPredicate, getPredicate(pi.get("checkInDate"), operator, values, type)); + } + else if("status".equals(field)){ + if (values.size() == 1) { + return getPredicate(pr.get(field), operator, values, "status"); + } + } + else if("tags".equals(field)){ + return getTagsPredicate(values); + } + else if("linkedDocuments".equals(field)){ + // should be ignored, returning always true for the moment + return cb.and(); + } + return getPredicate(pr.get(field), operator, values, type); + } + + private Predicate getTagsPredicate(List values) { + Root tag = cq.from(Tag.class); + Predicate prPredicate = tag.in(pr.get("tags")); + Predicate valuesPredicate = cb.equal(tag.get("label"),values); + return cb.and(prPredicate, valuesPredicate); + } + + private Predicate getPartMasterPredicate(String field, String operator, List values, String type) { + return getPredicate(pm.get(field), operator, values, type); + } + + // Instances Attributes + private Predicate getInstanceURLAttributePredicate(String field, String operator, List values, String type) { + Root iua = cq.from(InstanceURLAttribute.class); + Predicate valuesPredicate = getPredicate(iua.get("urlValue"), operator, values, "string"); + Predicate memberPredicate = iua.in(pi.get("instanceAttributes")); + return cb.and(cb.equal(iua.get("name"), field), valuesPredicate, memberPredicate); + } + + private Predicate getInstanceBooleanAttributePredicate(String field, String operator, List values, String type) { + if (values.size() == 1) { + Root iba = cq.from(InstanceBooleanAttribute.class); + Predicate valuesPredicate = cb.equal(iba.get("booleanValue"), Boolean.parseBoolean(values.get(0))); + Predicate memberPredicate = iba.in(pi.get("instanceAttributes")); + switch(operator){ + case "equal": + return cb.and(cb.equal(iba.get("name"),field),valuesPredicate,memberPredicate); + case "not_equal": + return cb.and(cb.equal(iba.get("name"),field),valuesPredicate.not(),memberPredicate); + default: + break; + } + } + + throw new IllegalArgumentException(); + } + + private Predicate getInstanceNumberAttributePredicate(String field, String operator, List values, String type) { + Root ina = cq.from(InstanceNumberAttribute.class); + Predicate valuesPredicate = getPredicate(ina.get("numberValue"), operator, values, "double"); + Predicate memberPredicate = ina.in(pi.get("instanceAttributes")); + return cb.and(cb.equal(ina.get("name"),field),valuesPredicate,memberPredicate); + } + + private Predicate getInstanceLovAttributePredicate(String field, String operator, List values, String type) { + if (values.size() == 1) { + Root ila = cq.from(InstanceListOfValuesAttribute.class); + Predicate valuesPredicate = cb.equal(ila.get("indexValue"), Integer.parseInt(values.get(0))); + Predicate memberPredicate = ila.in(pi.get("instanceAttributes")); + switch(operator) { + case "equal": + return cb.and(cb.equal(ila.get("name"), field), valuesPredicate, memberPredicate); + case "not_equal": + return cb.and(cb.equal(ila.get("name"), field), valuesPredicate.not(), memberPredicate); + default: + break; + } + } + + throw new IllegalArgumentException(); + } + + private Predicate getInstanceDateAttributePredicate(String field, String operator, List values, String type) { + Root ida = cq.from(InstanceDateAttribute.class); + Predicate valuesPredicate = getPredicate(ida.get("dateValue"), operator, values, "date"); + Predicate memberPredicate = ida.in(pi.get("instanceAttributes")); + return cb.and(cb.equal(ida.get("name"),field),valuesPredicate,memberPredicate); + } + + private Predicate getInstanceLongTextAttributePredicate(String field, String operator, List values, String type) { + Root ita = cq.from(InstanceLongTextAttribute.class); + Predicate valuesPredicate = getPredicate(ita.get("longTextValue"), operator, values, "string"); + Predicate memberPredicate = ita.in(pi.get("instanceAttributes")); + return cb.and(cb.equal(ita.get("name"),field),valuesPredicate,memberPredicate); + } + + private Predicate getInstanceTextAttributePredicate(String field, String operator, List values, String type) { + Root ita = cq.from(InstanceTextAttribute.class); + Predicate valuesPredicate = getPredicate(ita.get("textValue"), operator, values, "string"); + Predicate memberPredicate = ita.in(pi.get("instanceAttributes")); + return cb.and(cb.equal(ita.get("name"),field),valuesPredicate,memberPredicate); + } + + + // Rule parsing + + private Predicate getPredicate(Expression fieldExp, String operator, List values, String type){ + + List operands; + + switch(type){ + case "string" : + operands=values; + break; + case "date": + try { + //TODO: this formatting is already done by other method, should be refactored. + //TODO: the pattern for the date format should be declared somewhere. + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + df.setTimeZone(TimeZone.getTimeZone("UTC")); + List temp = new ArrayList(); + for (String string : values) { + temp.add(df.parse(string)); + } + operands = temp; + } catch (ParseException e) { + throw new IllegalArgumentException(); + } + break; + case "double": + try { + List temp = new ArrayList(); + for (String string : values) { + temp.add(Double.parseDouble(string)); + } + operands = temp; + }catch(NumberFormatException e){ + throw new IllegalArgumentException(); + } + break; + case "status": + List temp = new ArrayList<>(); + for (String string : values) { + temp.add(PartRevision.RevisionStatus.valueOf(string)); + } + operands = temp; + break; + default : + operands=values; + break; + } + + switch (operator){ + case "between" : + if (operands.size() == 2) { + if("date".equals(type)){ + return cb.between(fieldExp, (Date)operands.get(0), (Date)operands.get(1)); + + } else if("double".equals(type)){ + return cb.between(fieldExp, (Double) operands.get(0), (Double) operands.get(1)); + } + } + break; + case "equal" : + if("date".equals(type)){ + Date date1 = (Date) operands.get(0); + Calendar c = Calendar.getInstance(); + c.setTime(date1); + c.add(Calendar.DATE, 1); + Date date2 = c.getTime(); + return cb.between(fieldExp, date1, date2); + + } else { + return cb.equal(fieldExp,operands.get(0)); + } + case "not_equal" : return cb.equal(fieldExp, operands.get(0)).not(); + + case "contains" : return cb.like(fieldExp, "%" + operands.get(0) + "%"); + case "not_contains" : return cb.like(fieldExp, "%"+operands.get(0)+"%").not(); + + case "begins_with" : return cb.like(fieldExp, operands.get(0)+"%"); + case "not_begins_with" : return cb.like(fieldExp, operands.get(0)+"%").not(); + + case "ends_with" : return cb.like(fieldExp, "%"+operands.get(0)); + case "not_ends_with" : return cb.like(fieldExp, "%"+operands.get(0)).not(); + + case "less": + if("date".equals(type)){ + return cb.lessThan(fieldExp,(Date)operands.get(0)); + } else if("double".equals(type)){ + return cb.lessThan(fieldExp,(Double)operands.get(0)); + } + break; + case "less_or_equal": + if("date".equals(type)){ + return cb.lessThanOrEqualTo(fieldExp, (Date) operands.get(0)); + } else if("double".equals(type)){ + return cb.lessThanOrEqualTo(fieldExp, (Double) operands.get(0)); + } + break; + case "greater": + if("date".equals(type)){ + return cb.greaterThan(fieldExp, (Date) operands.get(0)); + } else if("double".equals(type)){ + return cb.greaterThan(fieldExp, (Double) operands.get(0)); + } + break; + case "greater_or_equal": + if("date".equals(type)){ + return cb.greaterThanOrEqualTo(fieldExp, (Date) operands.get(0)); + } else if("double".equals(type)){ + return cb.greaterThanOrEqualTo(fieldExp, (Double) operands.get(0)); + } + break; + default: + break; + } + + // Should have return a value + throw new IllegalArgumentException(); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/RoleDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/RoleDAO.java new file mode 100644 index 0000000000..c516a04334 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/RoleDAO.java @@ -0,0 +1,112 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + + +import com.docdoku.core.common.User; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.RoleAlreadyExistsException; +import com.docdoku.core.exceptions.RoleNotFoundException; +import com.docdoku.core.workflow.Role; +import com.docdoku.core.workflow.RoleKey; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Morgan Guimard + */ +public class RoleDAO { + + private EntityManager em; + private Locale mLocale; + + private static final Logger LOGGER = Logger.getLogger(RoleDAO.class.getName()); + + public RoleDAO(Locale pLocale, EntityManager pEM) { + mLocale=pLocale; + em=pEM; + } + + public RoleDAO(EntityManager pEM) { + mLocale=Locale.getDefault(); + em=pEM; + } + + public Role loadRole(RoleKey roleKey) throws RoleNotFoundException { + + Role role = em.find(Role.class, roleKey); + if (role == null) { + throw new RoleNotFoundException(mLocale, roleKey); + } else { + return role; + } + + } + + public List findRolesInWorkspace(String pWorkspaceId){ + return em.createNamedQuery("Role.findByWorkspace",Role.class).setParameter("workspaceId", pWorkspaceId).getResultList(); + } + + public void createRole(Role pRole) throws CreationException, RoleAlreadyExistsException { + try{ + em.persist(pRole); + em.flush(); + } catch (EntityExistsException pEEEx) { + LOGGER.log(Level.FINEST,null,pEEEx); + throw new RoleAlreadyExistsException(mLocale, pRole); + } catch (PersistenceException pPEx) { + LOGGER.log(Level.FINEST,null,pPEx); + throw new CreationException(mLocale); + } + } + + public void deleteRole(Role pRole){ + em.remove(pRole); + em.flush(); + } + + public boolean isRoleInUseInWorkflowModel(Role role) { + return !em.createNamedQuery("Role.findRolesInUseByRoleName") + .setParameter("roleName", role.getName()) + .setParameter("workspace", role.getWorkspace()) + .getResultList().isEmpty(); + + } + + public List findRolesInUseWorkspace(String pWorkspaceId) { + return em.createNamedQuery("Role.findRolesInUse",Role.class).setParameter("workspaceId", pWorkspaceId).getResultList(); + } + + public void removeUserFromRoles(User pUser) { + List roles = em.createNamedQuery("Role.findRolesWhereUserIsAssigned", Role.class) + .setParameter("user", pUser) + .getResultList(); + for(Role role:roles){ + role.removeUser(pUser); + } + em.flush(); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/SharedEntityDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/SharedEntityDAO.java new file mode 100644 index 0000000000..ca55d6860f --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/SharedEntityDAO.java @@ -0,0 +1,136 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + + +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.SharedEntityNotFoundException; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.sharing.SharedDocument; +import com.docdoku.core.sharing.SharedEntity; +import com.docdoku.core.sharing.SharedPart; + +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.TypedQuery; +import java.util.Locale; + +/** + * @author Morgan Guimard + */ +public class SharedEntityDAO { + + private EntityManager em; + private Locale mLocale; + + public SharedEntityDAO(Locale pLocale, EntityManager pEM) { + mLocale=pLocale; + em=pEM; + } + + public SharedEntityDAO(EntityManager pEM) { + mLocale=Locale.getDefault(); + em=pEM; + } + + public boolean isSharedDocument(String pUuid){ + return em.find(SharedDocument.class, pUuid) != null; + } + + public boolean isSharedPart(String pUuid){ + return em.find(SharedPart.class, pUuid) != null; + } + + public SharedDocument loadSharedDocument(String pUuid) throws SharedEntityNotFoundException { + + SharedDocument sharedDocument = em.find(SharedDocument.class, pUuid); + if (sharedDocument == null) { + throw new SharedEntityNotFoundException(mLocale, pUuid); + } else { + return sharedDocument; + } + + } + + public SharedPart loadSharedPart(String pUuid) throws SharedEntityNotFoundException { + + SharedPart sharedPart = em.find(SharedPart.class, pUuid); + if (sharedPart == null) { + throw new SharedEntityNotFoundException(mLocale, pUuid); + } else { + return sharedPart; + } + + } + + public void createSharedDocument(SharedDocument pSharedDocument) { + em.persist(pSharedDocument); + em.flush(); + } + + public void createSharedPart(SharedPart pSharedPart) { + em.persist(pSharedPart); + em.flush(); + } + + public void deleteSharedDocument(SharedDocument pSharedDocument){ + em.remove(pSharedDocument); + em.flush(); + } + + public void deleteSharedPart(SharedPart pSharedPart){ + em.remove(pSharedPart); + em.flush(); + } + + public void deleteSharesForDocument(DocumentRevision pDocR) { + TypedQuery query = em.createNamedQuery("SharedDocument.deleteSharesForGivenDocument", SharedDocument.class); + query.setParameter("pDocR", pDocR).executeUpdate(); + } + + public void deleteSharesForPart(PartRevision pPartR) { + TypedQuery query = em.createNamedQuery("SharedPart.deleteSharesForGivenPart", SharedPart.class); + query.setParameter("pPartR", pPartR).executeUpdate(); + } + + public SharedEntity loadSharedEntity(String pUuid) throws SharedEntityNotFoundException { + TypedQuery query = em.createNamedQuery("SharedEntity.findSharedEntityForGivenUuid", SharedEntity.class); + try { + return query.setParameter("pUuid", pUuid).getSingleResult(); + }catch(NoResultException ex){ + throw new SharedEntityNotFoundException(mLocale, pUuid); + } + + } + + public void deleteSharedEntity(SharedEntity pSharedEntity) { + + try { + SharedEntity sharedEntity = loadSharedEntity(pSharedEntity.getUuid()); + if(pSharedEntity instanceof SharedDocument){ + deleteSharedDocument((SharedDocument) sharedEntity); + }else if(pSharedEntity instanceof SharedPart){ + deleteSharedPart((SharedPart) sharedEntity); + } + } catch (SharedEntityNotFoundException e) { + } + + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/SubscriptionDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/SubscriptionDAO.java new file mode 100644 index 0000000000..a69dc414d4 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/SubscriptionDAO.java @@ -0,0 +1,192 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.common.User; +import com.docdoku.core.document.*; +import com.docdoku.core.gcm.GCMAccount; + +import javax.persistence.EntityManager; +import javax.persistence.EntityNotFoundException; +import javax.persistence.Query; +import javax.persistence.TypedQuery; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class SubscriptionDAO { + private static final Logger LOGGER = Logger.getLogger(SubscriptionDAO.class.getName()); + private final EntityManager em; + + public SubscriptionDAO(EntityManager pEM) { + em = pEM; + } + + public void createStateChangeSubscription(StateChangeSubscription pSubscription) { + em.merge(pSubscription); + } + + public void removeStateChangeSubscription(SubscriptionKey pKey) { + try { + StateChangeSubscription subscription = em.getReference(StateChangeSubscription.class, pKey); + em.remove(subscription); + em.flush(); + } catch (NullPointerException pNPEx) { + //em.getReference throws a NullPointerException when entity + //doesn't exist. It's probably a bug, as a workaround + //we silently catch this exception + LOGGER.log(Level.FINER,null,pNPEx); + } catch (EntityNotFoundException pENFEx) { + //not subscribed, no need to unsubscribe + LOGGER.log(Level.FINER,null,pENFEx); + } + } + + public void createIterationChangeSubscription(IterationChangeSubscription pSubscription) { + em.merge(pSubscription); + } + + public void removeIterationChangeSubscription(SubscriptionKey pKey) { + try { + IterationChangeSubscription subscription = em.getReference(IterationChangeSubscription.class, pKey); + em.remove(subscription); + em.flush(); + } catch (NullPointerException pNPEx) { + //em.getReference throws a NullPointerException when entity + //doesn't exist. It's probably a bug, as a workaround + //we silently catch this exception + LOGGER.log(Level.FINER,null,pNPEx); + } catch (EntityNotFoundException pENFEx) { + //not subscribed, no need to unsubscribe + LOGGER.log(Level.FINER,null,pENFEx); + } + } + + public void removeAllSubscriptions(DocumentRevision pDocR) { + Query query = em.createQuery("DELETE FROM StateChangeSubscription s WHERE s.observedDocumentRevision = :docR"); + query.setParameter("docR", pDocR); + query.executeUpdate(); + + Query query2 = em.createQuery("DELETE FROM IterationChangeSubscription s WHERE s.observedDocumentRevision = :docR"); + query2.setParameter("docR", pDocR); + query2.executeUpdate(); + } + + public void removeAllSubscriptions(User pUser) { + Query query = em.createQuery("DELETE FROM StateChangeSubscription s WHERE s.subscriber = :user"); + query.setParameter("user", pUser); + query.executeUpdate(); + + Query query2 = em.createQuery("DELETE FROM IterationChangeSubscription s WHERE s.subscriber = :user"); + query2.setParameter("user", pUser); + query2.executeUpdate(); + } + + public DocumentRevisionKey[] getIterationChangeEventSubscriptions(User pUser) { + DocumentRevisionKey[] docRKeys; + Query query = em.createQuery("SELECT s.observedDocumentRevisionWorkspaceId, s.observedDocumentRevisionId, s.observedDocumentRevisionVersion FROM IterationChangeSubscription s WHERE s.subscriber = :user"); + List listDocRKeys = query.setParameter("user", pUser).getResultList(); + docRKeys = new DocumentRevisionKey[listDocRKeys.size()]; + for (int i = 0; i < listDocRKeys.size(); i++) { + Object[] values = (Object[]) listDocRKeys.get(i); + docRKeys[i] = new DocumentRevisionKey((String) values[0], (String) values[1], (String) values[2]); + } + + + return docRKeys; + } + + public DocumentRevisionKey[] getStateChangeEventSubscriptions(User pUser) { + DocumentRevisionKey[] docRKeys; + Query query = em.createQuery("SELECT s.observedDocumentRevisionWorkspaceId, s.observedDocumentRevisionId, s.observedDocumentRevisionVersion FROM StateChangeSubscription s WHERE s.subscriber = :user"); + List listDocRKeys = query.setParameter("user", pUser).getResultList(); + docRKeys = new DocumentRevisionKey[listDocRKeys.size()]; + for (int i = 0; i < listDocRKeys.size(); i++) { + Object[] values = (Object[]) listDocRKeys.get(i); + docRKeys[i] = new DocumentRevisionKey((String) values[0], (String) values[1], (String) values[2]); + } + + + return docRKeys; + } + + + public boolean isUserStateChangeEventSubscribedForGivenDocument(User pUser, DocumentRevision docR) { + return ! em.createNamedQuery("StateChangeSubscription.findSubscriptionByUserAndDocRevision"). + setParameter("user", pUser).setParameter("docR", docR).getResultList().isEmpty(); + + } + + public boolean isUserIterationChangeEventSubscribedForGivenDocument(User pUser, DocumentRevision docR) { + return ! em.createNamedQuery("IterationChangeSubscription.findSubscriptionByUserAndDocRevision"). + setParameter("user", pUser).setParameter("docR", docR).getResultList().isEmpty(); + } + + + + public User[] getIterationChangeEventSubscribers(DocumentRevision pDocR) { + User[] users; + TypedQuery query = em.createQuery("SELECT DISTINCT s.subscriber FROM IterationChangeSubscription s WHERE s.observedDocumentRevision = :docR", User.class); + List listUsers = query.setParameter("docR", pDocR).getResultList(); + users = new User[listUsers.size()]; + for (int i = 0; i < listUsers.size(); i++) { + users[i] = listUsers.get(i); + } + + return users; + } + + public User[] getStateChangeEventSubscribers(DocumentRevision pDocR) { + User[] users; + TypedQuery query = em.createQuery("SELECT DISTINCT s.subscriber FROM StateChangeSubscription s WHERE s.observedDocumentRevision = :docR", User.class); + List listUsers = query.setParameter("docR", pDocR).getResultList(); + users = new User[listUsers.size()]; + for (int i = 0; i < listUsers.size(); i++) { + users[i] = listUsers.get(i); + } + + return users; + } + + public GCMAccount[] getIterationChangeEventSubscribersGCMAccount(DocumentRevision pDocR) { + GCMAccount[] gcmAccounts; + TypedQuery query = em.createQuery("SELECT DISTINCT gcm FROM GCMAccount gcm, IterationChangeSubscription s WHERE gcm.account.login = s.subscriber.login AND s.observedDocumentRevision = :docR", GCMAccount.class); + List gcmAccountsList = query.setParameter("docR", pDocR).getResultList(); + gcmAccounts = new GCMAccount[gcmAccountsList.size()]; + for (int i = 0; i < gcmAccountsList.size(); i++) { + gcmAccounts[i] = gcmAccountsList.get(i); + } + + return gcmAccounts; + } + + public GCMAccount[] getStateChangeEventSubscribersGCMAccount(DocumentRevision pDocR) { + GCMAccount[] gcmAccounts; + TypedQuery query = em.createQuery("SELECT DISTINCT gcm FROM GCMAccount gcm, StateChangeSubscription s WHERE gcm.account.login = s.subscriber.login AND s.observedDocumentRevision = :docR", GCMAccount.class); + List gcmAccountsList = query.setParameter("docR", pDocR).getResultList(); + gcmAccounts = new GCMAccount[gcmAccountsList.size()]; + for (int i = 0; i < gcmAccountsList.size(); i++) { + gcmAccounts[i] = gcmAccountsList.get(i); + } + + return gcmAccounts; + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/TagDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/TagDAO.java new file mode 100644 index 0000000000..dbd2bf83af --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/TagDAO.java @@ -0,0 +1,85 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.TagAlreadyExistsException; +import com.docdoku.core.exceptions.TagNotFoundException; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.meta.TagKey; + +import javax.persistence.*; +import java.util.List; +import java.util.Locale; + +public class TagDAO { + + private EntityManager em; + private Locale mLocale; + + public TagDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public Tag[] findAllTags(String pWorkspaceId) { + Tag[] tags; + TypedQuery query = em.createQuery("SELECT DISTINCT t FROM Tag t WHERE t.workspaceId = :workspaceId",Tag.class); + List listTags = query.setParameter("workspaceId", pWorkspaceId).getResultList(); + tags = new Tag[listTags.size()]; + for (int i = 0; i < listTags.size(); i++) { + tags[i] = listTags.get(i); + } + + return tags; + } + + public void deleteOrphanTags(String pWorkspaceId) { + TypedQuery query = em.createQuery("SELECT t FROM Tag t WHERE t.workspaceId = :workspaceId AND t.label <> ALL (SELECT t2.label FROM DocumentMaster m, IN (m.tags) t2 WHERE t2.workspaceId = :workspaceId)", Tag.class); + List tags = query.setParameter("workspaceId", pWorkspaceId).getResultList(); + for (Tag t : tags) { + em.remove(t); + } + } + + public void removeTag(TagKey pTagKey) throws TagNotFoundException { + Tag tag = em.find(Tag.class, pTagKey); + if (tag == null) { + throw new TagNotFoundException(mLocale, pTagKey); + } else { + em.remove(tag); + } + } + + public void createTag(Tag pTag) throws CreationException, TagAlreadyExistsException { + try { + //the EntityExistsException is thrown only when flush occurs + em.persist(pTag); + em.flush(); + } catch (EntityExistsException pEEEx) { + throw new TagAlreadyExistsException(mLocale, pTag); + } catch (PersistenceException pPEx) { + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/TaskDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/TaskDAO.java new file mode 100644 index 0000000000..0d92a3791b --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/TaskDAO.java @@ -0,0 +1,99 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserKey; +import com.docdoku.core.exceptions.TaskNotFoundException; +import com.docdoku.core.workflow.Task; +import com.docdoku.core.workflow.TaskKey; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.List; +import java.util.Locale; + +public class TaskDAO { + + private EntityManager em; + private Locale mLocale; + + public TaskDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale =pLocale; + } + + public TaskDAO(EntityManager pEM) { + em=pEM; + mLocale=Locale.getDefault(); + } + + public Task loadTask(TaskKey pTaskKey) throws TaskNotFoundException { + Task task = em.find(Task.class,pTaskKey); + if (task == null) { + throw new TaskNotFoundException(mLocale, pTaskKey); + } else { + return task; + } + } + + public Task[] findTasks(User pUser){ + Task[] tasks; + TypedQuery query = em.createQuery("SELECT DISTINCT t FROM Task t WHERE t.worker = :user", Task.class); + query.setParameter("user",pUser); + List listTasks = query.getResultList(); + tasks = new Task[listTasks.size()]; + for(int i=0;i query = em.createNamedQuery("Task.findAssignedTasks", Task.class); + query.setParameter("login", userLogin); + query.setParameter("workspaceId",workspaceId); + List listTasks = query.getResultList(); + tasks = new Task[listTasks.size()]; + for(int i=0;i query = em.createNamedQuery("Task.findInProgressTasks", Task.class); + query.setParameter("login", userLogin); + query.setParameter("workspaceId",workspaceId); + List listTasks = query.getResultList(); + tasks = new Task[listTasks.size()]; + for(int i=0;i. + */ +package com.docdoku.server.dao; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserKey; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.document.Folder; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.security.WorkspaceUserMembership; +import com.docdoku.core.security.WorkspaceUserMembershipKey; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import javax.persistence.Query; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; + +public class UserDAO { + + private EntityManager em; + private Locale mLocale; + + public UserDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public UserDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + public User loadUser(UserKey pUserKey) throws UserNotFoundException { + User user = em.find(User.class, pUserKey); + if (user == null) { + throw new UserNotFoundException(mLocale, pUserKey.getAccount()); + } else { + return user; + } + } + + public WorkspaceUserMembership loadUserMembership(WorkspaceUserMembershipKey pKey) { + return em.find(WorkspaceUserMembership.class, pKey); + } + + public void addUserMembership(Workspace pWorkspace, User pMember) { + WorkspaceUserMembership ms = em.find(WorkspaceUserMembership.class, new WorkspaceUserMembershipKey(pWorkspace.getId(), pWorkspace.getId(), pMember.getLogin())); + if (ms == null) { + ms = new WorkspaceUserMembership(pWorkspace, pMember); + em.persist(ms); + } + } + + public void removeUserMembership(WorkspaceUserMembershipKey pKey) { + WorkspaceUserMembership ms = em.find(WorkspaceUserMembership.class, pKey); + if (ms != null) { + em.remove(ms); + } + } + + public void updateUser(User pUser) { + em.merge(pUser); + } + + public User[] findAllUsers(String pWorkspaceId) { + User[] users; + Query query = em.createQuery("SELECT DISTINCT u FROM User u WHERE u.workspaceId = :workspaceId"); + List listUsers = query.setParameter("workspaceId", pWorkspaceId).getResultList(); + users = new User[listUsers.size()]; + for (int i = 0; i < listUsers.size(); i++) { + users[i] = (User) listUsers.get(i); + } + return users; + } + + public WorkspaceUserMembership[] findAllWorkspaceUserMemberships(String pWorkspaceId) { + WorkspaceUserMembership[] memberships; + Query query = em.createQuery("SELECT DISTINCT m FROM WorkspaceUserMembership m WHERE m.workspaceId = :workspaceId"); + List listMemberships = query.setParameter("workspaceId", pWorkspaceId).getResultList(); + memberships = new WorkspaceUserMembership[listMemberships.size()]; + for (int i = 0; i < listMemberships.size(); i++) { + memberships[i] = (WorkspaceUserMembership) listMemberships.get(i); + } + + return memberships; + } + + public void removeUser(User pUser) throws UserNotFoundException, NotAllowedException, FolderNotFoundException, EntityConstraintException { + removeUserMembership(new WorkspaceUserMembershipKey(pUser.getWorkspaceId(), pUser.getWorkspaceId(), pUser.getLogin())); + new SubscriptionDAO(em).removeAllSubscriptions(pUser); + new UserGroupDAO(mLocale, em).removeUserFromAllGroups(pUser); + new RoleDAO(mLocale,em).removeUserFromRoles(pUser); + new ACLDAO(em).removeAclUserEntries(pUser); + + boolean author = isDocMAuthor(pUser) || isDocAuthor(pUser) || isDocMTemplateAuthor(pUser) || isWorkflowModelAuthor(pUser); + boolean involved = isInvolvedInWF(pUser); + + if (author || involved) { + throw new NotAllowedException(mLocale, "NotAllowedException8"); + } else { + em.remove(pUser); + } + } + + public boolean isInvolvedInWF(User pUser) { + Query query = em.createQuery("SELECT DISTINCT u FROM User u WHERE EXISTS (SELECT t FROM Task t WHERE t.worker = u AND t.worker = :user)"); + List listUsers = query.setParameter("user", pUser).getResultList(); + return !listUsers.isEmpty(); + } + + public boolean isDocMAuthor(User pUser) { + Query query = em.createQuery("SELECT DISTINCT u FROM User u WHERE EXISTS (SELECT m FROM DocumentMaster m WHERE m.author = u AND m.author = :user)"); + List listUsers = query.setParameter("user", pUser).getResultList(); + return !listUsers.isEmpty(); + } + + public boolean isDocAuthor(User pUser) { + Query query = em.createQuery("SELECT DISTINCT u FROM User u WHERE EXISTS (SELECT d FROM DocumentIteration d WHERE d.author = u AND d.author = :user)"); + List listUsers = query.setParameter("user", pUser).getResultList(); + return !listUsers.isEmpty(); + } + + public boolean isDocMTemplateAuthor(User pUser) { + Query query = em.createQuery("SELECT DISTINCT u FROM User u WHERE EXISTS (SELECT t FROM DocumentMasterTemplate t WHERE t.author = u AND t.author = :user)"); + List listUsers = query.setParameter("user", pUser).getResultList(); + return !listUsers.isEmpty(); + } + + public boolean isWorkflowModelAuthor(User pUser) { + Query query = em.createQuery("SELECT DISTINCT u FROM User u WHERE EXISTS (SELECT w FROM WorkflowModel w WHERE w.author = u AND w.author = :user)"); + List listUsers = query.setParameter("user", pUser).getResultList(); + return !listUsers.isEmpty(); + } + + public void createUser(User pUser) throws UserAlreadyExistsException, FolderAlreadyExistsException, CreationException { + try { + //the EntityExistsException is thrown only when flush occurs + em.persist(pUser); + em.flush(); + new FolderDAO(mLocale, em).createFolder(new Folder(pUser.getWorkspaceId() + "/~" + pUser.getLogin())); + } catch (EntityExistsException pEEEx) { + throw new UserAlreadyExistsException(mLocale, pUser); + } catch (PersistenceException pPEx) { + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } + + public User[] getUsers(String pLogin) { + User[] users; + Query query = em.createQuery("SELECT DISTINCT u FROM User u WHERE u.login = :login"); + List listUsers = query.setParameter("login", pLogin).getResultList(); + users = new User[listUsers.size()]; + for (int i = 0; i < listUsers.size(); i++) { + users[i] = (User) listUsers.get(i); + } + + return users; + } + + public User[] findReachableUsersForCaller(String callerLogin) { + + Map users = new TreeMap<>(); + + List listWorkspaceId = em.createQuery("SELECT u.workspaceId FROM User u WHERE u.login = :login", String.class) + .setParameter("login", callerLogin).getResultList(); + + if(!listWorkspaceId.isEmpty()){ + + List listUsers = em.createQuery("SELECT u FROM User u where u.workspaceId IN :workspacesId", User.class) + .setParameter("workspacesId", listWorkspaceId).getResultList(); + + for (User user : listUsers) { + String loginUser = user.getLogin(); + if (!users.keySet().contains(loginUser)) { + users.put(loginUser,user); + } + /*else if(workspaceId.equals(user.getWorkspaceId())){ + users.remove(loginUser); + users.put(loginUser,user); + }*/ + } + + } + + + return users.values().toArray(new User[users.size()]); + + } + + public boolean hasCommonWorkspace(String userLogin1, String userLogin2){ + return ! em.createNamedQuery("findCommonWorkspacesForGivenUsers"). + setParameter("userLogin1", userLogin1). + setParameter("userLogin2", userLogin2). + getResultList(). + isEmpty(); + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/UserGroupDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/UserGroupDAO.java new file mode 100644 index 0000000000..dcf2551ec6 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/UserGroupDAO.java @@ -0,0 +1,162 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; +import com.docdoku.core.common.UserGroupKey; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.UserGroupAlreadyExistsException; +import com.docdoku.core.exceptions.UserGroupNotFoundException; +import com.docdoku.core.security.WorkspaceUserGroupMembership; +import com.docdoku.core.security.WorkspaceUserGroupMembershipKey; + +import javax.persistence.*; +import java.util.List; +import java.util.Locale; + +public class UserGroupDAO { + + private EntityManager em; + private Locale mLocale; + + public UserGroupDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public UserGroupDAO(EntityManager pEM) { + em = pEM; + mLocale = Locale.getDefault(); + } + + public UserGroup loadUserGroup(UserGroupKey pKey) throws UserGroupNotFoundException { + UserGroup group = em.find(UserGroup.class, pKey); + if (group == null) { + throw new UserGroupNotFoundException(mLocale, pKey); + } else { + return group; + } + } + + public WorkspaceUserGroupMembership[] getUserGroupMemberships(String pWorkspaceId, User pUser) { + WorkspaceUserGroupMembership[] ms; + TypedQuery query = em.createQuery("SELECT DISTINCT m FROM WorkspaceUserGroupMembership m WHERE m.workspaceId = :workspaceId AND :user MEMBER OF m.member.users", WorkspaceUserGroupMembership.class); + query.setParameter("workspaceId", pWorkspaceId); + query.setParameter("user", pUser); + List listUserGroupMemberships = query.getResultList(); + ms = new WorkspaceUserGroupMembership[listUserGroupMemberships.size()]; + for (int i = 0; i < listUserGroupMemberships.size(); i++) { + ms[i] = listUserGroupMemberships.get(i); + } + return ms; + } + + public UserGroup[] findAllUserGroups(String pWorkspaceId) { + UserGroup[] groups; + TypedQuery query = em.createQuery("SELECT DISTINCT g FROM UserGroup g WHERE g.workspaceId = :workspaceId", UserGroup.class); + List listUserGroups = query.setParameter("workspaceId", pWorkspaceId).getResultList(); + groups = new UserGroup[listUserGroups.size()]; + for (int i = 0; i < listUserGroups.size(); i++) { + groups[i] = listUserGroups.get(i); + } + return groups; + } + + public WorkspaceUserGroupMembership loadUserGroupMembership(WorkspaceUserGroupMembershipKey pKey) throws UserGroupNotFoundException { + WorkspaceUserGroupMembership workspaceUserGroupMembership = em.find(WorkspaceUserGroupMembership.class, pKey); + if (workspaceUserGroupMembership == null) { + throw new UserGroupNotFoundException(mLocale,new UserGroupKey(pKey.getWorkspaceId(),pKey.getMemberId())); + } else { + return workspaceUserGroupMembership; + } + } + + public void addUserGroupMembership(Workspace pWorkspace, UserGroup pMember) { + WorkspaceUserGroupMembership ms = em.find(WorkspaceUserGroupMembership.class, new WorkspaceUserGroupMembershipKey(pWorkspace.getId(), pWorkspace.getId(), pMember.getId())); + if (ms == null) { + ms = new WorkspaceUserGroupMembership(pWorkspace, pMember); + em.persist(ms); + } + } + + public void removeUserGroupMembership(WorkspaceUserGroupMembershipKey pKey) { + WorkspaceUserGroupMembership ms = em.find(WorkspaceUserGroupMembership.class, pKey); + if (ms != null) { + em.remove(ms); + } + } + + public void removeUserFromAllGroups(User pUser) { + TypedQuery query = em.createQuery("SELECT DISTINCT g FROM UserGroup g WHERE g.workspaceId = :workspaceId", UserGroup.class); + List listUserGroups = query.setParameter("workspaceId", pUser.getWorkspaceId()).getResultList(); + for (UserGroup listUserGroup : listUserGroups) { + listUserGroup.removeUser(pUser); + } + } + + public WorkspaceUserGroupMembership[] findAllWorkspaceUserGroupMemberships(String pWorkspaceId) { + WorkspaceUserGroupMembership[] memberships; + TypedQuery query = em.createQuery("SELECT DISTINCT m FROM WorkspaceUserGroupMembership m WHERE m.workspaceId = :workspaceId", WorkspaceUserGroupMembership.class); + List listMemberships = query.setParameter("workspaceId", pWorkspaceId).getResultList(); + memberships = new WorkspaceUserGroupMembership[listMemberships.size()]; + for (int i = 0; i < listMemberships.size(); i++) { + memberships[i] = listMemberships.get(i); + } + + return memberships; + } + + public void removeUserGroup(UserGroupKey pKey) throws UserGroupNotFoundException { + UserGroup group = loadUserGroup(pKey); + removeUserGroupMembership(new WorkspaceUserGroupMembershipKey(pKey.getWorkspaceId(), pKey.getWorkspaceId(), pKey.getId())); + em.remove(group); + } + + public boolean hasACLConstraint(UserGroupKey pKey){ + Query query = em.createQuery("SELECT DISTINCT a FROM ACLUserGroupEntry a WHERE a.principal.id = :id AND a.principal.workspaceId = :workspaceId"); + query.setParameter("id",pKey.getId()); + query.setParameter("workspaceId",pKey.getWorkspaceId()); + return !query.getResultList().isEmpty(); + } + + public void createUserGroup(UserGroup pUserGroup) throws CreationException, UserGroupAlreadyExistsException { + try { + //the EntityExistsException is thrown only when flush occurs + em.persist(pUserGroup); + em.flush(); + } catch (EntityExistsException pEEEx) { + throw new UserGroupAlreadyExistsException(mLocale, pUserGroup); + } catch (PersistenceException pPEx) { + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } + + public List getUserGroups(String workspaceId, User user) { + return em.createNamedQuery("UserGroup.findUserGroups", UserGroup.class). + setParameter("workspaceId", workspaceId). + setParameter("user", user). + getResultList(); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/WorkflowDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/WorkflowDAO.java new file mode 100644 index 0000000000..23f127f43a --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/WorkflowDAO.java @@ -0,0 +1,198 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; +import com.docdoku.core.document.DocumentMaster; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.product.PartMaster; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.workflow.*; + +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.TypedQuery; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public class WorkflowDAO { + + private EntityManager em; + + public WorkflowDAO(EntityManager pEM) { + this.em = pEM; + } + + public void createWorkflow(Workflow pWf) { + //Hack to prevent a bug inside the JPA implementation (Eclipse Link) + List activities = pWf.getActivities(); + pWf.setActivities(new ArrayList()); + em.persist(pWf); + em.flush(); + pWf.setActivities(activities); + } + + public Workflow createWorkflow(WorkflowModel workflowModel,Map> roleUserMap, Map> roleGroupMap){ + Workflow workflow = new Workflow(workflowModel.getFinalLifeCycleState()); + em.persist(workflow); + em.flush(); + + List activities = new ArrayList<>(); + for(ActivityModel model:workflowModel.getActivityModels()){ + Activity activity = model.createActivity(roleUserMap, roleGroupMap); + activity.setWorkflow(workflow); + Activity relaunchActivity = activity.getRelaunchActivity(); + if(relaunchActivity!=null){ + activity.setRelaunchActivity(activities.get(relaunchActivity.getStep())); + } + activities.add(activity); + } + workflow.setActivities(activities); + return workflow; + } + + public Workflow duplicateWorkflow(Workflow workflow){ + Workflow duplicatedWF = new Workflow(workflow.getFinalLifeCycleState()); + em.persist(duplicatedWF); + em.flush(); + + List rwActivities = new ArrayList<>(); + for (Activity activity : workflow.getActivities()){ + Activity clonedActivity = activity.clone(); + clonedActivity.setWorkflow(duplicatedWF); + Activity relaunchActivity = activity.getRelaunchActivity(); + if(relaunchActivity!=null){ + Activity clonedRelaunchActivity = rwActivities.get(relaunchActivity.getStep()); + clonedActivity.setRelaunchActivity(clonedRelaunchActivity); + } + rwActivities.add(clonedActivity); + } + + duplicatedWF.setActivities(rwActivities); + return duplicatedWF; + + } + + public DocumentRevision getDocumentTarget(Workflow pWorkflow) { + TypedQuery query = em.createNamedQuery("DocumentRevision.findByWorkflow", DocumentRevision.class); + try { + return query.setParameter("workflow", pWorkflow).getSingleResult(); + }catch(NoResultException e){ + return null; + } + } + + public PartRevision getPartTarget(Workflow pWorkflow) { + TypedQuery query = em.createNamedQuery("PartRevision.findByWorkflow", PartRevision.class); + try { + return query.setParameter("workflow", pWorkflow).getSingleResult(); + }catch(NoResultException e){ + return null; + } + } + + public void removeWorkflowConstraints(WorkspaceWorkflow ww) { + List workflows = ww.getAbortedWorkflows(); + Workflow workflow = ww.getWorkflow(); + removeWorkflowConstraints(workflows,workflow); + } + + public void removeWorkflowConstraints(DocumentMaster pDocM) { + for(DocumentRevision documentRevision : pDocM.getDocumentRevisions()){ + removeWorkflowConstraints(documentRevision); + } + } + + public void removeWorkflowConstraints(PartMaster pPartM) { + for(PartRevision partRevision : pPartM.getPartRevisions()){ + removeWorkflowConstraints(partRevision); + } + } + + public void removeWorkflowConstraints(DocumentRevision pDocR) { + List workflows = pDocR.getAbortedWorkflows(); + Workflow workflow = pDocR.getWorkflow(); + removeWorkflowConstraints(workflows,workflow); + } + + public void removeWorkflowConstraints(PartRevision pPartR) { + List workflows = pPartR.getAbortedWorkflows(); + Workflow workflow = pPartR.getWorkflow(); + removeWorkflowConstraints(workflows,workflow); + } + + private void removeWorkflowConstraints(List pWorkflows, Workflow pWorkflow){ + if(pWorkflows != null){ + for(Workflow workflow:pWorkflows){ + for(Activity activity:workflow.getActivities()){ + activity.setRelaunchActivity(null); + } + } + } + if(pWorkflow != null){ + for(Activity activity:pWorkflow.getActivities()){ + activity.setRelaunchActivity(null); + } + } + em.flush(); + } + + public Workflow getWorkflow(int workflowId) { + return em.find(Workflow.class, workflowId); + } + + public void createWorkspaceWorkflow(WorkspaceWorkflow workspaceWorkflow) { + em.persist(workspaceWorkflow); + em.flush(); + } + + public WorkspaceWorkflow getWorkspaceWorkflowTarget(String workspaceId, Workflow workflow) { + TypedQuery query = em.createQuery("SELECT w FROM WorkspaceWorkflow w WHERE w.workflow = :workflow AND w.workspace.id = :workspaceId", WorkspaceWorkflow.class); + try{ + return query.setParameter("workflow", workflow) + .setParameter("workspaceId", workspaceId) + .getSingleResult(); + }catch(NoResultException e){ + return null; + } + } + + public WorkspaceWorkflow getWorkspaceWorkflow(String workspaceId, String workspaceWorkflowId) { + return em.find(WorkspaceWorkflow.class, new WorkspaceWorkflowKey(workspaceId, workspaceWorkflowId)); + } + + public List getWorkspaceWorkflowList(String workspaceId) { + TypedQuery query = em.createQuery("SELECT w FROM WorkspaceWorkflow w WHERE w.workspace.id = :workspaceId", WorkspaceWorkflow.class); + try{ + return query.setParameter("workspaceId", workspaceId).getResultList(); + }catch(NoResultException e){ + return null; + } + } + + public void deleteWorkspaceWorkflow(WorkspaceWorkflow workspaceWorkflow) { + removeWorkflowConstraints(workspaceWorkflow); + em.remove(workspaceWorkflow); + em.flush(); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/WorkflowModelDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/WorkflowModelDAO.java new file mode 100644 index 0000000000..b81bae76f0 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/WorkflowModelDAO.java @@ -0,0 +1,137 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.dao; + +import com.docdoku.core.document.DocumentMasterTemplate; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.exceptions.WorkflowModelAlreadyExistsException; +import com.docdoku.core.exceptions.WorkflowModelNotFoundException; +import com.docdoku.core.product.PartMasterTemplate; +import com.docdoku.core.workflow.*; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import javax.persistence.TypedQuery; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class WorkflowModelDAO { + + private static final Logger LOGGER = Logger.getLogger(WorkflowModelDAO.class.getName()); + + + private EntityManager em; + private Locale mLocale; + + public WorkflowModelDAO(Locale pLocale, EntityManager pEM) { + em = pEM; + mLocale = pLocale; + } + + public void removeAllActivityModels(WorkflowModelKey pKey) throws WorkflowModelNotFoundException { + em.createQuery("DELETE FROM TaskModel t WHERE t.activityModel.workflowModel.id = :id AND t.activityModel.workflowModel.workspaceId = :workspaceId") + .setParameter("id", pKey.getId()) + .setParameter("workspaceId", pKey.getWorkspaceId()).executeUpdate(); + em.createQuery("DELETE FROM ActivityModel a WHERE a.workflowModel.id = :id AND a.workflowModel.workspaceId = :workspaceId") + .setParameter("id", pKey.getId()) + .setParameter("workspaceId", pKey.getWorkspaceId()).executeUpdate(); + } + + public void removeWorkflowModel(WorkflowModelKey pKey) throws WorkflowModelNotFoundException { + WorkflowModel model = loadWorkflowModel(pKey); + for(ActivityModel activity:model.getActivityModels()){ + activity.setRelaunchActivity(null); + } + em.flush(); + em.remove(model); + } + + public List findAllWorkflowModels(String pWorkspaceId) { + TypedQuery query = em.createQuery("SELECT DISTINCT w FROM WorkflowModel w WHERE w.workspaceId = :workspaceId",WorkflowModel.class); + return query.setParameter("workspaceId", pWorkspaceId).getResultList(); + } + + public void removeWorkflowModelConstraints(WorkflowModel pWorkflowModel){ + if(pWorkflowModel != null) { + List activityModels = pWorkflowModel.getActivityModels(); + for(ActivityModel activityModel:activityModels){ + activityModel.setRelaunchActivity(null); + } + em.flush(); + } + } + + public void createWorkflowModel(WorkflowModel pModel) throws WorkflowModelAlreadyExistsException, CreationException { + try { + //the EntityExistsException is thrown only when flush occurs + //Because ActivityModel has a generated id which is part of the TaskModel's PK + //we force generated it to avoid cache issue with the TaskModel. + List activityModels = pModel.getActivityModels(); + List> taskModels=new LinkedList<>(); + for(ActivityModel activityModel:activityModels){ + taskModels.add(activityModel.getTaskModels()); + activityModel.setTaskModels(new ArrayList<>()); + } + em.persist(pModel); + em.flush(); + int i=0; + for(ActivityModel activityModel:activityModels){ + activityModel.setTaskModels(taskModels.get(i++)); + } + } catch (EntityExistsException pEEEx) { + LOGGER.log(Level.FINEST,null,pEEEx); + throw new WorkflowModelAlreadyExistsException(mLocale, pModel); + } catch (PersistenceException pPEx) { + LOGGER.log(Level.FINEST,null,pPEx); + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } + + public WorkflowModel loadWorkflowModel(WorkflowModelKey pKey) throws WorkflowModelNotFoundException { + WorkflowModel model = em.find(WorkflowModel.class, pKey); + if (model == null) { + throw new WorkflowModelNotFoundException(mLocale, pKey.getId()); + } else { + return model; + } + } + + public boolean isInUseInDocumentMasterTemplate(WorkflowModel workflowModel) { + return !em.createNamedQuery("DocumentMasterTemplate.findWhereWorkflowModel", DocumentMasterTemplate.class) + .setParameter("workflowModel",workflowModel) + .getResultList() + .isEmpty(); + } + + public boolean isInUseInPartMasterTemplate(WorkflowModel workflowModel) { + return !em.createNamedQuery("PartMasterTemplate.findWhereWorkflowModel", PartMasterTemplate.class) + .setParameter("workflowModel",workflowModel) + .getResultList() + .isEmpty(); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/WorkspaceDAO.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/WorkspaceDAO.java new file mode 100644 index 0000000000..002669e8eb --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/dao/WorkspaceDAO.java @@ -0,0 +1,361 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.dao; + +import com.docdoku.core.common.*; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentLink; +import com.docdoku.core.document.DocumentMaster; +import com.docdoku.core.document.Folder; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartMaster; +import com.docdoku.core.product.PartSubstituteLink; +import com.docdoku.core.product.PartUsageLink; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.workflow.WorkflowModel; +import com.docdoku.core.workflow.WorkspaceWorkflow; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceException; +import java.io.IOException; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +public class WorkspaceDAO { + + + private EntityManager em; + private Locale mLocale; + private IDataManagerLocal dataManager; + + public WorkspaceDAO(Locale pLocale, EntityManager pEM) { + em=pEM; + mLocale=pLocale; + } + + public WorkspaceDAO(EntityManager pEM) { + em=pEM; + mLocale=Locale.getDefault(); + } + + public WorkspaceDAO(EntityManager pEM, IDataManagerLocal pDataManager) { + em=pEM; + dataManager = pDataManager; + } + + public void updateWorkspace(Workspace pWorkspace){ + em.merge(pWorkspace); + } + + public void createWorkspace(Workspace pWorkspace) throws WorkspaceAlreadyExistsException, CreationException, FolderAlreadyExistsException { + try{ + //the EntityExistsException is thrown only when flush occurs + em.persist(pWorkspace); + em.flush(); + new FolderDAO(mLocale, em).createFolder(new Folder(pWorkspace.getId())); + }catch(EntityExistsException pEEEx){ + throw new WorkspaceAlreadyExistsException(mLocale, pWorkspace); + }catch(PersistenceException pPEx){ + //EntityExistsException is case sensitive + //whereas MySQL is not thus PersistenceException could be + //thrown instead of EntityExistsException + throw new CreationException(mLocale); + } + } + + + public Workspace loadWorkspace(String pID) throws WorkspaceNotFoundException { + Workspace workspace=em.find(Workspace.class,pID); + if (workspace == null) { + throw new WorkspaceNotFoundException(mLocale, pID); + } else { + return workspace; + } + } + + public long getDiskUsageForWorkspace(String pWorkspaceId) { + Number result = (Number) em.createNamedQuery("BinaryResource.diskUsageInPath") + .setParameter("path", pWorkspaceId+"/%") + .getSingleResult(); + + return result != null ? result.longValue() : 0L; + } + + public List findWorkspacesWhereUserIsActive(String userLogin){ + return em.createNamedQuery("Workspace.findWorkspacesWhereUserIsActive", Workspace.class) + .setParameter("userLogin", userLogin) + .getResultList(); + } + + public void removeWorkspace(Workspace workspace) throws IOException, StorageException, EntityConstraintException, FolderNotFoundException { + + String workspaceId = workspace.getId(); + String pathToMatch = workspaceId.replace("_","\\_").replace("%","\\%")+"/%"; + + // Keep binaries in memory to delete them if google storage is the default storage provider + // We also could enhance the way we delete files by using gsutils from google api + List binaryResourcesInWorkspace = + em.createQuery("SELECT b FROM BinaryResource b where b.fullName LIKE :pathToMatch", BinaryResource.class) + .setParameter("pathToMatch",pathToMatch).getResultList(); + + // SharedEntities + em.createQuery("DELETE FROM SharedEntity s where s.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + + // Subscriptions + em.createQuery("DELETE FROM IterationChangeSubscription s where s.observedDocumentRevisionWorkspaceId = :workspaceId") + .setParameter("workspaceId",workspaceId).executeUpdate(); + + em.createQuery("DELETE FROM StateChangeSubscription s where s.observedDocumentRevisionWorkspaceId = :workspaceId") + .setParameter("workspaceId",workspaceId).executeUpdate(); + + // BaselinedPart + em.createQuery("DELETE FROM BaselinedPart bp where bp.targetPart.partRevision.partMasterWorkspaceId = :workspaceId") + .setParameter("workspaceId", workspaceId).executeUpdate(); + + // BaselinedDocument + em.createQuery("DELETE FROM BaselinedDocument bd where bd.targetDocument.documentRevision.documentMasterWorkspaceId = :workspaceId") + .setParameter("workspaceId", workspaceId).executeUpdate(); + + // ProductInstances + em.createQuery("DELETE FROM ProductInstanceIteration pii where pii.productInstanceMaster.instanceOf.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + em.createQuery("DELETE FROM ProductInstanceMaster pim where pim.instanceOf.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + // ProductBaselines + em.createQuery("DELETE FROM ProductBaseline b where b.configurationItem.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + // DocumentBaselines + em.createQuery("DELETE FROM DocumentBaseline b where b.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + + // Effectivity + em.createQuery("DELETE FROM Effectivity e where e.configurationItem.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + + // PartCollection + em.createQuery("DELETE FROM PartCollection pc where pc.author.workspaceId = :workspaceId") + .setParameter("workspaceId",workspaceId).executeUpdate(); + + // DocumentCollection + em.createQuery("DELETE FROM DocumentCollection dc where dc.author.workspaceId = :workspaceId") + .setParameter("workspaceId",workspaceId).executeUpdate(); + + // FoldereCollection + em.createQuery("DELETE FROM FolderCollection fc where fc.author.workspaceId = :workspaceId") + .setParameter("workspaceId",workspaceId).executeUpdate(); + + // Layers + em.createQuery("DELETE FROM Layer l where l.configurationItem.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + // Markers + em.createQuery("DELETE FROM Marker m where m.author.workspace = :workspace") + .setParameter("workspace", workspace).executeUpdate(); + + // ConfigurationItem + em.createQuery("DELETE FROM ConfigurationItem c where c.workspace = :workspace") + .setParameter("workspace", workspace).executeUpdate(); + + // DocumentMasterTemplate + em.createQuery("DELETE FROM DocumentMasterTemplate d where d.workspace = :workspace") + .setParameter("workspace", workspace).executeUpdate(); + + // PartMasterTemplate + em.createQuery("DELETE FROM PartMasterTemplate p where p.workspace = :workspace") + .setParameter("workspace", workspace).executeUpdate(); + + // Conversions + em.createQuery("DELETE FROM Conversion c where c.partIteration.partRevision.partMaster.workspace = :workspace") + .setParameter("workspace", workspace).executeUpdate(); + + // Notifications + em.createQuery("DELETE FROM ModificationNotification m where m.impactedPart.partRevision.partMaster.workspace = :workspace or m.modifiedPart.partRevision.partMaster.workspace = :workspace") + .setParameter("workspace", workspace).executeUpdate(); + + // Change order + em.createQuery("DELETE FROM ChangeOrder c where c.workspace = :workspace") + .setParameter("workspace", workspace).executeUpdate(); + + // Change requests + em.createQuery("DELETE FROM ChangeRequest c where c.workspace = :workspace") + .setParameter("workspace", workspace).executeUpdate(); + + // Change issues + em.createQuery("DELETE FROM ChangeIssue c where c.workspace = :workspace") + .setParameter("workspace", workspace).executeUpdate(); + + // Change issues / requests + em.createQuery("DELETE FROM Milestone m where m.workspace = :workspace") + .setParameter("workspace", workspace).executeUpdate(); + + // Clear all document links ... + List documentsIteration = + em.createQuery("SELECT d FROM DocumentIteration d WHERE d.documentRevision.documentMaster.workspace = :workspace", DocumentIteration.class) + .setParameter("workspace",workspace).getResultList(); + + List partsIteration = + em.createQuery("SELECT p FROM PartIteration p WHERE p.partRevision.partMaster.workspace = :workspace", PartIteration.class) + .setParameter("workspace",workspace).getResultList(); + + for (DocumentIteration d: documentsIteration) { + d.setLinkedDocuments(new HashSet()); + } + for (PartIteration p: partsIteration) { + p.setLinkedDocuments(new HashSet()); + for (PartUsageLink pul: p.getComponents()) { + pul.setSubstitutes(new LinkedList()); + } + p.setComponents(new LinkedList()); + } + em.flush(); + + // Clear all part substitute links + em.createQuery("DELETE FROM PartSubstituteLink psl WHERE psl.substitute.workspace = :workspace") + .setParameter("workspace", workspace).executeUpdate(); + + // Clear all part usage links + em.createQuery("DELETE FROM PartUsageLink pul WHERE pul.component.workspace = :workspace") + .setParameter("workspace", workspace).executeUpdate(); + + // Remove parents + List documentsMaster = + em.createQuery("SELECT d FROM DocumentMaster d WHERE d.workspace = :workspace", DocumentMaster.class) + .setParameter("workspace",workspace).getResultList(); + + WorkflowDAO workflowDAO = new WorkflowDAO(em); + for (DocumentMaster d: documentsMaster) { + workflowDAO.removeWorkflowConstraints(d); + em.remove(d); + } + em.flush(); + + + List partsMaster = + em.createQuery("SELECT p FROM PartMaster p WHERE p.workspace = :workspace", PartMaster.class) + .setParameter("workspace",workspace).getResultList(); + + for (PartMaster p: partsMaster) { + workflowDAO.removeWorkflowConstraints(p); + em.remove(p); + } + em.flush(); + + // Delete folders + em.createQuery("UPDATE Folder f SET f.parentFolder = NULL WHERE f.parentFolder.completePath = :workspaceId OR f.parentFolder.completePath LIKE :pathToMatch") + .setParameter("workspaceId",workspaceId) + .setParameter("pathToMatch",pathToMatch) + .executeUpdate(); + em.createQuery("DELETE FROM Folder f where f.completePath = :workspaceId OR f.completePath LIKE :pathToMatch") + .setParameter("workspaceId",workspaceId) + .setParameter("pathToMatch",pathToMatch) + .executeUpdate(); + em.flush(); + + List workflowModels = + em.createQuery("SELECT w FROM WorkflowModel w WHERE w.workspace = :workspace", WorkflowModel.class) + .setParameter("workspace",workspace).getResultList(); + + WorkflowModelDAO workflowModelDAO = new WorkflowModelDAO(new Locale("en"),em); + for (WorkflowModel w: workflowModels) { + workflowModelDAO.removeWorkflowModelConstraints(w); + em.remove(w); + } + em.flush(); + + List workspaceWorkflows = + em.createQuery("SELECT ww FROM WorkspaceWorkflow ww WHERE ww.workspace = :workspace", WorkspaceWorkflow.class) + .setParameter("workspace",workspace).getResultList(); + + for (WorkspaceWorkflow ww: workspaceWorkflows) { + workflowDAO.removeWorkflowConstraints(ww); + em.remove(ww); + } + + em.flush(); + + // Tags + em.createQuery("DELETE FROM Tag t where t.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + // Roles + em.createQuery("DELETE FROM Role r where r.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + + // LOV + em.createQuery("DELETE FROM ListOfValuesAttributeTemplate lovat where lovat.lov.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + em.createQuery("DELETE FROM ListOfValues lov where lov.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + + // Query + em.createQuery("DELETE FROM QueryContext qc where qc.workspaceId = :workspaceId") + .setParameter("workspaceId", workspaceId).executeUpdate(); + em.createQuery("DELETE FROM Query q where q.author.workspace = :workspace") + .setParameter("workspace", workspace).executeUpdate(); + + // WorkspaceUserGroupMembership + em.createQuery("DELETE FROM WorkspaceUserGroupMembership w where w.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + + // WorkspaceUserMembership + em.createQuery("DELETE FROM WorkspaceUserMembership w where w.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + + // User groups + em.createQuery("DELETE FROM UserGroup u where u.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + + List userGroups = + em.createQuery("SELECT u FROM UserGroup u WHERE u.workspace = :workspace",UserGroup.class) + .setParameter("workspace",workspace).getResultList(); + + for (UserGroup u: userGroups) { + u.setUsers(new HashSet()); + em.flush(); + em.remove(u); + } + + // Users + em.createQuery("DELETE FROM User u where u.workspace = :workspace") + .setParameter("workspace",workspace).executeUpdate(); + + // Finally delete the workspace + + em.flush(); + + em.remove(workspace); + + // Delete workspace files + dataManager.deleteWorkspaceFolder(workspaceId,binaryResourcesInWorkspace); + + em.flush(); + + } + + public List getAll() { + return em.createNamedQuery("Workspace.findAllWorkspaces", Workspace.class) + .getResultList(); + } +} + diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/documents/DocumentBaselineManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/documents/DocumentBaselineManagerBean.java new file mode 100644 index 0000000000..5073dea24f --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/documents/DocumentBaselineManagerBean.java @@ -0,0 +1,183 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.documents; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.configuration.BaselinedFolder; +import com.docdoku.core.configuration.DocumentBaseline; +import com.docdoku.core.configuration.FolderCollection; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.document.Folder; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDocumentBaselineManagerLocal; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.dao.DocumentBaselineDAO; +import com.docdoku.server.dao.DocumentRevisionDAO; +import com.docdoku.server.dao.FolderDAO; +import com.docdoku.server.dao.WorkspaceDAO; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) +@Local(IDocumentBaselineManagerLocal.class) +@Stateless(name = "DocumentBaselineManagerBean") +public class DocumentBaselineManagerBean implements IDocumentBaselineManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IDocumentManagerLocal documentService; + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentBaseline createBaseline(String workspaceId, String name, String description) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, FolderNotFoundException, UserNotActiveException, DocumentRevisionNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(workspaceId); + Workspace workspace = new WorkspaceDAO(new Locale(user.getLanguage()), em).loadWorkspace(workspaceId); + DocumentBaseline baseline = new DocumentBaseline(workspace,name,description); + new DocumentBaselineDAO(em, new Locale(user.getLanguage())).createBaseline(baseline); + snapshotAllFolders(baseline,workspaceId); + snapshotAllDocuments(baseline,workspaceId); + return baseline; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getBaselines(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + DocumentBaselineDAO documentBaselineDAO = new DocumentBaselineDAO(em, new Locale(user.getLanguage())); + return documentBaselineDAO.findBaselines(workspaceId); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteBaseline(int baselineId) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, BaselineNotFoundException, UserNotActiveException { + DocumentBaseline documentBaseline = getBaseline(baselineId); + User user = userManager.checkWorkspaceWriteAccess(documentBaseline.getWorkspace().getId()); + new DocumentBaselineDAO(em, new Locale(user.getLanguage())).deleteBaseline(documentBaseline); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentBaseline getBaseline(int baselineId) throws BaselineNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + DocumentBaseline documentBaseline = new DocumentBaselineDAO(em).loadBaseline(baselineId); + userManager.checkWorkspaceReadAccess(documentBaseline.getWorkspace().getId()); + return documentBaseline; + } + + private void fillBaselineFolder(DocumentBaseline baseline, String folderPath) throws FolderNotFoundException, WorkspaceNotFoundException, UserNotActiveException, UserNotFoundException{ + // Ignore already existing folder + if(baseline.hasBasedLinedFolder(folderPath)){ + return; + } + // Add current folder + FolderDAO folderDAO = new FolderDAO(em); + + Folder currentFolder = folderDAO.loadFolder(folderPath); + BaselinedFolder baselinedFolder = new BaselinedFolder(baseline.getFolderCollection(),currentFolder); + baseline.addBaselinedFolder(baselinedFolder); + + // Add all subFolders + Folder[] subFolders = folderDAO.getSubFolders(folderPath); + for(Folder subFolder : subFolders){ + fillBaselineFolder(baseline, subFolder.getCompletePath()); + } + } + + private void fillBaselineDocument(DocumentBaseline baseline, List revisionKeys) throws DocumentRevisionNotFoundException, FolderNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + // Add all document + for(DocumentRevisionKey revisionKey : revisionKeys){ + User user = userManager.checkWorkspaceReadAccess(revisionKey.getDocumentMaster().getWorkspace()); + + // Ignore already existing document + if(baseline.hasBaselinedDocument(revisionKey)){ + break; + } + + DocumentRevision documentRevision = new DocumentRevisionDAO(em).loadDocR(revisionKey); + documentRevision = filterDocumentRevisionBaselinable(user, documentRevision); + // Document non accessible + if(documentRevision==null){ + break; + } + + DocumentIteration documentIteration = documentRevision.getLastIteration(); + if(documentIteration!=null){ + baseline.addBaselinedDocument(documentIteration); + } + } + } + + private void snapshotAllFolders(DocumentBaseline baseline, String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, FolderNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + FolderCollection collection = baseline.getFolderCollection(); + collection.setCreationDate(new Date()); + collection.setAuthor(user); + fillBaselineFolder(baseline, workspaceId); + } + + private void snapshotAllDocuments(DocumentBaseline baseline, String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, FolderNotFoundException { + userManager.checkWorkspaceReadAccess(workspaceId); + DocumentRevision[] documentRevisions = documentService.getAllDocumentsInWorkspace(workspaceId); + List revisionKeyList = new ArrayList<>(); + for(DocumentRevision documentRevision : documentRevisions){ + revisionKeyList.add(documentRevision.getKey()); + } + + fillBaselineDocument(baseline, revisionKeyList); + } + + private DocumentRevision filterDocumentRevisionAccessRight(User user, DocumentRevision documentRevision){ + if(!user.isAdministrator() + && (documentRevision.getACL()!=null) + && !(documentRevision.getACL().hasReadAccess(user))) { + return null; + } + return documentRevision; + } + + private DocumentRevision filterDocumentRevisionBaselinable(User user, DocumentRevision documentRevision){ + DocumentRevision documentFiltered =filterDocumentRevisionAccessRight(user,documentRevision); + + if (documentFiltered!= null && documentFiltered.isCheckedOut()) { + em.detach(documentFiltered); + documentFiltered.removeLastIteration(); + } + return documentFiltered; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/documents/DocumentConfigSpecManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/documents/DocumentConfigSpecManagerBean.java new file mode 100644 index 0000000000..8aefef4f39 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/documents/DocumentConfigSpecManagerBean.java @@ -0,0 +1,209 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.documents; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.BaselinedFolder; +import com.docdoku.core.configuration.BaselinedFolderKey; +import com.docdoku.core.configuration.DocumentBaseline; +import com.docdoku.core.configuration.DocumentConfigSpec; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.document.Folder; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.meta.TagKey; +import com.docdoku.core.query.DocumentSearchQuery; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDocumentBaselineManagerLocal; +import com.docdoku.core.services.IDocumentConfigSpecManagerLocal; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.configuration.spec.BaselineDocumentConfigSpec; +import com.docdoku.server.configuration.spec.LatestDocumentConfigSpec; +import com.docdoku.server.dao.BaselinedDocumentDAO; +import com.docdoku.server.dao.BaselinedFolderDAO; +import com.docdoku.server.dao.FolderDAO; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.*; + +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) +@Local(IDocumentConfigSpecManagerLocal.class) +@Stateless(name = "DocumentConfigSpecManagerBean") +public class DocumentConfigSpecManagerBean implements IDocumentConfigSpecManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IDocumentBaselineManagerLocal documentBaselineService; + + @Inject + private IDocumentManagerLocal documentService; + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentConfigSpec getLatestConfigSpec(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + return new LatestDocumentConfigSpec(user); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentConfigSpec getConfigSpecForBaseline(int baselineId) throws BaselineNotFoundException, WorkspaceNotFoundException, UserNotActiveException, UserNotFoundException { + DocumentBaseline documentBaseline = documentBaselineService.getBaseline(baselineId); + User user = userManager.checkWorkspaceReadAccess(documentBaseline.getWorkspace().getId()); + return new BaselineDocumentConfigSpec(documentBaseline,user); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public String[] getFilteredFolders(String workspaceId, DocumentConfigSpec cs, String completePath) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + String[] shortNames; + if(cs!=null&& cs instanceof BaselineDocumentConfigSpec){ + int collectionId = ((BaselineDocumentConfigSpec) cs).getDocumentBaseline().getFolderCollection().getId(); + BaselinedFolderKey key = new BaselinedFolderKey(collectionId,completePath); + List subFolders = new BaselinedFolderDAO(locale, em).getSubFolders(key); + shortNames = new String[subFolders.size()]; + int i = 0; + for (BaselinedFolder f : subFolders) { + shortNames[i++] = f.getShortName(); + } + }else{ + Folder[] subFolders = new FolderDAO(locale, em).getSubFolders(completePath); + shortNames = new String[subFolders.length]; + int i = 0; + for (Folder f : subFolders) { + shortNames[i++] = f.getShortName(); + } + } + return shortNames; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision[] getFilteredDocuments(String workspaceId, DocumentConfigSpec cs, int start, int pMaxResults) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException { + userManager.checkWorkspaceReadAccess(workspaceId); + List docRs = Arrays.asList(documentService.getAllDocumentsInWorkspace(workspaceId, start, pMaxResults)); + List documentRevisionList = new ArrayList<>(docRs); + return filterDocumentRevisionList(cs, documentRevisionList).toArray(new DocumentRevision[docRs.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision[] getFilteredDocumentsByFolder(String workspaceId, DocumentConfigSpec cs, String completePath) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + if(cs instanceof BaselineDocumentConfigSpec){ + return getFilteredDocumentsByFolder((BaselineDocumentConfigSpec) cs,completePath,user); + }else{ + return documentService.findDocumentRevisionsByFolder(completePath); + } + } + + private DocumentRevision[] getFilteredDocumentsByFolder(BaselineDocumentConfigSpec cs, String completePath, User user) { + BaselinedFolderKey key = new BaselinedFolderKey(cs.getFolderCollectionId(),completePath); + List baselinedDocuments = new BaselinedDocumentDAO(new Locale(user.getLanguage()),em).findDocRsByFolder(key); + List returnList = new ArrayList<>(); + for(DocumentIteration baselinedDocument : baselinedDocuments){ + DocumentRevision docR = filterDocumentRevisionAccessRight(user,baselinedDocument.getDocumentRevision()); + returnList.add(docR); + } + return returnList.toArray(new DocumentRevision[returnList.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision[] getFilteredDocumentsByTag(String workspaceId, DocumentConfigSpec cs, TagKey tagKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException { + userManager.checkWorkspaceReadAccess(workspaceId); + List docRs = Arrays.asList(documentService.findDocumentRevisionsByTag(tagKey)); + List documentRevisionList = new ArrayList<>(docRs); + return filterDocumentRevisionList(cs, documentRevisionList).toArray(new DocumentRevision[docRs.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision[] searchFilteredDocuments(String workspaceId, DocumentConfigSpec cs, DocumentSearchQuery pQuery) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, ESServerException { + userManager.checkWorkspaceReadAccess(workspaceId); + List docRs = Arrays.asList(documentService.searchDocumentRevisions(pQuery)); + List documentRevisionList = new ArrayList<>(docRs); + return filterDocumentRevisionList(cs, documentRevisionList).toArray(new DocumentRevision[docRs.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public DocumentRevision getFilteredDocumentRevision(DocumentRevisionKey documentRevisionKey, DocumentConfigSpec configSpec) throws AccessRightException, NotAllowedException, WorkspaceNotFoundException, UserNotFoundException, DocumentRevisionNotFoundException, UserNotActiveException { + DocumentRevision docR = documentService.getDocumentRevision(documentRevisionKey); + docR = filterDocumentRevision(configSpec,docR); + return docR; + } + + private List filterDocumentRevisionList(DocumentConfigSpec configSpec, List pDocumentRs) throws DocumentRevisionNotFoundException { + List returnList = new ArrayList<>(); + for(DocumentRevision documentRevision : pDocumentRs){ + returnList.add(filterDocumentRevision(configSpec, documentRevision)); + } + return returnList; + } + + private DocumentRevision filterDocumentRevision(DocumentConfigSpec configSpec, DocumentRevision documentRevision) throws DocumentRevisionNotFoundException { + Locale locale = Locale.getDefault(); + if(documentRevision==null){ + throw new DocumentRevisionNotFoundException(""); + } + + DocumentIteration docI = configSpec.filter(documentRevision); + if(docI!=null){ + em.detach(documentRevision); + + if(documentRevision.getNumberOfIterations() > 1){ + documentRevision.removeFollowingIterations(docI.getIteration()); + } + documentRevision.setCheckOutDate(null); + documentRevision.setCheckOutUser(null); + documentRevision.setWorkflow(null); + documentRevision.setTags(new HashSet()); + return documentRevision; + } + + throw new DocumentRevisionNotFoundException(locale,documentRevision.getKey()); + } + + private DocumentRevision filterDocumentRevisionAccessRight(User user, DocumentRevision documentRevision){ + if(!user.isAdministrator() + && (documentRevision.getACL()!=null) + && !(documentRevision.getACL().hasReadAccess(user))) { + return null; + } + return documentRevision; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/documents/DocumentWorkflowManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/documents/DocumentWorkflowManagerBean.java new file mode 100644 index 0000000000..d863ed4a8f --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/documents/DocumentWorkflowManagerBean.java @@ -0,0 +1,202 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.documents; + +import com.docdoku.core.common.User; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.gcm.GCMAccount; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.*; +import com.docdoku.core.workflow.Activity; +import com.docdoku.core.workflow.Task; +import com.docdoku.core.workflow.TaskKey; +import com.docdoku.core.workflow.Workflow; +import com.docdoku.server.CheckActivity; +import com.docdoku.server.dao.DocumentRevisionDAO; +import com.docdoku.server.dao.SubscriptionDAO; +import com.docdoku.server.dao.TaskDAO; +import com.docdoku.server.dao.WorkflowDAO; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.Collection; +import java.util.List; +import java.util.Locale; + +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID,UserGroupMapping.ADMIN_ROLE_ID,UserGroupMapping.GUEST_PROXY_ROLE_ID}) +@Local(IDocumentWorkflowManagerLocal.class) +@Stateless(name = "DocumentWorkflowManagerBean") +public class DocumentWorkflowManagerBean implements IDocumentWorkflowManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IDocumentManagerLocal documentManager; + + @Inject + private IMailerLocal mailer; + + @Inject + private IGCMSenderLocal gcmNotifier; + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Workflow getCurrentWorkflow(DocumentRevisionKey documentRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, AccessRightException, WorkflowNotFoundException { + User user = userManager.checkWorkspaceReadAccess(documentRevisionKey.getDocumentMaster().getWorkspace()); + if(!documentManager.canUserAccess(user, documentRevisionKey)) { + throw new AccessRightException(new Locale(user.getLanguage()), user); + } + + Locale locale = new Locale(user.getLanguage()); + DocumentRevision docR = new DocumentRevisionDAO(locale, em).loadDocR(documentRevisionKey); + return docR.getWorkflow(); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Workflow[] getAbortedWorkflow(DocumentRevisionKey documentRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, DocumentRevisionNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(documentRevisionKey.getDocumentMaster().getWorkspace()); + if(!documentManager.canUserAccess(user, documentRevisionKey)) { + throw new AccessRightException(new Locale(user.getLanguage()), user); + } + + Locale locale = new Locale(user.getLanguage()); + DocumentRevision docR = new DocumentRevisionDAO(locale, em).loadDocR(documentRevisionKey); + List abortedWorkflows= docR.getAbortedWorkflows(); + return abortedWorkflows.toArray(new Workflow[abortedWorkflows.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @CheckActivity + @Override + public DocumentRevision approveTaskOnDocument(String pWorkspaceId, TaskKey pTaskKey, String pComment, String pSignature) throws WorkspaceNotFoundException, TaskNotFoundException, NotAllowedException, UserNotFoundException, UserNotActiveException, WorkflowNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + + Task task = new TaskDAO(new Locale(user.getLanguage()), em).loadTask(pTaskKey); + Workflow workflow = task.getActivity().getWorkflow(); + DocumentRevision docR = checkTaskAccess(user,task); + + int previousStep = workflow.getCurrentStep(); + task.approve(user, pComment, docR.getLastIteration().getIteration(), pSignature); + int currentStep = workflow.getCurrentStep(); + + if (previousStep != currentStep){ + SubscriptionDAO subscriptionDAO = new SubscriptionDAO(em); + + User[] subscribers = subscriptionDAO.getStateChangeEventSubscribers(docR); + if (subscribers.length != 0) { + mailer.sendStateNotification(subscribers, docR); + } + + GCMAccount[] gcmAccounts = subscriptionDAO.getStateChangeEventSubscribersGCMAccount(docR); + if (gcmAccounts.length != 0) { + gcmNotifier.sendStateNotification(gcmAccounts, docR); + } + } + + Collection runningTasks = workflow.getRunningTasks(); + for (Task runningTask : runningTasks) { + runningTask.start(); + } + mailer.sendApproval(runningTasks, docR); + return docR; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @CheckActivity + @Override + public DocumentRevision rejectTaskOnDocument(String pWorkspaceId, TaskKey pTaskKey, String pComment, String pSignature) throws WorkspaceNotFoundException, TaskNotFoundException, NotAllowedException, UserNotFoundException, UserNotActiveException, WorkflowNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + + Task task = new TaskDAO(new Locale(user.getLanguage()), em).loadTask(pTaskKey); + DocumentRevision docR = checkTaskAccess(user,task); + + task.reject(user, pComment, docR.getLastIteration().getIteration(), pSignature); + + // Relaunch Workflow ? + Activity currentActivity = task.getActivity(); + Activity relaunchActivity = currentActivity.getRelaunchActivity(); + + if(currentActivity.isStopped() && relaunchActivity != null){ + relaunchWorkflow(docR,relaunchActivity.getStep()); + + // Send mails for running tasks + mailer.sendApproval(docR.getWorkflow().getRunningTasks(), docR); + // Send notification for relaunch + mailer.sendDocumentRevisionWorkflowRelaunchedNotification(docR); + } + return docR; + } + + /** + * Check if a user can approve or reject a task + * @param user The specific user + * @param task The specific task + * @return The document concern by the task + * @throws WorkflowNotFoundException If no workflow was find for this task + * @throws NotAllowedException If you can not make this task + */ + private DocumentRevision checkTaskAccess(User user,Task task) throws WorkflowNotFoundException, NotAllowedException { + Locale locale = new Locale(user.getLanguage()); + Workflow workflow = task.getActivity().getWorkflow(); + DocumentRevision docR = new WorkflowDAO(em).getDocumentTarget(workflow); + if(docR == null){ + throw new WorkflowNotFoundException(locale,workflow.getId()); + } + if(!task.isInProgress()){ + throw new NotAllowedException(locale,"NotAllowedException15"); + } + if (!task.isPotentialWorker(user)) { + throw new NotAllowedException(locale, "NotAllowedException14"); + } + if (!workflow.getRunningTasks().contains(task)) { + throw new NotAllowedException(locale, "NotAllowedException15"); + } + if (docR.isCheckedOut()) { + throw new NotAllowedException(locale, "NotAllowedException16"); + } + return docR; + } + + private void relaunchWorkflow(DocumentRevision docR, int activityStep){ + Workflow workflow = docR.getWorkflow(); + // Clone new workflow + Workflow relaunchedWorkflow = new WorkflowDAO(em).duplicateWorkflow(workflow); + + // Move aborted workflow in docR list + workflow.abort(); + docR.addAbortedWorkflows(workflow); + // Set new workflow on document + docR.setWorkflow(relaunchedWorkflow); + // Reset some properties + relaunchedWorkflow.relaunch(activityStep); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESIndexer.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESIndexer.java new file mode 100644 index 0000000000..d2b1062f93 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESIndexer.java @@ -0,0 +1,541 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.esindexer; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentMaster; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartMaster; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.services.IAccountManagerLocal; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.services.IMailerLocal; +import com.docdoku.server.dao.DocumentMasterDAO; +import com.docdoku.server.dao.PartMasterDAO; +import com.docdoku.server.dao.WorkspaceDAO; +import org.elasticsearch.ElasticsearchIllegalArgumentException; +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.bulk.BulkRequestBuilder; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.delete.DeleteRequestBuilder; +import org.elasticsearch.action.update.UpdateRequestBuilder; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.transport.NoNodeAvailableException; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.indices.IndexAlreadyExistsException; +import org.elasticsearch.indices.InvalidIndexNameException; + +import javax.annotation.Resource; +import javax.ejb.Asynchronous; +import javax.ejb.SessionContext; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Index Method using ElasticSearch API. + * + * @author Taylor LABEJOF + */ +@Stateless(name = "ESIndexer") +public class ESIndexer { + + @PersistenceContext + private EntityManager em; + + @Resource + private SessionContext ctx; + + @Inject + private IDataManagerLocal dataManager; + + @Inject + private IAccountManagerLocal accountManager; + + @Inject + private IMailerLocal mailer; + + @Inject + private Client client; + + private static final String CONF_PROPERTIES = "/com/docdoku/server/esindexer/conf.properties"; + private static final Properties CONF = new Properties(); + private static final String I18N_CONF = "com.docdoku.core.i18n.LocalStrings"; + private static final Logger LOGGER = Logger.getLogger(ESIndexer.class.getName()); + + private static final String ES_INDEX_FAIL = " indexing failed.\n Cause by : "; + private static final String ES_INDEX_ERROR_1 = "ES_IndexError1"; + private static final String ES_INDEX_ERROR_2 = "ES_IndexError2"; + private static final String ES_INDEX_ERROR_3 = "ES_IndexError3"; + private static final String ES_INDEX_CREATION_ERROR_2 = "ES_IndexCreationError2"; + private static final String ES_DELETE_ERROR_1 = "ES_DeleteError1"; + + static { + try (InputStream inputStream = ESIndexer.class.getResourceAsStream(CONF_PROPERTIES)) { + CONF.load(inputStream); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + + /** + * Constructor + */ + public ESIndexer() { + super(); + } + + private void generateIndex(String pIndex) throws ESIndexAlreadyExistsException, ESIndexNamingException { + try { + client.admin().indices().prepareCreate(pIndex) + .setSettings(ImmutableSettings.settingsBuilder() + .put("number_of_shards", CONF.getProperty("number_of_shards")) + .put("number_of_replicas", CONF.getProperty("number_of_replicas")) + .put("auto_expand_replicas", CONF.getProperty("auto_expand_replicas"))) + .addMapping(ESMapper.PART_TYPE,this.partMapping()) + .addMapping(ESMapper.DOCUMENT_TYPE, this.docMapping()) + .setSource(defaultMapping()) + .execute().actionGet(); + } catch (IndexAlreadyExistsException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString("ES_IndexCreationError1"); + LOGGER.log(Level.FINEST, logMessage + " " + pIndex, e); + throw new ESIndexAlreadyExistsException(Locale.getDefault()); + } catch (InvalidIndexNameException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_INDEX_CREATION_ERROR_2); + LOGGER.log(Level.INFO, logMessage + " " + pIndex, e); + throw new ESIndexNamingException(Locale.getDefault()); + } catch(IOException e) { + LOGGER.log(Level.ALL,"Error on mapping creation" + pIndex,e); + } + } + + private XContentBuilder partMapping() throws IOException { + XContentBuilder tmp = XContentFactory.jsonBuilder().startObject(); + tmp.startObject(ESMapper.PART_TYPE); + tmp = commonMapping(tmp); + tmp.endObject(); + tmp.endObject(); + return tmp; + } + + private XContentBuilder defaultMapping() throws IOException { + XContentBuilder tmp = XContentFactory.jsonBuilder().startObject(); + tmp.startObject("mappings"); + tmp.startObject("_default_"); + tmp.startObject("_all"); + tmp.field("enabled","true"); + tmp.endObject(); + tmp.startArray("dynamic_templates"); + tmp.startObject(); + //All field with the name content should be analyzed for full text search + tmp.startObject("content_string"); + tmp.field("match",ESMapper.CONTENT_KEY); + tmp.field("match_mapping_type","string"); + tmp.startObject("mapping"); + tmp.field("type","string"); + tmp.field("index","analyzed"); + tmp.endObject(); + tmp.endObject(); + tmp.endObject(); + tmp.startObject(); + //set by default all the field as not_analyzed. + // data won't be flatten, term filter/query will be possible. + tmp.startObject("default_string"); + tmp.field("match","*"); + tmp.field("match_mapping_type","string"); + tmp.startObject("mapping"); + tmp.field("type","string"); + tmp.field("index","not_analyzed"); + tmp.endObject(); + tmp.endObject(); + tmp.endObject(); + tmp.endArray(); + tmp.endObject(); + + tmp.endObject(); + tmp.endObject(); + return tmp; + } + + private XContentBuilder docMapping() throws IOException { + XContentBuilder tmp = XContentFactory.jsonBuilder().startObject(); + tmp.startObject(ESMapper.DOCUMENT_TYPE); + tmp = commonMapping(tmp); + tmp.endObject(); + tmp.endObject(); + return tmp; + } + + private XContentBuilder commonMapping(XContentBuilder tmp) throws IOException { + tmp.startObject("properties") + .startObject(ESMapper.ITERATIONS_KEY) + .startObject("properties") + .startObject(ESMapper.ATTRIBUTES_KEY) + .field("type","nested") + .startObject("properties"); + //map the attributes values as non analyzed, string will not be decomposed + tmp.startObject(ESMapper.ATTRIBUTE_VALUE); + tmp.field("type","string"); + tmp.field("index", "not_analyzed"); + tmp.endObject(); + tmp.endObject(); + tmp.endObject(); + tmp.endObject(); + tmp.endObject(); + tmp.endObject(); + + return tmp; + } + + private void tryCreateIndex(String pIndex) throws ESIndexNamingException { + try { + generateIndex(pIndex); + } catch (ESIndexAlreadyExistsException e) { + LOGGER.log(Level.FINEST, null, e); + } + } + + private void deleteIndex(String pIndex, Client pClient) { + pClient.admin().indices().prepareDelete(pIndex) + .execute().actionGet(); + } + + /** + * Create a new index in ElasticSearch + * + * @param workspaceId The name of the new index. It should be a workspace name. + * @throws ESServerException If a problem occur with the ElasticSearch server. + * @throws ESIndexAlreadyExistsException If the index already exist. + * @throws ESIndexNamingException If the name doesn't suit for indexing. + */ + public void createIndex(String workspaceId) throws ESServerException, ESIndexAlreadyExistsException, ESIndexNamingException { + try { + generateIndex(ESTools.formatIndexName(workspaceId)); + } catch (NoNodeAvailableException e) { + LOGGER.log(Level.WARNING, "Cannot create index for : " + workspaceId + " : : The ElasticSearch server doesn't seem to respond"); + } + } + + /** + * Delete a index in ElasticSearch + * + * @param workspaceId The name of the index to delete. + */ + public void deleteIndex(String workspaceId) { + try { + client.admin().indices().prepareDelete(ESTools.formatIndexName(workspaceId)) + .execute().actionGet(); + } catch (NoNodeAvailableException e) { + LOGGER.log(Level.WARNING, "Cannot delete index : The ElasticSearch server doesn't seem to respond"); + } + } + + /** + * Index all content in all workspace + */ + @Asynchronous + public void indexAll() { + try { + BulkRequestBuilder bulkRequest = client.prepareBulk(); + WorkspaceDAO wDAO = new WorkspaceDAO(em); + + for (Workspace w : wDAO.getAll()) { + bulkRequest = bulkWorkspaceRequestBuilder(bulkRequest, w.getId(), true); + } + + BulkResponse bulkResponse = bulkRequest.execute().actionGet(); + + if (bulkResponse.hasFailures()) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_INDEX_ERROR_3); + LOGGER.log(Level.WARNING, logMessage + " \n " + bulkResponse.buildFailureMessage()); + } + } catch (NoNodeAvailableException | ESIndexNamingException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_INDEX_ERROR_2); + LOGGER.log(Level.WARNING, logMessage); + } + } + + /** + * Index all resources in this workspace + * + * @param workspaceId Workspace to index + */ + @Asynchronous + public void indexWorkspace(String workspaceId) { + String failureMessage = ""; + boolean hasSuccess = true; + + try { + BulkRequestBuilder bulkRequest = client.prepareBulk(); + bulkRequest = bulkWorkspaceRequestBuilder(bulkRequest, workspaceId, false); + BulkResponse bulkResponse = bulkRequest.execute().actionGet(); + + if (bulkResponse.hasFailures()) { + hasSuccess = false; + failureMessage = bulkResponse.buildFailureMessage(); + } + } catch (ActionRequestValidationException e) { + hasSuccess = false; + failureMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString("ES_IndexError4"); + } catch (ESIndexNamingException e) { + hasSuccess = false; + failureMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_INDEX_CREATION_ERROR_2) + " " + workspaceId; + } catch (NoNodeAvailableException e) { + hasSuccess = false; + failureMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_INDEX_ERROR_2); + } + + if (!hasSuccess) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_INDEX_ERROR_3); + LOGGER.log(Level.WARNING, logMessage + " \n " + failureMessage); + } else { + LOGGER.log(Level.INFO, "The workspace " + workspaceId + " has been indexed"); + } + sendNotification(workspaceId,hasSuccess,failureMessage); + } + + /** + * Index a documentIteration in ElasticSearch Cluster + * + * @param doc The document iteration to index + */ + @Asynchronous + public void index(DocumentIteration doc) { + String workspaceId = doc.getWorkspaceId(); + try { + tryCreateIndex(ESTools.formatIndexName(workspaceId)); + indexRequest(doc).execute() + .actionGet(); + } catch (NoNodeAvailableException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_INDEX_ERROR_1); + LOGGER.log(Level.WARNING, doc + ES_INDEX_FAIL + logMessage); + } catch (ESIndexNamingException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_INDEX_CREATION_ERROR_2); + LOGGER.log(Level.WARNING, doc + ES_INDEX_FAIL + logMessage + " " + workspaceId, e); + } catch (ElasticsearchIllegalArgumentException e){ + LOGGER.log(Level.SEVERE, ES_INDEX_FAIL + e.getMessage() , e); + } + } + + /** + * Index a partIteration in ElasticSearch Cluster + * + * @param part The part iteration to index + */ + @Asynchronous + public void index(PartIteration part) { + String workspaceId = part.getWorkspaceId(); + try { + tryCreateIndex(ESTools.formatIndexName(workspaceId)); + indexRequest(part).execute() + .actionGet(); + } catch (NoNodeAvailableException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_INDEX_ERROR_1); + LOGGER.log(Level.WARNING, part + ES_INDEX_FAIL + logMessage); + } catch (ESIndexNamingException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_INDEX_CREATION_ERROR_2); + LOGGER.log(Level.WARNING, part + ES_INDEX_FAIL + logMessage + " " + workspaceId, e); + } catch (ElasticsearchIllegalArgumentException e){ + LOGGER.log(Level.SEVERE, ES_INDEX_FAIL + e.getMessage(), e); + } + } + + /** + * Remove this docIteration from ElasticSearch Cluster + * + * @param doc The document iteration to remove from index + */ + @Asynchronous + public void delete(DocumentIteration doc) { + try { + deleteRequest(doc).execute() + .actionGet(); + } catch (NoNodeAvailableException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_DELETE_ERROR_1); + LOGGER.log(Level.WARNING, logMessage); + } + } + + /** + * Remove this partIteration from ElasticSearch Cluster + * + * @param part The part iteration to remove from index + */ + @Asynchronous + public void delete(PartIteration part) { + try { + deleteRequest(part).execute() + .actionGet(); + } catch (NoNodeAvailableException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_DELETE_ERROR_1); + LOGGER.log(Level.WARNING, logMessage); + } + } + + /** + * Delete index for all resources in this workspace + * + * @param workspaceId Workspace to delete + */ + @Asynchronous + public void deleteWorkspace(String workspaceId) { + String failureMessage = ""; + boolean hasSuccess = true; + try { + deleteIndex(workspaceId); + } catch (NoNodeAvailableException e) { + hasSuccess = false; + failureMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString("ES_DeleteError2"); + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString("ES_DeleteError3"); + LOGGER.log(Level.WARNING, logMessage + " \n " + failureMessage); + } + sendNotification(workspaceId,hasSuccess,failureMessage); + } + + private void sendNotification(String workspaceId, boolean hasSuccess, String failureMessage) { + try { + String login = ctx.getCallerPrincipal().getName(); + Account account = accountManager.getAccount(login); + mailer.sendIndexerResult(account, workspaceId, hasSuccess, failureMessage); + } catch (AccountNotFoundException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString("ES_MailError1"); + LOGGER.log(Level.SEVERE, logMessage, e); + } + } + + private BulkRequestBuilder bulkWorkspaceRequestBuilder(BulkRequestBuilder pBulkRequest, String workspaceId, boolean silent) throws ESIndexNamingException { + BulkRequestBuilder bulkRequest = pBulkRequest; + try { + generateIndex(ESTools.formatIndexName(workspaceId)); + } catch (ESIndexAlreadyExistsException e) { + LOGGER.log(Level.WARNING, "Cannot generate Workspace index : already exists"); + } catch (ESIndexNamingException e) { + LOGGER.log(Level.SEVERE, null, e); + if (!silent) { + throw e; + } + } + + bulkRequest = bulkDocumentsIndexRequestBuilder(bulkRequest, workspaceId); + bulkRequest = bulkPartsIndexRequestBuilder(bulkRequest, workspaceId); + + return bulkRequest; + } + + private BulkRequestBuilder bulkDocumentsIndexRequestBuilder(BulkRequestBuilder pBulkRequest, String workspaceId) { + DocumentMasterDAO docMasterDAO = new DocumentMasterDAO(em); + for (DocumentMaster docM : docMasterDAO.getAllByWorkspace(workspaceId)) { + for (DocumentRevision docR : docM.getDocumentRevisions()) { + pBulkRequest.add(indexRequest(docR.getLastIteration())); + } + } + return pBulkRequest; + } + + private BulkRequestBuilder bulkPartsIndexRequestBuilder(BulkRequestBuilder pBulkRequest, String workspaceId) { + PartMasterDAO partMasterDAO = new PartMasterDAO(em); + for (PartMaster partMaster : partMasterDAO.getAllByWorkspace(workspaceId)) { + for (PartRevision partRev : partMaster.getPartRevisions()) { + pBulkRequest.add(indexRequest(partRev.getLastIteration())); + } + } + return pBulkRequest; + } + + + /** + * Get the Index request for a documentIteration in ElasticSearch Cluster + * + * @param doc The document iteration to index + */ + private UpdateRequestBuilder indexRequest(DocumentIteration doc) throws NoNodeAvailableException { + Map binaryList = new HashMap<>(); + for (BinaryResource bin : doc.getAttachedFiles()) { + try (InputStream in = dataManager.getBinaryResourceInputStream(bin)){ + binaryList.put(bin.getName(), ESTools.streamToString(bin.getFullName(), in)); + } catch (StorageException | IOException e) { + LOGGER.log(Level.SEVERE, "Cannot read file " + bin.getFullName(), e); + } + } + XContentBuilder jsonDoc = ESMapper.documentRevisionToJSON(doc, binaryList); + Map params = ESMapper.docIterationMap(doc, binaryList); + return client.prepareUpdate(ESTools.formatIndexName(doc.getWorkspaceId()), ESMapper.DOCUMENT_TYPE, doc.getDocumentRevisionKey().toString()) + .setScript("ctx._source.iterations += iteration") + .addScriptParam("iteration", params) + .setUpsert(jsonDoc); + } + + /** + * Get the Index request for a partIteration in ElasticSearch Cluster + * + * @param part The part iteration to index + */ + private UpdateRequestBuilder indexRequest(PartIteration part) { + Map binaryList = new HashMap<>(); + for(BinaryResource bin : part.getAttachedFiles()) { + try (InputStream in = dataManager.getBinaryResourceInputStream(bin)){ + binaryList.put(bin.getName(),ESTools.streamToString(bin.getFullName(), in)); + } catch (StorageException | IOException e) { + LOGGER.log(Level.SEVERE, "Cannot read file " + bin.getFullName(), e); + } + } + XContentBuilder json = ESMapper.partRevisionToJson(part, binaryList); + Map params = ESMapper.partIterationMap(part, binaryList); + return client + .prepareUpdate(ESTools.formatIndexName(part.getWorkspaceId()), + ESMapper.PART_TYPE, part.getPartRevisionKey().toString()) + .setScript("ctx._source.iterations += iteration") + .addScriptParam("iteration", params) + .setUpsert(json); + } + + /** + * Get the Delete request for a documentIteration in ElasticSearch Cluster + * + * @param doc The document iteration to delete + */ + private DeleteRequestBuilder deleteRequest(DocumentIteration doc) throws NoNodeAvailableException { + return client.prepareDelete(ESTools.formatIndexName(doc.getWorkspaceId()), ESMapper.DOCUMENT_TYPE, doc.getKey().toString()); + } + + /** + * Get the Delete request for a partIteration in ElasticSearch Cluster + * + * @param part The part iteration to delete + */ + private DeleteRequestBuilder deleteRequest(PartIteration part) throws NoNodeAvailableException { + return client.prepareDelete(ESTools.formatIndexName(part.getWorkspaceId()), ESMapper.PART_TYPE, part.getKey().toString()); + } + + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESMapper.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESMapper.java new file mode 100644 index 0000000000..3ae778606d --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESMapper.java @@ -0,0 +1,453 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.esindexer; + +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.InstanceListOfValuesAttribute; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartRevisionKey; +import com.docdoku.core.workflow.Workflow; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; + +import java.io.IOException; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class is use to convert DocdokuPLM common item to JSON Builder for ElasticSearch databases. + * + * @author Taylor LABEJOF + */ +public class ESMapper { + private static final Logger LOGGER = Logger.getLogger(ESMapper.class.getName()); + public static final String WORKSPACE_ID_KEY = "workspaceId"; + public static final String ITERATIONS_KEY = "iterations"; + public static final String ITERATION_KEY = "iteration"; + public static final String VERSION_KEY = "version"; + public static final String AUTHOR_KEY = "author"; + public static final String AUTHOR_LOGIN_KEY = "login"; + public static final String AUTHOR_NAME_KEY = "name"; + public static final String AUTHOR_SEARCH_KEY = ITERATIONS_KEY + "." + AUTHOR_KEY + "." + AUTHOR_LOGIN_KEY; + public static final String CREATION_DATE_KEY = "creationDate"; + public static final String MODIFICATION_DATE_KEY = "modificationDate"; + public static final String TYPE_KEY = "type"; + public static final String DOCUMENT_ID_KEY = "docMId"; + public static final String PART_NUMBER_KEY = "partNumber"; + public static final String PART_NAME_KEY = "name"; + public static final String TITLE_KEY = "title"; + public static final String DESCRIPTION_KEY = "description"; + public static final String REVISION_NOTE_KEY = "revisionNote"; + public static final String WORKFLOW_KEY = "workflow"; + public static final String FOLDER_KEY = "folder"; + public static final String TAGS_KEY = "tags"; + public static final String ATTRIBUTES_KEY = "attributes"; + public static final String ATTRIBUTE_NAME = "attr_name"; + public static final String ATTRIBUTE_VALUE = "attr_value"; + public static final String FILES_KEY = "files"; + public static final String CONTENT_KEY = "content"; + public static final String STANDARD_PART_KEY = "standardPart"; + public static final String PART_TYPE = "part"; + public static final String DOCUMENT_TYPE = "document" ; + public static final String ATTR_NESTED_PATH = ITERATIONS_KEY +"."+ ATTRIBUTES_KEY; + + + private ESMapper() { + super(); + } + + /** + * Get the document revision key matching a hit line. + * + * @param source The source of a SearchHit + * @return The document revision key + */ + protected static DocumentRevisionKey getDocumentRevisionKey(Map source) { + return new DocumentRevisionKey(extractValue(source, WORKSPACE_ID_KEY), extractValue(source, DOCUMENT_ID_KEY), extractValue(source, VERSION_KEY)); + } + + /** + * Get the part revision key matching a hit line. + * + * @param source The source of a SearchHit + * @return The part revision key + */ + protected static PartRevisionKey getPartRevisionKey(Map source) { + return new PartRevisionKey(extractValue(source, WORKSPACE_ID_KEY), extractValue(source, PART_NUMBER_KEY), extractValue(source, VERSION_KEY)); + } + + /** + * Convert a Document Revision to a JSON Builder. + *"" + attr.getValue() + * @param doc Document to pass to JSON + * @param contentInputs Map of binary resources content + * @return A JSON Builder to index + */ + protected static XContentBuilder documentRevisionToJSON(DocumentIteration doc, Map contentInputs) { + try { + + XContentBuilder tmp = XContentFactory.jsonBuilder() + .startObject(); + setField(tmp, WORKSPACE_ID_KEY, doc.getWorkspaceId(), 0.6f); + setField(tmp, DOCUMENT_ID_KEY, doc.getDocumentRevision().getDocumentMasterId(), 4.75f); + setField(tmp, TITLE_KEY, doc.getTitle(), 5f); + setField(tmp, VERSION_KEY, doc.getVersion(), 0.10f); + setField(tmp, TYPE_KEY, doc.getDocumentRevision().getDocumentMaster().getType(), 2f); + setField(tmp, DESCRIPTION_KEY, doc.getDocumentRevision().getDescription(), 2f); + tmp.startArray(ITERATIONS_KEY); + for (DocumentIteration iteration : doc.getDocumentRevision().getDocumentIterations()) { + tmp.startObject(); + setField(tmp, ITERATION_KEY, "" + iteration.getIteration(), 0.10f); + if (doc.getAuthor() != null) { + tmp.startObject(AUTHOR_KEY); + setField(tmp, AUTHOR_LOGIN_KEY, iteration.getAuthor().getLogin(), 0.6f); + setField(tmp, AUTHOR_NAME_KEY, iteration.getAuthor().getName(), 0.6f); + tmp.endObject(); + } + setField(tmp, CREATION_DATE_KEY, iteration.getDocumentRevision().getCreationDate(), 0.4f); + setField(tmp, MODIFICATION_DATE_KEY, iteration.getModificationDate(), 0.4f); + setField(tmp, REVISION_NOTE_KEY, iteration.getRevisionNote(), 0.5f); + setField(tmp, WORKFLOW_KEY, iteration.getDocumentRevision().getWorkflow(), 0.5f); + setField(tmp, FOLDER_KEY, iteration.getDocumentRevision().getLocation().getShortName(), 0.5f); + if (!iteration.getDocumentRevision().getTags().isEmpty()) { + tmp.startArray(TAGS_KEY); + for (Tag tag : doc.getDocumentRevision().getTags()) { + tmp.value(tag.getLabel()); + } + tmp.endArray(); + } + if (!iteration.getInstanceAttributes().isEmpty()) { + Collection listAttr = iteration.getInstanceAttributes(); + tmp.startArray(ATTRIBUTES_KEY); + for (InstanceAttribute attr : listAttr) { + tmp.startObject(); + setAttrField(tmp, attr, 0.6f); + tmp.endObject(); + } + tmp.endArray(); + } + + if (!iteration.getAttachedFiles().isEmpty()) { + tmp.startArray(FILES_KEY); + for (Map.Entry contentInput : contentInputs.entrySet()) { + tmp.startObject(); + setField(tmp, AUTHOR_NAME_KEY, contentInput.getKey(), 0.8f); + setField(tmp, CONTENT_KEY, contentInput.getValue(), 0.6f); + tmp.endObject(); + } + tmp.endArray(); + } + tmp.endObject(); + } + tmp.endArray(); + tmp.endObject(); + return tmp; + } catch (IOException e) { + LOGGER.log(Level.WARNING, "The document " + doc + " can't be indexed.", e); + return null; + } + } + + + /** + * Convert a Document Revision to a JSON Builder. + * This will be used if and only if the DocumentRevision has not been indexed in elastic search. + * + * @param doc Document to pass to JSON + * @param contentInputs Map of binary resources content + * @return A JSON Builder to index + */ + protected static Map docIterationMap(DocumentIteration doc, Map contentInputs) { + Map params = new HashMap<>(); + setParam(params, WORKSPACE_ID_KEY, doc.getWorkspaceId(), 0.6f); + setParam(params, DOCUMENT_ID_KEY, doc.getDocumentRevision().getDocumentMasterId(), 4.75f); + setParam(params, TITLE_KEY, doc.getTitle(), 5f); + setParam(params, VERSION_KEY, doc.getVersion(), 0.10f); + setParam(params, ITERATION_KEY, "" + doc.getIteration(), 0.10f); + if (doc.getAuthor() != null) { + Map authorParams = new HashMap<>(); + params.put(AUTHOR_KEY, authorParams); + setParam(authorParams, AUTHOR_LOGIN_KEY, doc.getAuthor().getLogin(), 0.6f); + setParam(authorParams, AUTHOR_NAME_KEY, doc.getAuthor().getName(), 0.6f); + } + setParam(params, CREATION_DATE_KEY, doc.getDocumentRevision().getCreationDate(), 0.4f); + setParam(params, MODIFICATION_DATE_KEY, doc.getModificationDate(), 0.4f); + setParam(params, REVISION_NOTE_KEY, doc.getRevisionNote(), 0.5f); + setParam(params, WORKFLOW_KEY, doc.getDocumentRevision().getWorkflow(), 0.5f); + setParam(params, FOLDER_KEY, doc.getDocumentRevision().getLocation().getShortName(), 0.5f); + if (!doc.getDocumentRevision().getTags().isEmpty()) { + List labels = new ArrayList<>(); + for (Tag tag : doc.getDocumentRevision().getTags()) { + labels.add(tag.getLabel()); + } + params.put(TAGS_KEY, labels); + } + if (!doc.getInstanceAttributes().isEmpty()) { + Collection listAttr = doc.getInstanceAttributes(); + List> listAttributes = new ArrayList<>(); + params.put(ATTRIBUTES_KEY, listAttributes); + for (InstanceAttribute attr : listAttr) { + Map attributesParams = new HashMap<>(); + listAttributes.add(attributesParams); + setAttrParam(attributesParams, attr, 0.6f); + } + } + if (!doc.getAttachedFiles().isEmpty()) { + List> filesParams = new ArrayList<>(); + params.put(FILES_KEY, filesParams); + for (Map.Entry contentInput : contentInputs.entrySet()) { + Map map = new HashMap<>(); + filesParams.add(map); + setParam(map, AUTHOR_NAME_KEY, contentInput.getKey(), 0.8f); + setParam(map, CONTENT_KEY, contentInput.getValue(), 0.6f); + } + } + + return params; + + } + + /** + * Create the Json for a new Part. + * This will be used if and only if the PartRevision has not been indexed in elastic search. + * + * @param part the PartIteration which was checkin. + * @param binaryList + * @return The Json produced contains the PartRevision information and the information of all the iteration. + */ + protected static XContentBuilder partRevisionToJson(PartIteration part, Map binaryList) { + try { + XContentBuilder tmp = XContentFactory.jsonBuilder() + .startObject(); + setField(tmp, WORKSPACE_ID_KEY, part.getWorkspaceId(), 0.6f); + setField(tmp, PART_NUMBER_KEY, part.getPartNumber(), 4.75f); + setField(tmp, PART_NAME_KEY, part.getPartRevision().getPartMaster().getName(), 5f); + setField(tmp, TYPE_KEY, part.getPartRevision().getPartMaster().getType(), 2f); + setField(tmp, VERSION_KEY, part.getPartVersion(), 0.10f); + setField(tmp, DESCRIPTION_KEY, part.getPartRevision().getDescription(), 2f); + tmp.startArray(ITERATIONS_KEY); + for (PartIteration iteration : part.getPartRevision().getPartIterations()) { + tmp.startObject(); + + setField(tmp, ITERATION_KEY, iteration.getIteration(), 0.10f); + setField(tmp, STANDARD_PART_KEY, iteration.getPartRevision().getPartMaster().isStandardPart(), 0.05f); + if (iteration.getAuthor() != null) { + tmp.startObject(AUTHOR_KEY); + setField(tmp, AUTHOR_LOGIN_KEY, iteration.getAuthor().getLogin(), 0.6f); + setField(tmp, AUTHOR_NAME_KEY, iteration.getAuthor().getName(), 0.6f); + tmp.endObject(); + } + setField(tmp, CREATION_DATE_KEY, iteration.getCreationDate(), 0.4f); + setField(tmp, MODIFICATION_DATE_KEY, iteration.getModificationDate(), 0.4f); + setField(tmp, REVISION_NOTE_KEY, iteration.getIterationNote(), 0.5f); + setField(tmp, WORKFLOW_KEY, iteration.getPartRevision().getWorkflow(), 0.5f); + if (!iteration.getPartRevision().getTags().isEmpty()) { + tmp.startArray(TAGS_KEY); + for (Tag tag : iteration.getPartRevision().getTags()) { + tmp.value(tag.getLabel()); + } + tmp.endArray(); + } + if (!iteration.getInstanceAttributes().isEmpty()) { + Collection listAttr = iteration.getInstanceAttributes(); + tmp.startArray(ATTRIBUTES_KEY); + for (InstanceAttribute attr : listAttr) { + tmp.startObject(); + setAttrField(tmp,attr,0.6f); + tmp.endObject(); + } + tmp.endArray(); + } + + if (!iteration.getAttachedFiles().isEmpty()) { + tmp.startArray(FILES_KEY); + for (Map.Entry contentInput : binaryList.entrySet()) { + tmp.startObject(); + setField(tmp, AUTHOR_NAME_KEY, contentInput.getKey(), 0.8f); + setField(tmp, CONTENT_KEY, contentInput.getValue(), 0.6f); + tmp.endObject(); + } + tmp.endArray(); + } + + tmp.endObject(); + + } + tmp.endArray(); + tmp.endObject(); + return tmp; + } catch (IOException e) { + LOGGER.log(Level.WARNING, "The part " + part + " can't be indexed.", e); + return null; + } + } + + /** + * Create a Map of all the data of an iteration. + * + * @param part PartIteration to be Mapped. + * @param binaryList + * @return The Map is well formatted to be used by an elasticsearch Script + */ + protected static Map partIterationMap(PartIteration part, Map binaryList) { + Map params = new HashMap<>(); + + setParam(params, WORKSPACE_ID_KEY, part.getWorkspaceId(), 0.6f); + setParam(params, VERSION_KEY, part.getPartVersion(), 0.10f); + setParam(params, ITERATION_KEY, part.getIteration(), 0.10f); + setParam(params, STANDARD_PART_KEY, part.getPartRevision().getPartMaster().isStandardPart(), 0.05f); + + if (part.getAuthor() != null) { + // new Map equivalent of startObject of XContentBuilder. + Map authorParams = new HashMap<>(); + params.put(AUTHOR_KEY, authorParams); + setParam(authorParams, AUTHOR_LOGIN_KEY, part.getAuthor().getLogin(), 0.6f); + setParam(authorParams, AUTHOR_NAME_KEY, part.getAuthor().getName(), 0.6f); + } + + setParam(params, CREATION_DATE_KEY, part.getCreationDate(), 0.4f); + setParam(params, MODIFICATION_DATE_KEY, part.getModificationDate(), 0.4f); + setParam(params, REVISION_NOTE_KEY, part.getIterationNote(), 0.5f); + setParam(params, WORKFLOW_KEY, part.getPartRevision().getWorkflow(), 0.5f); + + if (!part.getPartRevision().getTags().isEmpty()) { + List labels = new ArrayList<>(); + for (Tag tag : part.getPartRevision().getTags()) { + labels.add(tag.getLabel()); + } + params.put(TAGS_KEY, labels); + } + + if (!part.getInstanceAttributes().isEmpty()) { + Collection listAttr = part.getInstanceAttributes(); + List> listAttributes = new ArrayList<>(); + params.put(ATTRIBUTES_KEY, listAttributes); + for (InstanceAttribute attr : listAttr) { + Map attributesParams = new HashMap<>(); + listAttributes.add(attributesParams); + setAttrParam(attributesParams, attr, 0.6f); + } + } + + if (!part.getAttachedFiles().isEmpty()) { + List> filesParams = new ArrayList<>(); + params.put(FILES_KEY, filesParams); + for (Map.Entry contentInput : binaryList.entrySet()) { + Map map = new HashMap<>(); + filesParams.add(map); + setParam(map, AUTHOR_NAME_KEY, contentInput.getKey(), 0.8f); + setParam(map, CONTENT_KEY, contentInput.getValue(), 0.6f); + } + } + + return params; + } + + private static void setAttrField(XContentBuilder object,InstanceAttribute attr, float coef ) throws IOException { + setField(object,ATTRIBUTE_NAME,attr.getNameWithoutWhiteSpace(),coef); + if(attr instanceof InstanceListOfValuesAttribute) { + InstanceListOfValuesAttribute lov = (InstanceListOfValuesAttribute) attr; + String lovItemName = !lov.getItems().isEmpty() ? lov.getItems().get(lov.getIndexValue()).getName() : ""; + setField(object,ATTRIBUTE_VALUE,lovItemName,coef); + } else { + setField(object,ATTRIBUTE_VALUE,""+attr.getValue(),coef); + } + + } + + private static void setAttrParam(Map params, InstanceAttribute attr,float coef) { + setParam(params, ATTRIBUTE_NAME, attr.getNameWithoutWhiteSpace(), coef); + if(attr instanceof InstanceListOfValuesAttribute) { + InstanceListOfValuesAttribute lov = (InstanceListOfValuesAttribute) attr; + String lovItemName = !lov.getItems().isEmpty() ? lov.getItems().get(lov.getIndexValue()).getName() : ""; + setParam(params, ATTRIBUTE_VALUE, lovItemName, coef); + } else { + setParam(params,ATTRIBUTE_VALUE,""+attr.getValue(),coef); + } + } + + private static void setParam(Map params, String name, Object value, float coef) { + if (value != null) { + Object array[] = new Object[2]; + array[0] = value; + array[1] = coef; + params.put(name, array); + } + } + + private static void setParam(Map params, String name, Workflow value, float coef) { + if (value != null) { + Object array[] = new Object[2]; + String finalLifeCycleState = value.getFinalLifeCycleState(); + finalLifeCycleState = (finalLifeCycleState != null && !finalLifeCycleState.isEmpty()) ? finalLifeCycleState : " "; + array[0] = finalLifeCycleState; + array[1] = coef; + params.put(name, array); + } + } + + private static XContentBuilder setField(XContentBuilder object, String field, String pValue, float coef) throws IOException { + String value = (pValue != null && !"".equals(pValue)) ? pValue : " "; + object.field(field, value, coef); + return object; + } + + private static XContentBuilder setField(XContentBuilder object, String field, int value, float coef) throws IOException { + object.field(field, "" + value, coef); + return object; + } + + private static XContentBuilder setField(XContentBuilder object, String field, Workflow value, float coef) throws IOException { + if (value != null) { + String finalLifeCycleState = value.getFinalLifeCycleState(); + finalLifeCycleState = (finalLifeCycleState != null && !finalLifeCycleState.isEmpty()) ? finalLifeCycleState : " "; + return object.field(field, "" + finalLifeCycleState, coef); + } + return null; + } + + private static XContentBuilder setField(XContentBuilder object, String field, Object value, float coef) throws IOException { + if (value != null) { + return object.field(field, value, coef); + } + return null; + } + + /** + * Extract a value from a ES result + * + * @param source Source of a ES hit + * @param key Key of the field to extract + * @return The value of the field "key" + */ + private static String extractValue(Map source, String key) { + Object ret = source.get(key); + if (ret instanceof List) { + return ((List) ret).get(0).toString(); + } else { + return ret.toString(); + } + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESQueryBuilder.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESQueryBuilder.java new file mode 100644 index 0000000000..fd0bfddfa6 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESQueryBuilder.java @@ -0,0 +1,82 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.esindexer; + +import org.elasticsearch.index.query.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author kelto on 09/07/15. + */ +public class ESQueryBuilder { + + private final List filters; + private final List queries; + + public ESQueryBuilder() { + filters = new ArrayList<>(); + queries = new ArrayList<>(); + } + + public void add(QueryBuilder query) { + queries.add(query); + } + + public void add(FilterBuilder filter) { + filters.add(filter); + } + + public QueryBuilder getFilteredQuery() { + return QueryBuilders.filteredQuery(getQuery(),getFilter()); + } + + private QueryBuilder getQuery() { + QueryBuilder query; + if(!queries.isEmpty()) { + BoolQueryBuilder bqr = QueryBuilders.boolQuery(); + for(QueryBuilder qr : queries) { + bqr.must(qr); + } + query = bqr; + } else { + // A FilteredQuery must have a query + // Therefore, we send a neutral query which will match anything. + query = QueryBuilders.matchAllQuery(); + } + return query; + } + + private FilterBuilder getFilter() { + FilterBuilder filter; + if(filters.isEmpty()) { + // a FilteredQuery can have no Filter. + filter = null; + } else { + FilterBuilder array[] = filters.toArray(new FilterBuilder[filters.size()]); + filter = FilterBuilders.boolFilter().must(array); + } + return filter; + } + + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESSearcher.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESSearcher.java new file mode 100644 index 0000000000..eb3cf0f6bc --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESSearcher.java @@ -0,0 +1,388 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.esindexer; + +import com.docdoku.core.common.Workspace; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.DocumentRevisionNotFoundException; +import com.docdoku.core.exceptions.ESServerException; +import com.docdoku.core.exceptions.PartRevisionNotFoundException; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.product.PartRevisionKey; +import com.docdoku.core.query.DocumentSearchQuery; +import com.docdoku.core.query.PartSearchQuery; +import com.docdoku.core.query.SearchQuery; +import com.docdoku.server.dao.DocumentRevisionDAO; +import com.docdoku.server.dao.PartRevisionDAO; +import com.docdoku.server.dao.WorkspaceDAO; +import org.elasticsearch.action.search.*; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.transport.NoNodeAvailableException; +import org.elasticsearch.index.query.*; +import org.elasticsearch.indices.IndexMissingException; +import org.elasticsearch.search.SearchHit; + +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.text.MessageFormat; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Search Method using ElasticSearch API. + * + * @author Taylor LABEJOF + */ +@Stateless(name = "ESSearcher") +public class ESSearcher { + private static final String I18N_CONF = "com.docdoku.core.i18n.LocalStrings"; + private static final Logger LOGGER = Logger.getLogger(ESSearcher.class.getName()); + private static final String ES_TYPE_DOCUMENT = "document"; + private static final String ES_TYPE_PART = "part"; + private static final String ES_SEARCH_ERROR_1 = "ES_SearchError1"; + private static final String ES_SEARCH_ERROR_2 = "ES_SearchError2"; + private static final String ES_SERVER_ERROR_1 = "IndexerServerException"; + private static final String ES_SERVER_ERROR_2 ="MissingIndexException"; + + @PersistenceContext + private EntityManager em; + + @Inject + private Client client; + + /** + * Constructor + */ + public ESSearcher() { + super(); + } + + /** + * Search a document + * + * @param docQuery DocumentSearchQuery + * @return List of document master + */ + public List search(DocumentSearchQuery docQuery) throws ESServerException { + try { + QueryBuilder qr = getQueryBuilder(docQuery); + SearchRequestBuilder srb = getSearchRequest(ESTools.formatIndexName(docQuery.getWorkspaceId()), ES_TYPE_DOCUMENT, qr); + SearchResponse sr = srb.execute().actionGet(); + + List listOfDocuments = new ArrayList<>(); + for (int i = 0; i < sr.getHits().getHits().length; i++) { + SearchHit hit = sr.getHits().getAt(i); + DocumentRevision docR = getDocumentRevision(hit); + if (docR != null && !listOfDocuments.contains(docR)) { + listOfDocuments.add(docR); + } + } + + //Todo FilterConfigSpec + + return listOfDocuments; + } catch (NoNodeAvailableException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_SEARCH_ERROR_1); + LOGGER.log(Level.WARNING, logMessage, e); + throw new ESServerException(Locale.getDefault(), ES_SERVER_ERROR_1); + } catch (IndexMissingException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF,Locale.getDefault()).getString(ES_SEARCH_ERROR_2); + LOGGER.log(Level.WARNING,logMessage,e); + throw new ESServerException(Locale.getDefault(),ES_SERVER_ERROR_2); + } + } + + /** + * Search a part + * + * @param partQuery PartSearchQuery + * @return List of part revision + */ + public List search(PartSearchQuery partQuery) throws ESServerException { + try { + QueryBuilder qr = getQueryBuilder(partQuery); + SearchRequestBuilder srb = getSearchRequest(ESTools.formatIndexName(partQuery.getWorkspaceId()), ES_TYPE_PART, qr); + SearchResponse sr = srb.execute().actionGet(); + + Set setOfParts = new HashSet<>(); + for (int i = 0; i < sr.getHits().getHits().length; i++) { + SearchHit hit = sr.getHits().getAt(i); + PartRevision partRevision = getPartRevision(hit); + if (partRevision != null) { + setOfParts.add(partRevision); + } + } + + //Todo FilterConfigSpec + return new ArrayList<>(setOfParts); + } catch (NoNodeAvailableException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_SEARCH_ERROR_1); + LOGGER.log(Level.WARNING, logMessage, e); + throw new ESServerException(Locale.getDefault(), ES_SERVER_ERROR_1); + } catch (IndexMissingException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF,Locale.getDefault()).getString(ES_SEARCH_ERROR_2); + LOGGER.log(Level.WARNING,logMessage,e); + throw new ESServerException(Locale.getDefault(),ES_SERVER_ERROR_2); + } + + } + + /** + * Search a document in all Workspace + * + * @param docQuery DocumentSearchQuery + * @return List of document master + */ + public List searchInAllWorkspace(DocumentSearchQuery docQuery) throws ESServerException { + try { + QueryBuilder qr = getQueryBuilder(docQuery); + MultiSearchRequestBuilder srbm = client.prepareMultiSearch(); + WorkspaceDAO wDAO = new WorkspaceDAO(em); + for (Workspace w : wDAO.getAll()) { + srbm.add(getSearchRequest(ESTools.formatIndexName(w.getId()), ES_TYPE_DOCUMENT, qr)); + } + MultiSearchResponse srm = srbm.execute().actionGet(); + + + List listOfDocuments = new ArrayList<>(); + for (MultiSearchResponse.Item sri : srm.getResponses()) { + if (!sri.isFailure()) { + SearchResponse sr = sri.getResponse(); + for (int i = 0; i < sr.getHits().getHits().length; i++) { + SearchHit hit = sr.getHits().getAt(i); + DocumentRevision docR = getDocumentRevision(hit); + if (docR != null && !listOfDocuments.contains(docR)) { + listOfDocuments.add(docR); + } + } + } + } + return listOfDocuments; + } catch (NoNodeAvailableException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_SEARCH_ERROR_1); + LOGGER.log(Level.WARNING, logMessage, e); + throw new ESServerException(Locale.getDefault(), ES_SERVER_ERROR_1); + } + } + + /** + * Search a part in all Workspace + * + * @param partQuery PartSearchQuery + * @return List of part revision + */ + public List searchInAllWorkspace(PartSearchQuery partQuery) throws ESServerException { + + try { + QueryBuilder qr = getQueryBuilder(partQuery); + MultiSearchRequestBuilder srbm = client.prepareMultiSearch(); + WorkspaceDAO wDAO = new WorkspaceDAO(em); + for (Workspace w : wDAO.getAll()) { + srbm.add(getSearchRequest(ESTools.formatIndexName(w.getId()), ES_TYPE_PART, qr)); + } + MultiSearchResponse srm = srbm.execute().actionGet(); + + + List listOfParts = new ArrayList<>(); + for (MultiSearchResponse.Item sri : srm.getResponses()) { + if (!sri.isFailure()) { + SearchResponse sr = sri.getResponse(); + for (int i = 0; i < sr.getHits().getHits().length; i++) { + SearchHit hit = sr.getHits().getAt(i); + PartRevision partRevision = getPartRevision(hit); + if (partRevision != null && !listOfParts.contains(partRevision)) { + listOfParts.add(partRevision); + } + } + } + } + + return listOfParts; + } catch (NoNodeAvailableException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString(ES_SEARCH_ERROR_1); + LOGGER.log(Level.WARNING, logMessage, e); + throw new ESServerException(Locale.getDefault(), ES_SERVER_ERROR_1); + } + } + + private QueryBuilder getFullTextQuery(SearchQuery query) { + // TODO Cut the query and make a boolQuery() with all the words + QueryBuilder fullTextQuery = QueryBuilders.matchQuery("_all",query.getFullText()) + .operator(MatchQueryBuilder.Operator.OR) + .fuzziness("AUTO"); + return fullTextQuery; + } + + /** + * Return a ElasticSearch Query for DocumentSearch + * + * @param docQuery DocumentSearchQuery wanted + * @return a ElasticSearch.QueryBuilder + */ + private QueryBuilder getQueryBuilder(DocumentSearchQuery docQuery) { + ESQueryBuilder queryBuilder = new ESQueryBuilder(); + + if (docQuery.getFullText() != null) { + QueryBuilder query = getFullTextQuery(docQuery); + queryBuilder.add(query); + } else { + if (docQuery.getDocMId() != null) { + queryBuilder.add(QueryBuilders.fuzzyQuery(ESMapper.DOCUMENT_ID_KEY, docQuery.getDocMId())); + } + if (docQuery.getTitle() != null) { + queryBuilder.add(QueryBuilders.fuzzyQuery(ESMapper.TITLE_KEY, docQuery.getTitle())); + } + addCommonQuery(queryBuilder,docQuery); + } + return queryBuilder.getFilteredQuery(); + } + + /** + * Add queries common to both part and doc. + * + * @param searchQuery The search query wanted + * @param queryBuilder The QueryBuilder initialized with the specific (doc/part) values + */ + private void addCommonQuery(ESQueryBuilder queryBuilder, SearchQuery searchQuery) { + + if (searchQuery.getVersion() != null) { + queryBuilder.add(FilterBuilders.termFilter(ESMapper.VERSION_KEY, searchQuery.getVersion())); + } + if (searchQuery.getAuthor() != null) { + queryBuilder.add(QueryBuilders.fuzzyQuery(ESMapper.AUTHOR_SEARCH_KEY, searchQuery.getAuthor())); + } + if (searchQuery.getType() != null) { + queryBuilder.add(QueryBuilders.fuzzyQuery(ESMapper.TYPE_KEY, searchQuery.getType())); + } + + if (searchQuery.getCreationDateFrom() != null) { + queryBuilder.add(FilterBuilders.rangeFilter(ESMapper.CREATION_DATE_KEY).from(searchQuery.getCreationDateFrom())); + } + if (searchQuery.getCreationDateTo() != null) { + queryBuilder.add(FilterBuilders.rangeFilter(ESMapper.CREATION_DATE_KEY).to(searchQuery.getCreationDateTo())); + } + if (searchQuery.getModificationDateFrom() != null) { + queryBuilder.add(FilterBuilders.rangeFilter(ESMapper.MODIFICATION_DATE_KEY).from(searchQuery.getModificationDateFrom())); + } + if (searchQuery.getModificationDateTo() != null) { + queryBuilder.add(FilterBuilders.rangeFilter(ESMapper.MODIFICATION_DATE_KEY).to(searchQuery.getModificationDateTo())); + } + if (searchQuery.getContent() != null) { + queryBuilder.add(QueryBuilders.matchQuery(ESMapper.CONTENT_KEY, searchQuery.getContent())); + } + if (searchQuery.getAttributes() != null) { + for (SearchQuery.AbstractAttributeQuery attr : searchQuery.getAttributes()) { + BoolFilterBuilder b = FilterBuilders.boolFilter(); + b.must(FilterBuilders.termFilter(ESMapper.ATTR_NESTED_PATH+ "." + ESMapper.ATTRIBUTE_NAME, attr.getNameWithoutWhiteSpace())); + + if(attr.hasValue()) { + b.must(FilterBuilders.termFilter(ESMapper.ATTR_NESTED_PATH + "." + ESMapper.ATTRIBUTE_VALUE, attr.toString())); + } + + NestedFilterBuilder nested = FilterBuilders.nestedFilter(ESMapper.ATTR_NESTED_PATH, b); + queryBuilder.add(nested); + } + } + if (searchQuery.getTags() != null) { + queryBuilder.add(FilterBuilders.inFilter(ESMapper.TAGS_KEY, searchQuery.getTags())); + } + } + + /** + * Return a ElasticSearch Query for PartSearch + * + * @param partQuery PartSearchQuery wanted + * @return a ElasticSearch.QueryBuilder + */ + private QueryBuilder getQueryBuilder(PartSearchQuery partQuery) { + ESQueryBuilder queryBuilder = new ESQueryBuilder(); + if (partQuery.getFullText() != null) { + QueryBuilder query = getFullTextQuery(partQuery); + queryBuilder.add(query); + } else { + if (partQuery.getPartNumber() != null) { + queryBuilder.add(QueryBuilders.fuzzyQuery(ESMapper.PART_NUMBER_KEY, partQuery.getPartNumber())); + } + if (partQuery.getName() != null) { + queryBuilder.add(QueryBuilders.fuzzyQuery(ESMapper.PART_NAME_KEY, partQuery.getName())); + } + if (partQuery.isStandardPart() != null) { + queryBuilder.add(FilterBuilders.termFilter(ESMapper.STANDARD_PART_KEY, partQuery.isStandardPart())); + } + addCommonQuery(queryBuilder,partQuery); + } + + return queryBuilder.getFilteredQuery(); + } + + /** + * Return a uniWorkspace Search Request for a type of resource + * + * @param workspaceId Workspace of research + * @param type Type of resource searched + * @param pQuery Search criterion + * @return the uniWorkspace Search Request + */ + private SearchRequestBuilder getSearchRequest(String workspaceId, String type, QueryBuilder pQuery) { + return client.prepareSearch(workspaceId) + .setTypes(type) + .setSearchType(SearchType.QUERY_THEN_FETCH) + .setQuery(pQuery); + } + + /** + * Get a document matching a search hit + * + * @param hit The search hit provide by ElasticSearch + */ + private DocumentRevision getDocumentRevision(SearchHit hit) { + DocumentRevisionKey docRevisionKey = ESMapper.getDocumentRevisionKey(hit.getSource()); + try { + return new DocumentRevisionDAO(em).loadDocR(docRevisionKey); + } catch (DocumentRevisionNotFoundException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString("DocumentRevisionNotFoundException"); + logMessage = MessageFormat.format(logMessage, docRevisionKey.getDocumentMaster().getWorkspace(), docRevisionKey.getVersion()); + LOGGER.log(Level.INFO, logMessage, e); + return null; + } + } + + /** + * Get a document matching a search hit + * + * @param hit The search hit provide by ElasticSearch + */ + private PartRevision getPartRevision(SearchHit hit) { + PartRevisionKey partRevisionKey = ESMapper.getPartRevisionKey(hit.getSource()); + try { + return new PartRevisionDAO(em).loadPartR(partRevisionKey); + } catch (PartRevisionNotFoundException e) { + String logMessage = ResourceBundle.getBundle(I18N_CONF, Locale.getDefault()).getString("PartRevisionNotFoundException"); + logMessage = MessageFormat.format(logMessage, partRevisionKey.getPartMaster().toString(), partRevisionKey.getVersion()); + LOGGER.log(Level.INFO, logMessage, e); + return null; + } + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESTools.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESTools.java new file mode 100644 index 0000000000..3146afe1fe --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/esindexer/ESTools.java @@ -0,0 +1,296 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.esindexer; + +import com.docdoku.core.exceptions.ESServerException; +import com.docdoku.core.util.Tools; +import com.itextpdf.text.pdf.PdfReader; +import com.itextpdf.text.pdf.parser.PdfTextExtractor; +import org.apache.poi.hslf.extractor.PowerPointExtractor; +import org.apache.poi.hssf.extractor.ExcelExtractor; +import org.apache.poi.hwpf.extractor.WordExtractor; +import org.apache.poi.openxml4j.exceptions.OpenXML4JException; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.xslf.extractor.XSLFPowerPointExtractor; +import org.apache.poi.xslf.usermodel.XMLSlideShow; +import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.apache.poi.xwpf.extractor.XWPFWordExtractor; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.xmlbeans.XmlException; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.transport.TransportClient; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.transport.InetSocketTransportAddress; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.Iterator; +import java.util.Locale; +import java.util.Properties; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * Tools for Search & Index Method using ElasticSearch API. + * @author Taylor LABEJOF + */ +public class ESTools { + private static final String CONF_PROPERTIES="/com/docdoku/server/esindexer/conf.properties"; + private static final Properties CONF = new Properties(); + private static final Logger LOGGER = Logger.getLogger(ESTools.class.getName()); + + static{ + try (InputStream inputStream = ESTools.class.getResourceAsStream(CONF_PROPERTIES)){ + CONF.load(inputStream); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + + private ESTools() { + super(); + } + + /** + * Create a ElasticSearch Client to make QueryRequest + */ + @Produces + @ApplicationScoped + public Client createClient() throws ESServerException { + try{ + Settings settings = ImmutableSettings.settingsBuilder() + .put("cluster.name", CONF.getProperty("cluster.name")).build(); + + return new TransportClient(settings).addTransportAddress(new InetSocketTransportAddress(CONF.getProperty("host"), Integer.parseInt(CONF.getProperty("port")))); + }catch (ElasticsearchException e){ + LOGGER.log(Level.WARNING, null, e); + throw new ESServerException(Locale.getDefault(), "IndexerServerException"); + } + } + + /** + * Convert the workspaceId to a Elastic Search index name + * + * @param workspaceId Id to convert + * @return The workspaceId without uppercase and space + */ + protected static String formatIndexName(String workspaceId){ + try { + return java.net.URLEncoder.encode(Tools.unAccent(workspaceId), "UTF-8").toLowerCase(); + } catch (UnsupportedEncodingException e) { + LOGGER.log(Level.FINEST,null,e); + return null; + } + } + + /** + * Get Stream for a Bin Resource + * @param fullName The full name of the resource + * @param inputStream Stream of the resource + * @return String to index + */ + protected static String streamToString(String fullName, InputStream inputStream) { + String strRet = " "; + + try { + int lastDotIndex = fullName.lastIndexOf('.'); + String extension = ""; + if (lastDotIndex != -1) { + extension = fullName.substring(lastDotIndex); + } + + switch (extension){ + case ".odt": + case ".ods": + case ".odp": + case ".odg": + case ".odc": + case ".odf": + case ".odb": + case ".odi": + case ".odm": + strRet = openOfficeDocumentToString(inputStream); + break; + case ".doc": + case ".docx": + strRet = microsoftWordDocumentToString(inputStream); + break; + case ".ppt": + case ".pps": + case ".pptx": + strRet = microsoftPowerPointDocumentToString(inputStream); + break; + case ".txt": //Text Document + case ".csv": //CSV Document + strRet = new Scanner(inputStream,"UTF-8").useDelimiter("\\A").next(); + break; + case ".xls": //MSExcelExtractor Document + case ".xlsx": //MSExcelExtractor Document + strRet = microsoftExcelDocumentToString(inputStream); + break; + case ".pdf": // PDF Document + strRet = pdfDocumentToString(inputStream, fullName); + break; + case ".html": + case ".htm": + case ".xml": + case ".rtf": + case ".msg": + break; + default: + break; + } + } catch (Exception ex) { + LOGGER.log(Level.WARNING, "The file " + fullName + " can't be indexed.",ex); + } + return strRet; + } + + private static String openOfficeDocumentToString(InputStream inputStream) throws IOException, SAXException, ParserConfigurationException { + final StringBuilder text = new StringBuilder(); + try(ZipInputStream zipOpenDoc = new ZipInputStream(new BufferedInputStream(inputStream))) { + ZipEntry zipEntry; + while((zipEntry=zipOpenDoc.getNextEntry())!=null) + + { + if ("content.xml".equals(zipEntry.getName())) { + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + SAXParser parser = saxParserFactory.newSAXParser(); + parser.parse(zipOpenDoc, new DefaultHandler() { + + @Override + public void characters(char[] ch, + int start, + int length) + throws SAXException { + for (int i = start; i < start + length; i++) { + text.append(ch[i]); + } + text.append("\r\n"); + } + }); + break; + } + } + } + return text.toString(); + } + + private static String microsoftWordDocumentToString(InputStream inputStream) throws IOException { + String strRet; + try(InputStream wordStream = new BufferedInputStream(inputStream)) { + if(POIFSFileSystem.hasPOIFSHeader(wordStream)){ + WordExtractor wordExtractor = new WordExtractor(wordStream); + strRet = wordExtractor.getText(); + }else{ + XWPFWordExtractor wordXExtractor = new XWPFWordExtractor(new XWPFDocument(wordStream)); + strRet = wordXExtractor.getText(); + } + } + return strRet; + } + + private static String microsoftPowerPointDocumentToString(InputStream inputStream) throws IOException { + String strRet; + try(InputStream pptStream = new BufferedInputStream(inputStream)) { + if(POIFSFileSystem.hasPOIFSHeader(pptStream)) { + PowerPointExtractor pptExtractor = new PowerPointExtractor(pptStream); + strRet = pptExtractor.getText(true, true); + }else{ + XSLFPowerPointExtractor pptExtractor = new XSLFPowerPointExtractor(new XMLSlideShow(pptStream)); + strRet = pptExtractor.getText(true,true,true); + } + } + return strRet; + } + + private static String microsoftExcelDocumentToString(InputStream inputStream) throws IOException, OpenXML4JException, XmlException { + StringBuilder sb = new StringBuilder(); + try(InputStream excelStream=new BufferedInputStream(inputStream)) { + if (POIFSFileSystem.hasPOIFSHeader(excelStream)) { // Before 2007 format files + POIFSFileSystem excelFS = new POIFSFileSystem(excelStream); + ExcelExtractor excelExtractor = new ExcelExtractor(excelFS); + sb.append(excelExtractor.getText()); + } else { // New format + XSSFWorkbook workBook = new XSSFWorkbook(excelStream); + int numberOfSheets = workBook.getNumberOfSheets(); + for (int i = 0; i < numberOfSheets; i++) { + XSSFSheet sheet = workBook.getSheetAt(0); + Iterator rowIterator = sheet.rowIterator(); + while (rowIterator.hasNext()) { + XSSFRow row = (XSSFRow) rowIterator.next(); + Iterator cellIterator = row.cellIterator(); + while (cellIterator.hasNext()) { + XSSFCell cell = (XSSFCell) cellIterator.next(); + sb.append(cell.toString()); + sb.append(" "); + } + sb.append("\n"); + } + sb.append("\n"); + } + } + } + return sb.toString(); + } + + private static String pdfDocumentToString(InputStream inputStream, String fullName) throws IOException { + StringBuilder buf = new StringBuilder(); + try(InputStream pdfStream=new BufferedInputStream(inputStream)){ + PdfReader reader = new PdfReader(pdfStream); + for(int i=1; i<=reader.getNumberOfPages(); i++){ + buf.append(pdfPageToString(reader,i,fullName)); + } + reader.close(); + } + + return buf.toString(); + } + + private static String pdfPageToString(PdfReader reader, int pageNumber, String fullName){ + try{ + return PdfTextExtractor.getTextFromPage(reader, pageNumber); + }catch (Exception e){ + Logger.getLogger(ESIndexer.class.getName()).log(Level.INFO,"A problem occur in the file : "+fullName+", indexing at page :"+pageNumber); + Logger.getLogger(ESIndexer.class.getName()).log(Level.FINER,null,e); + return ""; + } + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/CheckedIn.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/CheckedIn.java new file mode 100644 index 0000000000..e1be6a1017 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/CheckedIn.java @@ -0,0 +1,36 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.events; + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author Florent Garin + */ +@Qualifier +@Target({METHOD, FIELD, PARAMETER, TYPE}) +@Retention(RUNTIME) +public @interface CheckedIn { +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/Created.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/Created.java new file mode 100644 index 0000000000..af762dfdf1 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/Created.java @@ -0,0 +1,36 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.events; + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author Florent Garin + */ +@Qualifier +@Target({METHOD, FIELD, PARAMETER, TYPE}) +@Retention(RUNTIME) +public @interface Created { +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/PartIterationChangeEvent.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/PartIterationChangeEvent.java new file mode 100644 index 0000000000..cd20a554ae --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/PartIterationChangeEvent.java @@ -0,0 +1,42 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.events; + +import com.docdoku.core.product.PartIteration; + +/** + * @author Florent Garin + */ +public class PartIterationChangeEvent { + + private PartIteration modifiedPart; + + public PartIterationChangeEvent(PartIteration modifiedPart) { + this.modifiedPart = modifiedPart; + } + + public PartIteration getModifiedPart() { + return modifiedPart; + } + + public void setModifiedPart(PartIteration modifiedPart) { + this.modifiedPart = modifiedPart; + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/PartRevisionChangeEvent.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/PartRevisionChangeEvent.java new file mode 100644 index 0000000000..7c14265b6f --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/PartRevisionChangeEvent.java @@ -0,0 +1,42 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.events; + +import com.docdoku.core.product.PartRevision; + +/** + * @author Florent Garin + */ +public class PartRevisionChangeEvent { + + private PartRevision modifiedPart; + + public PartRevisionChangeEvent(PartRevision modifiedPart) { + this.modifiedPart = modifiedPart; + } + + public PartRevision getModifiedPart() { + return modifiedPart; + } + + public void setModifiedPart(PartRevision modifiedPart) { + this.modifiedPart = modifiedPart; + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/Read.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/Read.java new file mode 100644 index 0000000000..e583423d07 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/Read.java @@ -0,0 +1,36 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.events; + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author Florent Garin + */ +@Qualifier +@Target({METHOD, FIELD, PARAMETER, TYPE}) +@Retention(RUNTIME) +public @interface Read { +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/Removed.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/Removed.java new file mode 100644 index 0000000000..9496a3d0e1 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/Removed.java @@ -0,0 +1,36 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.events; + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author Florent Garin + */ +@Qualifier +@Target({METHOD, FIELD, PARAMETER, TYPE}) +@Retention(RUNTIME) +public @interface Removed { +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/UserRemovedEvent.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/UserRemovedEvent.java new file mode 100644 index 0000000000..59736503d4 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/UserRemovedEvent.java @@ -0,0 +1,42 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.events; + +import com.docdoku.core.common.User; + +/** + * @author Morgan Guimard + */ +public class UserRemovedEvent { + + private User removedUser; + + public UserRemovedEvent(User removedUser) { + this.removedUser = removedUser; + } + + public User getRemovedUser() { + return removedUser; + } + + public void setRemovedUser(User removedUser) { + this.removedUser = removedUser; + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/WorkspaceAccessEvent.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/WorkspaceAccessEvent.java new file mode 100644 index 0000000000..7b98696eb5 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/WorkspaceAccessEvent.java @@ -0,0 +1,45 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.events; + +import com.docdoku.core.common.User; + +/** + * @author Florent Garin + */ +public class WorkspaceAccessEvent { + + private User connectedUser; + + + public WorkspaceAccessEvent(User connectedUser) { + this.connectedUser = connectedUser; + } + + + + public User getConnectedUser() { + return connectedUser; + } + + public void setConnectedUser(User connectedUser) { + this.connectedUser = connectedUser; + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/WorkspaceChangeEvent.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/WorkspaceChangeEvent.java new file mode 100644 index 0000000000..39408b40af --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/WorkspaceChangeEvent.java @@ -0,0 +1,42 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.events; + +import com.docdoku.core.common.Workspace; + +/** + * @author Florent Garin + */ +public class WorkspaceChangeEvent { + + private Workspace modifiedWorkspace; + + public WorkspaceChangeEvent(Workspace modifiedWorkspace) { + this.modifiedWorkspace = modifiedWorkspace; + } + + public Workspace getModifiedWorkspace() { + return modifiedWorkspace; + } + + public void setModifiedWorkspace(Workspace modifiedWorkspace) { + this.modifiedWorkspace = modifiedWorkspace; + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/Write.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/Write.java new file mode 100644 index 0000000000..4501100528 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/events/Write.java @@ -0,0 +1,36 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.events; + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author Florent Garin + */ +@Qualifier +@Target({METHOD, FIELD, PARAMETER, TYPE}) +@Retention(RUNTIME) +public @interface Write { +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/factory/ACLFactory.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/factory/ACLFactory.java new file mode 100644 index 0000000000..01b3527676 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/factory/ACLFactory.java @@ -0,0 +1,110 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.factory; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; +import com.docdoku.core.common.UserGroupKey; +import com.docdoku.core.common.UserKey; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.ACLUserEntry; +import com.docdoku.core.security.ACLUserGroupEntry; +import com.docdoku.server.dao.ACLDAO; + +import javax.persistence.EntityManager; +import java.util.HashMap; +import java.util.Map; + +/* + * + * @author Asmae CHADID on 26/02/15. + */ +public class ACLFactory { + private EntityManager em; + + public ACLFactory(EntityManager em) { + this.em = em; + } + + public ACL createACL(String pWorkspaceId, Map pUserEntries, Map pGroupEntries) { + ACL acl = new ACL(); + if (pUserEntries != null) { + for (Map.Entry entry : pUserEntries.entrySet()) { + acl.addEntry(em.find(User.class, new UserKey(pWorkspaceId, entry.getKey())), + ACL.Permission.valueOf(entry.getValue())); + } + } + if (pGroupEntries != null) { + for (Map.Entry entry : pGroupEntries.entrySet()) { + acl.addEntry(em.find(UserGroup.class, new UserGroupKey(pWorkspaceId, entry.getKey())), + ACL.Permission.valueOf(entry.getValue())); + } + } + new ACLDAO(em).createACL(acl); + return acl; + } + public ACL createACLFromPermissions(String pWorkspaceId, Map pUserEntries, Map pGroupEntries) { + + Map userEntries = new HashMap<>(); + Map groupEntries = new HashMap<>(); + + for (Map.Entry entry : pUserEntries.entrySet()) { + userEntries.put(entry.getKey(), entry.getValue().name()); + } + + for (Map.Entry entry : pGroupEntries.entrySet()) { + groupEntries.put(entry.getKey(), entry.getValue().name()); + } + + + ACL acl = new ACL(); + if (userEntries != null) { + for (Map.Entry entry : userEntries.entrySet()) { + acl.addEntry(em.find(User.class, new UserKey(pWorkspaceId, entry.getKey())), + ACL.Permission.valueOf(entry.getValue())); + } + } + if (groupEntries != null) { + for (Map.Entry entry : groupEntries.entrySet()) { + acl.addEntry(em.find(UserGroup.class, new UserGroupKey(pWorkspaceId, entry.getKey())), + ACL.Permission.valueOf(entry.getValue())); + } + } + new ACLDAO(em).createACL(acl); + return acl; + } + + public ACL updateACL(String workspaceId, ACL acl, Map pUserEntries, Map pGroupEntries) { + + if(acl != null) { + new ACLDAO(em).removeACLEntries(acl); + acl.setUserEntries(new HashMap()); + acl.setGroupEntries(new HashMap()); + for (Map.Entry entry : pUserEntries.entrySet()) { + acl.addEntry(em.getReference(User.class, new UserKey(workspaceId, entry.getKey())), ACL.Permission.valueOf(entry.getValue())); + } + + for (Map.Entry entry : pGroupEntries.entrySet()) { + acl.addEntry(em.getReference(UserGroup.class, new UserGroupKey(workspaceId, entry.getKey())), ACL.Permission.valueOf(entry.getValue())); + } + } + return acl; + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/gcm/GCMSenderBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/gcm/GCMSenderBean.java new file mode 100644 index 0000000000..fc3c6f3f39 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/gcm/GCMSenderBean.java @@ -0,0 +1,140 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.gcm; + +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.gcm.GCMAccount; +import com.docdoku.core.services.IGCMSenderLocal; + +import javax.ejb.Asynchronous; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.net.ssl.HttpsURLConnection; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.net.URL; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + + +@Local(IGCMSenderLocal.class) +@Stateless(name = "GCMSenderBean") +public class GCMSenderBean implements IGCMSenderLocal { + + private static final String GCM_URL = "https://android.googleapis.com/gcm/send"; + private static final String CONF_PROPERTIES = "/com/docdoku/server/gcm/gcm.properties"; + private static final Properties CONF = new Properties(); + private static final Logger LOGGER = Logger.getLogger(GCMSenderBean.class.getName()); + + static { + try (InputStream inputStream = GCMSenderBean.class.getResourceAsStream(CONF_PROPERTIES)){ + CONF.load(inputStream); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + + @Override + @Asynchronous + public void sendStateNotification(GCMAccount[] pGCGcmAccounts, DocumentRevision documentRevision) { + for(GCMAccount gcmAccount:pGCGcmAccounts){ + sendStateNotification(gcmAccount,documentRevision); + } + } + + @Override + @Asynchronous + public void sendIterationNotification(GCMAccount[] pGCGcmAccounts, DocumentRevision documentRevision) { + for(GCMAccount gcmAccount:pGCGcmAccounts){ + sendIterationNotification(gcmAccount, documentRevision); + } + } + + private void sendStateNotification(GCMAccount gcmAccount, DocumentRevision documentRevision){ + JsonObjectBuilder data = Json.createObjectBuilder() + .add("type","stateNotification") + .add("workspaceId",documentRevision.getWorkspaceId()) + .add("documentMasterId",documentRevision.getId()) + .add("documentMasterVersion",documentRevision.getVersion()) + .add("documentMasterIteration", documentRevision.getLastIteration().getIteration()) + .add("hashCode", documentRevision.hashCode()); + LOGGER.info("gcm Sender : Sending state notification for the document " + documentRevision.getLastIteration()); + sendMessage(data.build(),gcmAccount); + } + + + private void sendIterationNotification(GCMAccount gcmAccount, DocumentRevision documentRevision) { + JsonObjectBuilder data = Json.createObjectBuilder() + .add("type","iterationNotification") + .add("workspaceId",documentRevision.getWorkspaceId()) + .add("documentMasterId",documentRevision.getId()) + .add("documentMasterVersion",documentRevision.getVersion()) + .add("documentMasterIteration",documentRevision.getLastIteration().getIteration()) + .add("hashCode", documentRevision.hashCode()); + LOGGER.info("gcm Sender : Sending iteration notification for the document " + documentRevision.getLastIteration()); + sendMessage(data.build(),gcmAccount); + } + + private void sendMessage(JsonObject message, GCMAccount gcmAccount) { + + try{ + String apiKey = CONF.getProperty("key"); + + JsonObjectBuilder body = Json.createObjectBuilder(); + JsonArrayBuilder registrationIds = Json.createArrayBuilder(); + registrationIds.add(gcmAccount.getGcmId()); + + body.add("registration_ids", registrationIds); + body.add("data", message); + + URL url = new URL(GCM_URL); + + HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Authorization", "key="+apiKey); + con.setRequestProperty("Content-Type","application/json; charset=utf-8"); + + con.setDoOutput(true); + con.setDoInput(true); + + OutputStreamWriter output = new OutputStreamWriter(con.getOutputStream(), "UTF-8"); + output.write(body.build().toString()); + output.flush(); + output.close(); + + int responseCode = con.getResponseCode(); + String responseMessage = con.getResponseMessage(); + LOGGER.info("gcm Sender : Response code is " + responseCode); + LOGGER.info("gcm Sender : Response message is " + responseMessage); + + }catch(IOException e){ + LOGGER.info("gcm Sender : Failed to send message : " + message.toString()); + } + + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/plugins/notifications/PartNotificationManager.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/plugins/notifications/PartNotificationManager.java new file mode 100644 index 0000000000..ea10a536fd --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/plugins/notifications/PartNotificationManager.java @@ -0,0 +1,60 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.plugins.notifications; + + +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.server.events.CheckedIn; +import com.docdoku.server.events.PartIterationChangeEvent; +import com.docdoku.server.events.PartRevisionChangeEvent; +import com.docdoku.server.events.Removed; + +import javax.enterprise.context.RequestScoped; +import javax.enterprise.event.Observes; +import javax.inject.Inject; +import javax.inject.Named; + +/** + * @author Florent Garin + */ +@Named +@RequestScoped +public class PartNotificationManager { + + @Inject + private IProductManagerLocal productService; + + private void onRemovePartIteration(@Observes @Removed PartIterationChangeEvent event){ + PartIteration partIteration = event.getModifiedPart(); + productService.removeModificationNotificationsOnIteration(partIteration.getKey()); + } + + private void onRemovePartRevision(@Observes @Removed PartRevisionChangeEvent event){ + PartRevision partRevision = event.getModifiedPart(); + productService.removeModificationNotificationsOnRevision(partRevision.getKey()); + } + private void onCheckInPartIteration(@Observes @CheckedIn PartIterationChangeEvent event) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + PartIteration partIteration = event.getModifiedPart(); + productService.createModificationNotifications(partIteration); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/plugins/notifications/UserNotificationManager.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/plugins/notifications/UserNotificationManager.java new file mode 100644 index 0000000000..8d0d61239d --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/plugins/notifications/UserNotificationManager.java @@ -0,0 +1,49 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.plugins.notifications; + + +import com.docdoku.core.common.User; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.server.events.Removed; +import com.docdoku.server.events.UserRemovedEvent; + +import javax.enterprise.context.RequestScoped; +import javax.enterprise.event.Observes; +import javax.inject.Inject; +import javax.inject.Named; + +/** + * @author Morgan Guimard + */ +@Named +@RequestScoped +public class UserNotificationManager { + + @Inject + private IDocumentManagerLocal documentService; + + private void onRemoveUser(@Observes @Removed UserRemovedEvent userRemovedEvent) throws ESServerException, EntityConstraintException, WorkspaceNotFoundException, UserNotFoundException, NotAllowedException, DocumentRevisionNotFoundException, FolderNotFoundException, AccessRightException, UserNotActiveException { + User user = userRemovedEvent.getRemovedUser(); + documentService.deleteUserFolder(user); + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/products/PartWorkflowManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/products/PartWorkflowManagerBean.java new file mode 100644 index 0000000000..7a3e5a90e3 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/products/PartWorkflowManagerBean.java @@ -0,0 +1,196 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.products; + +import com.docdoku.core.common.User; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.product.PartRevisionKey; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IMailerLocal; +import com.docdoku.core.services.IPartWorkflowManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.core.workflow.Activity; +import com.docdoku.core.workflow.Task; +import com.docdoku.core.workflow.TaskKey; +import com.docdoku.core.workflow.Workflow; +import com.docdoku.server.dao.PartRevisionDAO; +import com.docdoku.server.dao.TaskDAO; +import com.docdoku.server.dao.WorkflowDAO; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.Collection; +import java.util.List; +import java.util.Locale; + +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID,UserGroupMapping.ADMIN_ROLE_ID,UserGroupMapping.GUEST_PROXY_ROLE_ID}) +@Local(IPartWorkflowManagerLocal.class) +@Stateless(name = "PartWorkflowManagerBean") +public class PartWorkflowManagerBean implements IPartWorkflowManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IProductManagerLocal productManager; + + @Inject + private IMailerLocal mailer; + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Workflow getCurentWorkflow(PartRevisionKey partRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException{ + User user = userManager.checkWorkspaceReadAccess(partRevisionKey.getPartMaster().getWorkspace()); + if(!productManager.canUserAccess(user, partRevisionKey)) { + throw new AccessRightException(new Locale(user.getLanguage()), user); + } + + Locale locale = new Locale(user.getLanguage()); + PartRevision partR = new PartRevisionDAO(locale, em).loadPartR(partRevisionKey); + return partR.getWorkflow(); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public Workflow[] getAbortedWorkflow(PartRevisionKey partRevisionKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(partRevisionKey.getPartMaster().getWorkspace()); + if(!productManager.canUserAccess(user, partRevisionKey)) { + throw new AccessRightException(new Locale(user.getLanguage()), user); + } + + Locale locale = new Locale(user.getLanguage()); + PartRevision partR = new PartRevisionDAO(locale, em).loadPartR(partRevisionKey); + List abortedWorkflows= partR.getAbortedWorkflows(); + + return abortedWorkflows.toArray(new Workflow[abortedWorkflows.size()]); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision[] getPartRevisionsWithAssignedTasksForGivenUser(String pWorkspaceId, String assignedUserLogin) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + // Todo + return new PartRevision[0]; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision[] getPartRevisionsWithOpenedTasksForGivenUser(String pWorkspaceId, String assignedUserLogin) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + // Todo + return new PartRevision[0]; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision approveTaskOnPart(String pWorkspaceId, TaskKey pTaskKey, String pComment, String pSignature) throws WorkspaceNotFoundException, TaskNotFoundException, NotAllowedException, UserNotFoundException, UserNotActiveException, WorkflowNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + + Task task = new TaskDAO(new Locale(user.getLanguage()), em).loadTask(pTaskKey); + Workflow workflow = task.getActivity().getWorkflow(); + PartRevision partRevision = checkTaskAccess(user,task); + + task.approve(user,pComment, partRevision.getLastIteration().getIteration(), pSignature); + + Collection runningTasks = workflow.getRunningTasks(); + for (Task runningTask : runningTasks) { + runningTask.start(); + } + mailer.sendApproval(runningTasks, partRevision); + return partRevision; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public PartRevision rejectTaskOnPart(String pWorkspaceId, TaskKey pTaskKey, String pComment, String pSignature) throws WorkspaceNotFoundException, TaskNotFoundException, NotAllowedException, UserNotFoundException, UserNotActiveException, WorkflowNotFoundException { + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + + Task task = new TaskDAO(new Locale(user.getLanguage()), em).loadTask(pTaskKey); + PartRevision partR = checkTaskAccess(user,task); + + task.reject(user,pComment, partR.getLastIteration().getIteration(), pSignature); + + // Relaunch Workflow ? + Activity currentActivity = task.getActivity(); + Activity relaunchActivity = currentActivity.getRelaunchActivity(); + + if(currentActivity.isStopped() && relaunchActivity != null){ + relaunchWorkflow(partR,relaunchActivity.getStep()); + + // Send mails for running tasks + mailer.sendApproval(partR.getWorkflow().getRunningTasks(), partR); + // Send notification for relaunch + mailer.sendPartRevisionWorkflowRelaunchedNotification(partR); + } + return partR; + } + + /** + * Check if a user can approve or reject a task + * @param user The specific user + * @param task The specific task + * @return The part concern by the task + * @throws WorkflowNotFoundException If no workflow was find for this task + * @throws NotAllowedException If you can not make this task + */ + private PartRevision checkTaskAccess(User user,Task task) throws WorkflowNotFoundException, NotAllowedException { + Locale locale = new Locale(user.getLanguage()); + Workflow workflow = task.getActivity().getWorkflow(); + PartRevision partR = new WorkflowDAO(em).getPartTarget(workflow); + if(partR == null){ + throw new WorkflowNotFoundException(locale,workflow.getId()); + } + if(!task.isInProgress()){ + throw new NotAllowedException(locale,"NotAllowedException15"); + } + if (!task.isPotentialWorker(user)) { + throw new NotAllowedException(locale, "NotAllowedException14"); + } + if (!workflow.getRunningTasks().contains(task)) { + throw new NotAllowedException(locale, "NotAllowedException15"); + } + if (partR.isCheckedOut()) { + throw new NotAllowedException(locale, "NotAllowedException17"); + } + return partR; + } + + private void relaunchWorkflow(PartRevision partR, int activityStep){ + Workflow workflow = partR.getWorkflow(); + // Clone new workflow + Workflow relaunchedWorkflow = new WorkflowDAO(em).duplicateWorkflow(workflow); + + // Move aborted workflow in docR list + workflow.abort(); + partR.addAbortedWorkflows(workflow); + // Set new workflow on document + partR.setWorkflow(relaunchedWorkflow); + // Reset some properties + relaunchedWorkflow.relaunch(activityStep); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/products/ProductBaselineManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/products/ProductBaselineManagerBean.java new file mode 100644 index 0000000000..95b18f3e63 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/products/ProductBaselineManagerBean.java @@ -0,0 +1,658 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.products; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.configuration.*; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentLink; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.*; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IProductBaselineManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.core.util.Tools; +import com.docdoku.server.configuration.PSFilterVisitor; +import com.docdoku.server.configuration.filter.LatestPSFilter; +import com.docdoku.server.configuration.filter.ReleasedPSFilter; +import com.docdoku.server.configuration.spec.ProductBaselineConfigSpec; +import com.docdoku.server.configuration.spec.ProductBaselineCreationConfigSpec; +import com.docdoku.server.dao.*; +import com.docdoku.server.factory.ACLFactory; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.*; + +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) +@Local(IProductBaselineManagerLocal.class) +@Stateless(name = "ProductBaselineManagerBean") +public class ProductBaselineManagerBean implements IProductBaselineManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IProductManagerLocal productManager; + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ProductBaseline createBaseline(ConfigurationItemKey ciKey, String name, ProductBaseline.BaselineType pType, String description, List partIterationKeys, List substituteLinks, List optionalUsageLinks) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, PartRevisionNotReleasedException, PartIterationNotFoundException, UserNotActiveException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException, CreationException, BaselineNotFoundException, PathToPathLinkAlreadyExistsException { + + User user = userManager.checkWorkspaceWriteAccess(ciKey.getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + + if(null == name || name.isEmpty()){ + throw new NotAllowedException(locale,"NotAllowedException61"); + } + + ConfigurationItemDAO configurationItemDAO = new ConfigurationItemDAO(locale, em); + ConfigurationItem configurationItem = configurationItemDAO.loadConfigurationItem(ciKey); + + List partIterations = new ArrayList<>(); + for(PartIterationKey piKey: partIterationKeys){ + partIterations.add(em.find(PartIteration.class,piKey)); + } + + ProductBaselineCreationConfigSpec filter = new ProductBaselineCreationConfigSpec(user,pType,partIterations,substituteLinks,optionalUsageLinks); + + PSFilterVisitor psFilterVisitor = new PSFilterVisitor(em, user, filter) { + @Override + public void onIndeterminateVersion(PartMaster partMaster, List partIterations) throws NotAllowedException { + throw new NotAllowedException(locale, "NotAllowedException48"); + } + + @Override + public void onUnresolvedVersion(PartMaster partMaster) throws NotAllowedException { + throw new NotAllowedException(locale, "NotAllowedException49"); + } + + @Override + public void onIndeterminatePath(List pCurrentPath, List pCurrentPathPartIterations) throws NotAllowedException { + throw new NotAllowedException(locale, "NotAllowedException50"); + } + + @Override + public void onUnresolvedPath(List pCurrentPath, List partIterations) throws NotAllowedException { + throw new NotAllowedException(locale, "NotAllowedException51"); + } + + @Override + public void onBranchDiscovered(List pCurrentPath, List copyPartIteration) { + // Unused here + } + + @Override + public void onOptionalPath(List path, List partIterations) { + // Unused here + } + + @Override + public boolean onPathWalk(List path, List parts) { + // Unused here + return true; + } + + }; + + psFilterVisitor.visit(configurationItem.getDesignItem(), -1); + + // Visitor has finished, and should have thrown an exception if errors + ProductBaseline baseline = new ProductBaseline(user,configurationItem, name, pType, description); + new PartCollectionDAO(em).createPartCollection(baseline.getPartCollection()); + new DocumentCollectionDAO(em).createDocumentCollection(baseline.getDocumentCollection()); + + baseline.getPartCollection().setCreationDate(new Date()); + baseline.getPartCollection().setAuthor(user); + + baseline.getDocumentCollection().setCreationDate(new Date()); + baseline.getDocumentCollection().setAuthor(user); + + + for(PartIteration partIteration: filter.getRetainedPartIterations() ){ + baseline.addBaselinedPart(partIteration); + for (DocumentLink docLink :partIteration.getLinkedDocuments()){ + DocumentIteration docI = docLink.getTargetDocument().getLastCheckedInIteration(); + if(docI!=null) + baseline.addBaselinedDocument(docI); + } + } + + baseline.getSubstituteLinks().addAll(filter.getRetainedSubstituteLinks()); + baseline.getOptionalUsageLinks().addAll(filter.getRetainedOptionalUsageLinks()); + + new ProductBaselineDAO(locale, em).createBaseline(baseline); + + copyPathToPathLinks(user, baseline); + + return baseline; + } + + private void copyPathToPathLinks(User user, ProductBaseline baseline) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ConfigurationItemNotFoundException, BaselineNotFoundException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException, PathToPathLinkAlreadyExistsException, CreationException { + ConfigurationItem configurationItem = baseline.getConfigurationItem(); + PartLink rootPartUsageLink = productManager.getRootPartUsageLink(configurationItem.getKey()); + PSFilter filter = new ProductBaselineConfigSpec(baseline, user); + + List startPath = new ArrayList<>(); + startPath.add(rootPartUsageLink); + + List visitedPaths = new ArrayList<>(); + + // Reset the list + baseline.setPathToPathLinks(new ArrayList<>()); + + PSFilterVisitor psFilterVisitor = new PSFilterVisitor(em, user, filter) { + @Override + public void onIndeterminateVersion(PartMaster partMaster, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onIndeterminatePath(List pCurrentPath, List pCurrentPathPartIterations) { + // Unused here + } + + @Override + public void onUnresolvedPath(List pCurrentPath, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onBranchDiscovered(List pCurrentPath, List copyPartIteration) { + // Unused here + } + + @Override + public void onOptionalPath(List path, List partIterations) { + // Unused here + } + + @Override + public boolean onPathWalk(List path, List parts) { + String encodedPath = Tools.getPathAsString(path); + visitedPaths.add(encodedPath); + return true; + } + + @Override + public void onUnresolvedVersion(PartMaster partMaster) { + // Unused here + } + }; + + psFilterVisitor.visit(startPath, -1); + + PathToPathLinkDAO pathToPathLinkDAO = new PathToPathLinkDAO(new Locale(user.getLanguage()), em); + List links = pathToPathLinkDAO.getPathToPathLinkFromPathList(configurationItem,visitedPaths); + for(PathToPathLink link : links){ + PathToPathLink clone = link.clone(); + pathToPathLinkDAO.createPathToPathLink(clone); + baseline.addPathToPathLink(clone); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getAllBaselines(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + userManager.checkWorkspaceReadAccess(workspaceId); + return new ProductBaselineDAO(em).findBaselines(workspaceId); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getBaselines(ConfigurationItemKey configurationItemKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + userManager.checkWorkspaceReadAccess(configurationItemKey.getWorkspace()); + return new ProductBaselineDAO(em).findBaselines(configurationItemKey.getId(), configurationItemKey.getWorkspace()); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteBaseline(String pWorkspaceId,int baselineId) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, BaselineNotFoundException, UserNotActiveException, EntityConstraintException { + + User user = userManager.checkWorkspaceReadAccess(pWorkspaceId); + Locale locale = new Locale(user.getLanguage()); + + ProductBaselineDAO productBaselineDAO = new ProductBaselineDAO(locale,em); + ProductBaseline productBaseline = productBaselineDAO.loadBaseline(baselineId); + + userManager.checkWorkspaceWriteAccess(productBaseline.getConfigurationItem().getWorkspaceId()); + + // Check for product instances based on this baseline + + ProductInstanceIterationDAO productInstanceIterationDAO = new ProductInstanceIterationDAO(locale,em); + + if(productInstanceIterationDAO.isBaselinedUsed(productBaseline)){ + throw new EntityConstraintException(locale,"EntityConstraintException16"); + } + + productBaselineDAO.deleteBaseline(productBaseline); + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ProductBaseline getBaseline(int baselineId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException { + ProductBaseline productBaseline = new ProductBaselineDAO(em).loadBaseline(baselineId); + userManager.checkWorkspaceReadAccess(productBaseline.getConfigurationItem().getWorkspaceId()); + return productBaseline; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ProductBaseline getBaselineById(int baselineId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + ProductBaselineDAO productBaselineDAO = new ProductBaselineDAO(em); + ProductBaseline productBaseline = productBaselineDAO.findBaselineById(baselineId); + Workspace workspace = productBaseline.getConfigurationItem().getWorkspace(); + userManager.checkWorkspaceReadAccess(workspace.getId()); + return productBaseline; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getBaselinedPartWithReference(int baselineId, String q, int maxResults) throws BaselineNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + ProductBaselineDAO productBaselineDAO = new ProductBaselineDAO(em); + ProductBaseline productBaseline = productBaselineDAO.loadBaseline(baselineId); + User user = userManager.checkWorkspaceReadAccess(productBaseline.getConfigurationItem().getWorkspaceId()); + return new ProductBaselineDAO(new Locale(user.getLanguage()), em).findBaselinedPartWithReferenceLike(productBaseline.getPartCollection().getId(), q, maxResults); + } + + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getBaselineCreationPathChoices(ConfigurationItemKey ciKey, ProductBaseline.BaselineType type) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, PartMasterNotFoundException, NotAllowedException, EntityConstraintException { + + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + + ConfigurationItemDAO configurationItemDAO = new ConfigurationItemDAO(new Locale(user.getLanguage()), em); + ConfigurationItem configurationItem = configurationItemDAO.loadConfigurationItem(ciKey); + + PSFilter filter; + + if(type == null || type.equals(ProductBaseline.BaselineType.RELEASED)){ + filter = new ReleasedPSFilter(user, true); + }else{ + filter = new LatestPSFilter(user, true); + } + + List choices = new ArrayList<>(); + + PSFilterVisitor psFilterVisitor = new PSFilterVisitor(em, user, filter) { + + @Override + public void onIndeterminateVersion(PartMaster partMaster, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onIndeterminatePath(List pCurrentPath, List pCurrentPathPartIterations) { + addPartChoice(pCurrentPath, pCurrentPathPartIterations); + } + + @Override + public void onUnresolvedPath(List pCurrentPath, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onBranchDiscovered(List pCurrentPath, List copyPartIteration) { + // Unused here + } + + @Override + public void onOptionalPath(List pCurrentPath, List pCurrentPathPartIterations) { + addPartChoice(pCurrentPath, pCurrentPathPartIterations); + } + + @Override + public boolean onPathWalk(List path, List parts) { + // Unused here + return true; + } + + @Override + public void onUnresolvedVersion(PartMaster partMaster) { + // Unused here + } + + private void addPartChoice(List pCurrentPath, List pCurrentPathPartIterations) { + List resolvedPath = new ArrayList<>(); + for(int i = 0; i < pCurrentPathPartIterations.size(); i++){ + resolvedPath.add(new ResolvedPartLink(pCurrentPathPartIterations.get(i),pCurrentPath.get(i))); + } + choices.add(new PathChoice(resolvedPath, pCurrentPath.get(pCurrentPath.size()-1))); + } + + }; + + psFilterVisitor.visit(configurationItem.getDesignItem(), -1); + + return choices; + } + + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getBaselineCreationVersionsChoices(ConfigurationItemKey ciKey) throws ConfigurationItemNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartMasterNotFoundException, NotAllowedException, EntityConstraintException { + + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + + ConfigurationItemDAO configurationItemDAO = new ConfigurationItemDAO(new Locale(user.getLanguage()), em); + ConfigurationItem configurationItem = configurationItemDAO.loadConfigurationItem(ciKey); + + Set parts = new HashSet<>(); + + PSFilter filter = new ReleasedPSFilter(user, true); + + PSFilterVisitor psFilterVisitor = new PSFilterVisitor(em, user, filter) { + @Override + public void onIndeterminateVersion(PartMaster partMaster, List partIterations) throws NotAllowedException { + parts.addAll(partIterations); + } + + @Override + public void onIndeterminatePath(List pCurrentPath, List pCurrentPathPartIterations) { + // Unused here + } + + @Override + public void onUnresolvedPath(List pCurrentPath, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onBranchDiscovered(List pCurrentPath, List copyPartIteration) { + // Unused here + } + + @Override + public void onOptionalPath(List path, List partIterations) { + // Unused here + } + + @Override + public boolean onPathWalk(List path, List parts) { + // Unused here + return true; + } + + @Override + public void onUnresolvedVersion(PartMaster partMaster) { + // Unused here + } + }; + + psFilterVisitor.visit(configurationItem.getDesignItem(), -1); + + return new ArrayList<>(parts); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ProductConfiguration createProductConfiguration(ConfigurationItemKey ciKey, String name, String description, List substituteLinks, List optionalUsageLinks, Map userEntries, Map groupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, CreationException, AccessRightException { + User user = userManager.checkWorkspaceWriteAccess(ciKey.getWorkspace()); + + Locale locale = new Locale(user.getLanguage()); + ProductConfigurationDAO productConfigurationDAO = new ProductConfigurationDAO(locale,em); + + ConfigurationItemDAO configurationItemDAO = new ConfigurationItemDAO(new Locale(user.getLanguage()), em); + ConfigurationItem configurationItem = configurationItemDAO.loadConfigurationItem(ciKey); + + ProductConfiguration productConfiguration = new ProductConfiguration(user,configurationItem, name,description,null); + + if (!userEntries.isEmpty() || !groupEntries.isEmpty()) { + ACLFactory aclFactory = new ACLFactory(em); + ACL acl = aclFactory.createACLFromPermissions(ciKey.getWorkspace(), userEntries, groupEntries); + productConfiguration.setAcl(acl); + } + productConfiguration.setOptionalUsageLinks(new HashSet<>(optionalUsageLinks)); + productConfiguration.setSubstituteLinks(new HashSet<>(substituteLinks)); + + productConfigurationDAO.createProductConfiguration(productConfiguration); + return productConfiguration; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getAllProductConfigurations(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + ProductConfigurationDAO productConfigurationDAO = new ProductConfigurationDAO(locale,em); + List productConfigurations = productConfigurationDAO.getAllProductConfigurations(workspaceId); + + ListIterator ite = productConfigurations.listIterator(); + + while(ite.hasNext()){ + ProductConfiguration next = ite.next(); + try { + checkProductConfigurationReadAccess(workspaceId, next, user); + } catch (AccessRightException e) { + ite.remove(); + } + } + + return productConfigurations; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getAllProductConfigurationsByConfigurationItemId(ConfigurationItemKey ciKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException { + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + + ProductConfigurationDAO productConfigurationDAO = new ProductConfigurationDAO(locale,em); + List productConfigurations = productConfigurationDAO.getAllProductConfigurationsByConfigurationItem(ciKey); + + ListIterator ite = productConfigurations.listIterator(); + + while(ite.hasNext()){ + ProductConfiguration next = ite.next(); + try { + checkProductConfigurationReadAccess(ciKey.getWorkspace(), next, user); + } catch (AccessRightException e) { + ite.remove(); + } + } + + return productConfigurations; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ProductConfiguration getProductConfiguration(ConfigurationItemKey ciKey, int productConfigurationId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductConfigurationNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + ProductConfigurationDAO productConfigurationDAO = new ProductConfigurationDAO(locale,em); + ProductConfiguration productConfiguration = productConfigurationDAO.getProductConfiguration(productConfigurationId); + + String workspaceId = productConfiguration.getConfigurationItem().getWorkspace().getId(); + user = userManager.checkWorkspaceReadAccess(workspaceId); + checkProductConfigurationReadAccess(workspaceId, productConfiguration, user); + + return productConfiguration; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ProductConfiguration updateProductConfiguration(ConfigurationItemKey ciKey, int productConfigurationId, String name, String description, List substituteLinks, List optionalUsageLinks) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductConfigurationNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + ProductConfigurationDAO productConfigurationDAO = new ProductConfigurationDAO(locale,em); + ProductConfiguration productConfiguration = productConfigurationDAO.getProductConfiguration(productConfigurationId); + + String workspaceId = productConfiguration.getConfigurationItem().getWorkspace().getId(); + user = userManager.checkWorkspaceReadAccess(workspaceId); + checkProductConfigurationWriteAccess(workspaceId,productConfiguration,user); + + productConfiguration.setName(name); + productConfiguration.setDescription(description); + productConfiguration.setSubstituteLinks(new HashSet<>(substituteLinks)); + productConfiguration.setOptionalUsageLinks(new HashSet<>(optionalUsageLinks)); + + return null; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteProductConfiguration(ConfigurationItemKey ciKey, int productConfigurationId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductConfigurationNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + ProductConfigurationDAO productConfigurationDAO = new ProductConfigurationDAO(locale,em); + ProductConfiguration productConfiguration = productConfigurationDAO.getProductConfiguration(productConfigurationId); + + String workspaceId = productConfiguration.getConfigurationItem().getWorkspace().getId(); + user = userManager.checkWorkspaceReadAccess(workspaceId); + checkProductConfigurationWriteAccess(workspaceId,productConfiguration,user); + + productConfigurationDAO.deleteProductConfiguration(productConfiguration); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void updateACLForConfiguration(ConfigurationItemKey ciKey, int productConfigurationId, Map userEntries, Map groupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductConfigurationNotFoundException, AccessRightException { + + ACLFactory aclFactory = new ACLFactory(em); + + // Check the read access to the workspace + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + Locale userLocale = new Locale(user.getLanguage()); + + ProductConfigurationDAO productConfigurationDAO = new ProductConfigurationDAO(userLocale,em); + ProductConfiguration productConfiguration = productConfigurationDAO.getProductConfiguration(productConfigurationId); + + String workspaceId = productConfiguration.getConfigurationItem().getWorkspaceId(); + user = userManager.checkWorkspaceReadAccess(workspaceId); + + checkProductConfigurationWriteAccess(workspaceId, productConfiguration, user); + + if (productConfiguration.getAcl() == null) { + ACL acl = aclFactory.createACL(workspaceId, userEntries, groupEntries); + productConfiguration.setAcl(acl); + } else { + aclFactory.updateACL(workspaceId, productConfiguration.getAcl(), userEntries, groupEntries); + } + } + + private User checkProductConfigurationWriteAccess(String workspaceId, ProductConfiguration productConfiguration, User user) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException { + if (user.isAdministrator()) { + // Check if it is the workspace's administrator + return user; + } + if (productConfiguration.getAcl() == null) { + // Check if the item haven't ACL + return userManager.checkWorkspaceWriteAccess(workspaceId); + } else if (productConfiguration.getAcl().hasWriteAccess(user)) { + // Check if there is a write access + return user; + } else { + // Else throw a AccessRightException + throw new AccessRightException(new Locale(user.getLanguage()), user); + } + } + + + + private User checkProductConfigurationReadAccess(String workspaceId, ProductConfiguration productConfiguration, User user) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException { + if (user.isAdministrator()) { + // Check if it is the workspace's administrator + return user; + } + if (productConfiguration.getAcl() == null) { + // Check if the item haven't ACL + return userManager.checkWorkspaceReadAccess(workspaceId); + } else if (productConfiguration.getAcl().hasReadAccess(user)) { + // Check if there is a write access + return user; + } else { + // Else throw a AccessRightException + throw new AccessRightException(new Locale(user.getLanguage()), user); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void removeACLFromConfiguration(ConfigurationItemKey ciKey, int productConfigurationId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductConfigurationNotFoundException, AccessRightException { + + User user = userManager.checkWorkspaceReadAccess(ciKey.getWorkspace()); + Locale userLocale = new Locale(user.getLanguage()); + + ProductConfigurationDAO productConfigurationDAO = new ProductConfigurationDAO(userLocale,em); + ProductConfiguration productConfiguration = productConfigurationDAO.getProductConfiguration(productConfigurationId); + + String workspaceId = productConfiguration.getConfigurationItem().getWorkspaceId(); + user = userManager.checkWorkspaceReadAccess(workspaceId); + + checkProductConfigurationWriteAccess(workspaceId,productConfiguration,user); + + ACL acl = productConfiguration.getAcl(); + if (acl != null) { + new ACLDAO(em).removeACLEntries(acl); + productConfiguration.setAcl(null); + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getObsoletePartRevisionsInBaseline(String workspaceId, int baselineId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + ProductBaselineDAO productBaselineDAO = new ProductBaselineDAO(locale, em); + ProductBaseline baseline = productBaselineDAO.loadBaseline(baselineId); + List obsoletePartsInBaseline = productBaselineDAO.findObsoletePartsInBaseline(workspaceId, baseline); + return obsoletePartsInBaseline; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getPathToPathLinkFromSourceAndTarget(String workspaceId, String configurationItemId, int baselineId, String sourcePath, String targetPath) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + // Load the baseline + ProductBaselineDAO productBaselineDAO = new ProductBaselineDAO(locale, em); + ProductBaseline baseline = productBaselineDAO.loadBaseline(baselineId); + return new PathToPathLinkDAO(locale, em).getPathToPathLinkFromSourceAndTarget(baseline, sourcePath, targetPath); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getPathToPathLinkTypes(String workspaceId, String configurationItemId, int baselineId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + // Load the baseline + ProductBaselineDAO productBaselineDAO = new ProductBaselineDAO(locale, em); + ProductBaseline baseline = productBaselineDAO.loadBaseline(baselineId); + + return new PathToPathLinkDAO(locale, em).getDistinctPathToPathLinkTypes(baseline); + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/products/ProductInstanceManagerBean.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/products/ProductInstanceManagerBean.java new file mode 100644 index 0000000000..29da06c6c6 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/products/ProductInstanceManagerBean.java @@ -0,0 +1,1311 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.products; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.*; +import com.docdoku.core.document.DocumentLink; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.product.*; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.services.IProductInstanceManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.core.util.NamingConvention; +import com.docdoku.core.util.Tools; +import com.docdoku.server.LogDocument; +import com.docdoku.server.configuration.PSFilterVisitor; +import com.docdoku.server.configuration.spec.ProductBaselineConfigSpec; +import com.docdoku.server.dao.*; +import com.docdoku.server.factory.ACLFactory; +import com.docdoku.server.validation.AttributesConsistencyUtils; + +import javax.annotation.Resource; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.*; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) +@Local(IProductInstanceManagerLocal.class) +@Stateless(name = "ProductInstanceManagerBean") +public class ProductInstanceManagerBean implements IProductInstanceManagerLocal { + + @PersistenceContext + private EntityManager em; + + @Resource + private SessionContext ctx; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IDataManagerLocal dataManager; + + private static final Logger LOGGER = Logger.getLogger(ProductInstanceManagerBean.class.getName()); + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getProductInstanceMasters(String workspaceId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + List productInstanceMasters = productInstanceMasterDAO.findProductInstanceMasters(workspaceId); + + ListIterator ite = productInstanceMasters.listIterator(); + + while(ite.hasNext()){ + ProductInstanceMaster next = ite.next(); + try { + checkProductInstanceReadAccess(workspaceId, next, user); + } catch (AccessRightException e) { + ite.remove(); + } + } + + return productInstanceMasters; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getProductInstanceMasters(ConfigurationItemKey configurationItemKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(configurationItemKey.getWorkspace()); + Locale locale = new Locale(user.getLanguage()); + + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + List productInstanceMasters = productInstanceMasterDAO.findProductInstanceMasters(configurationItemKey.getId(), configurationItemKey.getWorkspace()); + + ListIterator ite = productInstanceMasters.listIterator(); + + while(ite.hasNext()){ + ProductInstanceMaster next = ite.next(); + try { + checkProductInstanceReadAccess(configurationItemKey.getWorkspace(), next, user); + } catch (AccessRightException e) { + ite.remove(); + } + } + + return productInstanceMasters; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ProductInstanceMaster getProductInstanceMaster(ProductInstanceMasterKey productInstanceMasterKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException { + User user = userManager.checkWorkspaceReadAccess(productInstanceMasterKey.getInstanceOf().getWorkspace()); + Locale userLocale = new Locale(user.getLanguage()); + return new ProductInstanceMasterDAO(userLocale, em).loadProductInstanceMaster(productInstanceMasterKey); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public List getProductInstanceIterations(ProductInstanceMasterKey productInstanceMasterKey) throws ProductInstanceMasterNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + User user = userManager.checkWorkspaceReadAccess(productInstanceMasterKey.getInstanceOf().getWorkspace()); + Locale userLocale = new Locale(user.getLanguage()); + ProductInstanceMaster productInstanceMaster = new ProductInstanceMasterDAO(userLocale, em).loadProductInstanceMaster(productInstanceMasterKey); + return productInstanceMaster.getProductInstanceIterations(); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ProductInstanceIteration getProductInstanceIteration(ProductInstanceIterationKey productInstanceIterationKey) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceIterationNotFoundException, ProductInstanceMasterNotFoundException { + User user = userManager.checkWorkspaceReadAccess(productInstanceIterationKey.getProductInstanceMaster().getInstanceOf().getWorkspace()); + Locale userLocale = new Locale(user.getLanguage()); + return new ProductInstanceIterationDAO(userLocale, em).loadProductInstanceIteration(productInstanceIterationKey); + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ProductInstanceMaster createProductInstance(String workspaceId, ConfigurationItemKey configurationItemKey, String serialNumber, int baselineId, Map userEntries, Map groupEntries, List attributes, DocumentRevisionKey[] links, String[] documentLinkComments) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, BaselineNotFoundException, CreationException, ProductInstanceAlreadyExistsException, NotAllowedException, EntityConstraintException, UserNotActiveException, PathToPathLinkAlreadyExistsException, PartMasterNotFoundException, ProductInstanceMasterNotFoundException { + User user = userManager.checkWorkspaceWriteAccess(configurationItemKey.getWorkspace()); + Locale userLocale = new Locale(user.getLanguage()); + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(userLocale, em); + + checkNameValidity(serialNumber,userLocale); + + try {// Check if ths product instance already exist + ProductInstanceMaster productInstanceMaster = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, configurationItemKey.getWorkspace(), configurationItemKey.getId())); + throw new ProductInstanceAlreadyExistsException(userLocale, productInstanceMaster); + } catch (ProductInstanceMasterNotFoundException e) { + LOGGER.log(Level.FINEST, null, e); + } + + ConfigurationItem configurationItem = new ConfigurationItemDAO(em).loadConfigurationItem(configurationItemKey); + ProductInstanceMaster productInstanceMaster = new ProductInstanceMaster(configurationItem, serialNumber); + + if (!userEntries.isEmpty() || !groupEntries.isEmpty()) { + ACLFactory aclFactory = new ACLFactory(em); + ACL acl = aclFactory.createACLFromPermissions(workspaceId, userEntries, groupEntries); + productInstanceMaster.setAcl(acl); + } + Date now = new Date(); + ProductInstanceIteration productInstanceIteration = productInstanceMaster.createNextIteration(); + productInstanceIteration.setIterationNote("Initial"); + productInstanceIteration.setAuthor(user); + productInstanceIteration.setCreationDate(now); + productInstanceIteration.setModificationDate(now); + + PartCollection partCollection = new PartCollection(); + new PartCollectionDAO(em).createPartCollection(partCollection); + partCollection.setAuthor(user); + partCollection.setCreationDate(now); + + DocumentCollection documentCollection = new DocumentCollection(); + new DocumentCollectionDAO(em).createDocumentCollection(documentCollection); + documentCollection.setAuthor(user); + documentCollection.setCreationDate(now); + + ProductBaseline productBaseline = new ProductBaselineDAO(em).loadBaseline(baselineId); + productInstanceIteration.setBasedOn(productBaseline); + productInstanceIteration.setSubstituteLinks(new HashSet<>(productBaseline.getSubstituteLinks())); + productInstanceIteration.setOptionalUsageLinks(new HashSet<>(productBaseline.getOptionalUsageLinks())); + + productInstanceMasterDAO.createProductInstanceMaster(productInstanceMaster); + + for (BaselinedPart baselinedPart : productBaseline.getBaselinedParts().values()) { + partCollection.addBaselinedPart(baselinedPart.getTargetPart()); + } + + for (BaselinedDocument baselinedDocument : productBaseline.getBaselinedDocuments().values()) { + documentCollection.addBaselinedDocument(baselinedDocument.getTargetDocument()); + } + + productInstanceIteration.setPartCollection(partCollection); + productInstanceIteration.setDocumentCollection(documentCollection); + + productInstanceIteration.setInstanceAttributes(attributes); + DocumentLinkDAO linkDAO = new DocumentLinkDAO(userLocale, em); + + if (links != null) { + Set currentLinks = new HashSet<>(productInstanceIteration.getLinkedDocuments()); + + for (DocumentLink link : currentLinks) { + productInstanceIteration.getLinkedDocuments().remove(link); + } + + int counter = 0; + for (DocumentRevisionKey link : links) { + DocumentLink newLink = new DocumentLink(em.getReference(DocumentRevision.class, link)); + newLink.setComment(documentLinkComments[counter]); + linkDAO.createLink(newLink); + productInstanceIteration.getLinkedDocuments().add(newLink); + counter++; + } + } + + copyPathToPathLinks(user,productInstanceIteration); + + return productInstanceMaster; + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ProductInstanceMaster updateProductInstance(String workspaceId, int iteration, String iterationNote, ConfigurationItemKey configurationItemKey, String serialNumber, int baselineId, List attributes, DocumentRevisionKey[] links, String[] documentLinkComments) throws ProductInstanceMasterNotFoundException, UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ProductInstanceIterationNotFoundException, UserNotActiveException, BaselineNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale userLocale = new Locale(user.getLanguage()); + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(userLocale, em); + ProductInstanceMasterKey pInstanceIterationKey = new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemKey.getId()); + ProductInstanceMaster productInstanceMaster = productInstanceMasterDAO.loadProductInstanceMaster(pInstanceIterationKey); + + ProductInstanceIteration lastIteration = productInstanceMaster.getLastIteration(); + + ProductInstanceIteration productInstanceIteration = productInstanceMaster.getProductInstanceIterations().get(iteration - 1); + // Check the access to the product instance + checkProductInstanceWriteAccess(workspaceId, productInstanceMaster, user); + + if (productInstanceIteration != null) { + productInstanceIteration.setIterationNote(iterationNote); + productInstanceIteration.setInstanceAttributes(attributes); + productInstanceIteration.setSubstituteLinks(new HashSet<>(lastIteration.getSubstituteLinks())); + productInstanceIteration.setOptionalUsageLinks(new HashSet<>(lastIteration.getOptionalUsageLinks())); + productInstanceIteration.setModificationDate(new Date()); + DocumentLinkDAO linkDAO = new DocumentLinkDAO(userLocale, em); + if (links != null) { + + Set currentLinks = new HashSet<>(productInstanceIteration.getLinkedDocuments()); + + for (DocumentLink link : currentLinks) { + productInstanceIteration.getLinkedDocuments().remove(link); + } + + int counter = 0; + for (DocumentRevisionKey link : links) { + DocumentLink newLink = new DocumentLink(em.getReference(DocumentRevision.class, link)); + newLink.setComment(documentLinkComments[counter]); + linkDAO.createLink(newLink); + productInstanceIteration.getLinkedDocuments().add(newLink); + counter++; + } + } + ProductBaseline productBaseline = new ProductBaselineDAO(em).loadBaseline(baselineId); + for (BaselinedDocument baselinedDocument : productBaseline.getBaselinedDocuments().values()) { + if(!productBaseline.getDocumentCollection().hasBaselinedDocument(baselinedDocument.getTargetDocument().getDocumentRevisionKey())){ + productBaseline.getDocumentCollection().addBaselinedDocument(baselinedDocument.getTargetDocument()); + } + + } + + return productInstanceMaster; + + } else { + throw new ProductInstanceIterationNotFoundException(userLocale, new ProductInstanceIterationKey(serialNumber, configurationItemKey.getWorkspace(), configurationItemKey.getId(), iteration)); + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ProductInstanceMaster rebaseProductInstance(String workspaceId, String serialNumber, ConfigurationItemKey configurationItemKey, int baselineId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException, BaselineNotFoundException, NotAllowedException, ConfigurationItemNotFoundException, PathToPathLinkAlreadyExistsException, PartMasterNotFoundException, CreationException, EntityConstraintException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale userLocale = new Locale(user.getLanguage()); + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(userLocale, em); + ProductInstanceMasterKey pInstanceIterationKey = new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemKey.getId()); + ProductInstanceMaster productInstanceMaster = productInstanceMasterDAO.loadProductInstanceMaster(pInstanceIterationKey); + + ProductInstanceIteration lastIteration = productInstanceMaster.getLastIteration(); + + // Check the access to the product instance + checkProductInstanceWriteAccess(workspaceId, productInstanceMaster, user); + + // Load the new baseline + ProductBaselineDAO productBaselineDAO = new ProductBaselineDAO(userLocale, em); + ProductBaseline baseline = productBaselineDAO.loadBaseline(baselineId); + + // Check valid parameters + // Config key should be baseline product's one, same for product instance + if (baseline.getConfigurationItem().getKey().equals(configurationItemKey) + && baseline.getConfigurationItem().getKey().equals(productInstanceMaster.getInstanceOf().getKey())) { + + + // Create a new iteration + ProductInstanceIteration nextIteration = productInstanceMaster.createNextIteration(); + new ProductInstanceIterationDAO(userLocale, em).createProductInstanceIteration(nextIteration); + + nextIteration.setIterationNote(lastIteration.getIterationNote()); + + Date now = new Date(); + + PartCollection partCollection = new PartCollection(); + new PartCollectionDAO(em).createPartCollection(partCollection); + partCollection.setAuthor(user); + partCollection.setCreationDate(now); + + DocumentCollection documentCollection = new DocumentCollection(); + new DocumentCollectionDAO(em).createDocumentCollection(documentCollection); + documentCollection.setAuthor(user); + documentCollection.setCreationDate(now); + + nextIteration.setAuthor(user); + nextIteration.setCreationDate(now); + nextIteration.setModificationDate(now); + + for (BaselinedPart baselinedPart : baseline.getBaselinedParts().values()) { + partCollection.addBaselinedPart(baselinedPart.getTargetPart()); + } + + for (BaselinedDocument baselinedDocument : baseline.getBaselinedDocuments().values()) { + documentCollection.addBaselinedDocument(baselinedDocument.getTargetDocument()); + } + + nextIteration.setPartCollection(partCollection); + nextIteration.setDocumentCollection(documentCollection); + + nextIteration.setBasedOn(baseline); + nextIteration.setSubstituteLinks(new HashSet<>(baseline.getSubstituteLinks())); + nextIteration.setOptionalUsageLinks(new HashSet<>(baseline.getOptionalUsageLinks())); + + Set linkedDocuments = lastIteration.getLinkedDocuments(); + Set newLinks = new HashSet<>(); + for(DocumentLink link : linkedDocuments){ + newLinks.add(link.clone()); + } + nextIteration.setLinkedDocuments(newLinks); + + copyPathToPathLinks(user, nextIteration); + copyPathDataMasterList(workspaceId, user, lastIteration, nextIteration); + + } else { + throw new NotAllowedException(userLocale, "NotAllowedException53"); + } + + return productInstanceMaster; + + } + + private void copyPathDataMasterList(String workspaceId, User user, ProductInstanceIteration lastIteration, ProductInstanceIteration nextIteration) throws NotAllowedException, EntityConstraintException, PartMasterNotFoundException { + + List pathDataMasterList = new ArrayList<>(); + ProductBaseline productBaseline = nextIteration.getBasedOn(); + PartMaster partMaster = productBaseline.getConfigurationItem().getDesignItem(); + String serialNumber = lastIteration.getSerialNumber(); + BinaryResourceDAO binDAO = new BinaryResourceDAO(new Locale(user.getLanguage()), em); + + PSFilter filter = new ProductBaselineConfigSpec(productBaseline,user); + + PSFilterVisitor psFilterVisitor = new PSFilterVisitor(em, user, filter) { + @Override + public void onIndeterminateVersion(PartMaster partMaster, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onIndeterminatePath(List pCurrentPath, List pCurrentPathPartIterations) { + // Unused here + } + + @Override + public void onUnresolvedPath(List pCurrentPath, List partIterations) throws NotAllowedException { + // Unused here + } + + @Override + public void onBranchDiscovered(List pCurrentPath, List copyPartIteration) { + // Unused here + } + + @Override + public void onOptionalPath(List path, List partIterations) { + // Unused here + } + + @Override + public boolean onPathWalk(List path, List parts) { + // Find pathData in previous iteration which is on this path. Copy it. + String pathAsString = Tools.getPathAsString(path); + for (PathDataMaster pathDataMaster : lastIteration.getPathDataMasterList()) { + if (pathAsString.equals(pathDataMaster.getPath())) { + pathDataMasterList.add(clonePathDataMaster(pathDataMaster)); + } + } + return true; + } + + private PathDataMaster clonePathDataMaster(PathDataMaster pathDataMaster) { + PathDataMaster clone = new PathDataMaster(); + + // Need to persist and flush to get an id + em.persist(clone); + em.flush(); + + clone.setPath(pathDataMaster.getPath()); + + List pathDataIterations = new ArrayList<>(); + for (PathDataIteration pathDataIteration : pathDataMaster.getPathDataIterations()) { + PathDataIteration clonedIteration = clonePathDataIteration(workspaceId, clone, pathDataIteration); + pathDataIterations.add(clonedIteration); + } + clone.setPathDataIterations(pathDataIterations); + + return clone; + } + + private PathDataIteration clonePathDataIteration(String workspaceId, PathDataMaster newPathDataMaster, PathDataIteration pathDataIteration) { + PathDataIteration clone = new PathDataIteration(); + + clone.setPathDataMaster(newPathDataMaster); + clone.setDateIteration(pathDataIteration.getDateIteration()); + clone.setIteration(pathDataIteration.getIteration()); + clone.setIterationNote(pathDataIteration.getIterationNote()); + + // Attributes + List clonedAttributes = new ArrayList<>(); + for (InstanceAttribute attribute : pathDataIteration.getInstanceAttributes()) { + InstanceAttribute clonedAttribute = attribute.clone(); + clonedAttributes.add(clonedAttribute); + } + clone.setInstanceAttributes(clonedAttributes); + + // Attached files + for (BinaryResource sourceFile : pathDataIteration.getAttachedFiles()) { + String fileName = sourceFile.getName(); + long length = sourceFile.getContentLength(); + Date lastModified = sourceFile.getLastModified(); + String fullName = workspaceId + "/product-instances/" + serialNumber + "/pathdata/" + newPathDataMaster.getId() + "/iterations/" + pathDataIteration.getIteration() + '/' + fileName; + BinaryResource targetFile = new BinaryResource(fullName, length, lastModified); + try { + copyBinary(sourceFile, targetFile); + clone.getAttachedFiles().add(targetFile); + } catch (FileAlreadyExistsException | CreationException e) { + LOGGER.log(Level.FINEST, null, e); + } + } + + // Linked documents + Set newLinks = new HashSet<>(); + for (DocumentLink documentLink : pathDataIteration.getLinkedDocuments()) { + newLinks.add(documentLink.clone()); + } + clone.setLinkedDocuments(newLinks); + + return clone; + } + + private void copyBinary(BinaryResource sourceFile, BinaryResource targetFile) throws FileAlreadyExistsException, CreationException { + binDAO.createBinaryResource(targetFile); + try { + dataManager.copyData(sourceFile, targetFile); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + + @Override + public void onUnresolvedVersion(PartMaster partMaster) { + // Unused here + } + }; + + psFilterVisitor.visit(partMaster, -1); + + nextIteration.setPathDataMasterList(pathDataMasterList); + + } + + private void copyPathToPathLinks(User user, ProductInstanceIteration productInstanceIteration) throws PathToPathLinkAlreadyExistsException, CreationException, UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ConfigurationItemNotFoundException, BaselineNotFoundException, ProductInstanceMasterNotFoundException, NotAllowedException, EntityConstraintException, PartMasterNotFoundException { + PathToPathLinkDAO pathToPathLinkDAO = new PathToPathLinkDAO(new Locale(user.getLanguage()), em); + List links = productInstanceIteration.getBasedOn().getPathToPathLinks(); + for(PathToPathLink link : links){ + PathToPathLink clone = link.clone(); + pathToPathLinkDAO.createPathToPathLink(clone); + productInstanceIteration.addPathToPathLink(clone); + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public ProductInstanceMaster removeFileFromProductInstanceIteration(String workspaceId, int iteration, String fullName, ProductInstanceMasterKey productInstanceMasterKey) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, UserNotActiveException, FileNotFoundException, ProductInstanceMasterNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale userLocale = new Locale(user.getLanguage()); + ProductInstanceMaster productInstanceMaster = getProductInstanceMaster(productInstanceMasterKey); + + ProductInstanceIteration productInstanceIteration = productInstanceMaster.getProductInstanceIterations().get(iteration - 1); + BinaryResourceDAO binDAO = new BinaryResourceDAO(userLocale, em); + BinaryResource file = binDAO.loadBinaryResource(fullName); + checkProductInstanceWriteAccess(workspaceId, productInstanceMaster, user); + + productInstanceIteration.removeFile(file); + binDAO.removeBinaryResource(file); + + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + + return productInstanceMaster; + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public BinaryResource renameFileInProductInstance(String pFullName, String pNewName, String serialNumber, String cId, int iteration) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, FileNotFoundException, ProductInstanceMasterNotFoundException, NotAllowedException, AccessRightException, FileAlreadyExistsException, CreationException, StorageException { + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(pFullName)); + Locale userLocale = new Locale(user.getLanguage()); + + BinaryResourceDAO binDAO = new BinaryResourceDAO(new Locale(user.getLanguage()), em); + BinaryResource file = binDAO.loadBinaryResource(pFullName); + + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(userLocale, em); + ProductInstanceMasterKey pInstanceIterationKey = new ProductInstanceMasterKey(serialNumber, user.getWorkspaceId(), cId); + ProductInstanceMaster productInstanceMaster = productInstanceMasterDAO.loadProductInstanceMaster(pInstanceIterationKey); + checkNameFileValidity(pNewName, userLocale); + + try{ + + binDAO.loadBinaryResource(file.getNewFullName(pNewName)); + throw new FileAlreadyExistsException(userLocale, pNewName); + + }catch(FileNotFoundException e){ + + ProductInstanceIteration productInstanceIteration = productInstanceMaster.getProductInstanceIterations().get(iteration - 1); + //check access rights on product instance + checkProductInstanceWriteAccess(user.getWorkspaceId(), productInstanceMaster, user); + + dataManager.renameFile(file, pNewName); + productInstanceIteration.removeFile(file); + binDAO.removeBinaryResource(file); + + BinaryResource newFile = new BinaryResource(file.getNewFullName(pNewName), file.getContentLength(), file.getLastModified()); + binDAO.createBinaryResource(newFile); + productInstanceIteration.addFile(newFile); + return newFile; + } + + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void deleteProductInstance(String workspaceId, String configurationItemId, String serialNumber) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, UserNotActiveException, ProductInstanceMasterNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale userLocale = new Locale(user.getLanguage()); + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(userLocale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + checkProductInstanceWriteAccess(workspaceId, prodInstM, user); + + productInstanceMasterDAO.deleteProductInstanceMaster(prodInstM); + + for (ProductInstanceIteration pii : prodInstM.getProductInstanceIterations()) { + for (BinaryResource file : pii.getAttachedFiles()) { + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + } + } + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void updateACLForProductInstanceMaster(String workspaceId, String configurationItemId, String serialNumber, Map userEntries, Map groupEntries) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException { + + ACLFactory aclFactory = new ACLFactory(em); + + // Check the read access to the workspace + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale userLocale = new Locale(user.getLanguage()); + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(userLocale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + + // Check the access to the product instance master + checkProductInstanceWriteAccess(workspaceId, prodInstM, user); + + if (prodInstM.getAcl() == null) { + ACL acl = aclFactory.createACL(workspaceId, userEntries, groupEntries); + prodInstM.setAcl(acl); + } else { + aclFactory.updateACL(workspaceId, prodInstM.getAcl(), userEntries, groupEntries); + } + } + + + @RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) + @Override + public void removeACLFromProductInstanceMaster(String workspaceId, String configurationItemId, String serialNumber) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, ProductInstanceMasterNotFoundException { + + // Check the read access to the workspace + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + + // Check the access to the product instance + checkProductInstanceWriteAccess(workspaceId, prodInstM, user); + + ACL acl = prodInstM.getAcl(); + if (acl != null) { + new ACLDAO(em).removeACLEntries(acl); + prodInstM.setAcl(null); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public BinaryResource saveFileInProductInstance(String workspaceId, ProductInstanceIterationKey pdtIterationKey, String fileName, int pSize) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, ProductInstanceMasterNotFoundException, AccessRightException, ProductInstanceIterationNotFoundException, FileAlreadyExistsException, CreationException { + // Check the read access to the workspace + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + checkNameFileValidity(fileName, locale); + + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(pdtIterationKey.getProductInstanceMaster().getSerialNumber(), workspaceId, pdtIterationKey.getProductInstanceMaster().getInstanceOf().getId())); + // Check the access to the product instance + checkProductInstanceWriteAccess(workspaceId, prodInstM, user); + + // Load the product instance iteration + ProductInstanceIteration productInstanceIteration = this.getProductInstanceIteration(pdtIterationKey); + + + BinaryResource binaryResource = null; + String fullName = workspaceId + "/product-instances/" + prodInstM.getSerialNumber() + "/iterations/" + productInstanceIteration.getIteration() + "/" + fileName; + + for (BinaryResource bin : productInstanceIteration.getAttachedFiles()) { + if (bin.getFullName().equals(fullName)) { + binaryResource = bin; + break; + } + } + + if (binaryResource == null) { + binaryResource = new BinaryResource(fullName, pSize, new Date()); + new BinaryResourceDAO(locale, em).createBinaryResource(binaryResource); + productInstanceIteration.addFile(binaryResource); + } else { + binaryResource.setContentLength(pSize); + binaryResource.setLastModified(new Date()); + } + return binaryResource; + + } + + + @LogDocument + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) + @Override + public BinaryResource getBinaryResource(String fullName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, FileNotFoundException, NotAllowedException { + + if (ctx.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)) { + // Don't check access right because it is done before. (Is public or isShared) + return new BinaryResourceDAO(em).loadBinaryResource(fullName); + } + + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(fullName)); + Locale userLocale = new Locale(user.getLanguage()); + BinaryResourceDAO binDAO = new BinaryResourceDAO(userLocale, em); + BinaryResource binaryResource = binDAO.loadBinaryResource(fullName); + + ProductInstanceIteration productInstanceIteration = binDAO.getProductInstanceIterationHolder(binaryResource); + if (productInstanceIteration != null) { + ProductInstanceMaster productInstanceMaster = productInstanceIteration.getProductInstanceMaster(); + + if (isACLGrantReadAccess(user, productInstanceMaster)) { + return binaryResource; + } else { + throw new NotAllowedException(userLocale, "NotAllowedException34"); + } + } else { + throw new FileNotFoundException(userLocale, fullName); + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public PathDataMaster addNewPathDataIteration(String workspaceId, String configurationItemId, String serialNumber, int pathDataId, List attributes, String note, DocumentRevisionKey[] links, String[] documentLinkComments) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, UserNotActiveException, NotAllowedException, PathDataAlreadyExistsException, FileAlreadyExistsException, CreationException, PathDataMasterNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + + // Check the access to the product instance + checkProductInstanceWriteAccess(workspaceId, prodInstM, user); + + ProductInstanceIteration prodInstI = prodInstM.getLastIteration(); + //PathDataMaster pathDataMaster = pathDataMasterDAO.findByPathIdAndProductInstanceIteration(pathDataId, prodInstI); + //strangely retrieving pathDataMaster from a query as the side effect of not synchronized its pathDataIterations to the L2 cache + List pathDataMasterList = prodInstI.getPathDataMasterList(); + PathDataMaster pathDataMaster = null; + if(pathDataMasterList!=null){ + for(PathDataMaster pd:pathDataMasterList){ + if(pd.getId()==pathDataId){ + pathDataMaster=pd; + break; + } + } + } + if(pathDataMaster==null) + throw new PathDataMasterNotFoundException(locale, pathDataId); + + BinaryResourceDAO binDAO = new BinaryResourceDAO(locale, em); + Set sourceFiles = pathDataMaster.getLastIteration().getAttachedFiles(); + Set targetFiles = new HashSet<>(); + + if (pathDataMaster.getLastIteration() != null) { + int iteration = pathDataMaster.getLastIteration().getIteration() + 1; + if (!sourceFiles.isEmpty()) { + for (BinaryResource sourceFile : sourceFiles) { + String fileName = sourceFile.getName(); + long length = sourceFile.getContentLength(); + Date lastModified = sourceFile.getLastModified(); + String fullName = workspaceId + "/product-instances/" + serialNumber + "/pathdata/" + pathDataId + "/iterations/" + iteration + '/' + fileName; + BinaryResource targetFile = new BinaryResource(fullName, length, lastModified); + binDAO.createBinaryResource(targetFile); + targetFiles.add(targetFile); + try { + dataManager.copyData(sourceFile, targetFile); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + } + } + + PathDataIteration pathDataIteration = pathDataMaster.createNextIteration(); + pathDataIteration.setInstanceAttributes(attributes); + pathDataIteration.setIterationNote(note); + createDocumentLink(locale, pathDataIteration, links, documentLinkComments); + pathDataIteration.setAttachedFiles(targetFiles); + return pathDataMaster; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public PathDataMaster updatePathData(String workspaceId, String configurationItemId, String serialNumber, int pathDataMasterId, int iteration, List attributes, String note, DocumentRevisionKey[] pLinkKeys, String[] documentLinkComments) throws UserNotActiveException, WorkspaceNotFoundException, UserNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException, NotAllowedException { + + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + + // Check the access to the product instance + checkProductInstanceWriteAccess(workspaceId, prodInstM, user); + + PathDataMaster pathDataMaster = em.find(PathDataMaster.class, pathDataMasterId); + PathDataIteration pathDataIteration = pathDataMaster.getPathDataIterations().get(iteration - 1); + + // This path data isn't owned by product master. + if (!prodInstM.getLastIteration().getPathDataMasterList().contains(pathDataMaster)) { + throw new NotAllowedException(locale, "NotAllowedException52"); + } + + boolean valid = AttributesConsistencyUtils.hasValidChange(attributes, false, pathDataIteration.getInstanceAttributes()); + if(!valid) { + throw new NotAllowedException(locale, "NotAllowedException59"); + } + pathDataIteration.setInstanceAttributes(attributes); + pathDataIteration.setIterationNote(note); + + if (pLinkKeys != null) { + + // Set links + DocumentLinkDAO linkDAO = new DocumentLinkDAO(locale, em); + + Set currentLinks = new HashSet<>(pathDataIteration.getLinkedDocuments()); + + for (DocumentLink link : currentLinks) { + pathDataIteration.getLinkedDocuments().remove(link); + } + + int counter = 0; + for (DocumentRevisionKey link : pLinkKeys) { + DocumentLink newLink = new DocumentLink(em.getReference(DocumentRevision.class, link)); + newLink.setComment(documentLinkComments[counter]); + linkDAO.createLink(newLink); + pathDataIteration.getLinkedDocuments().add(newLink); + counter++; + } + } + + return pathDataMaster; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public void deletePathData(String workspaceId, String configurationItemId, String serialNumber, int pathDataId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException, NotAllowedException { + + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + + checkProductInstanceWriteAccess(workspaceId, prodInstM, user); + + PathDataMasterDAO pathDataMasterDAO = new PathDataMasterDAO(locale, em); + PathDataMaster pathDataMaster = em.find(PathDataMaster.class, pathDataId); + + ProductInstanceIteration prodInstI = prodInstM.getLastIteration(); + + // This path data isn't owned by product master. + if (!prodInstI.getPathDataMasterList().contains(pathDataMaster)) { + throw new NotAllowedException(locale, "NotAllowedException52"); + } + + prodInstI.getPathDataMasterList().remove(pathDataMaster); + pathDataMasterDAO.removePathData(pathDataMaster); + + for(PathDataIteration pathDataIteration : pathDataMaster.getPathDataIterations()) { + for (BinaryResource file : pathDataIteration.getAttachedFiles()) { + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + } + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public PathDataMaster getPathDataByPath(String workspaceId, String configurationItemId, String serialNumber, String pathAsString) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException, ProductInstanceMasterNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + + // Check the access to the product instance + checkProductInstanceReadAccess(workspaceId, prodInstM, user); + + ProductInstanceIteration prodInstI = prodInstM.getLastIteration(); + PathDataMasterDAO pathDataMasterDAO = new PathDataMasterDAO(locale, em); + PathDataMaster pathDataMaster=null; + try{ + pathDataMaster=pathDataMasterDAO.findByPathAndProductInstanceIteration(pathAsString, prodInstI); + }catch(PathDataMasterNotFoundException pEx){ + //not found return null; + } + return pathDataMaster; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public boolean canWrite(String workspaceId, String configurationItemId, String serialNumber) { + try { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + checkProductInstanceWriteAccess(workspaceId, prodInstM, user); + return true; + } catch (ProductInstanceMasterNotFoundException | AccessRightException | UserNotActiveException | WorkspaceNotFoundException | UserNotFoundException e){ + return false; + } + + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public BinaryResource saveFileInPathData(String workspaceId, String configurationItemId, String serialNumber, int pathDataId, int iteration, String fileName, int pSize) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, AccessRightException, ProductInstanceMasterNotFoundException, FileAlreadyExistsException, CreationException { + // Check the read access to the workspace + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + checkNameFileValidity(fileName, locale); + + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + // Check the access to the product instance + checkProductInstanceWriteAccess(workspaceId, prodInstM, user); + + // Load path data + PathDataMaster pathDataMaster = em.find(PathDataMaster.class, pathDataId); + PathDataIteration pathDataIteration = pathDataMaster.getPathDataIterations().get(iteration - 1); + + // This path data isn't owned by product master. + if (!prodInstM.getLastIteration().getPathDataMasterList().contains(pathDataMaster)) { + throw new NotAllowedException(locale, "NotAllowedException52"); + } + + BinaryResource binaryResource = null; + String fullName = workspaceId + "/product-instances/" + prodInstM.getSerialNumber() + "/pathdata/" + pathDataMaster.getId() + "/iterations/" + iteration + '/' + fileName; + + for (BinaryResource bin : pathDataIteration.getAttachedFiles()) { + if (bin.getFullName().equals(fullName)) { + binaryResource = bin; + break; + } + } + + if (binaryResource == null) { + binaryResource = new BinaryResource(fullName, pSize, new Date()); + new BinaryResourceDAO(locale, em).createBinaryResource(binaryResource); + pathDataIteration.addFile(binaryResource); + } else { + binaryResource.setContentLength(pSize); + binaryResource.setLastModified(new Date()); + } + return binaryResource; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public BinaryResource getPathDataBinaryResource(String fullName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, FileNotFoundException, AccessRightException { + + if (ctx.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)) { + // Don't check access right because it is done before. (Is public or isShared) + return new BinaryResourceDAO(em).loadBinaryResource(fullName); + } + + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(fullName)); + Locale userLocale = new Locale(user.getLanguage()); + BinaryResourceDAO binDAO = new BinaryResourceDAO(userLocale, em); + BinaryResource binaryResource = binDAO.loadBinaryResource(fullName); + + PathDataIteration pathDataIteration = binDAO.getPathDataHolder(binaryResource); + PathDataMaster pathDataMaster = pathDataIteration.getPathDataMaster(); + PathDataMasterDAO pathDataMasterDAO = new PathDataMasterDAO(userLocale, em); + + if (pathDataMaster != null) { + + ProductInstanceMaster productInstanceMaster = pathDataMasterDAO.findByPathData(pathDataMaster); + + String workspaceId = productInstanceMaster.getInstanceOf().getWorkspaceId(); + checkProductInstanceReadAccess(workspaceId, productInstanceMaster, user); + + return binaryResource; + + } else { + throw new FileNotFoundException(userLocale, fullName); + } + } + + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public BinaryResource renameFileInPathData(String workspaceId, String configurationItemId, String serialNumber, int pathDataId, int iteration, String pFullName, String pNewName) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, FileNotFoundException, ProductInstanceMasterNotFoundException, NotAllowedException, AccessRightException, FileAlreadyExistsException, CreationException { + + User user = userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(pFullName)); + Locale userLocale = new Locale(user.getLanguage()); + + BinaryResourceDAO binDAO = new BinaryResourceDAO(new Locale(user.getLanguage()), em); + BinaryResource file = binDAO.loadBinaryResource(pFullName); + + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(userLocale, em); + ProductInstanceMasterKey pInstanceIterationKey = new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId); + ProductInstanceMaster productInstanceMaster = productInstanceMasterDAO.loadProductInstanceMaster(pInstanceIterationKey); + + checkNameFileValidity(pNewName, userLocale); + + try{ + binDAO.loadBinaryResource(file.getNewFullName(pNewName)); + throw new FileAlreadyExistsException(userLocale, pNewName); + }catch(FileNotFoundException e){ + + //check access rights on product master + checkProductInstanceWriteAccess(user.getWorkspaceId(), productInstanceMaster, user); + + PathDataMaster pathDataMaster = em.find(PathDataMaster.class, pathDataId); + + //allowed on last iteration only + if (pathDataMaster.getPathDataIterations().size() != (iteration - 1)) { + throw new NotAllowedException(userLocale, "NotAllowedException55"); + } + + PathDataIteration pathDataIteration = pathDataMaster.getPathDataIterations().get(iteration - 1); + + // This path data isn't owned by product master. + if (!productInstanceMaster.getLastIteration().getPathDataMasterList().contains(pathDataMaster)) { + throw new NotAllowedException(userLocale, "NotAllowedException52"); + } + + try { + dataManager.renameFile(file, pNewName); + pathDataIteration.removeFile(file); + binDAO.removeBinaryResource(file); + + BinaryResource newFile = new BinaryResource(file.getNewFullName(pNewName), file.getContentLength(), file.getLastModified()); + binDAO.createBinaryResource(newFile); + pathDataIteration.addFile(newFile); + return newFile; + + + } catch (StorageException se) { + LOGGER.log(Level.INFO, null, se); + return null; + } + + } + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public ProductInstanceMaster removeFileFromPathData(String workspaceId, String configurationItemId, String serialNumber, int pathDataId, int iteration, String fullName, ProductInstanceMaster productInstanceMaster) throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, UserNotActiveException, NotAllowedException, FileNotFoundException { + + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale userLocale = new Locale(user.getLanguage()); + + BinaryResourceDAO binDAO = new BinaryResourceDAO(userLocale, em); + BinaryResource file = binDAO.loadBinaryResource(fullName); + checkProductInstanceWriteAccess(workspaceId, productInstanceMaster, user); + + PathDataMaster pathDataMaster = em.find(PathDataMaster.class, pathDataId); + PathDataIteration pathDataIteration = pathDataMaster.getPathDataIterations().get(iteration - 1); + + // This path data isn't owned by product master. + if (!productInstanceMaster.getLastIteration().getPathDataMasterList().contains(pathDataMaster)) { + throw new NotAllowedException(userLocale, "NotAllowedException52"); + } + + pathDataIteration.removeFile(file); + binDAO.removeBinaryResource(file); + + try { + dataManager.deleteData(file); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + } + + return productInstanceMaster; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public BinaryResource saveFileInPathDataIteration(String workspaceId, String configurationItemId, String serialNumber, int path, int iteration, String fileName, int pSize) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, NotAllowedException, AccessRightException, ProductInstanceMasterNotFoundException, FileAlreadyExistsException, CreationException, PathDataMasterNotFoundException { + // Check the read access to the workspace + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + checkNameFileValidity(fileName, locale); + + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + // Check the access to the product instance + checkProductInstanceWriteAccess(workspaceId, prodInstM, user); + + ProductInstanceIteration prodInstI = prodInstM.getLastIteration(); + + // Load path data master + PathDataMasterDAO pathDataMasterDAO = new PathDataMasterDAO(locale, em); + PathDataMaster pathDataMaster = pathDataMasterDAO.findByPathIdAndProductInstanceIteration(path, prodInstI); + + PathDataIteration pathDataIteration = pathDataMaster.getPathDataIterations().get(iteration - 1); + // This path data isn't owned by product master. + if (!prodInstI.getPathDataMasterList().contains(pathDataMaster)) { + throw new NotAllowedException(locale, "NotAllowedException52"); + } + + BinaryResource binaryResource = null; + String fullName = workspaceId + "/product-instances/" + prodInstM.getSerialNumber() + "/pathdata/" + pathDataMaster.getId() + "/iterations/" + iteration + '/' + fileName; + + for (BinaryResource bin : pathDataIteration.getAttachedFiles()) { + if (bin.getFullName().equals(fullName)) { + binaryResource = bin; + break; + } + } + + if (binaryResource == null) { + binaryResource = new BinaryResource(fullName, pSize, new Date()); + new BinaryResourceDAO(locale, em).createBinaryResource(binaryResource); + pathDataIteration.addFile(binaryResource); + } else { + binaryResource.setContentLength(pSize); + binaryResource.setLastModified(new Date()); + } + return binaryResource; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public PathDataMaster createPathDataMaster(String workspaceId, String configurationItemId, String serialNumber, String path, List attributes, String iterationNote) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + + // Check the access to the product instance + checkProductInstanceWriteAccess(workspaceId, prodInstM, user); + + PathDataMasterDAO pathDataMasterDAO = new PathDataMasterDAO(locale, em); + + PathDataMaster pathDataMaster = new PathDataMaster(); + pathDataMaster.setPath(path); + pathDataMasterDAO.createPathData(pathDataMaster); + + ProductInstanceIteration prodInstI = prodInstM.getLastIteration(); + + // Check if not already a path data for this configuration + for (PathDataMaster master : prodInstI.getPathDataMasterList()) { + if (master.getPath()!= null && master.getPath().equals(path)) { + PathDataIteration pathDataIteration = pathDataMaster.createNextIteration(); + pathDataIteration.setInstanceAttributes(attributes); + pathDataIteration.setIterationNote(iterationNote); + PathDataIterationDAO pathDataIterationDAO = new PathDataIterationDAO(em); + pathDataIterationDAO.createPathDataIteration(pathDataIteration); + + return pathDataMaster; + } + } + PathDataIteration pathDataIteration = pathDataMaster.createNextIteration(); + pathDataIteration.setInstanceAttributes(attributes); + pathDataIteration.setIterationNote(iterationNote); + PathDataIterationDAO pathDataIterationDAO = new PathDataIterationDAO(em); + pathDataIterationDAO.createPathDataIteration(pathDataIteration); + prodInstI.getPathDataMasterList().add(pathDataMaster); + return pathDataMaster; + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public PathToPathLink getPathToPathLink(String workspaceId, String configurationItemId, String serialNumber, int pathToPathLinkId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException, PathToPathLinkNotFoundException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + + checkProductInstanceReadAccess(workspaceId, prodInstM, user); + PathToPathLinkDAO pathToPathLinkDAO = new PathToPathLinkDAO(locale, em); + return pathToPathLinkDAO.loadPathToPathLink(pathToPathLinkId); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public List getPathToPathLinkTypes(String workspaceId, String configurationItemId, String serialNumber) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException { + + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + + checkProductInstanceReadAccess(workspaceId,prodInstM,user); + return new PathToPathLinkDAO(locale, em).getDistinctPathToPathLinkTypes(prodInstM.getLastIteration()); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public List getPathToPathLinks(String workspaceId, String configurationItemId, String serialNumber) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException { + + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + + checkProductInstanceReadAccess(workspaceId,prodInstM,user); + return new PathToPathLinkDAO(locale, em).getDistinctPathToPathLink(prodInstM.getLastIteration()); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public List getPathToPathLinkFromSourceAndTarget(String workspaceId, String configurationItemId, String serialNumber, String sourcePath, String targetPath) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + checkProductInstanceReadAccess(workspaceId, prodInstM, user); + + return new PathToPathLinkDAO(locale, em).getPathToPathLinkFromSourceAndTarget(prodInstM.getLastIteration(), sourcePath, targetPath); + } + + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + @Override + public List getRootPathToPathLinks(String workspaceId, String configurationItemId, String serialNumber, String type) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user.getLanguage()); + // Load the product instance + ProductInstanceMasterDAO productInstanceMasterDAO = new ProductInstanceMasterDAO(locale, em); + ProductInstanceMaster prodInstM = productInstanceMasterDAO.loadProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + checkProductInstanceReadAccess(workspaceId, prodInstM, user); + return new PathToPathLinkDAO(locale, em).findRootPathToPathLinks(prodInstM.getLastIteration(), type); + } + + @Override + public List getProductInstanceMasters(PartRevision pPartRevision) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + String workspaceId = pPartRevision.getWorkspaceId(); + User user = userManager.checkWorkspaceReadAccess(workspaceId); + List productInstanceMasters = new ProductInstanceMasterDAO(em).findProductInstanceMasters(pPartRevision); + ListIterator ite = productInstanceMasters.listIterator(); + + while(ite.hasNext()){ + ProductInstanceMaster next = ite.next(); + try { + checkProductInstanceWriteAccess(workspaceId, next, user); + } catch (AccessRightException e) { + ite.remove(); + } + } + + return productInstanceMasters; + + } + + private User checkProductInstanceReadAccess(String workspaceId, ProductInstanceMaster prodInstM, User user) throws AccessRightException, WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + if (user.isAdministrator()) { + // Check if the user is workspace administrator + return user; + } + if (prodInstM.getAcl() == null) { + // Check if the item has no ACL + return userManager.checkWorkspaceReadAccess(workspaceId); + } else if (prodInstM.getAcl().hasReadAccess(user)) { + // Check if there is a write access + return user; + } else { + // Else throw a AccessRightException + throw new AccessRightException(new Locale(user.getLanguage()), user); + } + } + + private User checkProductInstanceWriteAccess(String workspaceId, ProductInstanceMaster prodInstM, User user) throws AccessRightException, WorkspaceNotFoundException, UserNotFoundException { + if (user.isAdministrator()) { + // Check if it is the workspace's administrator + return user; + } + if (prodInstM.getAcl() == null) { + // Check if the item haven't ACL + return userManager.checkWorkspaceWriteAccess(workspaceId); + } else if (prodInstM.getAcl().hasWriteAccess(user)) { + // Check if there is a write access + return user; + } else { + // Else throw a AccessRightException + throw new AccessRightException(new Locale(user.getLanguage()), user); + } + } + + private boolean isACLGrantReadAccess(User user, ProductInstanceMaster productInstanceMaster) { + return user.isAdministrator() || productInstanceMaster.getAcl().hasReadAccess(user); + } + + private void checkNameValidity(String name, Locale locale) throws NotAllowedException { + if (!NamingConvention.correct(name)) { + throw new NotAllowedException(locale, "NotAllowedException9", name); + } + } + + private void checkNameFileValidity(String name, Locale locale) throws NotAllowedException { + if (name != null) { + name = name.trim(); + } + if (!NamingConvention.correctNameFile(name)) { + throw new NotAllowedException(locale, "NotAllowedException9", name); + } + } + + private PathDataIteration createDocumentLink(Locale locale, PathDataIteration pathDataIteration, DocumentRevisionKey[] links, String[] documentLinkComments) { + DocumentLinkDAO linkDAO = new DocumentLinkDAO(locale, em); + if (links != null) { + Set currentLinks = new HashSet<>(pathDataIteration.getLinkedDocuments()); + + for (DocumentLink link : currentLinks) { + pathDataIteration.getLinkedDocuments().remove(link); + } + + int counter = 0; + for (DocumentRevisionKey link : links) { + DocumentLink newLink = new DocumentLink(em.getReference(DocumentRevision.class, link)); + newLink.setComment(documentLinkComments[counter]); + linkDAO.createLink(newLink); + pathDataIteration.getLinkedDocuments().add(newLink); + counter++; + } + return pathDataIteration; + } + return pathDataIteration; + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/storage/StorageProvider.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/storage/StorageProvider.java new file mode 100644 index 0000000000..8e487bd87b --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/storage/StorageProvider.java @@ -0,0 +1,43 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.storage; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.FileNotFoundException; +import com.docdoku.core.exceptions.StorageException; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +public interface StorageProvider { + InputStream getBinaryResourceInputStream(BinaryResource pBinaryResource) throws StorageException, FileNotFoundException; + File getBinaryResourceFile(BinaryResource pBinaryResource) throws StorageException, FileNotFoundException; + OutputStream getBinaryResourceOutputStream(BinaryResource pBinaryResource) throws StorageException; + void copyData(BinaryResource pSourceBinaryResource, BinaryResource pTargetBinaryResource) throws StorageException, FileNotFoundException; + File copyFile(File file, BinaryResource pTargetBinaryResource) throws StorageException, FileNotFoundException; + void delData(BinaryResource pBinaryResource) throws StorageException; + String getExternalResourceURI(BinaryResource binaryResource); + String getShortenExternalResourceURI(BinaryResource binaryResource); + void deleteWorkspaceFolder(String workspaceId, List binaryResourcesInWorkspace) throws StorageException; + void renameData(File file, String pNewName) throws StorageException; +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/storage/filesystem/FileStorageProvider.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/storage/filesystem/FileStorageProvider.java new file mode 100644 index 0000000000..f0bacbc16b --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/storage/filesystem/FileStorageProvider.java @@ -0,0 +1,230 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.storage.filesystem; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.FileNotFoundException; +import com.docdoku.core.exceptions.StorageException; +import com.docdoku.core.util.FileIO; +import com.docdoku.core.util.Tools; +import com.docdoku.server.storage.StorageProvider; +import org.apache.commons.io.FileUtils; + +import java.io.*; +import java.util.Date; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + + +public class FileStorageProvider implements StorageProvider { + + private final String vaultPath; + + private static final Logger LOGGER = Logger.getLogger(FileStorageProvider.class.getName()); + + public FileStorageProvider(String vaultPath) { + this.vaultPath = vaultPath; + } + + private String getVirtualPath(BinaryResource pBinaryResource) { + String normalizedName = Tools.unAccent(pBinaryResource.getFullName()); + return new StringBuilder().append(this.vaultPath).append("/").append(normalizedName).toString(); + } + + @Override + public InputStream getBinaryResourceInputStream(BinaryResource pBinaryResource) throws StorageException, FileNotFoundException { + File file = new File(getVirtualPath(pBinaryResource)); + return getInputStream(file); + } + + @Override + public File getBinaryResourceFile(BinaryResource pBinaryResource) throws StorageException, FileNotFoundException { + File file = new File(getVirtualPath(pBinaryResource)); + if (file.exists()) { + try { + FileInputStream fileInputStream = new FileInputStream(file); + fileInputStream.close(); + return file; + } catch (java.io.FileNotFoundException e) { + throw new StorageException(e.getMessage(), e); + } catch (IOException e) { + throw new StorageException(e.getMessage(), e); + } + } else { + throw new FileNotFoundException(new StringBuilder().append(file.getAbsolutePath()).append(" not found").toString()); + } + } + + public InputStream getBinarySubResourceInputStream(BinaryResource pBinaryResource, String subResourceVirtualPath) throws StorageException, FileNotFoundException { + File subResourceFile = new File(getSubResourceFolder(pBinaryResource), Tools.unAccent(subResourceVirtualPath)); + return getInputStream(subResourceFile); + } + + private File getSubResourceFolder(BinaryResource pBinaryResource) { + File binaryResourceFile = new File(getVirtualPath(pBinaryResource)); + return new File(binaryResourceFile.getParentFile(), "_" + binaryResourceFile.getName()); + } + + private InputStream getInputStream(File file) throws StorageException, FileNotFoundException { + if (file.exists()) { + try { + return new BufferedInputStream(new FileInputStream(file)); + } catch (java.io.FileNotFoundException e) { + throw new StorageException(e.getMessage(), e); + } + } else { + throw new FileNotFoundException(new StringBuilder().append(file.getAbsolutePath()).append(" not found").toString()); + } + } + + @Override + public OutputStream getBinaryResourceOutputStream(BinaryResource pBinaryResource) throws StorageException { + File file = new File(getVirtualPath(pBinaryResource)); + file.getParentFile().mkdirs(); + try { + return new BufferedOutputStream(new FileOutputStream(file)); + } catch (java.io.FileNotFoundException e) { + throw new StorageException(e.getMessage(), e); + } + } + + public OutputStream getBinarySubResourceOutputStream(BinaryResource binaryResource, String subResourceVirtualPath) throws StorageException { + File subResourceFile = new File(getSubResourceFolder(binaryResource), Tools.unAccent(subResourceVirtualPath)); + subResourceFile.getParentFile().mkdirs(); + try { + return new BufferedOutputStream(new FileOutputStream(subResourceFile)); + } catch (java.io.FileNotFoundException e) { + throw new StorageException(e.getMessage(), e); + } + } + + @Override + public void copyData(BinaryResource pSourceBinaryResource, BinaryResource pTargetBinaryResource) throws StorageException, FileNotFoundException { + File source = new File(getVirtualPath(pSourceBinaryResource)); + if (source.exists()) { + File target = new File(getVirtualPath(pTargetBinaryResource)); + try { + FileIO.copyFile(source, target); + } catch (IOException e) { + throw new StorageException(new StringBuilder().append("Error in copying ").append(pSourceBinaryResource.getFullName()).append(" to ").append(pTargetBinaryResource.getFullName()).toString(), e); + } + } else { + throw new FileNotFoundException(new StringBuilder("Can't find source file to copy ").append(pSourceBinaryResource.getFullName()).toString()); + } + } + + @Override + public File copyFile(File source, BinaryResource pTargetBinaryResource) throws StorageException, FileNotFoundException { + if (source.exists()) { + File target = new File(getVirtualPath(pTargetBinaryResource)); + try { + FileIO.copyFile(source, target); + return target; + } catch (IOException e) { + throw new StorageException(new StringBuilder().append("Error in copying ").append(source.getAbsolutePath()).append(" to ").append(pTargetBinaryResource.getFullName()).toString(), e); + } + } else { + throw new FileNotFoundException(new StringBuilder("Can't find source file to copy ").append(source.getAbsolutePath()).toString()); + } + } + + @Override + public void delData(BinaryResource pBinaryResource) { + File fileToRemove = new File(getVirtualPath(pBinaryResource)); + fileToRemove.delete(); + } + + @Override + public String getExternalResourceURI(BinaryResource binaryResource) { + return null; + } + + @Override + public String getShortenExternalResourceURI(BinaryResource binaryResource) { + return null; + } + + public void cleanParentFolders(BinaryResource pBinaryResource){ + cleanRemove(new File(getVirtualPath(pBinaryResource)).getParentFile()); + } + + private void cleanRemove(File pFile) { + if(!pFile.equals(new File(vaultPath)) && pFile.delete()) + cleanRemove(pFile.getParentFile()); + } + + public boolean exists(BinaryResource binaryResource, String subResourceVirtualPath) { + File subResourceFile = new File(getSubResourceFolder(binaryResource), Tools.unAccent(subResourceVirtualPath)); + return subResourceFile.exists(); + } + + public void copySubResources(BinaryResource source, BinaryResource destination) throws StorageException { + File subResourceFolder = getSubResourceFolder(source); + if (subResourceFolder.exists()) { + try { + FileUtils.copyDirectory(subResourceFolder, getSubResourceFolder(destination)); + } catch (IOException e) { + LOGGER.log(Level.WARNING, null, e); + throw new StorageException("Can't copy subResourceFolder from " + source.getFullName() + " to " + destination.getFullName(), e); + } + } + } + + public void deleteSubResources(BinaryResource binaryResource) { + File subResourceFolder = getSubResourceFolder(binaryResource); + if (subResourceFolder.exists()) { + FileIO.rmDir(subResourceFolder); + } + } + + public Date getLastModified(BinaryResource binaryResource, String subResourceVirtualPath) throws FileNotFoundException { + File subResourceFile = new File(getSubResourceFolder(binaryResource), Tools.unAccent(subResourceVirtualPath)); + if (subResourceFile.exists()) { + return new Date(subResourceFile.lastModified()); + } else { + throw new FileNotFoundException(new StringBuilder("Can't find source file to get last modified date ").append(binaryResource.getFullName()).toString()); + } + } + + @Override + public void deleteWorkspaceFolder(String workspaceId, List binaryResourcesInWorkspace) throws StorageException { + if(workspaceId != null && !workspaceId.isEmpty()){ + try{ + File rootFolder = new File(new StringBuilder().append(vaultPath).append("/").append(workspaceId).toString()); + if(rootFolder.exists()){ + FileUtils.deleteDirectory(rootFolder); + } + } catch (IOException e) { + throw new StorageException(new StringBuilder().append("Error in deleting workspace directory for workspace : ").append(workspaceId).toString(), e); + } + } + } + + @Override + public void renameData(File src, String pNewName) throws StorageException { + if(src.exists()){ + src.renameTo(new File(new StringBuilder().append(src.getParentFile().getAbsolutePath()).append("/").append(Tools.unAccent(pNewName)).toString())); + }else{ + throw new StorageException(new StringBuilder().append("Error in renaming file : ").append(src.getAbsolutePath()).toString()); + } + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/validation/AttributesConsistencyUtils.java b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/validation/AttributesConsistencyUtils.java new file mode 100644 index 0000000000..25708c5412 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/java/com/docdoku/server/validation/AttributesConsistencyUtils.java @@ -0,0 +1,165 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.validation; + +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.InstanceAttributeTemplate; + +import java.util.*; + +/** + * @author kelto on 15/07/15. + */ + +public class AttributesConsistencyUtils { + + private AttributesConsistencyUtils() { + } + + public static boolean hasValidChange(List pAttributes, boolean attributesLocked, List currentAttrs) { + if (attributesLocked) { + //Check attributes haven't changed + return currentAttrs.size() == pAttributes.size() && checkAttributesEquality(currentAttrs, pAttributes); + } else { + return lockedAttributesConsistency(currentAttrs,pAttributes); + } + } + + public static boolean isTemplateAttributesValid(List pAttributes, boolean attributesLocked) { + for(InstanceAttributeTemplate instanceAttributeTemplate: pAttributes) { + if(attributesLocked && !instanceAttributeTemplate.isLocked()) { + return false; + } + + if(instanceAttributeTemplate.isMandatory() && !instanceAttributeTemplate.isLocked()) { + return false; + } + } + return true; + } + + /** + * Check if the structure has not been changed. The following rules apply: The attributes must be in the same order, + * the type of attribute and the name must stay the same. + * @param currentAttrs Current list of attributes + * @param pAttributes List of attributes to be used. + * @return A boolean stating the equality. + */ + private static boolean checkAttributesEquality(List currentAttrs, List pAttributes) { + //Can not use the equal() function, it is already implemented using the id. + for (int i = 0; i < currentAttrs.size(); i++) { + InstanceAttribute currentAttr = currentAttrs.get(i); + InstanceAttribute newAttr = pAttributes.get(i); + if (newAttr == null + || !newAttr.getName().equals(currentAttr.getName()) + || !newAttr.getClass().equals(currentAttr.getClass())) { + // Attribute has been swapped with a new attributes or his type has changed + return false; + } + if(!checkValidAttribute(newAttr)){ + return false; + } + } + return true; + } + + /** + * Check the validity of an attribute. + * @param newAttr + * @return a boolean stating the validity of the attribute + */ + private static boolean checkValidAttribute(InstanceAttribute newAttr) { + if(newAttr.isMandatory()) { + if(!newAttr.isLocked()) { + // An attribute mandatory must be locked + return false; + } + if(newAttr.getValue() == null || newAttr.getValue().toString().isEmpty()) { + return false; + } + } + // once an attribute properties has been defined, it can not be changed. + //return newAttr.isLocked() == currentAttr.isLocked() && newAttr.isMandatory() == currentAttr.isMandatory(); + return true; + } + + /** + * Check if an attribute is lock in currentAttrs, that it still exists in pAttributes. + * Since an attribute does not have a uniq id, for X locked attributes of name N in currentAttrs, + * we must have X locked attributes of name N in pAttributes. + * This function will also check for all attributes in pAttributes their invidual validity. + * @param currentAttrs Current list of attributes + * @param pAttributes List of attributes to be used. + * @return a boolean stating the consistency of the attributes + */ + private static boolean lockedAttributesConsistency(List currentAttrs, List pAttributes) { + Map> pMapAttributes = getMappedAttributes(pAttributes); + if(pMapAttributes == null) { + // the map was not constructed because an attribute was not valid. + return false; + } + for(InstanceAttribute attribute : currentAttrs) { + if(attribute.isLocked()) { + List attributes = pMapAttributes.get(attribute.getName()); + if(!existSameProperties(attribute,attributes)) { + return false; + } + } + } + return true; + } + + /** + * Assert that an attribute is present in the list of attribute, and remove + * @param attribute Attribute to look for + * @param attributes List of attributes + * @return True if it's exist + */ + private static boolean existSameProperties(InstanceAttribute attribute, List attributes) { + //Can not use the List.remove() function, since the equal() function is already implemented using the id. + for(Iterator iterator = attributes.iterator(); iterator.hasNext();) { + InstanceAttribute attr = iterator.next(); + if(attribute.isLocked() == attr.isLocked() + && attribute.isMandatory() == attr.isMandatory()) { + iterator.remove(); + return true; + } + } + return false; + } + + private static Map> getMappedAttributes(List attributes) { + Map> map = new HashMap<>(); + for(InstanceAttribute attribute : attributes) { + if(!checkValidAttribute(attribute)) { + // if an attribute is not valid, we return null since the map is not valid. + return null; + } + if(map.get(attribute.getName()) == null) { + map.put(attribute.getName(), new ArrayList<>()); + } + map.get(attribute.getName()).add(attribute); + } + + return map; + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/LICENSE.txt b/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 0000000000..2def0e8831 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/beans.xml b/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..8ee5642b92 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/beans.xml @@ -0,0 +1,15 @@ + + + + com.docdoku.server.FileImportInterceptor + com.docdoku.server.CADConvertInterceptor + com.docdoku.server.DocumentLoggerInterceptor + com.docdoku.server.ActivityCheckerInterceptor + + + + diff --git a/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/ejb-jar.xml b/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/ejb-jar.xml new file mode 100644 index 0000000000..bf4eab1eb0 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/ejb-jar.xml @@ -0,0 +1,65 @@ + + + + + + WorkflowManagerBean + + + DocumentManagerBean + + + DocumentBaselineManagerBean + + + DocumentConfigSpecManagerBean + + + LOVManagerBean + + + ProductManagerBean + + + ProductBaselineManagerBean + + + ProductInstanceManagerBean + + + UserManagerBean + + + AccountManagerBean + + + ChangeManagerBean + + + DataManagerBean + + vaultPath + java.lang.String + /var/lib/docdoku/vault + + + + MailerBean + + codebase + java.lang.String + http://docdokuplm.net + + + + + + com.docdoku.core.exceptions.ApplicationException + true + + + docdoku-common.jar + diff --git a/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/orm.xml b/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/orm.xml new file mode 100644 index 0000000000..be78d5296d --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/orm.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/persistence.xml b/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000000..a342f4a87c --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,110 @@ + + + + jdbc/docdokuPU + com.docdoku.core.common.Account + com.docdoku.core.common.BinaryResource + com.docdoku.core.common.Organization + com.docdoku.core.common.User + com.docdoku.core.common.UserGroup + com.docdoku.core.common.Workspace + com.docdoku.core.workflow.WorkspaceWorkflow + com.docdoku.core.change.ChangeItem + com.docdoku.core.change.ChangeIssue + com.docdoku.core.change.ChangeOrder + com.docdoku.core.change.ChangeRequest + com.docdoku.core.change.Milestone + com.docdoku.core.change.ModificationNotification + com.docdoku.core.configuration.ProductConfiguration + com.docdoku.core.configuration.ProductBaseline + com.docdoku.core.configuration.BaselinedPart + com.docdoku.core.configuration.PartCollection + com.docdoku.core.configuration.ProductInstanceIteration + com.docdoku.core.configuration.ProductInstanceMaster + com.docdoku.core.configuration.PathDataIteration + com.docdoku.core.configuration.PathDataMaster + com.docdoku.core.document.DocumentIteration + com.docdoku.core.document.DocumentLink + com.docdoku.core.document.DocumentMaster + com.docdoku.core.document.DocumentMasterTemplate + com.docdoku.core.document.DocumentRevision + com.docdoku.core.document.Folder + com.docdoku.core.document.IterationChangeSubscription + com.docdoku.core.document.StateChangeSubscription + com.docdoku.core.document.StatusChange + com.docdoku.core.configuration.DocumentBaseline + com.docdoku.core.configuration.DocumentCollection + com.docdoku.core.configuration.FolderCollection + com.docdoku.core.configuration.BaselinedDocument + com.docdoku.core.configuration.BaselinedFolder + com.docdoku.core.gcm.GCMAccount + com.docdoku.core.log.DocumentLog + com.docdoku.core.log.PartLog + com.docdoku.core.log.WorkspaceLog + com.docdoku.core.meta.DefaultAttributeTemplate + com.docdoku.core.meta.InstanceAttribute + com.docdoku.core.meta.InstanceAttributeTemplate + com.docdoku.core.meta.InstanceBooleanAttribute + com.docdoku.core.meta.InstanceDateAttribute + com.docdoku.core.meta.InstanceListOfValuesAttribute + com.docdoku.core.meta.ListOfValuesAttributeTemplate + com.docdoku.core.meta.InstanceNumberAttribute + com.docdoku.core.meta.InstanceTextAttribute + com.docdoku.core.meta.InstanceLongTextAttribute + com.docdoku.core.meta.InstanceURLAttribute + com.docdoku.core.meta.ListOfValues + com.docdoku.core.meta.Tag + com.docdoku.core.product.ConfigurationItem + com.docdoku.core.product.DateBasedEffectivity + com.docdoku.core.product.Effectivity + com.docdoku.core.product.Geometry + com.docdoku.core.product.LotBasedEffectivity + com.docdoku.core.product.PartIteration + com.docdoku.core.product.PartMaster + com.docdoku.core.product.PartMasterTemplate + com.docdoku.core.product.PartRevision + com.docdoku.core.product.PartSubstituteLink + com.docdoku.core.product.PartUsageLink + com.docdoku.core.product.SerialNumberBasedEffectivity + com.docdoku.core.product.Marker + com.docdoku.core.product.Layer + com.docdoku.core.product.CADInstance + com.docdoku.core.product.Conversion + com.docdoku.core.product.Import + com.docdoku.core.security.ACL + com.docdoku.core.security.ACLUserEntry + com.docdoku.core.security.ACLUserGroupEntry + com.docdoku.core.security.Credential + com.docdoku.core.security.PasswordRecoveryRequest + com.docdoku.core.security.UserGroupMapping + com.docdoku.core.security.WorkspaceUserGroupMembership + com.docdoku.core.security.WorkspaceUserMembership + com.docdoku.core.sharing.SharedEntity + com.docdoku.core.sharing.SharedPart + com.docdoku.core.sharing.SharedDocument + com.docdoku.core.workflow.Activity + com.docdoku.core.workflow.ActivityModel + com.docdoku.core.workflow.ParallelActivity + com.docdoku.core.workflow.ParallelActivityModel + com.docdoku.core.workflow.SequentialActivity + com.docdoku.core.workflow.SequentialActivityModel + com.docdoku.core.workflow.Task + com.docdoku.core.workflow.TaskModel + com.docdoku.core.workflow.Workflow + com.docdoku.core.workflow.WorkflowModel + com.docdoku.core.workflow.Role + com.docdoku.core.query.Query + com.docdoku.core.query.QueryRule + com.docdoku.core.query.QueryContext + com.docdoku.core.product.PathToPathLink + com.docdoku.core.product.StatusChange + true + + + + + + + + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/sun-ejb-jar.xml b/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/sun-ejb-jar.xml new file mode 100644 index 0000000000..dfde1dd95c --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/resources/META-INF/sun-ejb-jar.xml @@ -0,0 +1,32 @@ + + + + + users + users + + + + admin + admin + + + + MailerBean + ejb/MailerBean + + mail/docdokuSMTP + mail/docdokuSMTP + + + + ConverterBean + ejb/ConverterBean + + 1 + 1 + 1 + + + + diff --git a/docdoku-server/docdoku-server-ejb/src/main/resources/com/docdoku/server/esindexer/conf.properties b/docdoku-server/docdoku-server-ejb/src/main/resources/com/docdoku/server/esindexer/conf.properties new file mode 100644 index 0000000000..1719cfd630 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/resources/com/docdoku/server/esindexer/conf.properties @@ -0,0 +1,6 @@ +host=localhost +port=9300 +number_of_replicas=0 +number_of_shards=5 +auto_expand_replicas=0-3 +cluster.name=elasticsearch \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/resources/com/docdoku/server/templates/MailText.properties b/docdoku-server/docdoku-server-ejb/src/main/resources/com/docdoku/server/templates/MailText.properties new file mode 100644 index 0000000000..4e539f9d83 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/resources/com/docdoku/server/templates/MailText.properties @@ -0,0 +1,31 @@ +IterationNotification_title=DocDoku: iteration notification +IterationNotification_text=You have subscribed an automatic notification on document "{0}".
Since {1} a new iteration, number {2}, created by "{3}" is available. + +StateNotification_title=DocDoku: state notification +StateNotification_text=You have subscribed an automatic notification on document "{0}".
Since {1} its state has changed to {3}. +FinalState_name=Final State + +Approval_title=DocDoku: task to complete +Approval_document_text=The task "{5}" on document "{7}" has been assigned to you.
Detailed instructions of the task:
{8}
To mark the task as done or to reject it, just use the link below.
Mark as done or reject +Approval_part_text=The task "{5}" on part "{7}" has been assigned to you.
Detailed instructions of the task:
{8}
To mark the task as done or to reject it, just use the link below.
Mark as done or reject + +Recovery_title=DocDoku: password recovery +Recovery_text=To start recovering your password associated with the login {1}, just follow the link below:
{0} + +WorkspaceDeletion_title=DocDoku: Workspace deletion +WorkspaceDeletion_text=The workspace {0} has been successfully deleted. +WorkspaceDeletionError_text=An error occurred while deleting the workspace {0}. Please contact an administrator. + +PartRevision_workflow_relaunched_title=DocDoku: Workflow relaunched +PartRevision_workflow_relaunched_text=The workflow on part revision {0} in the workspace {1} has been relaunched on activity : {2}. + +DocumentRevision_workflow_relaunched_title=DocDoku: Workflow relaunched +DocumentRevision_workflow_relaunched_text=The workflow on document {0} in the workspace {1} has been relaunched on activity : {2}. + +Indexer_success_title=Docdoku: Indexing success +Indexer_success_text=The workspace : {0} has been indexed successfully. +Indexer_failure_title=Docdoku: Indexing fail +Indexer_failure_text=The workspace : {0} has encountered some error during execution :
{1}. + +SignUp_success_title=Docdoku: Sign Up success +SignUp_success_text=You successfully Sign Up with the login {0}.
{1} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/resources/com/docdoku/server/templates/MailText_en.properties b/docdoku-server/docdoku-server-ejb/src/main/resources/com/docdoku/server/templates/MailText_en.properties new file mode 100644 index 0000000000..4e539f9d83 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/resources/com/docdoku/server/templates/MailText_en.properties @@ -0,0 +1,31 @@ +IterationNotification_title=DocDoku: iteration notification +IterationNotification_text=You have subscribed an automatic notification on document "{0}".
Since {1} a new iteration, number {2}, created by "{3}" is available. + +StateNotification_title=DocDoku: state notification +StateNotification_text=You have subscribed an automatic notification on document "{0}".
Since {1} its state has changed to {3}. +FinalState_name=Final State + +Approval_title=DocDoku: task to complete +Approval_document_text=The task "{5}" on document "{7}" has been assigned to you.
Detailed instructions of the task:
{8}
To mark the task as done or to reject it, just use the link below.
Mark as done or reject +Approval_part_text=The task "{5}" on part "{7}" has been assigned to you.
Detailed instructions of the task:
{8}
To mark the task as done or to reject it, just use the link below.
Mark as done or reject + +Recovery_title=DocDoku: password recovery +Recovery_text=To start recovering your password associated with the login {1}, just follow the link below:
{0} + +WorkspaceDeletion_title=DocDoku: Workspace deletion +WorkspaceDeletion_text=The workspace {0} has been successfully deleted. +WorkspaceDeletionError_text=An error occurred while deleting the workspace {0}. Please contact an administrator. + +PartRevision_workflow_relaunched_title=DocDoku: Workflow relaunched +PartRevision_workflow_relaunched_text=The workflow on part revision {0} in the workspace {1} has been relaunched on activity : {2}. + +DocumentRevision_workflow_relaunched_title=DocDoku: Workflow relaunched +DocumentRevision_workflow_relaunched_text=The workflow on document {0} in the workspace {1} has been relaunched on activity : {2}. + +Indexer_success_title=Docdoku: Indexing success +Indexer_success_text=The workspace : {0} has been indexed successfully. +Indexer_failure_title=Docdoku: Indexing fail +Indexer_failure_text=The workspace : {0} has encountered some error during execution :
{1}. + +SignUp_success_title=Docdoku: Sign Up success +SignUp_success_text=You successfully Sign Up with the login {0}.
{1} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/main/resources/com/docdoku/server/templates/MailText_fr.properties b/docdoku-server/docdoku-server-ejb/src/main/resources/com/docdoku/server/templates/MailText_fr.properties new file mode 100644 index 0000000000..f7bfbab7e3 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/main/resources/com/docdoku/server/templates/MailText_fr.properties @@ -0,0 +1,31 @@ +IterationNotification_title=DocDoku: notification d'iteration +IterationNotification_text=Vous avez souscrit \u00e0 une notification automatique sur le document "{0}".
Depuis {1} une nouvelle iteration, num\u00e9ro {2}, cr\u00e9\u00e9e par "{3}" est disponible. + +StateNotification_title=DocDoku: notification d'\u00e9tat +StateNotification_text=Vous avez souscrit \u00e0 une notification automatique sur le document "{0}".
Depuis {1} son \u00e9tat a chang\u00e9 : {3}. +FinalState_name=Etat Final + +Approval_title=DocDoku: t\u00e2che \u00e0 accomplir +Approval_document_text=La t\u00e2che "{5}" sur le document "{7}" vous a \u00e9t\u00e9 assign\u00e9e.
Instructions d\u00e9taill\u00e9es de la t\u00e2che:
{8}
Pour marquer cette t\u00e2che comme effectu\u00e9e ou pour la rejeter, cliquez juste sur le lien ci-dessous.
Marquer comme effectu\u00e9e ou rejeter +Approval_part_text=La t\u00e2che "{5}" sur l''article "{7}" vous a \u00e9t\u00e9 assign\u00e9e.
Instructions d\u00e9taill\u00e9es de la t\u00e2che:
{8}
Pour marquer cette t\u00e2che comme effectu\u00e9e ou pour la rejeter, cliquez juste sur le lien ci-dessous.
Marquer comme effectu\u00e9e ou rejeter + +Recovery_title=DocDoku: r\u00e9cup\u00e9ration du mot de passe +Recovery_text=Pour lancer le processus de r\u00e9initialisation du mot de passe associ\u00e9 \u00e0 votre identifiant {1}, cliquez sur le lien ci-dessous :
{0} + +WorkspaceDeletion_title=DocDoku: Suppression de l'espace de travail +WorkspaceDeletion_text=L''espace de travail {0} a \u00e9t\u00e9 supprim\u00e9 avec succ\u00e8s +WorkspaceDeletionError_text=Une erreur s'est produite en supprimant l''espace de travail {0}. Veuillez prendre contact avec un administrateur. + +PartRevision_workflow_relaunched_title=DocDoku: Workflow relanc\u00e9 +PartRevision_workflow_relaunched_text=Le workflow sur l''article {0} dans l''espace de travail {1} a \u00e9t\u00e9 relanc\u00e9 \u00e0 l''activit\u00e9 : {2}. + +DocumentRevision_workflow_relaunched_title=DocDoku: Workflow relanc\u00e9 +DocumentRevision_workflow_relaunched_text=Le workflow sur le document {0} dans l''espace de travail {1} a \u00e9t\u00e9 relanc\u00e9 \u00e0 l''activit\u00e9 : {2}. + +Indexer_success_title=Docdoku: indexation r\u00e9ussie +Indexer_success_text=L'indexation de l'espace de travail : {0} a terminé avec succès. +Indexer_failure_title=Docdoku: Echec de l'indexation +Indexer_failure_text=L'indexation de l'espace de travail : {0} a rencontré les problèmes suivants:
{1}. + +SignUp_success_title=Docdoku: Inscription r\u00e9ussie +SignUp_success_text=Vous venez de vous inscrire avec succ\u00e8s avec l''identifiant {0}.
{1} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/DataManagerBeanTest.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/DataManagerBeanTest.java new file mode 100644 index 0000000000..15dc862904 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/DataManagerBeanTest.java @@ -0,0 +1,57 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.server.storage.StorageProvider; +import com.docdoku.server.storage.filesystem.FileStorageProvider; +import com.docdoku.server.util.DocumentUtil; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.BufferedOutputStream; +import java.util.Date; + + +public class DataManagerBeanTest { + + public static final String TARGET_FILE_STORAGE=""; + + private StorageProvider defaultStorageProvider; + private BinaryResource binaryResource; + + @Before + public void setUp() throws Exception { + //Given + defaultStorageProvider = new FileStorageProvider(System.getProperty("java.io.tmpdir")+TARGET_FILE_STORAGE); + binaryResource = new BinaryResource(DocumentUtil.FULL_NAME4,DocumentUtil.DOCUMENT_SIZE,new Date()); + } + + @Test + public void testGetBinaryResourceOutputStream() throws Exception { + //When + BufferedOutputStream outputStream = (BufferedOutputStream)defaultStorageProvider.getBinaryResourceOutputStream(binaryResource); + //Then + Assert.assertTrue(outputStream != null); + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/DocumentManagerBeanTest.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/DocumentManagerBeanTest.java new file mode 100644 index 0000000000..abc8daf215 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/DocumentManagerBeanTest.java @@ -0,0 +1,394 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.document.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.InstanceDateAttribute; +import com.docdoku.core.meta.InstanceTextAttribute; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.*; +import com.docdoku.server.esindexer.ESIndexer; +import com.docdoku.server.esindexer.ESSearcher; +import com.docdoku.server.util.DocumentUtil; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; + +import javax.ejb.SessionContext; +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import static org.mockito.MockitoAnnotations.initMocks; + +public class DocumentManagerBeanTest { + + + + @InjectMocks + DocumentManagerBean documentManagerBean = new DocumentManagerBean(); + + @Mock + private EntityManager em; + @Mock + private SessionContext ctx; + + @Mock + private IUserManagerLocal userManager; + @Mock + private IContextManagerLocal contextManager; + @Mock + private IMailerLocal mailer; + @Mock + private IGCMSenderLocal gcmNotifier; + @Mock + private ESIndexer esIndexer; + @Mock + private ESSearcher esSearcher; + @Mock + private IDataManagerLocal dataManager; + @Mock + private TypedQuery documentIterationQuery; + @Mock + private TypedQuery aclTypedQuery; + + private Account account; + private Workspace workspace ; + private User user; + private DocumentMasterTemplate documentMasterTemplate; + private BinaryResource binaryResource; + private DocumentIteration documentIteration; + private DocumentRevision documentRevision; + private ACL acl; + private Folder folder; + + @Before + public void setup() throws Exception { + initMocks(this); + account = new Account(DocumentUtil.USER_2_LOGIN, DocumentUtil.USER_2_NAME, DocumentUtil.USER2_MAIL, DocumentUtil.LANGUAGE,new Date(),null); + workspace = new Workspace(DocumentUtil.WORKSPACE_ID,account, DocumentUtil.WORKSPACE_DESCRIPTION, false); + user = new User(workspace, new Account(DocumentUtil.USER_1_LOGIN, DocumentUtil.USER_1_NAME, DocumentUtil.USER1_MAIL, DocumentUtil.LANGUAGE, new Date(), null)); + documentMasterTemplate= new DocumentMasterTemplate(workspace, DocumentUtil.DOCUMENT_TEMPLATE_ID, user,"",""); + binaryResource = new BinaryResource(DocumentUtil.FULL_NAME,DocumentUtil.DOCUMENT_SIZE,new Date()); + documentIteration = new DocumentIteration(); + acl = new ACL(); + acl.addEntry(user, ACL.Permission.READ_ONLY); + + folder = new Folder(DocumentUtil.WORKSPACE_ID+"/"+user.getName()+"/folders/"+DocumentUtil.FOLDER); + documentRevision = new DocumentRevision(); + documentRevision.setLocation(folder); + documentIteration.setDocumentRevision(documentRevision); + documentRevision.setACL(acl); + } + + /** + * test the upload of file in document template's + * @throws Exception + */ + @Test + public void saveFileInTemplate() throws Exception { + //Given + DocumentMasterTemplateKey pDocMTemplateKey = Mockito.spy(new DocumentMasterTemplateKey(DocumentUtil.WORKSPACE_ID, DocumentUtil.DOCUMENT_ID)); + + Mockito.when(userManager.checkWorkspaceWriteAccess(DocumentUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(userManager.checkWorkspaceReadAccess(DocumentUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(em.find(DocumentMasterTemplate.class, pDocMTemplateKey)).thenReturn(documentMasterTemplate); + //When + BinaryResource binaryResource= documentManagerBean.saveFileInTemplate(pDocMTemplateKey, DocumentUtil.FILE1_NAME, DocumentUtil.DOCUMENT_SIZE); + //Then + Assert.assertTrue(binaryResource.getLastModified()!= null); + Assert.assertTrue(binaryResource.getContentLength() == DocumentUtil.DOCUMENT_SIZE); + Assert.assertTrue(!binaryResource.getFullName().isEmpty()); + Assert.assertTrue(binaryResource.getFullName().equals(DocumentUtil.WORKSPACE_ID+"/document-templates/"+DocumentUtil.DOCUMENT_TEMPLATE_ID+"/"+ DocumentUtil.FILE1_NAME)); + } + + /** + * test the upload of file in documents + * @throws Exception + */ + @Test + public void saveFileInDocument() throws Exception { + //Given + DocumentMaster documentMaster = Mockito.spy(new DocumentMaster(workspace, DocumentUtil.DOCUMENT_ID,user)); + DocumentRevision documentRevision = Mockito.spy(new DocumentRevision(documentMaster, DocumentUtil.VERSION,user)); + ArrayList iterations =new ArrayList(); + iterations.add(new DocumentIteration(documentRevision, user)); + documentRevision.setDocumentIterations(iterations); + documentRevision.setCheckOutUser(user); + DocumentRevisionKey documentRevisionKey = Mockito.spy(new DocumentRevisionKey(DocumentUtil.WORKSPACE_ID, DocumentUtil.DOCUMENT_ID, DocumentUtil.VERSION)); + DocumentIterationKey iterationKey = Mockito.spy(new DocumentIterationKey(DocumentUtil.WORKSPACE_ID, DocumentUtil.DOCUMENT_ID, DocumentUtil.VERSION,DocumentUtil.ITERATION)); + + Mockito.when(userManager.checkWorkspaceWriteAccess(DocumentUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(userManager.checkWorkspaceReadAccess(DocumentUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(em.find(DocumentRevision.class, documentRevisionKey)).thenReturn(documentRevision); + Mockito.when(em.find(DocumentRevision.class, new DocumentRevisionKey(DocumentUtil.WORKSPACE_ID, DocumentUtil.DOCUMENT_ID, DocumentUtil.VERSION))).thenReturn(documentRevision); + //When + BinaryResource binaryResource= documentManagerBean.saveFileInDocument(iterationKey, DocumentUtil.FILE1_NAME, DocumentUtil.DOCUMENT_SIZE); + //Then + Assert.assertTrue(binaryResource.getLastModified()!= null); + Assert.assertTrue(binaryResource.getContentLength() == DocumentUtil.DOCUMENT_SIZE); + Assert.assertTrue(!binaryResource.getFullName().isEmpty()); + Assert.assertTrue(binaryResource.getFullName().equals(DocumentUtil.WORKSPACE_ID+"/documents/"+ DocumentUtil.DOCUMENT_ID +"/"+DocumentUtil.VERSION+"/"+DocumentUtil.ITERATION+"/"+ DocumentUtil.FILE1_NAME)); + + } + + /** + * test the upload of file with special characters in documents + * @throws Exception + */ + @Test + public void saveFileWithSpecialCharactersInDocument() throws Exception { + //Given + DocumentMaster documentMaster = Mockito.spy(new DocumentMaster(workspace, DocumentUtil.DOCUMENT_ID,user)); + DocumentRevision documentRevision = Mockito.spy(new DocumentRevision(documentMaster, DocumentUtil.VERSION,user)); + ArrayList iterations =new ArrayList(); + iterations.add(new DocumentIteration(documentRevision, user)); + documentRevision.setDocumentIterations(iterations); + documentRevision.setCheckOutUser(user); + DocumentRevisionKey documentRevisionKey = Mockito.spy(new DocumentRevisionKey(DocumentUtil.WORKSPACE_ID, DocumentUtil.DOCUMENT_ID, DocumentUtil.VERSION)); + DocumentIterationKey iterationKey = Mockito.spy(new DocumentIterationKey(DocumentUtil.WORKSPACE_ID, DocumentUtil.DOCUMENT_ID, DocumentUtil.VERSION,DocumentUtil.ITERATION)); + + Mockito.when(userManager.checkWorkspaceWriteAccess(DocumentUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(userManager.checkWorkspaceReadAccess(DocumentUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(em.find(DocumentRevision.class, documentRevisionKey)).thenReturn(documentRevision); + Mockito.when(em.find(DocumentRevision.class, new DocumentRevisionKey(DocumentUtil.WORKSPACE_ID, DocumentUtil.DOCUMENT_ID, DocumentUtil.VERSION))).thenReturn(documentRevision); + //When + BinaryResource binaryResource= documentManagerBean.saveFileInDocument(iterationKey, DocumentUtil.FILE2_NAME, DocumentUtil.DOCUMENT_SIZE); + //Then + Assert.assertTrue(binaryResource.getLastModified()!= null); + Assert.assertTrue(binaryResource.getContentLength() == DocumentUtil.DOCUMENT_SIZE); + Assert.assertTrue(!binaryResource.getFullName().isEmpty()); + Assert.assertTrue(binaryResource.getFullName().equals(DocumentUtil.WORKSPACE_ID+"/documents/"+ DocumentUtil.DOCUMENT_ID +"/"+DocumentUtil.VERSION+"/"+DocumentUtil.ITERATION+"/"+ DocumentUtil.FILE2_NAME)); + + } + + /** + * test the upload of file with forbidden characters in documents + * @throws Exception + */ + @Test(expected = NotAllowedException.class) + public void saveFileWithForbiddenCharactersInDocument() throws Exception { + //Given + DocumentMaster documentMaster = Mockito.spy(new DocumentMaster(workspace, DocumentUtil.DOCUMENT_ID,user)); + DocumentRevision documentRevision = Mockito.spy(new DocumentRevision(documentMaster, DocumentUtil.VERSION,user)); + ArrayList iterations =new ArrayList(); + iterations.add(new DocumentIteration(documentRevision, user)); + documentRevision.setDocumentIterations(iterations); + documentRevision.setCheckOutUser(user); + DocumentRevisionKey documentRevisionKey = Mockito.spy(new DocumentRevisionKey(DocumentUtil.WORKSPACE_ID, DocumentUtil.DOCUMENT_ID, DocumentUtil.VERSION)); + DocumentIterationKey iterationKey = Mockito.spy(new DocumentIterationKey(DocumentUtil.WORKSPACE_ID, DocumentUtil.DOCUMENT_ID, DocumentUtil.VERSION,DocumentUtil.ITERATION)); + + Mockito.when(userManager.checkWorkspaceWriteAccess(DocumentUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(userManager.checkWorkspaceReadAccess(DocumentUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(em.find(DocumentRevision.class, documentRevisionKey)).thenReturn(documentRevision); + Mockito.when(em.find(DocumentRevision.class, new DocumentRevisionKey(DocumentUtil.WORKSPACE_ID, DocumentUtil.DOCUMENT_ID, DocumentUtil.VERSION))).thenReturn(documentRevision); + //When + documentManagerBean.saveFileInDocument(iterationKey, DocumentUtil.FILE3_NAME, DocumentUtil.DOCUMENT_SIZE); + + } + + /** + * + * Test to download a document file as a guest + */ + @Test + public void getBinaryResourceAsGuest()throws Exception{ + //Given + Mockito.when(ctx.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)).thenReturn(true); + Mockito.when(userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(DocumentUtil.FULL_NAME))).thenReturn(user); + Mockito.when(contextManager.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)).thenReturn(true); + Mockito.when(em.find(BinaryResource.class, DocumentUtil.FULL_NAME)).thenReturn(binaryResource); + BinaryResource binaryResource = documentManagerBean.getBinaryResource(DocumentUtil.FULL_NAME); + } + /** + * + * Test to download a document file as not a guest + */ + @Test + public void getBinaryResourceAsNotGuest()throws Exception{ + //Given + Mockito.when(ctx.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID)).thenReturn(true); + Mockito.when(userManager.checkWorkspaceReadAccess(BinaryResource.parseWorkspaceId(DocumentUtil.FULL_NAME))).thenReturn(user); + Mockito.when(em.find(BinaryResource.class, DocumentUtil.FULL_NAME)).thenReturn(binaryResource); + Mockito.when(documentIterationQuery.getSingleResult()).thenReturn(documentIteration); + Mockito.when(documentIterationQuery.setParameter("binaryResource", binaryResource)).thenReturn(documentIterationQuery); + Mockito.when(em.createQuery("SELECT d FROM DocumentIteration d WHERE :binaryResource MEMBER OF d.attachedFiles", DocumentIteration.class)).thenReturn(documentIterationQuery); + BinaryResource binaryResource = documentManagerBean.getBinaryResource(DocumentUtil.FULL_NAME); + //Then + Assert.assertNotNull(binaryResource); + Assert.assertTrue(binaryResource.getContentLength() == DocumentUtil.DOCUMENT_SIZE); + } + + /** + * + * This test will check if the attributes is well manages if the documents has a template with freeze attributes + * + */ + @Test + public void changeAttributesWithLockedTemplate()throws Exception{ + + DocumentMaster documentMaster = new DocumentMaster(workspace, DocumentUtil.DOCUMENT_ID,user); + + + documentRevision = new DocumentRevision(documentMaster, "A", user); + documentIteration = new DocumentIteration(documentRevision, user); + documentRevision.setCheckOutUser(user); + documentRevision.setCheckOutDate(new Date()); + ArrayList iterations = new ArrayList(); + iterations.add(documentIteration); + documentRevision.setDocumentIterations(iterations); + + DocumentRevisionKey rKey = new DocumentRevisionKey(workspace.getId(), documentMaster.getId(), documentRevision.getVersion()); + + //Creation of current attributes of the iteration + InstanceAttribute attribute = new InstanceTextAttribute("Nom", "Testeur", false); + List attributesOfIteration = new ArrayList<>(); + attributesOfIteration.add(attribute); + documentIteration.setInstanceAttributes(attributesOfIteration); + + documentMaster.setAttributesLocked(true); + + Mockito.when(userManager.checkWorkspaceReadAccess(workspace.getId())).thenReturn(user); + Mockito.when(userManager.checkWorkspaceWriteAccess(workspace.getId())).thenReturn(user); + Mockito.when(em.find(DocumentRevision.class, null)).thenReturn(documentRevision); + Mockito.when(em.find(DocumentRevision.class, rKey)).thenReturn(documentRevision); + + + try{ + //Test to remove attribute + documentManagerBean.updateDocument(documentIteration.getKey(), "test", Arrays.asList(new InstanceAttribute[]{}), new DocumentRevisionKey[]{}, null); + Assert.assertTrue("updateDocument should have raise an exception because we have removed attributes", false); + }catch (NotAllowedException notAllowedException){ + try{ + //Test with a swipe of attribute + documentManagerBean.updateDocument(documentIteration.getKey(), "test", Arrays.asList(new InstanceAttribute[]{new InstanceDateAttribute("Nom", new Date(), false)}), new DocumentRevisionKey[]{}, null); + Assert.assertTrue("updateDocument should have raise an exception because we have changed the attribute type attributes", false); + }catch (NotAllowedException notAllowedException2){ + try { + //Test without modifying the attribute + documentManagerBean.updateDocument(documentIteration.getKey(), "test", Arrays.asList(new InstanceAttribute[]{attribute}), new DocumentRevisionKey[]{}, null); + //Test with a new value of the attribute + documentManagerBean.updateDocument(documentIteration.getKey(), "test", Arrays.asList(new InstanceAttribute[]{new InstanceTextAttribute("Nom", "Testeur change", false)}), new DocumentRevisionKey[]{}, null); + } catch (NotAllowedException notAllowedException3){ + Assert.assertTrue("updateDocument shouldn't have raised an exception because we haven't change the number of attribute or the type", false); + } + } + } + } + + /** + * + * This test will check if the attributes is well manages if the documents has a template with freeze attributes + * + */ + @Test + public void changeAttributesWithUnlockedTemplate()throws Exception{ + + DocumentMaster documentMaster = new DocumentMaster(workspace, DocumentUtil.DOCUMENT_ID,user); + + documentRevision = new DocumentRevision(documentMaster, "A", user); + documentIteration = new DocumentIteration(documentRevision, user); + documentRevision.setCheckOutUser(user); + documentRevision.setCheckOutDate(new Date()); + ArrayList iterations = new ArrayList(); + iterations.add(documentIteration); + documentRevision.setDocumentIterations(iterations); + + DocumentRevisionKey rKey = new DocumentRevisionKey(workspace.getId(), documentMaster.getId(), documentRevision.getVersion()); + + //Creation of current attributes of the iteration + InstanceAttribute attribute = new InstanceTextAttribute("Nom", "Testeur", false); + List attributesOfIteration = new ArrayList<>(); + attributesOfIteration.add(attribute); + documentIteration.setInstanceAttributes(attributesOfIteration); + + documentMaster.setAttributesLocked(false); + + Mockito.when(userManager.checkWorkspaceReadAccess(workspace.getId())).thenReturn(user); + Mockito.when(userManager.checkWorkspaceWriteAccess(workspace.getId())).thenReturn(user); + Mockito.when(em.find(DocumentRevision.class, null)).thenReturn(documentRevision); + Mockito.when(em.find(DocumentRevision.class, rKey)).thenReturn(documentRevision); + + + try{ + //Test to remove attribute + documentManagerBean.updateDocument(documentIteration.getKey(), "test", Arrays.asList(new InstanceAttribute[]{}), new DocumentRevisionKey[]{}, null); + //Add the attribute + documentManagerBean.updateDocument(documentIteration.getKey(), "test", Arrays.asList(new InstanceAttribute[]{attribute}), new DocumentRevisionKey[]{}, null); + //Change the value of the attribute + documentManagerBean.updateDocument(documentIteration.getKey(), "test", Arrays.asList(new InstanceAttribute[]{new InstanceTextAttribute("Nom", "Testeur change", false)}), new DocumentRevisionKey[]{}, null); + //Change the type of the attribute + documentManagerBean.updateDocument(documentIteration.getKey(), "test", Arrays.asList(new InstanceAttribute[]{new InstanceDateAttribute("Nom", new Date(), false)}), new DocumentRevisionKey[]{}, null); + } catch (NotAllowedException notAllowedException3){ + Assert.assertTrue("updateDocument shouldn't have raised an exception because the attribute are not frozen", false); + } + } + + /** + * + * This test will check if the ACL is null when removing it from a document + * + */ + + @Test + public void removeACLFromDocument()throws Exception{ + + user = new User(workspace, new Account(DocumentUtil.USER_1_LOGIN, DocumentUtil.USER_1_NAME, DocumentUtil.USER1_MAIL, DocumentUtil.LANGUAGE, new Date(),null)); + + DocumentMaster documentMaster = new DocumentMaster(workspace, DocumentUtil.DOCUMENT_ID,user); + documentRevision = new DocumentRevision(documentMaster, "A", user); + documentIteration = new DocumentIteration(documentRevision, user); + documentRevision.setCheckOutUser(user); + documentRevision.setCheckOutDate(new Date()); + acl = new ACL(); + acl.addEntry(user, ACL.Permission.READ_ONLY); + documentRevision.setACL(acl); + + DocumentRevisionKey pKey = new DocumentRevisionKey(workspace.getId(), documentMaster.getId(), documentRevision.getVersion()); + + Mockito.when(userManager.checkWorkspaceReadAccess(workspace.getId())).thenReturn(user); + Mockito.when(em.getReference(DocumentRevision.class, pKey)).thenReturn(documentRevision); + + Mockito.when(aclTypedQuery.setParameter(Matchers.anyString(),Matchers.any())).thenReturn(aclTypedQuery); + Mockito.when(em.createNamedQuery(Matchers.any())).thenReturn(aclTypedQuery); + + documentManagerBean.removeACLFromDocumentRevision(documentRevision.getKey()); + Assert.assertTrue(documentRevision.getACL() == null); + } + + public void removeTagFromDocument(){ + + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/LOVManagerBeanTest.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/LOVManagerBeanTest.java new file mode 100644 index 0000000000..d138d57f03 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/LOVManagerBeanTest.java @@ -0,0 +1,105 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.exceptions.CreationException; +import com.docdoku.core.meta.NameValuePair; +import com.docdoku.core.services.IUserManagerLocal; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; + +import javax.persistence.EntityManager; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import static org.mockito.MockitoAnnotations.initMocks; + +/** + * @author lebeaujulien on 12/03/15. + */ +public class LOVManagerBeanTest extends LOVManagerBean { + + public static final String WORKSPACE_ID="TestWorkspace"; + public static final String USER_LOGIN="User1"; + public static final String USER_NAME="UserName"; + public static final String USER_MAIL="UserMail@mail.com"; + public static final String USER_LANGUAGE="fr"; + + @InjectMocks + LOVManagerBean lovManager; + @Mock + private IUserManagerLocal userManager; + @Mock + private EntityManager em; + + private Account account; + private Workspace workspace ; + private User user; + + @Before + public void setup() throws Exception { + initMocks(this); + account = new Account(USER_LOGIN, USER_NAME, USER_MAIL, USER_LANGUAGE, new Date(), null); + workspace = new Workspace(WORKSPACE_ID,account, "pDescription", false); + user = new User(workspace, new Account(USER_LOGIN, USER_LOGIN, USER_MAIL,USER_LANGUAGE, new Date(), null)); + } + + @Test + public void creationNominal() throws Exception { + Mockito.when(userManager.checkWorkspaceWriteAccess(workspace.getId())).thenReturn(user); + Mockito.when(userManager.checkWorkspaceReadAccess(workspace.getId())).thenReturn(user); + Mockito.when(em.find(Workspace.class,workspace.getId())).thenReturn(workspace); + + List possibleValue = new ArrayList<>(); + possibleValue.add(new NameValuePair("first", "value")); + + lovManager.createLov(workspace.getId(), "NominalName", possibleValue); + } + + @Test (expected = CreationException.class) + public void creationNoName() throws Exception { + Mockito.when(userManager.checkWorkspaceWriteAccess(workspace.getId())).thenReturn(user); + Mockito.when(userManager.checkWorkspaceReadAccess(workspace.getId())).thenReturn(user); + Mockito.when(em.find(Workspace.class,workspace.getId())).thenReturn(workspace); + + + List possibleValue = new ArrayList<>(); + possibleValue.add(new NameValuePair("first", "value")); + + lovManager.createLov(workspace.getId(), null, possibleValue); + } + + @Test (expected = CreationException.class) + public void creationNoPossibleValue() throws Exception { + Mockito.when(userManager.checkWorkspaceWriteAccess(workspace.getId())).thenReturn(user); + Mockito.when(userManager.checkWorkspaceReadAccess(workspace.getId())).thenReturn(user); + Mockito.when(em.find(Workspace.class,workspace.getId())).thenReturn(workspace); + + lovManager.createLov(workspace.getId(), "Name", null); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/ProductManagerBeanTest.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/ProductManagerBeanTest.java new file mode 100644 index 0000000000..d689dfe3c4 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/ProductManagerBeanTest.java @@ -0,0 +1,367 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.meta.*; +import com.docdoku.core.product.*; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IContextManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.dao.PartUsageLinkDAO; +import com.docdoku.server.dao.PathToPathLinkDAO; +import com.docdoku.server.esindexer.ESIndexer; +import com.docdoku.server.products.ProductBaselineManagerBean; +import com.docdoku.server.util.CyclicAssemblyRule; +import com.docdoku.server.util.ProductUtil; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.*; + +import javax.ejb.SessionContext; +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.*; + +import static org.mockito.Matchers.any; +import static org.mockito.MockitoAnnotations.initMocks; + +public class ProductManagerBeanTest { + + @InjectMocks + ProductManagerBean productManagerBean = new ProductManagerBean(); + + @Mock + private EntityManager em; + @Mock + private IUserManagerLocal userManager; + @Mock + private IContextManagerLocal contextManager; + @Mock + SessionContext ctx; + @Mock + private ESIndexer esIndexer; + @Mock + TypedQuery tagsQuery; + @Mock + ProductBaselineManagerBean productBaselineManager; + @Rule + public CyclicAssemblyRule cyclicAssemblyRule; + + @Mock + private TypedQuery partUsageLinkTypedQuery; + + @Mock + private TypedQuery configurationItemTypedQuery; + + @Spy + private PathToPathLinkDAO pathToPathLinkDAO = new PathToPathLinkDAO(Locale.getDefault(),em); + + @Spy + private PartUsageLinkDAO partUsageLinkDAO = new PartUsageLinkDAO(Locale.getDefault(),em); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Account account; + private Workspace workspace ; + private User user; + private User user2; + private PartMaster partMaster; + private PartMasterTemplate partMasterTemplate; + private PartIteration partIteration; + private PartRevision partRevision; + + + @Before + public void setup() throws Exception { + initMocks(this); + account = new Account(ProductUtil.USER_2_LOGIN, ProductUtil.USER_2_NAME, ProductUtil.USER_1_MAIL, ProductUtil.USER_1_LANGUAGE, new Date(), null); + workspace = new Workspace(ProductUtil.WORKSPACE_ID,account, "pDescription", false); + user = new User(workspace, new Account(ProductUtil.USER_1_LOGIN , ProductUtil.USER_1_LOGIN, ProductUtil.USER_1_MAIL,ProductUtil.USER_1_LANGUAGE, new Date(), null)); + user2 = new User(workspace, new Account(ProductUtil.USER_2_LOGIN , ProductUtil.USER_2_LOGIN, ProductUtil.USER_2_MAIL,ProductUtil.USER_2_LANGUAGE, new Date(), null)); + partMaster = new PartMaster(workspace, ProductUtil.PART_ID, user); + partMasterTemplate = new PartMasterTemplate(workspace, ProductUtil.PART_MASTER_TEMPLATE_ID, user, ProductUtil.PART_TYPE, "", true); + partRevision = new PartRevision(partMaster,ProductUtil.VERSION,user); + partIteration = new PartIteration(partRevision, ProductUtil.ITERATION,user); + ArrayList iterations = new ArrayList(); + iterations.add(partIteration); + + partRevision.setPartIterations(iterations); + partRevision.setCheckOutUser(user); + partRevision.setCheckOutDate(new Date()); + partIteration.setPartRevision(partRevision); + + } + + @Test + public void updatePartWithLockedAttributes() throws Exception { + //Creation of current attributes of the iteration + InstanceAttribute attribute = new InstanceTextAttribute("Test", "Testeur", false); + List attributesOfIteration = new ArrayList<>(); + attributesOfIteration.add(attribute); + partIteration.setInstanceAttributes(attributesOfIteration); + + PartRevisionKey partRevisionKey = new PartRevisionKey(workspace.getId(), ProductUtil.PART_ID, ProductUtil.VERSION); + partMaster.setAttributesLocked(true); + + Mockito.when(userManager.checkWorkspaceReadAccess(workspace.getId())).thenReturn(user); + Mockito.when(userManager.checkWorkspaceWriteAccess(workspace.getId())).thenReturn(user); + Mockito.when(em.find(PartRevision.class, null)).thenReturn(partRevision); + Mockito.when(em.find(PartRevision.class, partRevisionKey)).thenReturn(partRevision); + Mockito.when(em.createNamedQuery("PartUsageLink.findOrphans", PartUsageLink.class)).thenReturn(partUsageLinkTypedQuery); + Mockito.when(em.createNamedQuery("ConfigurationItem.getConfigurationItemsInWorkspace", ConfigurationItem.class)).thenReturn(configurationItemTypedQuery); + + //PartIterationKey pKey, String pIterationNote, Source source, List pUsageLinks, List pAttributes, DocumentIterationKey[] pLinkKeys + ArrayList partUsageLinks = new ArrayList<>(); + + ArrayList newAttributes = new ArrayList<>(); + ArrayList newAttributeTemplates = new ArrayList<>(); + String[] lovNames = new String[0]; + + + + try{ + //Test to remove attribute + productManagerBean.updatePartIteration(partIteration.getKey(), "Iteration note", null, partUsageLinks, newAttributes, newAttributeTemplates, new DocumentRevisionKey[]{}, null, lovNames); + Assert.assertTrue("updatePartIteration should have raise an exception because we have removed attributes", false); + }catch (NotAllowedException notAllowedException){ + try{ + //Test with a swipe of attribute + newAttributes.add(new InstanceDateAttribute("Test", new Date(), false)); + productManagerBean.updatePartIteration(partIteration.getKey(), "Iteration note", null, partUsageLinks, newAttributes, newAttributeTemplates, new DocumentRevisionKey[]{}, null, lovNames); + Assert.assertTrue("updateDocument should have raise an exception because we have changed the attribute type attributes", false); + }catch (NotAllowedException notAllowedException2){ + try { + //Test without modifying the attribute + newAttributes = new ArrayList<>(); + newAttributes.add(attribute); + productManagerBean.updatePartIteration(partIteration.getKey(), "Iteration note", null, partUsageLinks, newAttributes, newAttributeTemplates, new DocumentRevisionKey[]{}, null, lovNames); + //Test with a new value of the attribute + newAttributes = new ArrayList<>(); + newAttributes.add(new InstanceTextAttribute("Test", "newValue", false)); + productManagerBean.updatePartIteration(partIteration.getKey(), "Iteration note", null, partUsageLinks, newAttributes, newAttributeTemplates, new DocumentRevisionKey[]{}, null, lovNames); + } catch (NotAllowedException notAllowedException3){ + Assert.assertTrue("updateDocument shouldn't have raised an exception because we haven't change the number of attribute or the type", false); + } + } + } + Mockito.verify(esIndexer,Mockito.never()).index(Mockito.any(PartIteration.class)); + + } + + @Test + public void updatePartWithUnlockedAttributes() throws Exception { + //Creation of current attributes of the iteration + InstanceAttribute attribute = new InstanceTextAttribute("Test", "Testeur", false); + List attributesOfIteration = new ArrayList<>(); + attributesOfIteration.add(attribute); + partIteration.setInstanceAttributes(attributesOfIteration); + + PartRevisionKey partRevisionKey = new PartRevisionKey(workspace.getId(),ProductUtil.PART_ID, ProductUtil.VERSION); + partMaster.setAttributesLocked(false); + + Mockito.when(userManager.checkWorkspaceReadAccess(workspace.getId())).thenReturn(user); + Mockito.when(userManager.checkWorkspaceWriteAccess(workspace.getId())).thenReturn(user); + Mockito.when(em.find(PartRevision.class, null)).thenReturn(partRevision); + Mockito.when(em.find(PartRevision.class, partRevisionKey)).thenReturn(partRevision); + Mockito.when(em.createNamedQuery("PartUsageLink.findOrphans", PartUsageLink.class)).thenReturn(partUsageLinkTypedQuery); + Mockito.when(em.createNamedQuery("ConfigurationItem.getConfigurationItemsInWorkspace", ConfigurationItem.class)).thenReturn(configurationItemTypedQuery); + + //PartIterationKey pKey, String pIterationNote, Source source, List pUsageLinks, List pAttributes, DocumentIterationKey[] pLinkKeys + ArrayList partUsageLinks = new ArrayList<>(); + + ArrayList newAttributes = new ArrayList<>(); + ArrayList newAttributeTemplates = new ArrayList<>(); + String[] lovNames = new String[0]; + + + try{ + //Test to remove attribute + productManagerBean.updatePartIteration(partIteration.getKey(), "Iteration note", null, partUsageLinks, newAttributes, newAttributeTemplates, new DocumentRevisionKey[]{}, null, lovNames); + //Test with a swipe of attribute + newAttributes.add(new InstanceDateAttribute("Test", new Date(), false)); + productManagerBean.updatePartIteration(partIteration.getKey(), "Iteration note", null, partUsageLinks, newAttributes, newAttributeTemplates, new DocumentRevisionKey[]{}, null, lovNames); + //Test without modifying the attribute + newAttributes = new ArrayList<>(); + newAttributes.add(attribute); + productManagerBean.updatePartIteration(partIteration.getKey(), "Iteration note", null, partUsageLinks, newAttributes, newAttributeTemplates, new DocumentRevisionKey[]{}, null, lovNames); + //Test with a new value of the attribute + newAttributes = new ArrayList<>(); + newAttributes.add(new InstanceTextAttribute("Test", "newValue", false)); + productManagerBean.updatePartIteration(partIteration.getKey(), "Iteration note", null, partUsageLinks, newAttributes, newAttributeTemplates, new DocumentRevisionKey[]{}, null, lovNames); + + }catch (NotAllowedException notAllowedException){ + Assert.assertTrue("updateDocument shouldn't have raised an exception because the attributes are not frozen", false); + } + Mockito.verify(esIndexer,Mockito.never()).index(Mockito.any(PartIteration.class)); + + } + + /** + * test the add of new tags to a part that doesn't have any tag + * @throws UserNotFoundException + * @throws WorkspaceNotFoundException + * @throws UserNotActiveException + * @throws PartRevisionNotFoundException + * @throws AccessRightException + */ + @Test + public void addTagToPartWithNoTags() throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + + + PartRevisionKey partRevisionKey = partRevision.getKey(); + + String[]tags = new String[3]; + tags[0]="Important"; + tags[1]="ToCheck"; + tags[2]="ToDelete"; + + Mockito.when(userManager.checkWorkspaceReadAccess(ProductUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(userManager.checkWorkspaceWriteAccess(ProductUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(em.find(PartRevision.class, partRevisionKey)).thenReturn(partRevision); + + Mockito.when(em.createQuery("SELECT DISTINCT t FROM Tag t WHERE t.workspaceId = :workspaceId", Tag.class)).thenReturn(tagsQuery); + Mockito.when(tagsQuery.setParameter("workspaceId", ProductUtil.WORKSPACE_ID)).thenReturn(tagsQuery); + Mockito.when(tagsQuery.getResultList()).thenReturn(new ArrayList()); + + PartRevision partRevisionResult = productManagerBean.saveTags(partRevisionKey, (String[]) tags); + + Assert.assertEquals(partRevisionResult.getTags().size() ,3); + int i = 0; + for (Iterator it = partRevisionResult.getTags().iterator(); it.hasNext(); ) { + Tag tag = it.next(); + Assert.assertEquals(tag.getLabel() ,tags[i++]); + } + Mockito.verify(esIndexer,Mockito.times(1)).index(Mockito.any(PartIteration.class)); + + } + + + @Test + public void removeTagFromPart() throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + Set tags = new LinkedHashSet(); + tags.add(new Tag(workspace, "Important")); + tags.add(new Tag(workspace, "ToRemove")); + tags.add(new Tag(workspace, "Urgent")); + partRevision.setTags(tags); + + PartRevisionKey partRevisionKey = partRevision.getKey(); + Mockito.when(ctx.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)).thenReturn(true); + Mockito.when(userManager.checkWorkspaceReadAccess(ProductUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(userManager.checkWorkspaceWriteAccess(ProductUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(em.find(PartRevision.class, partRevisionKey)).thenReturn(partRevision); + + PartRevision partRevisionResult = productManagerBean.removeTag(partRevision.getKey(), "Important"); + Mockito.verify(esIndexer,Mockito.times(1)).index(partRevisionResult.getLastIteration()); + Assert.assertEquals(partRevisionResult.getTags().size() ,2); + Assert.assertFalse(partRevisionResult.getTags().contains(new Tag(workspace,"Important"))); + Assert.assertTrue(partRevisionResult.getTags().contains(new Tag(workspace,"Urgent"))); + Assert.assertTrue(partRevisionResult.getTags().contains(new Tag(workspace,"ToRemove"))); + + } + + @Test(expected = IllegalArgumentException.class) + public void addNullTagToOnePart() throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + String[] tags = null; + partRevision.setTags(null); + PartRevisionKey partRevisionKey = partRevision.getKey(); + Mockito.when(ctx.isCallerInRole(UserGroupMapping.GUEST_PROXY_ROLE_ID)).thenReturn(true); + Mockito.when(userManager.checkWorkspaceReadAccess(ProductUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(userManager.checkWorkspaceWriteAccess(ProductUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(em.find(PartRevision.class, partRevisionKey)).thenReturn(partRevision); + try { + productManagerBean.saveTags(partRevisionKey,tags); + } catch (IllegalArgumentException e) { + Mockito.verify(esIndexer,Mockito.never()).index(Mockito.any(PartIteration.class)); + throw e; + } + + + } + + @Test + public void checkCyclicDetection() throws EntityConstraintException, PartMasterNotFoundException, UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, NotAllowedException { + + cyclicAssemblyRule = new CyclicAssemblyRule("user1"); + Mockito.when(em.find(PartMaster.class, cyclicAssemblyRule.getP1().getKey())).thenReturn(cyclicAssemblyRule.getP1()); + Mockito.when(em.find(PartMaster.class, cyclicAssemblyRule.getP2().getKey())).thenReturn(cyclicAssemblyRule.getP2()); + Mockito.when(userManager.checkWorkspaceReadAccess(Matchers.anyString())).thenReturn(cyclicAssemblyRule.getUser()); + + thrown.expect(EntityConstraintException.class); + + productManagerBean.checkCyclicAssemblyForPartIteration(cyclicAssemblyRule.getP1().getLastRevision().getLastIteration()); + Mockito.verify(esIndexer,Mockito.never()).index(Mockito.any(PartIteration.class)); + + } + + + @Test + public void checkPathToPathUpgrade(){ + + String oldFullId = "u1058"; + String newFullId = "u9999"; + + String path1 = "-1-u1058-u1057"; + String path2 = "-1-u1058"; + String path3 = "-1-u1058000-u1057"; + + String expect1 = "-1-u9999-u1057"; + String expect2 = "-1-u9999"; + String expect3 = "-1-u1058000-u1057"; + + String result1 = pathToPathLinkDAO.upgradePath(path1, oldFullId, newFullId); + String result2 = pathToPathLinkDAO.upgradePath(path2, oldFullId, newFullId); + String result3 = pathToPathLinkDAO.upgradePath(path3, oldFullId,newFullId); + + Assert.assertEquals(expect1, result1); + Assert.assertEquals(expect2, result2); + Assert.assertEquals(expect3, result3); + + } + + @Test(expected = NotAllowedException.class) + public void getPartIterationCheckedOutByOther() throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, PartRevisionNotFoundException, AccessRightException, PartIterationNotFoundException, NotAllowedException { + Mockito.when(userManager.checkWorkspaceReadAccess(partRevision.getKey().getPartMaster().getWorkspace())).thenReturn(user2); + Mockito.when(em.find(PartRevision.class, partRevision.getKey())).thenReturn(partRevision); + Mockito.when(em.find(PartIteration.class, partIteration.getKey())).thenReturn(partIteration); + + productManagerBean.getPartIteration(partIteration.getKey()); + } + + @Test(expected = NotAllowedException.class) + public void updatePartIterationCheckedOutByOther() throws ListOfValuesNotFoundException, PartMasterNotFoundException, EntityConstraintException, WorkspaceNotFoundException, UserNotFoundException, NotAllowedException, UserNotActiveException, PartUsageLinkNotFoundException, AccessRightException, PartRevisionNotFoundException { + + Mockito.when(userManager.checkWorkspaceReadAccess(partRevision.getKey().getPartMaster().getWorkspace())).thenReturn(user2); + Mockito.when(em.find(PartRevision.class, partRevision.getKey())).thenReturn(partRevision); + Mockito.when(em.find(PartIteration.class, partIteration.getKey())).thenReturn(partIteration); + + productManagerBean.updatePartIteration(partIteration.getKey(), null, null, null, null, null, null,null,null); + } + +} diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/WorkflowManagerBeanTest.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/WorkflowManagerBeanTest.java new file mode 100644 index 0000000000..3d62034fec --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/WorkflowManagerBeanTest.java @@ -0,0 +1,183 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server; + +import com.docdoku.core.common.*; +import com.docdoku.core.exceptions.AccessRightException; +import com.docdoku.core.security.ACL; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.core.workflow.WorkflowModel; +import com.docdoku.core.workflow.WorkflowModelKey; +import com.docdoku.server.util.WorkflowUtil; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; + +import javax.persistence.EntityManager; +import javax.persistence.StoredProcedureQuery; +import javax.persistence.TypedQuery; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.MockitoAnnotations.initMocks; + +public class WorkflowManagerBeanTest { + + + @InjectMocks + WorkflowManagerBean workflowManagerBean = new WorkflowManagerBean(); + + @Mock + private EntityManager em; + @Mock + private IUserManagerLocal userManager; + @Mock + TypedQuery aclTypedQuery; + @Mock + StoredProcedureQuery storedProcedureQuery; + + private User user; + private Account account; + private Workspace workspace; + + @Before + public void setup() throws Exception { + initMocks(this); + account = new Account(WorkflowUtil.ADMIN_LOGIN, WorkflowUtil.ADMIN_NAME, WorkflowUtil.ADMIN_MAIL, "en", new Date(), null); + workspace = new Workspace(WorkflowUtil.WORKSPACE_ID, account, WorkflowUtil.WORKSPACE_DESCRIPTION, false); + user = new User(workspace,new Account(WorkflowUtil.USER_LOGIN, WorkflowUtil.USER_NAME,WorkflowUtil.USER_MAIL, "en", new Date(), null)); + } + + /** + * test the remove of acl from a workflow operated by user who doesn't have write access to the workflow + * @throws Exception + */ + @Test(expected = AccessRightException.class) + public void testRemoveACLFromWorkflow() throws Exception { + + //Given + WorkflowModel workflowModel = new WorkflowModel(workspace, WorkflowUtil.WORKSPACE_ID, user, ""); + ACL acl = new ACL(); + acl.addEntry(user, ACL.Permission.READ_ONLY); + workflowModel.setAcl(acl); + // User has read access to the workspace + Mockito.when(userManager.checkWorkspaceReadAccess(WorkflowUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(em.find(WorkflowModel.class, new WorkflowModelKey(WorkflowUtil.WORKSPACE_ID,WorkflowUtil.WORKFLOW_MODEL_ID))).thenReturn(workflowModel); + //When + workflowManagerBean.removeACLFromWorkflow(WorkflowUtil.WORKSPACE_ID, WorkflowUtil.WORKFLOW_MODEL_ID); + //Then, removeACLFromWorkflow should throw AccessRightException, user doesn't have write access to the workflow + } + + /** + * create an ACL of the workflow, user is the admin of the workspace + * @throws Exception + */ + @Test + public void testUpdateACLForWorkflowWithNoACL() throws Exception { + + //Given + WorkflowModel workflowModel = new WorkflowModel(workspace, WorkflowUtil.WORKSPACE_ID, user, ""); + Map userEntries = new HashMap<>(); + User user2 = new User(workspace,new Account(WorkflowUtil.USER2_LOGIN , WorkflowUtil.USER2_NAME,WorkflowUtil.USER2_MAIL, "en", new Date(), null)); + User user3 = new User(workspace,new Account(WorkflowUtil.USER3_LOGIN , WorkflowUtil.USER3_NAME,WorkflowUtil.USER3_MAIL, "en", new Date(), null)); + userEntries.put(user.getLogin(), ACL.Permission.FORBIDDEN.name()); + userEntries.put(user2.getLogin(), ACL.Permission.READ_ONLY.name()); + userEntries.put(user3.getLogin(), ACL.Permission.FULL_ACCESS.name()); + + // User has read access to the workspace + Mockito.when(userManager.checkWorkspaceReadAccess(WorkflowUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(em.find(WorkflowModel.class, new WorkflowModelKey(WorkflowUtil.WORKSPACE_ID, WorkflowUtil.WORKFLOW_MODEL_ID))).thenReturn(workflowModel); + Mockito.when(em.find(User.class, new UserKey(WorkflowUtil.WORKSPACE_ID, WorkflowUtil.USER_LOGIN))).thenReturn(user); + Mockito.when(em.find(User.class, new UserKey(WorkflowUtil.WORKSPACE_ID,WorkflowUtil.USER2_LOGIN))).thenReturn(user2); + Mockito.when(em.find(User.class, new UserKey(WorkflowUtil.WORKSPACE_ID,WorkflowUtil.USER3_LOGIN))).thenReturn(user3); + + //When + WorkflowModel workflow= workflowManagerBean.updateACLForWorkflow(WorkflowUtil.WORKSPACE_ID, WorkflowUtil.WORKFLOW_MODEL_ID, userEntries, null); + //Then + Assert.assertEquals(workflow.getAcl().getGroupEntries().size() ,0 ); + Assert.assertEquals(workflow.getAcl().getUserEntries().size() , 3); + Assert.assertEquals(workflow.getAcl().getUserEntries().get(user).getPermission() , ACL.Permission.FORBIDDEN); + Assert.assertEquals(workflow.getAcl().getUserEntries().get(user2).getPermission() , ACL.Permission.READ_ONLY); + Assert.assertEquals(workflow.getAcl().getUserEntries().get(user3).getPermission() , ACL.Permission.FULL_ACCESS); + + + } + + @Test + public void testUpdateACLForWorkflowWithAnExistingACL() throws Exception { + //Given + Map userEntries = new HashMap<>(); + Map grpEntries = new HashMap<>(); + User user2 = new User(workspace,new Account(WorkflowUtil.USER2_LOGIN , WorkflowUtil.USER2_NAME,WorkflowUtil.USER2_MAIL, "en", new Date(), null)); + User user3 = new User(workspace,new Account(WorkflowUtil.USER3_LOGIN , WorkflowUtil.USER3_NAME,WorkflowUtil.USER3_MAIL, "en", new Date(), null)); + UserGroup group1 = new UserGroup(workspace,WorkflowUtil.GRP1_ID); + + WorkflowModel workflowModel = new WorkflowModel(workspace, WorkflowUtil.WORKSPACE_ID, user, ""); + ACL acl = new ACL(); + // user2 had READ_ONLY access in the existing acl + acl.addEntry(user2, ACL.Permission.READ_ONLY); + acl.addEntry(group1, ACL.Permission.FULL_ACCESS); + workflowModel.setAcl(acl); + + userEntries.put(user.getLogin(), ACL.Permission.FORBIDDEN.name()); + // user2 has non access FORBIDDEN in the new acl + userEntries.put(user2.getLogin(), ACL.Permission.FORBIDDEN.name()); + userEntries.put(user3.getLogin(), ACL.Permission.FULL_ACCESS.name()); + + + //user2 belong to group1 + group1.addUser(user2); + group1.addUser(user); + //group1 has FULL_ACCESS + grpEntries.put(group1.getId(),ACL.Permission.FULL_ACCESS.name()); + + + // User has read access to the workspace + Mockito.when(userManager.checkWorkspaceReadAccess(WorkflowUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(em.find(WorkflowModel.class, new WorkflowModelKey(WorkflowUtil.WORKSPACE_ID, WorkflowUtil.WORKFLOW_MODEL_ID))).thenReturn(workflowModel); + Mockito.when(em.find(User.class, new UserKey(WorkflowUtil.WORKSPACE_ID,WorkflowUtil.USER_LOGIN))).thenReturn(user); + Mockito.when(em.find(User.class, new UserKey(WorkflowUtil.WORKSPACE_ID,WorkflowUtil.USER2_LOGIN))).thenReturn(user2); + Mockito.when(em.find(User.class, new UserKey(WorkflowUtil.WORKSPACE_ID,WorkflowUtil.USER3_LOGIN))).thenReturn(user3); + Mockito.when(em.getReference(UserGroup.class, new UserGroupKey(WorkflowUtil.WORKSPACE_ID, group1.getId()))).thenReturn(group1); + Mockito.when(em.getReference(User.class, user.getKey())).thenReturn(user); + Mockito.when(em.getReference(User.class, user2.getKey())).thenReturn(user2); + Mockito.when(em.getReference(User.class, user3.getKey())).thenReturn(user3); + Mockito.when(aclTypedQuery.setParameter(Matchers.anyString(),Matchers.any())).thenReturn(aclTypedQuery); + Mockito.when(em.createNamedQuery(Matchers.any())).thenReturn(aclTypedQuery); + + //When + WorkflowModel workflow= workflowManagerBean.updateACLForWorkflow(WorkflowUtil.WORKSPACE_ID, WorkflowUtil.WORKFLOW_MODEL_ID, userEntries, grpEntries); + //Then + Assert.assertEquals(workflow.getAcl().getGroupEntries().size(),1 ); + Assert.assertEquals(workflow.getAcl().getUserEntries().size() , 3); + Assert.assertEquals(workflow.getAcl().getUserEntries().get(user).getPermission() , ACL.Permission.FORBIDDEN); + Assert.assertEquals(workflow.getAcl().getUserEntries().get(user2).getPermission() , ACL.Permission.FORBIDDEN); + Assert.assertEquals(workflow.getAcl().getUserEntries().get(user3).getPermission() , ACL.Permission.FULL_ACCESS); + + } + + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/configuration/filter/WIPPSFilterTest.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/configuration/filter/WIPPSFilterTest.java new file mode 100644 index 0000000000..457a4df0fa --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/configuration/filter/WIPPSFilterTest.java @@ -0,0 +1,103 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.configuration.filter; + +import com.docdoku.core.common.User; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartMaster; +import com.docdoku.core.product.PartRevision; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author kelto on 13/01/16. + */ +@RunWith(MockitoJUnitRunner.class) +public class WIPPSFilterTest { + + private WIPPSFilter filter; + private PartMaster partMaster; + private List partRevisions; + private User user; + + @Before + public void setup() { + user = Mockito.spy(new User()); + Mockito.doReturn("test").when(user).getLogin(); + filter = new WIPPSFilter(user); + partMaster = Mockito.spy(new PartMaster()); + partRevisions = new ArrayList<>(); + Mockito.doReturn(partRevisions).when(partMaster).getPartRevisions(); + } + + + @Test + public void testFilterNoIterationAccessible() throws Exception { + PartRevision partRevision = Mockito.spy(new PartRevision()); + Mockito.doReturn(null).when(partRevision).getLastAccessibleIteration(Mockito.any()); + partRevisions.add(partRevision); + + + //Should return empty list of partIteration if only one partRevision with no accessible iteration + Assert.assertTrue(filter.filter(partMaster).isEmpty()); + + partRevision = Mockito.spy(new PartRevision()); + Mockito.doReturn(null).when(partRevision).getLastAccessibleIteration(Mockito.any()); + partRevisions.add(partRevision); + partRevision = Mockito.spy(new PartRevision()); + Mockito.doReturn(null).when(partRevision).getLastAccessibleIteration(Mockito.any()); + partRevisions.add(partRevision); + partRevision = Mockito.spy(new PartRevision()); + Mockito.doReturn(null).when(partRevision).getLastAccessibleIteration(Mockito.any()); + partRevisions.add(partRevision); + + //Should still return empty list of partiteration with multiple partRevision + Assert.assertTrue(filter.filter(partMaster).isEmpty()); + } + + @Test + public void testFilterLastRevisionNoIteration() throws Exception { + PartRevision partRevision = Mockito.spy(new PartRevision()); + Mockito.doReturn(Mockito.mock(PartIteration.class)).when(partRevision).getLastAccessibleIteration(Mockito.any()); + partRevisions.add(partRevision); + + Assert.assertTrue(filter.filter(partMaster).size() == 1); + + partRevision = Mockito.spy(new PartRevision()); + Mockito.doReturn(null).when(partRevision).getLastAccessibleIteration(Mockito.any()); + partRevisions.add(partRevision); + + Assert.assertTrue(filter.filter(partMaster).size() == 1); + + partRevision = Mockito.spy(new PartRevision()); + Mockito.doReturn(null).when(partRevision).getLastAccessibleIteration(Mockito.any()); + partRevisions.add(partRevision); + + Assert.assertTrue(filter.filter(partMaster).size() == 1); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/documents/DocumentBaselineManagerBeanTest.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/documents/DocumentBaselineManagerBeanTest.java new file mode 100644 index 0000000000..769c1f2eac --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/documents/DocumentBaselineManagerBeanTest.java @@ -0,0 +1,159 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.documents; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.configuration.DocumentBaseline; +import com.docdoku.core.document.DocumentMaster; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.document.Folder; +import com.docdoku.core.security.ACL; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.DataManagerBean; +import com.docdoku.server.dao.WorkspaceDAO; +import com.docdoku.server.util.DocumentUtil; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.ArrayList; +import java.util.Date; +import java.util.Locale; + +@RunWith(MockitoJUnitRunner.class) +public class DocumentBaselineManagerBeanTest { + + @InjectMocks + DocumentBaselineManagerBean docBaselineManagerBean = new DocumentBaselineManagerBean(); + @Mock + IDocumentManagerLocal documentManagerLocal; + @Mock + IUserManagerLocal userManager; + @Mock + EntityManager em; + @Mock + DataManagerBean dataManager; + @Mock + TypedQuery folderTypedQuery; + @Mock + IDocumentManagerLocal documentService; + + + private Account account = new Account(DocumentUtil.USER_2_LOGIN, DocumentUtil.USER_2_NAME, DocumentUtil.USER2_MAIL, DocumentUtil.LANGUAGE,new Date(),null); + private Workspace workspace = new Workspace("workspace01",account, DocumentUtil.WORKSPACE_DESCRIPTION, false); + private User user = new User(workspace, new Account(DocumentUtil.USER_1_LOGIN , DocumentUtil.USER_1_NAME, DocumentUtil.USER1_MAIL, DocumentUtil.LANGUAGE,new Date(),null)); + private Folder folder = new Folder("workspace01"); + + + + /** + * test that we cannot baseline an empty workspace + * @throws Exception + */ + @Test + public void shouldNotBaselineEmptyWorkspace() throws Exception { + //Given + Mockito.when(userManager.checkWorkspaceWriteAccess(workspace.getId())).thenReturn(user); + Mockito.when(em.find(Workspace.class, workspace.getId())).thenReturn(workspace); + Mockito.when(em.createQuery("SELECT DISTINCT f FROM Folder f WHERE f.parentFolder.completePath = :completePath", Folder.class)).thenReturn(folderTypedQuery); + Mockito.when(folderTypedQuery.getResultList()).thenReturn(new ArrayList(0)); + Mockito.when(new WorkspaceDAO(new Locale("en"), em).loadWorkspace(workspace.getId())).thenReturn(workspace); + Mockito.when(em.find(Folder.class,workspace.getId())).thenReturn(folder); + Mockito.when(documentService.getAllDocumentsInWorkspace(workspace.getId())).thenReturn(new DocumentRevision[0]); + + //when + DocumentBaseline documentBaseline= docBaselineManagerBean.createBaseline(workspace.getId(), "name", "description"); + + //Then + //TODO should test documentBaseline is null after code update + Assert.assertTrue("description".equals(documentBaseline.getDescription())); + Assert.assertTrue(documentBaseline.hasBasedLinedFolder(workspace.getId())); + Assert.assertTrue(documentBaseline.getBaselinedFolders().size() == 1); + Assert.assertTrue(documentBaseline.getWorkspace().getId().equals(workspace.getId())); + } + + + /** + * test that we can baseline documents only when we have read permission + * @throws Exception + */ + @Test + public void baselineDocuments() throws Exception { + + //Given + Mockito.when(userManager.checkWorkspaceWriteAccess(workspace.getId())).thenReturn(user); + Mockito.when(em.find(Workspace.class, workspace.getId())).thenReturn(workspace); + Mockito.when(em.createQuery("SELECT DISTINCT f FROM Folder f WHERE f.parentFolder.completePath = :completePath", Folder.class)).thenReturn(folderTypedQuery); + Mockito.when(folderTypedQuery.getResultList()).thenReturn(new ArrayList(0)); + Mockito.when(new WorkspaceDAO(new Locale("en"), em).loadWorkspace(workspace.getId())).thenReturn(workspace); + Mockito.when(em.find(Folder.class,workspace.getId())).thenReturn(folder); + DocumentRevision[] revisions= new DocumentRevision[2]; + + DocumentMaster documentMaster1 = new DocumentMaster(workspace,"doc1",user); + DocumentMaster documentMaster2 = new DocumentMaster(workspace,"doc2",user); + documentMaster1.setId("doc001"); + documentMaster2.setId("doc002"); + + DocumentRevision documentRevision1 = documentMaster1.createNextRevision(user); + DocumentRevision documentRevision2 = documentMaster2.createNextRevision(user); + + ACL acl1=new ACL(); + ACL acl2=new ACL(); + + acl1.addEntry(user, ACL.Permission.FORBIDDEN); + acl2.addEntry(user, ACL.Permission.READ_ONLY); + documentRevision1.setACL(acl1); + documentRevision2.setACL(acl2); + + revisions[0] = documentRevision2; + revisions[1] = documentRevision1; + documentRevision2.createNextIteration(user); + documentRevision1.createNextIteration(user); + documentRevision1.setLocation(folder); + documentRevision2.setLocation(folder); + Mockito.when(em.find(DocumentRevision.class, documentRevision1.getKey())).thenReturn(documentRevision1); + Mockito.when(em.find(DocumentRevision.class, documentRevision2.getKey())).thenReturn(documentRevision2); + + Mockito.when(documentService.getAllDocumentsInWorkspace(workspace.getId())).thenReturn(revisions); + Mockito.when(userManager.checkWorkspaceReadAccess(workspace.getId())).thenReturn(user); + + //when + DocumentBaseline documentBaseline= docBaselineManagerBean.createBaseline(workspace.getId(), "name", "description"); + + //Then + Assert.assertTrue(documentBaseline != null); + Assert.assertTrue(documentBaseline.hasBasedLinedFolder(workspace.getId())); + Assert.assertTrue(documentBaseline.getBaselinedFolders().size() == 1); + Assert.assertNull(documentBaseline.getBaselinedDocument(documentRevision1.getKey())); + Assert.assertNotNull(documentBaseline.getBaselinedDocument(documentRevision2.getKey())); + + + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/extras/TitleBlockGeneratorTest.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/extras/TitleBlockGeneratorTest.java new file mode 100644 index 0000000000..f53cb9c56e --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/extras/TitleBlockGeneratorTest.java @@ -0,0 +1,186 @@ +package com.docdoku.server.extras; + +import com.docdoku.core.common.User; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartRevision; +import com.itextpdf.text.Chunk; +import com.itextpdf.text.Document; +import com.itextpdf.text.DocumentException; +import com.itextpdf.text.pdf.PdfReader; +import com.itextpdf.text.pdf.PdfWriter; +import com.itextpdf.text.pdf.parser.PdfReaderContentParser; +import com.itextpdf.text.pdf.parser.SimpleTextExtractionStrategy; +import com.itextpdf.text.pdf.parser.TextExtractionStrategy; +import org.apache.commons.io.FileUtils; +import java.nio.file.Files; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.Locale; + +import static org.mockito.MockitoAnnotations.initMocks; + +/** + * Created by kelto on 04/01/16. + */ +@RunWith(MockitoJUnitRunner.class) +public class TitleBlockGeneratorTest { + + private File tmpDir; + private Date date; + + private DocumentIteration documentIteration; + private PartIteration partIteration; + + @Before + public void setup() throws Exception { + initMocks(this); + tmpDir = Files.createTempDirectory("docdoku-").toFile(); + User user = Mockito.mock(User.class); + Mockito.when(user.getLogin()).thenReturn("DocdokuTest"); + Mockito.when(user.getName()).thenReturn("DocdokuTest"); + + DocumentRevision documentRevision = new DocumentRevision(); + documentRevision.setCreationDate(new Date()); + documentRevision.setAuthor(user); + documentRevision.setTitle("TestTitleOrName"); + documentRevision.setTags(new HashSet<>()); + documentRevision.setDescription("TestDescription"); + + documentIteration = Mockito.spy(new DocumentIteration()); + documentIteration.setDocumentRevision(documentRevision); + date = new Date(); + documentIteration.setCreationDate(date); + Mockito.doReturn("TestIdOrNumber").when(documentIteration).getId(); + Mockito.doReturn("TestIdOrNumber-A-154").when(documentIteration).toString(); + Mockito.doReturn("A").when(documentIteration).getVersion(); + Mockito.when(documentIteration.getInstanceAttributes()).thenReturn(new ArrayList()); + documentIteration.setAuthor(user); + documentIteration.setRevisionNote("RevisionNote"); + documentIteration.setIteration(154); + + PartRevision partRevision = Mockito.spy(new PartRevision()); + partRevision.setCreationDate(new Date()); + partRevision.setAuthor(user); + Mockito.doReturn("TestTitleOrName").when(partRevision).getPartName(); + partRevision.setTags(new HashSet<>()); + partRevision.setDescription("TestDescription"); + + partIteration = Mockito.spy(new PartIteration()); + partIteration.setPartRevision(partRevision); + date = new Date(); + partIteration.setCreationDate(date); + Mockito.doReturn("TestIdOrNumber").when(partIteration).getNumber(); + Mockito.doReturn("A").when(partIteration).getVersion(); + Mockito.when(partIteration.getInstanceAttributes()).thenReturn(new ArrayList()); + partIteration.setAuthor(user); + partIteration.setIterationNote("RevisionNote"); + partIteration.setIteration(154); + + } + + @After + public void cleanup() throws IOException { + FileUtils.deleteDirectory(tmpDir); + } + + private InputStream createSimplePdf(String text) throws IOException, DocumentException { + File pdf = new File(tmpDir, ""+System.currentTimeMillis()); + pdf.createNewFile(); + Document document = new Document(); + PdfWriter.getInstance(document, new FileOutputStream(pdf)); + document.open(); + document.add(new Chunk(text)); + document.add(Chunk.NEWLINE); + document.close(); + + return new FileInputStream(pdf); + } + + @Test + public void testAddBlockTitleToPDF() throws Exception { + InputStream fullPdf = TitleBlockGenerator.addBlockTitleToPDF(createSimplePdf(""), documentIteration, Locale.ENGLISH); + PdfReader pdfReader = new PdfReader(fullPdf); + //The number page is always + Assert.assertTrue(pdfReader.getNumberOfPages() >= 2); + String content = parsePdf(pdfReader,1); + assertInContent(content); + + fullPdf= TitleBlockGenerator.addBlockTitleToPDF(createSimplePdf("Some text from a binary ressource. This text should be on the second page of the pdf. DocdokuKeyword.") + ,documentIteration,Locale.ENGLISH); + pdfReader = new PdfReader(fullPdf); + Assert.assertTrue(pdfReader.getNumberOfPages() >= 2); + content = parsePdf(pdfReader,1); + assertInContent(content); + //Assert that the second page still exist. + content = parsePdf(pdfReader,2); + Assert.assertTrue(content.contains("DocdokuKeyword.")); + + fullPdf = TitleBlockGenerator.addBlockTitleToPDF(createSimplePdf(""), partIteration, Locale.ENGLISH); + pdfReader = new PdfReader(fullPdf); + //The number page is always + Assert.assertTrue(pdfReader.getNumberOfPages() >= 2); + content = parsePdf(pdfReader,1); + assertInContent(content); + + fullPdf= TitleBlockGenerator.addBlockTitleToPDF(createSimplePdf("Some text from a binary ressource. This text should be on the second page of the pdf. DocdokuKeyword.") + ,documentIteration,Locale.ENGLISH); + pdfReader = new PdfReader(fullPdf); + Assert.assertTrue(pdfReader.getNumberOfPages() >= 2); + content = parsePdf(pdfReader,1); + assertInContent(content); + //Assert that the second page still exist. + content = parsePdf(pdfReader,2); + Assert.assertTrue(content.contains("DocdokuKeyword.")); + + } + + private void assertInContent(String content) throws Exception{ + Assert.assertTrue(content.contains("DocdokuTest")); + Assert.assertTrue(content.contains("TestIdOrNumber")); + Assert.assertTrue(content.contains("TestIdOrNumber-A")); + Assert.assertTrue(content.contains(""+154)); + SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy/MM/dd"); + Assert.assertTrue(content.contains(simpleFormat.format(date))); + Assert.assertTrue(content.contains("RevisionNote")); + //Currently, we do not display the title of the document. Could be a good idea to do so. + //Assert.assertTrue(content.contains("DocumentTitle")); + } + + @Test + public void testMergePdfDocuments() throws Exception { + InputStream inputStream = TitleBlockGenerator.mergePdfDocuments( + createSimplePdf("Some text from a binary ressource. This text should be on the second page of the pdf. DocdokuKeyword."), + createSimplePdf("Some text from a binary ressource. This text should be on the second page of the pdf. SecondDocdokuKeyword.")); + PdfReader pdfReader = new PdfReader(inputStream); + String firstPage = new String(pdfReader.getPageContent(1)); + Assert.assertTrue(firstPage.contains("DocdokuKeyword.")); + String secondPage = new String(pdfReader.getPageContent(2)); + Assert.assertTrue(secondPage.contains("SecondDocdokuKeyword.")); + } + + private String parsePdf(PdfReader reader, int page) throws IOException { +// PdfReader reader = new PdfReader(pdf); + PdfReaderContentParser parser = new PdfReaderContentParser(reader); +// PrintWriter out = new PrintWriter(new FileOutputStream(txt)); + TextExtractionStrategy strategy; + strategy = parser.processContent(page, new SimpleTextExtractionStrategy()); + return strategy.getResultantText(); + +// out.flush(); +// out.close(); + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/products/ProductBaselineManagerBeanTest.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/products/ProductBaselineManagerBeanTest.java new file mode 100644 index 0000000000..d863b5dafc --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/products/ProductBaselineManagerBeanTest.java @@ -0,0 +1,208 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.products; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.ProductBaseline; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.ConfigurationItem; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PathToPathLink; +import com.docdoku.server.DataManagerBean; +import com.docdoku.server.ProductManagerBean; +import com.docdoku.server.UserManagerBean; +import com.docdoku.server.dao.ConfigurationItemDAO; +import com.docdoku.server.dao.PartIterationDAO; +import com.docdoku.server.util.BaselineRule; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import javax.ejb.SessionContext; +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Locale; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.MockitoAnnotations.initMocks; + +@RunWith(MockitoJUnitRunner.class) +public class ProductBaselineManagerBeanTest { + + @InjectMocks + ProductBaselineManagerBean productBaselineService = new ProductBaselineManagerBean(); + @Mock + SessionContext ctx; + @Mock + Principal principal; + @Mock + UserManagerBean userManager; + @Mock + EntityManager em; + @Mock + DataManagerBean dataManager; + @Mock + ProductManagerBean productService; + + @Rule + public BaselineRule baselineRuleNotReleased; + @Rule + public BaselineRule baselineRuleReleased; + @Rule + public BaselineRule baselineRuleLatest; + @Mock + private TypedQuery mockedQuery; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void setup() throws Exception { + initMocks(this); + Mockito.when(ctx.getCallerPrincipal()).thenReturn(principal); + Mockito.when(principal.getName()).thenReturn("user1"); + } + + + /** + * test the creation of Released baseline + */ + @Test + public void createReleasedBaseline() throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, NotAllowedException, UserNotActiveException, PartIterationNotFoundException, PartRevisionNotReleasedException, EntityConstraintException, PartMasterNotFoundException, CreationException, BaselineNotFoundException, PathToPathLinkAlreadyExistsException { + + //Given + baselineRuleReleased = new BaselineRule("myBaseline", ProductBaseline.BaselineType.RELEASED, "description", "workspace01", "user1", "part01", "product01", true); + doReturn(new User()).when(userManager).checkWorkspaceWriteAccess(Matchers.anyString()); + Mockito.when(userManager.checkWorkspaceWriteAccess(Matchers.anyString())).thenReturn(baselineRuleReleased.getUser()); + Mockito.when(em.find(ConfigurationItem.class, baselineRuleReleased.getConfigurationItemKey())).thenReturn(baselineRuleReleased.getConfigurationItem()); + Mockito.when(new ConfigurationItemDAO(new Locale("en"), em).loadConfigurationItem(baselineRuleReleased.getConfigurationItemKey())).thenReturn(baselineRuleReleased.getConfigurationItem()); + + Mockito.when(productService.getRootPartUsageLink(Matchers.any())).thenReturn(baselineRuleReleased.getRootPartUsageLink()); + Mockito.when(mockedQuery.setParameter(Matchers.anyString(), Matchers.any())).thenReturn(mockedQuery); + Mockito.when(em.createNamedQuery("PathToPathLink.findPathToPathLinkByPathListInProduct", PathToPathLink.class)).thenReturn(mockedQuery); + + //When + ProductBaseline baseline = productBaselineService.createBaseline(baselineRuleReleased.getConfigurationItemKey(), baselineRuleReleased.getName(), baselineRuleReleased.getType(), baselineRuleReleased.getDescription(), new ArrayList<>(), baselineRuleReleased.getSubstituteLinks(), baselineRuleReleased.getOptionalUsageLinks()); + + //Then + Assert.assertTrue(baseline != null); + Assert.assertTrue(baseline.getDescription().equals(baselineRuleReleased.getDescription())); + Assert.assertTrue(baseline.getType().equals(baselineRuleReleased.getType())); + Assert.assertTrue(baseline.getConfigurationItem().getWorkspaceId().equals(baselineRuleReleased.getWorkspace().getId())); + + } + + /** + * test the creation of a released baseline with a product that contains a part that has not been released yet + * + * @throws Exception PartRevisionNotReleasedException + */ + @Test + public void createReleasedBaselineUsingPartNotReleased() throws Exception{ + + //Given + baselineRuleNotReleased = new BaselineRule("myBaseline", ProductBaseline.BaselineType.RELEASED, "description", "workspace01", "user1", "part01", "product01", false); + + doReturn(new User()).when(userManager).checkWorkspaceWriteAccess(Matchers.anyString()); + Mockito.when(userManager.checkWorkspaceWriteAccess(Matchers.anyString())).thenReturn(baselineRuleNotReleased.getUser()); + Mockito.when(em.find(ConfigurationItem.class, baselineRuleNotReleased.getConfigurationItemKey())).thenReturn(baselineRuleNotReleased.getConfigurationItem()); + Mockito.when(new ConfigurationItemDAO(new Locale("en"), em).loadConfigurationItem(baselineRuleNotReleased.getConfigurationItemKey())).thenReturn(baselineRuleNotReleased.getConfigurationItem()); + thrown.expect(NotAllowedException.class); + //When + productBaselineService.createBaseline(baselineRuleNotReleased.getConfigurationItemKey(), baselineRuleNotReleased.getName(), baselineRuleNotReleased.getType(), baselineRuleNotReleased.getDescription(),new ArrayList<>(), baselineRuleNotReleased.getSubstituteLinks(), baselineRuleNotReleased.getOptionalUsageLinks()); + + } + /** + * test the creation of latest baseline + */ + @Test + public void createLatestBaseline() throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, EntityConstraintException, UserNotActiveException, NotAllowedException, PartIterationNotFoundException, PartRevisionNotReleasedException, PartMasterNotFoundException, CreationException, BaselineNotFoundException, PathToPathLinkAlreadyExistsException { + + //Given + baselineRuleLatest = new BaselineRule("myBaseline", ProductBaseline.BaselineType.LATEST, "description", "workspace01", "user1", "part01", "product01", true); + doReturn(new User()).when(userManager).checkWorkspaceWriteAccess(Matchers.anyString()); + Mockito.when(userManager.checkWorkspaceWriteAccess(Matchers.anyString())).thenReturn(baselineRuleLatest.getUser()); + Mockito.when(em.find(ConfigurationItem.class, baselineRuleLatest.getConfigurationItemKey())).thenReturn(baselineRuleLatest.getConfigurationItem()); + Mockito.when(new ConfigurationItemDAO(new Locale("en"), em).loadConfigurationItem(baselineRuleLatest.getConfigurationItemKey())).thenReturn(baselineRuleLatest.getConfigurationItem()); + + Mockito.when(productService.getRootPartUsageLink(Matchers.any())).thenReturn(baselineRuleLatest.getRootPartUsageLink()); + Mockito.when(mockedQuery.setParameter(Matchers.anyString(), Matchers.any())).thenReturn(mockedQuery); + Mockito.when(em.createNamedQuery("PathToPathLink.findPathToPathLinkByPathListInProduct", PathToPathLink.class)).thenReturn(mockedQuery); + + //When + ProductBaseline baseline = productBaselineService.createBaseline(baselineRuleLatest.getConfigurationItemKey(), baselineRuleLatest.getName(), baselineRuleLatest.getType(), baselineRuleLatest.getDescription(), new ArrayList<>(), baselineRuleLatest.getSubstituteLinks(), baselineRuleLatest.getOptionalUsageLinks()); + + //Then + Assert.assertTrue(baseline != null); + Assert.assertTrue(baseline.getDescription().equals(baselineRuleLatest.getDescription())); + Assert.assertTrue(baseline.getType().equals(baselineRuleLatest.getType())); + Assert.assertTrue(baseline.getConfigurationItem().getWorkspaceId().equals(baselineRuleLatest.getWorkspace().getId())); + + } + + /** + * @throws UserNotFoundException + * @throws AccessRightException + * @throws WorkspaceNotFoundException + * @throws ConfigurationItemNotFoundException + * @throws NotAllowedException + * @throws UserNotActiveException + * @throws PartIterationNotFoundException + * @throws com.docdoku.core.exceptions.PartRevisionNotReleasedException + */ + @Test + public void createLatestBaselineWithCheckedPart() throws UserNotFoundException, AccessRightException, WorkspaceNotFoundException, ConfigurationItemNotFoundException, NotAllowedException, UserNotActiveException, PartIterationNotFoundException, PartRevisionNotReleasedException, EntityConstraintException, PartMasterNotFoundException, CreationException, BaselineNotFoundException, PathToPathLinkAlreadyExistsException { + + //Given + baselineRuleReleased = new BaselineRule("myBaseline", ProductBaseline.BaselineType.LATEST , "description", "workspace01", "user1", "part01", "product01", true, false); + doReturn(new User()).when(userManager).checkWorkspaceWriteAccess(Matchers.anyString()); + Mockito.when(userManager.checkWorkspaceWriteAccess(Matchers.anyString())).thenReturn(baselineRuleReleased.getUser()); + Mockito.when(em.find(ConfigurationItem.class, baselineRuleReleased.getConfigurationItemKey())).thenReturn(baselineRuleReleased.getConfigurationItem() + ); + Mockito.when(new ConfigurationItemDAO(new Locale("en"), em).loadConfigurationItem(baselineRuleReleased.getConfigurationItemKey())).thenReturn(baselineRuleReleased.getConfigurationItem()); + Mockito.when(em.find(PartIteration.class, baselineRuleReleased.getPartMaster().getLastReleasedRevision().getIteration(1).getKey())).thenReturn(baselineRuleReleased.getPartMaster().getLastReleasedRevision().getIteration(1)); + Mockito.when(new PartIterationDAO(new Locale("en"), em).loadPartI(baselineRuleReleased.getPartMaster().getLastReleasedRevision().getIteration(1).getKey())).thenReturn(baselineRuleReleased.getPartMaster().getLastReleasedRevision().getIteration(1)); + Mockito.when(productService.getRootPartUsageLink(Matchers.any())).thenReturn(baselineRuleReleased.getRootPartUsageLink()); + Mockito.when(mockedQuery.setParameter(Matchers.anyString(), Matchers.any())).thenReturn(mockedQuery); + Mockito.when(em.createNamedQuery("PathToPathLink.findPathToPathLinkByPathListInProduct", PathToPathLink.class)).thenReturn(mockedQuery); + + //When + ProductBaseline baseline = productBaselineService.createBaseline(baselineRuleReleased.getConfigurationItemKey(), baselineRuleReleased.getName(), baselineRuleReleased.getType(), baselineRuleReleased.getDescription(), new ArrayList<>(), baselineRuleReleased.getSubstituteLinks(), baselineRuleReleased.getOptionalUsageLinks()); + + //Then + Assert.assertTrue(baseline != null); + Assert.assertTrue(baseline.getDescription().equals(baselineRuleReleased.getDescription())); + Assert.assertTrue(baseline.getType().equals(ProductBaseline.BaselineType.LATEST)); + Assert.assertTrue(baseline.getConfigurationItem().getWorkspaceId().equals(baselineRuleReleased.getWorkspace().getId())); + + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/BaselineRule.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/BaselineRule.java new file mode 100644 index 0000000000..6bf3fcb491 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/BaselineRule.java @@ -0,0 +1,203 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.util; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.configuration.ProductBaseline; +import com.docdoku.core.product.*; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/* + * + * @author Asmae CHADID on 19/12/14. + */ + public class BaselineRule implements TestRule { + + private String name ; + private ProductBaseline.BaselineType type; + private String description; + private Workspace workspace; + private User user; + private PartMaster partMaster; + private ConfigurationItemKey configurationItemKey; + private ConfigurationItem configurationItem; + private List substituteLinks = new ArrayList<>(); + private List optionalUsageLinks = new ArrayList<>();; + + public BaselineRule(String baselineName,ProductBaseline.BaselineType type,String description,String workspaceId,String login,String partId,String productId,boolean released){ + name = baselineName; + this.type = type; + this.description = description; + this.workspace = new Workspace(workspaceId); + user = new User(workspace, new Account(login,login,login+"@docdoku.com", "en", new Date() ,null)); + partMaster = new PartMaster(workspace, partId, user); + configurationItemKey = new ConfigurationItemKey("workspace1",productId); + configurationItem = new ConfigurationItem(user,workspace, productId, "description"); + partMaster.setPartRevisions(new ArrayList()); + if (released){ + List revisions = new ArrayList(); + List iterationLists = new ArrayList(); + PartRevision revision = new PartRevision(partMaster, "A", user); + iterationLists.add(new PartIteration(revision, user)); + revision.setPartIterations(iterationLists); + revision.setStatus(PartRevision.RevisionStatus.RELEASED); + revisions.add(revision); + partMaster.setPartRevisions(revisions); + } + + configurationItem.setDesignItem(partMaster); + } + + public BaselineRule(String baselineName,ProductBaseline.BaselineType type,String description,String workspaceId,String login,String partId,String productId,boolean released,boolean checkedOut){ + this(baselineName,type,description,workspaceId,login,partId,productId,released); + if (checkedOut){ + this.partMaster.getLastReleasedRevision().getIteration(1).getPartRevision().setCheckOutUser(this.user); + } + } + + + @Override + public Statement apply(Statement statement, Description description) { + return new BaselineStatement(statement,this.configurationItem); + } + + public PartLink getRootPartUsageLink() { + return new PartLink() { + @Override + public int getId() { + return 1; + } + + @Override + public Character getCode() { + return '-'; + } + + @Override + public String getFullId() { + return "-1"; + } + + @Override + public double getAmount() { + return 1; + } + + @Override + public String getUnit() { + return null; + } + + @Override + public String getComment() { + return null; + } + + @Override + public boolean isOptional() { + return false; + } + + @Override + public PartMaster getComponent() { + return configurationItem.getDesignItem(); + } + + @Override + public List getSubstitutes() { + return null; + } + + @Override + public String getReferenceDescription() { + return null; + } + + @Override + public List getCadInstances() { + List cads = new ArrayList<>(); + CADInstance cad = new CADInstance(0d, 0d, 0d, 0d, 0d, 0d); + cad.setId(0); + cads.add(cad); + return cads; + } + }; + } + + public class BaselineStatement extends Statement{ + private final Statement statement; + public BaselineStatement(Statement s,ConfigurationItem configurationItem){ + this.statement = s; + } + @Override + public void evaluate() throws Throwable { + statement.evaluate(); + } + } + + public ConfigurationItem getConfigurationItem(){ + return this.configurationItem; + } + + public String getName() { + return name; + } + + public ProductBaseline.BaselineType getType() { + return type; + } + + public String getDescription() { + return description; + } + + public Workspace getWorkspace() { + return workspace; + } + + public User getUser() { + return user; + } + + public PartMaster getPartMaster() { + return partMaster; + } + + public ConfigurationItemKey getConfigurationItemKey() { + return configurationItemKey; + } + + public List getOptionalUsageLinks() { + return optionalUsageLinks; + } + + public List getSubstituteLinks() { + return substituteLinks; + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/CyclicAssemblyRule.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/CyclicAssemblyRule.java new file mode 100644 index 0000000000..65be41a069 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/CyclicAssemblyRule.java @@ -0,0 +1,131 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.util; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.configuration.ProductBaseline; +import com.docdoku.core.product.*; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @author Morgan Guimard + */ +public class CyclicAssemblyRule implements TestRule { + + private Workspace workspace = new Workspace("workspace"); + private PartMaster p1 = new PartMaster(workspace,"P1"); + private PartMaster p2 = new PartMaster(workspace,"P2"); + private ConfigurationItemKey configurationItemKey; + private ConfigurationItem configurationItem; + private User user; + + public CyclicAssemblyRule(String login){ + + user = new User(workspace, new Account(login , login ,login+"@docdoku.com", "en", new Date(), null)); + + // Create P1 + List revisionsP1 = new ArrayList<>(); + List iterationListsP1 = new ArrayList<>(); + PartRevision revisionP1 = new PartRevision(p1, "A", user); + iterationListsP1.add(new PartIteration(revisionP1, user)); + revisionP1.setPartIterations(iterationListsP1); + revisionsP1.add(revisionP1); + p1.setPartRevisions(revisionsP1); + + // Create P2 + List revisionsP2 = new ArrayList<>(); + List iterationListsP2 = new ArrayList<>(); + PartRevision revisionP2 = new PartRevision(p2, "A", user); + iterationListsP2.add(new PartIteration(revisionP2, user)); + revisionP2.setPartIterations(iterationListsP2); + revisionsP2.add(revisionP2); + p2.setPartRevisions(revisionsP2); + + // Add P2 to P1 usage links + PartUsageLink p1p2 = new PartUsageLink(p2,1, null, false); + p1.getLastRevision().getLastIteration().getComponents().add(p1p2); + + // Add P1 to P2 usage links + PartUsageLink p2p1 = new PartUsageLink(p1,1, null, false); + p2.getLastRevision().getLastIteration().getComponents().add(p2p1); + + configurationItemKey = new ConfigurationItemKey(getWorkspaceId(),"productId"); + configurationItem = new ConfigurationItem(user,workspace, "productId", "description"); + configurationItem.setDesignItem(p1); + + } + + @Override + public Statement apply(Statement statement, Description description) { + return new CyclicAssemblyStatement(statement); + } + + public String getWorkspaceId() { + return workspace.getId(); + } + + public PartMaster getP1() { + return p1; + } + + public PartMaster getP2() { + return p2; + } + + public class CyclicAssemblyStatement extends Statement{ + private final Statement statement; + public CyclicAssemblyStatement(Statement s){ + this.statement = s; + } + @Override + public void evaluate() throws Throwable { + statement.evaluate(); + } + } + + public ConfigurationItemKey getConfigurationItemKey() { + return configurationItemKey; + } + + public String getName() { + return "baseline"; + } + + public ProductBaseline.BaselineType getType() { + return ProductBaseline.BaselineType.LATEST; + } + + public String getDescription() { + return "description"; + } + + public User getUser() { + return user; + } +} diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/DocumentUtil.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/DocumentUtil.java new file mode 100644 index 0000000000..2621f65f7f --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/DocumentUtil.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.util; + +/* + * + * @author Asmae CHADID on 09/03/15. + */ + +public class DocumentUtil { + + public static final String WORKSPACE_ID="TestWorkspace"; + public static final String DOCUMENT_ID ="TestDocument"; + public static final String DOCUMENT_TEMPLATE_ID="temp_1"; + public static final String FILE1_NAME ="uplodedFile"; + public static final String FILE2_NAME="file_à-tèsté.txt"; + public static final String FILE3_NAME="file_à-t*st?! .txt"; + public static final String FILE4_NAME ="uploadedFile"; + public static final long DOCUMENT_SIZE = 22; + public static final String VERSION ="A" ; + public static final int ITERATION = 1; + public static final String FULL_NAME = WORKSPACE_ID+"/documents/"+DOCUMENT_ID+"/"+VERSION+"/"+ITERATION+"/"+ FILE1_NAME; + public static final String FULL_NAME4 = WORKSPACE_ID+"/documents/"+DOCUMENT_ID+"/"+VERSION+"/"+ITERATION+"/"+ FILE4_NAME; + public static final String FOLDER = "newFolder"; + public static final String USER_2_LOGIN = "user2"; + public static final String USER_2_NAME = "user2"; + public static final String USER2_MAIL = "user2@docdoku.com"; + public static final String LANGUAGE = "en"; + public static final String USER_1_LOGIN = "user1"; + public static final String USER_1_NAME = "user1"; + public static final String USER1_MAIL = "user1@docdoku.com"; + + public static final String WORKSPACE_DESCRIPTION = "pDescription"; + +} diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/ProductUtil.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/ProductUtil.java new file mode 100644 index 0000000000..9e0e38f1eb --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/ProductUtil.java @@ -0,0 +1,48 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.util; + +/* + * + * @author Asmae CHADID on 02/03/15. + */ +public class ProductUtil { + + + public static final String WORKSPACE_ID="TestWorkspace"; + public static final String VERSION ="A" ; + public static final int ITERATION = 1; + public static final String PART_ID ="TestPart"; + public static final String PART_MASTER_TEMPLATE_ID="template"; + public static final String PART_TYPE="PartType"; + public static final String USER_2_NAME = "user2"; + public static final String USER_2_LOGIN = "user2"; + public static final String USER_1_NAME = "user1"; + public static final String USER_1_LOGIN = "user1"; + + public static final String USER_1_MAIL = "user1@docdoku.com"; + public static final String USER_1_LANGUAGE = "fr"; + public static final String USER_2_MAIL = "user2@docdoku.com"; + public static final String USER_2_LANGUAGE = "en"; + public static final String USER_GRP1 = "grp1" ; + + public static String WORKSPACE_DESCRIPTION ="description of the workspace"; +} diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/WorkflowUtil.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/WorkflowUtil.java new file mode 100644 index 0000000000..c294aaf1e4 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/util/WorkflowUtil.java @@ -0,0 +1,46 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.util; + +/* + * + * @author Asmae CHADID on 25/02/15. + */ +public class WorkflowUtil { + + public static final String USER_LOGIN = "joe"; + public static final String USER_NAME = "joelle"; + public static final String USER_MAIL = "joe@docdoku.com"; + public static final String WORKSPACE_DESCRIPTION = "DEMO WORKSPCAE"; + public static final String ADMIN_LOGIN = "admin"; + public static final String ADMIN_NAME = "admin"; + public static final String ADMIN_MAIL = "ad"; + public static final String USER2_LOGIN = "leo"; + public static final String USER2_NAME = "leo"; + public static final String USER2_MAIL = "leo@docdoku.com"; + public static final String USER3_LOGIN = "tomy"; + public static final String USER3_NAME = "tomy"; + public static final String USER3_MAIL = "tomy@docdoku.com"; + public static final String GRP1_ID = "grp001"; + + public static String WORKFLOW_MODEL_ID = "workflowModel01"; + public static String WORKSPACE_ID = "workspace01"; +} diff --git a/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/validation/AttributesConsistencyUtilsTest.java b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/validation/AttributesConsistencyUtilsTest.java new file mode 100644 index 0000000000..cbf4ecb2d7 --- /dev/null +++ b/docdoku-server/docdoku-server-ejb/src/test/java/com/docdoku/server/validation/AttributesConsistencyUtilsTest.java @@ -0,0 +1,183 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.validation; + +import com.docdoku.core.meta.DefaultAttributeTemplate; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.InstanceAttributeTemplate; +import com.docdoku.core.meta.InstanceTextAttribute; +import junit.framework.TestCase; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * @author kelto on 16/07/15. + */ +@RunWith(Parameterized.class) +public class AttributesConsistencyUtilsTest extends TestCase { + //TODO: use mockito for the test + @Parameterized.Parameters + public static Collection data() { + Object[][] parameters = new Object[4][4]; + List current = new ArrayList<>(); + List newAttrs = new ArrayList<>(); + InstanceAttribute attribute = new InstanceTextAttribute("name","value1",false); + InstanceAttribute attribute1 = new InstanceTextAttribute("name","value2",true); + attribute1.setLocked(true); + InstanceAttribute attribute2 = new InstanceTextAttribute("test",null,false); + current.add(attribute); + current.add(attribute1); + current.add(attribute2); + newAttrs.add(attribute); + newAttrs.add(attribute1); + + parameters[0][0] = current; + parameters[0][1] = newAttrs; + parameters[0][2] = false; + parameters[0][3] = true; + + current = new ArrayList<>(); + newAttrs = new ArrayList<>(); + current.add(new InstanceTextAttribute("name", "value", true)); + newAttrs.add(new InstanceTextAttribute("name", "value", true)); + + parameters[1][0] = current; + parameters[1][1] = newAttrs; + parameters[1][2] = false; + //should fail, the attribute is not locked. + parameters[1][3] = false; + + attribute = new InstanceTextAttribute("name","value",true); + attribute.setLocked(true); + attribute1 = new InstanceTextAttribute("name",null,true); + attribute1.setLocked(true); + current = new ArrayList<>(); + newAttrs = new ArrayList<>(); + current.add(attribute); + newAttrs.add(attribute1); + parameters[2][0] = current; + parameters[2][1] = newAttrs; + parameters[2][2] = false; + //should fail, the attribute is mandatory but null value. + parameters[2][3] = false; + + attribute = new InstanceTextAttribute("name","value",true); + attribute.setLocked(true); + attribute1 = new InstanceTextAttribute("name","",true); + attribute1.setLocked(true); + current = new ArrayList<>(); + newAttrs = new ArrayList<>(); + current.add(attribute); + newAttrs.add(attribute1); + parameters[3][0] = current; + parameters[3][1] = newAttrs; + parameters[3][2] = false; + //should fail, the attribute is mandatory but empty value. + parameters[3][3] = false; + return Arrays.asList(parameters); + } + @Parameterized.Parameter + public List currentAttrs; + + @Parameterized.Parameter(value = 1) + public List pAttributes; + + @Parameterized.Parameter(value = 2) + public boolean isAttributesLocked; + + @Parameterized.Parameter(value = 3) + public boolean expect; + + @Test + public void testHasValidChange() throws Exception { + Assert.assertEquals("Wrong result from hasValidChange", expect, AttributesConsistencyUtils.hasValidChange(pAttributes, isAttributesLocked, currentAttrs)); + } + + @Test + public void testIsTemplateAttributesValid() throws Exception { + List instanceAttributeTemplates = new ArrayList<>(); + + //bad attribute: can not be "not locked" and mandatory + InstanceAttributeTemplate attr = new DefaultAttributeTemplate("test", InstanceAttributeTemplate.AttributeType.BOOLEAN); + attr.setLocked(false); + attr.setMandatory(true); + instanceAttributeTemplates.add(attr); + + attr = new DefaultAttributeTemplate("test", InstanceAttributeTemplate.AttributeType.TEXT); + attr.setLocked(true); + attr.setMandatory(true); + instanceAttributeTemplates.add(attr); + + //bad attribute: can not be "not locked" and mandatory + attr = new DefaultAttributeTemplate("test1", InstanceAttributeTemplate.AttributeType.LOV); + attr.setLocked(false); + attr.setMandatory(true); + instanceAttributeTemplates.add(attr); + + //Should failed either attributesLocked or not. + Assert.assertFalse(AttributesConsistencyUtils.isTemplateAttributesValid(instanceAttributeTemplates,false)); + Assert.assertFalse(AttributesConsistencyUtils.isTemplateAttributesValid(instanceAttributeTemplates,true)); + + instanceAttributeTemplates.clear(); + attr = new DefaultAttributeTemplate("test", InstanceAttributeTemplate.AttributeType.BOOLEAN); + attr.setLocked(false); + attr.setMandatory(false); + instanceAttributeTemplates.add(attr); + attr = new DefaultAttributeTemplate("test", InstanceAttributeTemplate.AttributeType.TEXT); + attr.setLocked(true); + attr.setMandatory(true); + instanceAttributeTemplates.add(attr); + attr = new DefaultAttributeTemplate("test1", InstanceAttributeTemplate.AttributeType.LOV); + attr.setLocked(true); + attr.setMandatory(false); + instanceAttributeTemplates.add(attr); + + //Should fail if attributesLocked, some attributes are not locked. + Assert.assertTrue(AttributesConsistencyUtils.isTemplateAttributesValid(instanceAttributeTemplates,false)); + Assert.assertFalse(AttributesConsistencyUtils.isTemplateAttributesValid(instanceAttributeTemplates,true)); + + instanceAttributeTemplates.clear(); + attr = new DefaultAttributeTemplate("test", InstanceAttributeTemplate.AttributeType.BOOLEAN); + attr.setLocked(true); + attr.setMandatory(false); + instanceAttributeTemplates.add(attr); + attr = new DefaultAttributeTemplate("test", InstanceAttributeTemplate.AttributeType.TEXT); + attr.setLocked(true); + attr.setMandatory(true); + instanceAttributeTemplates.add(attr); + attr = new DefaultAttributeTemplate("test1", InstanceAttributeTemplate.AttributeType.LOV); + attr.setLocked(true); + attr.setMandatory(false); + instanceAttributeTemplates.add(attr); + + //All attributes are locked and valid, should succeed for either attributesLocked true or false. + Assert.assertTrue(AttributesConsistencyUtils.isTemplateAttributesValid(instanceAttributeTemplates, false)); + Assert.assertTrue(AttributesConsistencyUtils.isTemplateAttributesValid(instanceAttributeTemplates,true)); + } + +} diff --git a/docdoku-server/docdoku-server-ext/pom.xml b/docdoku-server/docdoku-server-ext/pom.xml new file mode 100644 index 0000000000..ef5f0acfa6 --- /dev/null +++ b/docdoku-server/docdoku-server-ext/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-ext + jar + docdoku-server-ext Java EE 7 Extensions API + + + com.docdoku + docdoku-common + ${project.version} + jar + + + javax + javaee-api + 7.0 + provided + + + com.github.spullara.mustache.java + compiler + 0.8.11 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + ${project.artifactId} + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/InternalService.java b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/InternalService.java new file mode 100644 index 0000000000..0031549f2f --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/InternalService.java @@ -0,0 +1,35 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server; + + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Retention(RUNTIME) +@Target({TYPE, METHOD, FIELD, PARAMETER}) +public @interface InternalService { +} diff --git a/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/ServiceLocator.java b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/ServiceLocator.java new file mode 100644 index 0000000000..dc69cb2f30 --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/ServiceLocator.java @@ -0,0 +1,115 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server; + +import com.docdoku.core.services.*; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import java.util.logging.Level; +import java.util.logging.Logger; + + +@ApplicationScoped +public class ServiceLocator { + private static final String DATA_MANAGER = "java:global/docdoku-server-ear/docdoku-server-ejb/DataManagerBean!com.docdoku.core.services.IDataManagerLocal"; + private static final String PRODUCT_MANAGER = "java:global/docdoku-server-ear/docdoku-server-ejb/ProductManagerBean!com.docdoku.core.services.IProductManagerLocal"; + private static final String PRODUCT_INSTANCE_MANAGER = "java:global/docdoku-server-ear/docdoku-server-ejb/ProductInstanceManagerBean!com.docdoku.core.services.IProductInstanceManagerLocal"; + private static final String USER_MANAGER = "java:global/docdoku-server-ear/docdoku-server-ejb/UserManagerBean!com.docdoku.core.services.IUserManagerLocal"; + private static final String SHARE_MANAGER = "java:global/docdoku-server-ear/docdoku-server-ejb/ShareManagerBean!com.docdoku.core.services.IShareManagerLocal"; + private static final String PART_WORKFLOW_MANAGER = "java:global/docdoku-server-ear/docdoku-server-ejb/PartWorkflowManagerBean!com.docdoku.core.services.IPartWorkflowManagerLocal"; + private static final String DOCUMENT_WORKFLOW_MANAGER = "java:global/docdoku-server-ear/docdoku-server-ejb/DocumentWorkflowManagerBean!com.docdoku.core.services.IDocumentWorkflowManagerLocal"; + private static final String CASCADE_ACTION_MANAGER = "java:global/docdoku-server-ear/docdoku-server-ejb/CascadeActionManagerBean!com.docdoku.core.services.ICascadeActionManagerLocal"; + private static final String LOV_MANAGER = "java:global/docdoku-server-ear/docdoku-server-ejb/LOVManagerBean!com.docdoku.core.services.ILOVManagerLocal"; + + private static final Logger LOGGER = Logger.getLogger(ServiceLocator.class.getName()); + + private Context context; + + @PostConstruct + private void init(){ + try{ + context = new InitialContext(); + } + catch (NamingException e) { + LOGGER.log(Level.SEVERE,e.getMessage(),e); + } + } + + @InternalService + @Produces + public IProductManagerLocal findProductManager() throws NamingException { + return (IProductManagerLocal) context.lookup(PRODUCT_MANAGER); + } + + @InternalService + @Produces + public IDataManagerLocal findDataManager() throws NamingException { + return (IDataManagerLocal) context.lookup(DATA_MANAGER); + } + + @InternalService + @Produces + public IProductInstanceManagerLocal findProductInstanceManager() throws NamingException { + return (IProductInstanceManagerLocal) context.lookup(PRODUCT_INSTANCE_MANAGER); + } + + @InternalService + @Produces + public IUserManagerLocal findUserManager() throws NamingException { + return (IUserManagerLocal) context.lookup(USER_MANAGER); + } + + @InternalService + @Produces + public IShareManagerLocal findShareManager() throws NamingException { + return (IShareManagerLocal) context.lookup(SHARE_MANAGER); + } + + @InternalService + @Produces + public IPartWorkflowManagerLocal findPartWorkflowManager() throws NamingException { + return (IPartWorkflowManagerLocal) context.lookup(PART_WORKFLOW_MANAGER); + } + + @InternalService + @Produces + public IDocumentWorkflowManagerLocal findDocumentWorkflowManager() throws NamingException { + return (IDocumentWorkflowManagerLocal) context.lookup(DOCUMENT_WORKFLOW_MANAGER); + } + + @InternalService + @Produces + public ICascadeActionManagerLocal findCascadeActionManager() throws NamingException { + return (ICascadeActionManagerLocal) context.lookup(CASCADE_ACTION_MANAGER); + } + + @InternalService + @Produces + public ILOVManagerLocal findLOVManager() throws NamingException { + return (ILOVManagerLocal) context.lookup(LOV_MANAGER); + } + +} diff --git a/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/converters/CADConverter.java b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/converters/CADConverter.java new file mode 100644 index 0000000000..d7ef6c83e1 --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/converters/CADConverter.java @@ -0,0 +1,32 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.product.PartIteration; +import com.docdoku.server.converters.utils.ConversionResult; + +import java.io.File; + +public interface CADConverter { + ConversionResult convert(PartIteration partToConvert, BinaryResource cadFile, File tempDir) throws Exception; + boolean canConvertToOBJ(String cadFileExtension); +} diff --git a/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/converters/utils/ConversionResult.java b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/converters/utils/ConversionResult.java new file mode 100644 index 0000000000..6595980d28 --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/converters/utils/ConversionResult.java @@ -0,0 +1,82 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.utils; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class ConversionResult { + + private File convertedFile; + private List materials = new ArrayList<>(); + private String stdOutput; + private String errorOutput; + + + public ConversionResult(File convertedFile) { + this.convertedFile = convertedFile; + } + + public ConversionResult(File convertedFile, List materials) { + this.convertedFile = convertedFile; + this.materials = materials; + } + + public ConversionResult(File convertedFile, List materials, String stdOutput, String errorOutput) { + this.convertedFile = convertedFile; + this.materials = materials; + this.stdOutput = stdOutput; + this.errorOutput = errorOutput; + } + + public File getConvertedFile() { + return convertedFile; + } + + public void setConvertedFile(File convertedFile) { + this.convertedFile = convertedFile; + } + + public List getMaterials() { + return materials; + } + + public void setMaterials(List materials) { + this.materials = materials; + } + + public String getStdOutput() { + return stdOutput; + } + + public void setStdOutput(String stdOutput) { + this.stdOutput = stdOutput; + } + + public String getErrorOutput() { + return errorOutput; + } + + public void setErrorOutput(String errorOutput) { + this.errorOutput = errorOutput; + } +} diff --git a/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/converters/utils/ConverterUtils.java b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/converters/utils/ConverterUtils.java new file mode 100644 index 0000000000..f3a9c9c0a8 --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/converters/utils/ConverterUtils.java @@ -0,0 +1,42 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.utils; + + +import java.io.*; + +public class ConverterUtils { + + private ConverterUtils() { + } + + public static String getOutput(InputStream is) throws IOException { + StringBuilder output = new StringBuilder(); + String line; + try(InputStreamReader isr = new InputStreamReader(is,"UTF-8");BufferedReader br = new BufferedReader(isr)) { + while ((line = br.readLine()) != null) { + output.append(line).append("\n"); + } + } + return output.toString(); + } + +} diff --git a/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/converters/utils/GeometryParser.java b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/converters/utils/GeometryParser.java new file mode 100644 index 0000000000..43bbaec55c --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/converters/utils/GeometryParser.java @@ -0,0 +1,107 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.converters.utils; + +import javax.json.Json; +import javax.json.JsonException; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import java.io.*; +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class GeometryParser { + + private static final String CONF_PROPERTIES="/com/docdoku/server/converters/utils/conf.properties"; + private static final Properties CONF = new Properties(); + private static final Logger LOGGER = Logger.getLogger(GeometryParser.class.getName()); + + private GeometryParser() { + } + + public static double[] calculateBox(File file) { + + String result = ""; + String nodeServerUrl = ""; + + try (InputStream inputStream = GeometryParser.class.getResourceAsStream(CONF_PROPERTIES)){ + + CONF.load(inputStream); + + nodeServerUrl = CONF.getProperty("nodeServerUrl"); + + URL url = new URL(nodeServerUrl+"/box"); + + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Content-Type", "application/json; charset=utf-8"); + + JsonObjectBuilder body = Json.createObjectBuilder().add("filename",file.getAbsolutePath()); + con.setDoOutput(true); + OutputStreamWriter wr = new OutputStreamWriter(con.getOutputStream(), "UTF-8"); + wr.write(body.build().toString()); + wr.flush(); + wr.close(); + + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(),"UTF-8")); + String inputLine; + StringBuilder response = new StringBuilder(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + result = response.toString(); + + JsonObject box = Json.createReader(new StringReader(result)).readObject(); + + JsonObject min = box.getJsonObject("min"); + JsonObject max = box.getJsonObject("max"); + + + return new double[]{ + min.getJsonNumber("x").doubleValue(), + min.getJsonNumber("y").doubleValue(), + min.getJsonNumber("z").doubleValue(), + max.getJsonNumber("x").doubleValue(), + max.getJsonNumber("y").doubleValue(), + max.getJsonNumber("z").doubleValue() + }; + + } + catch (ConnectException e){ + LOGGER.log(Level.WARNING, "Communication with server failed. Is it listening on '"+nodeServerUrl+"' ?"); + } + catch (IOException e){ + LOGGER.log(Level.SEVERE, null, e); + } + catch(JsonException e) { + LOGGER.log(Level.SEVERE, "Cannot parse program output : \n "+ result , e); + } + + return new double[6]; + } +} diff --git a/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/importers/PartImporter.java b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/importers/PartImporter.java new file mode 100644 index 0000000000..1aac5b4656 --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/importers/PartImporter.java @@ -0,0 +1,35 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.importers; + +import com.docdoku.core.product.ImportResult; +import com.docdoku.core.product.PartRevision; + +import java.io.File; +import java.util.List; + +public interface PartImporter { + + ImportResult importFile(String workspaceId, File file, String revisionNote, boolean autoCheckout, boolean autoCheckin, boolean permissiveUpdate); + boolean canImportFile(String importFileName); + List dryRunImport(String workspaceId, File file, String originalFileName, boolean autoCheckout, boolean autoCheckin, boolean permissiveUpdate); + +} diff --git a/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/importers/PathDataImporter.java b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/importers/PathDataImporter.java new file mode 100644 index 0000000000..f15bfe6a89 --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/importers/PathDataImporter.java @@ -0,0 +1,31 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.importers; + +import com.docdoku.core.product.ImportResult; + +import java.io.File; + +public interface PathDataImporter { + + ImportResult importFile(String workspaceId, File file, String revisionNote, boolean autoFreezeAfterUpdate, boolean permissiveUpdate); + boolean canImportFile(String importFileName); +} diff --git a/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/postuploaders/DocumentPostUploader.java b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/postuploaders/DocumentPostUploader.java new file mode 100644 index 0000000000..2d95fe1601 --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/postuploaders/DocumentPostUploader.java @@ -0,0 +1,28 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.postuploaders; + +import com.docdoku.core.common.BinaryResource; + +public interface DocumentPostUploader { + boolean canProcess(BinaryResource binaryResource); + void process(BinaryResource binaryResource); +} diff --git a/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/resourcegetters/DocumentResourceGetter.java b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/resourcegetters/DocumentResourceGetter.java new file mode 100644 index 0000000000..02197e919d --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/resourcegetters/DocumentResourceGetter.java @@ -0,0 +1,37 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.resourcegetters; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.exceptions.ConvertedResourceException; +import com.docdoku.core.product.PartIteration; + +import java.io.InputStream; +import java.util.Locale; + +public interface DocumentResourceGetter { + boolean canGetConvertedResource(String outputFormat, BinaryResource binaryResource); + InputStream getConvertedResource(String outputFormat, BinaryResource binaryResource, DocumentIteration docI, Locale locale) throws ConvertedResourceException; + InputStream getConvertedResource(String outputFormat, BinaryResource binaryResource, PartIteration partIteration, Locale locale) throws ConvertedResourceException; + boolean canGetSubResourceVirtualPath(BinaryResource binaryResource); + String getSubResourceVirtualPath(BinaryResource binaryResource, String subResourceUri); +} diff --git a/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/viewers/DocumentViewer.java b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/viewers/DocumentViewer.java new file mode 100644 index 0000000000..1a2374cf63 --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/viewers/DocumentViewer.java @@ -0,0 +1,28 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.viewers; + +import com.docdoku.core.common.BinaryResource; + +public interface DocumentViewer { + boolean canRenderViewerTemplate(BinaryResource binaryResource); + String renderHtmlForViewer(BinaryResource binaryResource, String uuid) throws Exception; +} diff --git a/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/viewers/ViewerUtils.java b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/viewers/ViewerUtils.java new file mode 100644 index 0000000000..f1a83d7804 --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/java/com/docdoku/server/viewers/ViewerUtils.java @@ -0,0 +1,75 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.viewers; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.services.IDataManagerLocal; +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class ViewerUtils { + + private ViewerUtils(){ + + } + + public static String getURI(BinaryResource binaryResource, String uuid) { + if(uuid == null){ + return "/api/files/" + binaryResource.getFullName(); + }else{ + return "/api/files/" + binaryResource.getFullName() + "/uuid/" + uuid; + } + } + + public static String getViewerTemplate(IDataManagerLocal dataManager, BinaryResource binaryResource, String uuid, String viewer, boolean pdf) throws IOException { + MustacheFactory mf = new DefaultMustacheFactory(); + Mustache mustache = mf.compile("com/docdoku/server/viewers/viewer_template.mustache"); + Map scopes = new HashMap<>(); + scopes.put("uriResource", ViewerUtils.getURI(binaryResource, uuid)); + if(pdf) { + scopes.put("pdfUri", ViewerUtils.getURI(binaryResource, uuid)+"?output=pdf"); + } + + String externalURL = dataManager.getExternalStorageURI(binaryResource); + scopes.put("externalUriResource", externalURL); + + String shortenExternalURL = dataManager.getShortenExternalStorageURI(binaryResource); + scopes.put("shortenExternalUriResource", shortenExternalURL); + + scopes.put("fileName", binaryResource.getName()); + scopes.put("thisId", UUID.randomUUID().toString()); + + scopes.put("viewer", viewer); + + StringWriter templateWriter = new StringWriter(); + mustache.execute(templateWriter, scopes).flush(); + + return templateWriter.toString(); + } + +} diff --git a/docdoku-server/docdoku-server-ext/src/main/resources/META-INF/beans.xml b/docdoku-server/docdoku-server-ext/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..23bd68ff81 --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/resources/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ext/src/main/resources/com/docdoku/server/converters/utils/conf.properties b/docdoku-server/docdoku-server-ext/src/main/resources/com/docdoku/server/converters/utils/conf.properties new file mode 100644 index 0000000000..eac8d2a6aa --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/resources/com/docdoku/server/converters/utils/conf.properties @@ -0,0 +1,2 @@ +nodeServerUrl=http://localhost:8888 +decimater=/opt/decimater/openMeshDecimater.sh \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ext/src/main/resources/com/docdoku/server/viewers/default_viewer.mustache b/docdoku-server/docdoku-server-ext/src/main/resources/com/docdoku/server/viewers/default_viewer.mustache new file mode 100644 index 0000000000..6b04f475c2 --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/resources/com/docdoku/server/viewers/default_viewer.mustache @@ -0,0 +1 @@ +{{fileName}} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-ext/src/main/resources/com/docdoku/server/viewers/viewer_template.mustache b/docdoku-server/docdoku-server-ext/src/main/resources/com/docdoku/server/viewers/viewer_template.mustache new file mode 100644 index 0000000000..302458ad42 --- /dev/null +++ b/docdoku-server/docdoku-server-ext/src/main/resources/com/docdoku/server/viewers/viewer_template.mustache @@ -0,0 +1,17 @@ +
+ + + {{#externalUriResource}} + + {{/externalUriResource}} + {{fileName}} +
+
+
+ {{#externalUriResource}} + +

+ {{/externalUriResource}} + {{{viewer}}} +
+
\ No newline at end of file diff --git a/docdoku-server/docdoku-server-office-doc/pom.xml b/docdoku-server/docdoku-server-office-doc/pom.xml new file mode 100644 index 0000000000..ba7457d6a1 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-office-doc + jar + docdoku-server-office-doc PDF Converter + + + com.docdoku + docdoku-server-ext + ${project.version} + jar + + + javax + javaee-api + 7.0 + provided + + + com.artofsolving + jodconverter + 3.0-beta-5 + + + com.google.guava + guava + 16.0 + + + com.github.spullara.mustache.java + compiler + 0.8.11 + + + com.itextpdf + itextpdf + 5.5.2 + jar + + + com.itextpdf.tool + xmlworker + 5.5.2 + jar + + + org.bouncycastle + bcprov-jdk15on + 1.49 + + + + + repo + file://${project.basedir}/repo + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + ${project.artifactId} + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.jar b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.jar new file mode 100644 index 0000000000..ac6efcc142 Binary files /dev/null and b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.jar differ diff --git a/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.jar.md5 b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.jar.md5 new file mode 100644 index 0000000000..831ab84f80 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.jar.md5 @@ -0,0 +1 @@ +998e49120c701817c156b9d0be0cc1d8 \ No newline at end of file diff --git a/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.jar.sha1 b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.jar.sha1 new file mode 100644 index 0000000000..5bc997a2c9 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.jar.sha1 @@ -0,0 +1 @@ +9df4272e764a9bc0ad8868decd73537e0f7f6dd4 \ No newline at end of file diff --git a/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.pom b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.pom new file mode 100644 index 0000000000..1bc7500cd8 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.pom @@ -0,0 +1,33 @@ + + + 4.0.0 + com.artofsolving + jodconverter + 3.0-beta-5 + POM was created from install:install-file + + + + commons-io + commons-io + 1.4 + + + org.openoffice + juh + 3.2.1 + + + org.openoffice + ridl + 3.2.1 + + + org.openoffice + unoil + 3.2.1 + + + + diff --git a/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.pom.md5 b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.pom.md5 new file mode 100644 index 0000000000..f7a877f237 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.pom.md5 @@ -0,0 +1 @@ +d473967775d75659721f811ea9327d19 \ No newline at end of file diff --git a/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.pom.sha1 b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.pom.sha1 new file mode 100644 index 0000000000..a2e4fec6bd --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/3.0-beta-5/jodconverter-3.0-beta-5.pom.sha1 @@ -0,0 +1 @@ +89944211e5f24847dc08c84e5b0f57f7463e9006 \ No newline at end of file diff --git a/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/maven-metadata-local.xml b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/maven-metadata-local.xml new file mode 100644 index 0000000000..0cbd30d4e0 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/maven-metadata-local.xml @@ -0,0 +1,14 @@ + + + com.artofsolving + jodconverter + + 3.0-beta-5 + + 3.0-beta-2 + 3.0-beta-4 + 3.0-beta-5 + + 20130617104850 + + diff --git a/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/maven-metadata-local.xml.md5 b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/maven-metadata-local.xml.md5 new file mode 100644 index 0000000000..a485712029 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/maven-metadata-local.xml.md5 @@ -0,0 +1 @@ +06978572fa6cb6f174fe1beec18a42d2 \ No newline at end of file diff --git a/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/maven-metadata-local.xml.sha1 b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/maven-metadata-local.xml.sha1 new file mode 100644 index 0000000000..f13036e3a2 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/repo/com/artofsolving/jodconverter/maven-metadata-local.xml.sha1 @@ -0,0 +1 @@ +351ad8026c1c211989d79464be4da8540badf52a \ No newline at end of file diff --git a/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/extras/DocumentTitleBlockGenerator.java b/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/extras/DocumentTitleBlockGenerator.java new file mode 100644 index 0000000000..de88fe4994 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/extras/DocumentTitleBlockGenerator.java @@ -0,0 +1,58 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.extras; + +import com.docdoku.core.document.DocumentIteration; + +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * @author kelto on 05/01/16. + * + * This class should be used to override the default Pdf generation for document. + * @see com.docdoku.server.extras.TitleBlockGenerator + */ +class DocumentTitleBlockGenerator extends TitleBlockGenerator { + + DocumentTitleBlockGenerator(InputStream inputStream,DocumentIteration documentIteration, Locale locale) { + pLocale = locale; + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + SimpleDateFormat dateFormat = new SimpleDateFormat(bundle.getString("date.format")); + authorName = documentIteration.getAuthor().getName(); + version = documentIteration.getVersion(); + creationDate = dateFormat.format(documentIteration.getDocumentRevision().getCreationDate()); + iterationDate = dateFormat.format(documentIteration.getCreationDate()); + keywords = documentIteration.getDocumentRevision().getTags().toString(); + description = documentIteration.getDocumentRevision().getDescription(); + instanceAttributes = documentIteration.getInstanceAttributes(); + currentIteration = String.valueOf(documentIteration.getIteration()); + workflow = documentIteration.getDocumentRevision().getWorkflow(); + revisionNote = documentIteration.getRevisionNote(); + lifeCycleState = documentIteration.getDocumentRevision().getLifeCycleState(); + + // No hydratation ? not really necessary to create more field ... + title = documentIteration.getId() + "-" + version; + subject = documentIteration.getTitle(); + } + +} diff --git a/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/extras/PartTitleBlockGenerator.java b/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/extras/PartTitleBlockGenerator.java new file mode 100644 index 0000000000..5ac446183e --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/extras/PartTitleBlockGenerator.java @@ -0,0 +1,58 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.extras; + +import com.docdoku.core.product.PartIteration; + +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * @author kelto on 05/01/16. + * + * This class should be used to override the default Pdf generation for parts. + * @see com.docdoku.server.extras.TitleBlockGenerator + */ +public class PartTitleBlockGenerator extends TitleBlockGenerator{ + + PartTitleBlockGenerator(InputStream inputStream,PartIteration partIteration, Locale locale) { + pLocale = locale; + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + SimpleDateFormat dateFormat = new SimpleDateFormat(bundle.getString("date.format")); + authorName = partIteration.getAuthor().getName(); + version = partIteration.getVersion(); + creationDate = dateFormat.format(partIteration.getPartRevision().getCreationDate()); + iterationDate = dateFormat.format(partIteration.getCreationDate()); + keywords = partIteration.getPartRevision().getTags().toString(); + description = partIteration.getPartRevision().getDescription(); + instanceAttributes = partIteration.getInstanceAttributes(); + currentIteration = String.valueOf(partIteration.getIteration()); + workflow = partIteration.getPartRevision().getWorkflow(); + revisionNote = partIteration.getIterationNote(); + lifeCycleState = partIteration.getPartRevision().getLifeCycleState(); + + // No hydratation ? not really necessary to create more field ... + title = partIteration.getNumber() + "-" + version; + subject = partIteration.getName(); + } +} diff --git a/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/extras/TitleBlockGenerator.java b/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/extras/TitleBlockGenerator.java new file mode 100644 index 0000000000..1ec102b6f2 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/extras/TitleBlockGenerator.java @@ -0,0 +1,400 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.extras; + +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.workflow.Activity; +import com.docdoku.core.workflow.Task; +import com.docdoku.core.workflow.Workflow; +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.*; +import com.itextpdf.text.pdf.draw.LineSeparator; + +import javax.xml.bind.DatatypeConverter; +import java.io.*; +import java.nio.file.Files; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * @author Morgan Guimard + * + * This class define the default pdf generation for both part and document. + * This behaviour can be overridden: + * @see com.docdoku.server.extras.PartTitleBlockGenerator + * @see com.docdoku.server.extras.DocumentTitleBlockGenerator + */ +public abstract class TitleBlockGenerator { + + private static Font NORMAL_12; + private static Font BOLD_12; + private static Font BOLD_18; + + protected static final float TABLE_PERCENT_WIDTH = 100f; + protected static final float SIGNATURE_SIZE_W = 80f; + protected static final float SIGNATURE_SIZE_H = 60f; + + protected static final String TEMP_FILE_NAME = "output.pdf"; + + protected static final String BASE_NAME = "com.docdoku.server.viewers.localization.TitleBlockGenerator"; + + protected static final Logger LOGGER = Logger.getLogger(TitleBlockGenerator.class.getName()); + static{ + try { + BaseFont normal = BaseFont.createFont("com/docdoku/server/viewers/fonts/DejaVuSans.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED); + BaseFont bold = BaseFont.createFont("com/docdoku/server/viewers/fonts/DejaVuSans-Bold.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED); + NORMAL_12 = new Font(normal, 12, Font.NORMAL); + BOLD_12 = new Font(bold, 12, Font.BOLD); + BOLD_18 = new Font(bold, 18, Font.BOLD); + } catch (DocumentException | IOException e) { + LOGGER.log(Level.SEVERE,null,e); + } + } + + protected String title ; + protected String subject; + protected String authorName; + protected String version; + + protected String creationDate; + protected String iterationDate; + protected String keywords; + protected String description; + protected java.util.List instanceAttributes; + protected String currentIteration; + protected Workflow workflow; + protected Locale pLocale; + protected String revisionNote; + protected String lifeCycleState; + + protected void generateHeader(Paragraph preface, ResourceBundle bundle) { + // Title + description + preface.add(new Paragraph(title, BOLD_18)); + addEmptyLine(preface, 1); + + if(!description.isEmpty()){ + preface.add(new Paragraph(description, NORMAL_12)); + } + + // Separator + LineSeparator separator = new LineSeparator(); + preface.add(separator); + addEmptyLine(preface, 1); + + } + + protected void generateTable(Paragraph preface, ResourceBundle bundle) { + // Author + date + PdfPTable table = new PdfPTable(2); + table.setWidthPercentage(TABLE_PERCENT_WIDTH); + + PdfPCell cell; + + cell = new PdfPCell(new Phrase(authorName,NORMAL_12)); + cell.setBorder(Rectangle.NO_BORDER); + cell.setHorizontalAlignment(Element.ALIGN_LEFT); + table.addCell(cell); + + cell = new PdfPCell(new Phrase(creationDate,NORMAL_12)); + cell.setBorder(Rectangle.NO_BORDER); + cell.setHorizontalAlignment(Element.ALIGN_RIGHT); + table.addCell(cell); + + preface.add(table); + + addEmptyLine(preface, 1); + + preface.add(new Paragraph(bundle.getString("iteration"), BOLD_12)); + addEmptyLine(preface,1); + + PdfPTable iterationTable = new PdfPTable(5); + iterationTable.setWidthPercentage(TABLE_PERCENT_WIDTH); + + // Table header + cell = new PdfPCell(new Phrase(bundle.getString("iteration.version"),BOLD_12)); + cell.setBackgroundColor(BaseColor.LIGHT_GRAY); + iterationTable.addCell(cell); + cell = new PdfPCell(new Phrase(bundle.getString("iteration.iteration"),BOLD_12)); + cell.setBackgroundColor(BaseColor.LIGHT_GRAY); + iterationTable.addCell(cell); + cell = new PdfPCell(new Phrase(bundle.getString("iteration.date"),BOLD_12)); + cell.setBackgroundColor(BaseColor.LIGHT_GRAY); + iterationTable.addCell(cell); + cell = new PdfPCell(new Phrase(bundle.getString("iteration.author"),BOLD_12)); + cell.setBackgroundColor(BaseColor.LIGHT_GRAY); + iterationTable.addCell(cell); + cell = new PdfPCell(new Phrase(bundle.getString("iteration.Notes"),BOLD_12)); + cell.setBackgroundColor(BaseColor.LIGHT_GRAY); + iterationTable.addCell(cell); + + // Table body + cell = new PdfPCell(new Phrase(version,NORMAL_12)); + iterationTable.addCell(cell); + cell = new PdfPCell(new Phrase(currentIteration,NORMAL_12)); + iterationTable.addCell(cell); + cell = new PdfPCell(new Phrase(iterationDate,NORMAL_12)); + iterationTable.addCell(cell); + cell = new PdfPCell(new Phrase(authorName,NORMAL_12)); + iterationTable.addCell(cell); + cell = new PdfPCell(new Phrase(revisionNote,NORMAL_12)); + iterationTable.addCell(cell); + + preface.add(iterationTable); + } + + public void generateAttribute(Paragraph preface, ResourceBundle bundle) { + + + // Table title + preface.add(new Paragraph(bundle.getString("attributes"), BOLD_12)); + addEmptyLine(preface,1); + + PdfPTable attributesTable = new PdfPTable(2); + attributesTable.setWidthPercentage(100f); + + // Table head + PdfPCell cell = new PdfPCell(new Phrase(bundle.getString("attributes.name"),BOLD_12)); + cell.setBackgroundColor(BaseColor.LIGHT_GRAY); + attributesTable.addCell(cell); + cell = new PdfPCell(new Phrase(bundle.getString("attributes.value"),BOLD_12)); + cell.setBackgroundColor(BaseColor.LIGHT_GRAY); + attributesTable.addCell(cell); + + // Table body + + for (InstanceAttribute attr : instanceAttributes) { + cell = new PdfPCell(new Phrase(attr.getName(), NORMAL_12)); + attributesTable.addCell(cell); + Object value = attr.getValue(); + cell = new PdfPCell(new Phrase(value != null ? String.valueOf(value) : "", NORMAL_12)); + attributesTable.addCell(cell); + } + + preface.add(attributesTable); + + } + + private void generateLyfeCycleState(Paragraph preface, ResourceBundle bundle) { + // Table title + preface.add(new Paragraph(bundle.getString("lifecycle") + " : " + lifeCycleState, BOLD_12)); + addEmptyLine(preface,1); + + PdfPTable lifeCycleTable = new PdfPTable(5); + lifeCycleTable.setWidthPercentage(100f); + PdfPCell cell; + for(Activity activity : workflow.getActivities()){ + + boolean headerRendered = false ; + + for(Task task : activity.getTasks()){ + + if (task.isApproved() || task.isRejected()) { + + if(!headerRendered){ + // Table head + cell = new PdfPCell(new Phrase(activity.getLifeCycleState(),BOLD_12)); + cell.setBackgroundColor(BaseColor.LIGHT_GRAY); + cell.setColspan(5); + lifeCycleTable.addCell(cell); + + cell = new PdfPCell(new Phrase(bundle.getString("lifecycle.task"),BOLD_12)); + cell.setBackgroundColor(BaseColor.LIGHT_GRAY); + lifeCycleTable.addCell(cell); + + cell = new PdfPCell(new Phrase(bundle.getString("lifecycle.date"),BOLD_12)); + cell.setBackgroundColor(BaseColor.LIGHT_GRAY); + lifeCycleTable.addCell(cell); + + cell = new PdfPCell(new Phrase(bundle.getString("lifecycle.author"),BOLD_12)); + cell.setBackgroundColor(BaseColor.LIGHT_GRAY); + lifeCycleTable.addCell(cell); + + cell = new PdfPCell(new Phrase(bundle.getString("lifecycle.comments"),BOLD_12)); + cell.setBackgroundColor(BaseColor.LIGHT_GRAY); + lifeCycleTable.addCell(cell); + + cell = new PdfPCell(new Phrase(bundle.getString("lifecycle.signature"),BOLD_12)); + cell.setBackgroundColor(BaseColor.LIGHT_GRAY); + lifeCycleTable.addCell(cell); + + headerRendered = true; + } + + // Table body + cell = new PdfPCell(new Phrase(task.getTitle(),NORMAL_12)); + lifeCycleTable.addCell(cell); + + SimpleDateFormat simpleFormat = new SimpleDateFormat(bundle.getString("date.format")); + cell = new PdfPCell(new Phrase(simpleFormat.format(task.getClosureDate()), NORMAL_12)); + lifeCycleTable.addCell(cell); + + String workerName = task.getWorker()!=null?task.getWorker().getName():""; + cell = new PdfPCell(new Phrase(workerName, NORMAL_12)); + lifeCycleTable.addCell(cell); + + cell = new PdfPCell(new Phrase(task.getClosureComment(), NORMAL_12)); + lifeCycleTable.addCell(cell); + + if (task.getSignature() != null) { + try { + byte[] imageByte; + int indexOfFirstComma = task.getSignature().indexOf(","); + String base64 = task.getSignature().substring(indexOfFirstComma+1,task.getSignature().length()); + imageByte = DatatypeConverter.parseBase64Binary(base64); + Image image = Image.getInstance(imageByte); + image.setCompressionLevel(Image.ORIGINAL_NONE); + image.scaleToFit(SIGNATURE_SIZE_W,SIGNATURE_SIZE_H); + cell = new PdfPCell(image); + lifeCycleTable.addCell(cell); + } catch (Exception e) { + cell = new PdfPCell(new Phrase(bundle.getString("signature.error"), NORMAL_12)); + lifeCycleTable.addCell(cell); + } + + } else { + cell = new PdfPCell(new Phrase("")); + lifeCycleTable.addCell(cell); + } + + } + } + + } + + preface.add(lifeCycleTable); + } + + /* +* Generate a block title pdf page and add it to the pdf given in the input stream +* */ + public InputStream generateBlockTitleToPDF(InputStream inputStream) throws IOException, DocumentException { + + File tmpDir = Files.createTempDirectory("docdoku-").toFile(); + File blockTitleFile = new File(tmpDir, inputStream.toString()); + + ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, pLocale); + + Document document = new Document(); + PdfWriter.getInstance(document, new FileOutputStream(blockTitleFile)); + document.open(); + + // Main paragraph + Paragraph preface = new Paragraph(); + generateHeader(preface,bundle); + generateTable(preface,bundle); + addEmptyLine(preface,1); + if(!instanceAttributes.isEmpty()){ + generateAttribute(preface,bundle); + addEmptyLine(preface,1); + } + if(workflow != null){ + generateLyfeCycleState(preface,bundle); + } + + document.add(preface); + + addMetaData(document); + + document.close(); + + tmpDir.deleteOnExit(); + + // Merge the pdf generated with the pdf given in the input stream + //TODO: use PdfStamper to insert into the existing pdf. + return mergePdfDocuments(new FileInputStream(blockTitleFile),inputStream); + + } + + /* + * Merge two pdf from input streams + * */ + public static InputStream mergePdfDocuments(InputStream input1, InputStream input2){ + + try { + File tmpDir = Files.createTempDirectory("docdoku-").toFile(); + File tmpCopyFile = new File(tmpDir, TEMP_FILE_NAME); + InputStream[] files = { input1, input2 }; + + Document doc = new Document(); + PdfCopy copy = new PdfCopy(doc, new FileOutputStream(tmpCopyFile)); + doc.open(); + PdfReader pdfReader; + + int n; + // TODO check for resources to be closed + for (InputStream file : files) { + pdfReader = new PdfReader(file); + n = pdfReader.getNumberOfPages(); + for (int page = 0; page < n; ) { + copy.addPage(copy.getImportedPage(pdfReader, ++page)); + } + } + + doc.close(); + + tmpDir.deleteOnExit(); + + return new FileInputStream(tmpCopyFile); + } + catch (Exception e){ + LOGGER.log(Level.INFO, null, e); + } + + return null; + } + + /* + * Generate a block title pdf page and add it to the pdf given in the input stream + * */ + public static InputStream addBlockTitleToPDF(InputStream inputStream, DocumentIteration docI, Locale pLocale) throws IOException, DocumentException { + DocumentTitleBlockGenerator documentTitleBlockGenerator = new DocumentTitleBlockGenerator(inputStream,docI,pLocale); + return documentTitleBlockGenerator.generateBlockTitleToPDF(inputStream); + } + + public static InputStream addBlockTitleToPDF(InputStream inputStream, PartIteration partIteration, Locale pLocale) throws IOException, DocumentException { + PartTitleBlockGenerator partTitleBlockGenerator = new PartTitleBlockGenerator(inputStream, partIteration, pLocale); + return partTitleBlockGenerator.generateBlockTitleToPDF(inputStream); + } + + + protected static void addEmptyLine(Paragraph paragraph, int number) { + for (int i = 0; i < number; i++) { + paragraph.add(new Paragraph(" ")); + } + } + + protected void addMetaData(Document document) { + document.addTitle(title); + document.addSubject(subject); + document.addKeywords(keywords); + document.addAuthor(authorName); + document.addCreator(authorName); + } + + +} diff --git a/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/resourcegetters/FileConverter.java b/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/resourcegetters/FileConverter.java new file mode 100644 index 0000000000..b4b403ad32 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/resourcegetters/FileConverter.java @@ -0,0 +1,96 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.resourcegetters; + +import org.artofsolving.jodconverter.OfficeDocumentConverter; +import org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration; +import org.artofsolving.jodconverter.office.OfficeManager; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Singleton; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +@Singleton +public class FileConverter { + + private static final String PROPERTIES_FILE = "/com/docdoku/server/viewers/conf.properties"; + private static final String OO_HOME_KEY = "com.docdoku.server.viewers.ooHome"; + private static final String OO_PORT_KEY = "com.docdoku.server.viewers.ooPort"; + private static final Logger LOGGER = Logger.getLogger(FileConverter.class.getName()); + + private OfficeManager officeManager; + + @PostConstruct + private void init() { + + try (InputStream inputStream = FileConverter.class.getResourceAsStream(PROPERTIES_FILE)){ + + Properties properties = new Properties(); + + properties.load(inputStream); + String ooHome = properties.getProperty(OO_HOME_KEY); + int ooPort = Integer.parseInt(properties.getProperty(OO_PORT_KEY)); + officeManager = new DefaultOfficeManagerConfiguration() + .setOfficeHome(new File(ooHome)) + .setPortNumber(ooPort) + .buildOfficeManager(); + officeManager.start(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, null, e); + throw new RuntimeException(e); + } + } + + @PreDestroy + private void close(){ + officeManager.stop(); + } + + public synchronized InputStream convertToPDF(String sourceName, final InputStream streamToConvert) throws IOException { + File tmpDir = Files.createTempDirectory("docdoku-").toFile(); + File fileToConvert = new File(tmpDir, sourceName); + + Files.copy(streamToConvert, fileToConvert.toPath()); + + File pdfFile = convertToPDF(fileToConvert); + + //clean-up + tmpDir.deleteOnExit(); + + return new FileInputStream(pdfFile); + } + + private File convertToPDF(File fileToConvert) { + File pdfFile = new File(fileToConvert.getParentFile(), "converted.pdf"); + OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager); + converter.convert(fileToConvert, pdfFile); + return pdfFile; + } + +} diff --git a/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/resourcegetters/OfficeDocumentResourceGetter.java b/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/resourcegetters/OfficeDocumentResourceGetter.java new file mode 100644 index 0000000000..5dd692650c --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/resourcegetters/OfficeDocumentResourceGetter.java @@ -0,0 +1,141 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.resourcegetters; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.exceptions.ConvertedResourceException; +import com.docdoku.core.exceptions.StorageException; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.util.FileIO; +import com.docdoku.core.util.Tools; +import com.docdoku.server.InternalService; +import com.docdoku.server.extras.TitleBlockGenerator; +import com.google.common.io.ByteStreams; +import com.itextpdf.text.DocumentException; + +import javax.inject.Inject; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Locale; +import java.util.logging.Logger; + +public class OfficeDocumentResourceGetter implements DocumentResourceGetter { + + private static final Logger LOGGER = Logger.getLogger(DocumentResourceGetter.class.getName()); + + @Inject + private FileConverter fileConverter; + + @InternalService + @Inject + private IDataManagerLocal dataManager; + + + @Override + public boolean canGetConvertedResource(String outputFormat, BinaryResource binaryResource) { + return FileIO.isDocFile(binaryResource.getName()) && outputSupported(outputFormat); + } + + private boolean outputSupported(String outputFormat) { + return outputFormat != null && "pdf".equals(outputFormat); + } + + @Override + public InputStream getConvertedResource(String outputFormat, BinaryResource binaryResource, DocumentIteration docI, Locale locale) throws ConvertedResourceException { + try { + // TODO check for resources to be closed + InputStream inputStream=null; + + if ("pdf".equals(outputFormat)) { + inputStream = getPdfConvertedResource(binaryResource); + } + + if ("documents".equals(binaryResource.getHolderType()) && docI != null){ + return TitleBlockGenerator.addBlockTitleToPDF(inputStream, docI, locale); + } + + return inputStream; + } catch (StorageException | DocumentException | IOException e) { + throw new ConvertedResourceException(locale,e); + } + } + + @Override + public InputStream getConvertedResource(String outputFormat, BinaryResource binaryResource, PartIteration partIteration, Locale locale) throws ConvertedResourceException { + try { + InputStream inputStream=null; + + if("pdf".equals(outputFormat)) { + inputStream = getPdfConvertedResource(binaryResource); + } + + if("parts".equals(binaryResource.getHolderType()) && partIteration != null) { + return TitleBlockGenerator.addBlockTitleToPDF(inputStream,partIteration,locale); + } + + return inputStream; + } catch (StorageException | DocumentException | IOException e) { + throw new ConvertedResourceException(locale,e); + } + } + + private InputStream getPdfConvertedResource(BinaryResource binaryResource) throws StorageException, IOException { + String extension = FileIO.getExtension(binaryResource.getName()); + InputStream inputStream; + + if ("pdf".equals(extension)) { + inputStream = dataManager.getBinaryResourceInputStream(binaryResource); + } else { + String subResourceVirtualPath = FileIO.getFileNameWithoutExtension(binaryResource.getName()) + ".pdf"; + if (dataManager.exists(binaryResource, subResourceVirtualPath) && + dataManager.getLastModified(binaryResource, subResourceVirtualPath).after(binaryResource.getLastModified())) { + //if the resource is already converted, return it + inputStream = dataManager.getBinarySubResourceInputStream(binaryResource, subResourceVirtualPath); + } else { + String normalizedName = Tools.unAccent(binaryResource.getName()); + //copy the converted file for further reuse + + // TODO check for resources to be closed + try (OutputStream outputStream = dataManager.getBinarySubResourceOutputStream(binaryResource, subResourceVirtualPath); + InputStream binaryResourceInputStream = dataManager.getBinaryResourceInputStream(binaryResource); + InputStream inputStreamConverted = fileConverter.convertToPDF(normalizedName,binaryResourceInputStream)) { + ByteStreams.copy(inputStreamConverted, outputStream); + } + inputStream = dataManager.getBinarySubResourceInputStream(binaryResource, subResourceVirtualPath); + } + } + + return inputStream; + } + + @Override + public boolean canGetSubResourceVirtualPath(BinaryResource binaryResource) { + return false; + } + + @Override + public String getSubResourceVirtualPath(BinaryResource binaryResource, String subResourceUri) { + return null; + } + +} diff --git a/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/viewers/OfficeDocumentViewerImpl.java b/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/viewers/OfficeDocumentViewerImpl.java new file mode 100644 index 0000000000..4744529732 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/src/main/java/com/docdoku/server/viewers/OfficeDocumentViewerImpl.java @@ -0,0 +1,61 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.viewers; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.util.FileIO; +import com.docdoku.server.InternalService; +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; + +import javax.inject.Inject; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class OfficeDocumentViewerImpl implements DocumentViewer { + + @InternalService + @Inject + private IDataManagerLocal dataManager; + + @Override + public boolean canRenderViewerTemplate(BinaryResource binaryResource) { + return FileIO.isDocFile(binaryResource.getName()); + } + + @Override + public String renderHtmlForViewer(BinaryResource docResource, String uuid) throws Exception { + MustacheFactory mf = new DefaultMustacheFactory(); + Mustache mustache = mf.compile("com/docdoku/server/viewers/document_viewer.mustache"); + Map scopes = new HashMap<>(); + scopes.put("uriResource", ViewerUtils.getURI(docResource,uuid)); + scopes.put("thisId", UUID.randomUUID().toString()); + scopes.put("fileName", docResource.getName()); + StringWriter templateWriter = new StringWriter(); + mustache.execute(templateWriter, scopes).flush(); + + return ViewerUtils.getViewerTemplate(dataManager, docResource, uuid, templateWriter.toString(),true); + } + +} diff --git a/docdoku-server/docdoku-server-office-doc/src/main/resources/META-INF/beans.xml b/docdoku-server/docdoku-server-office-doc/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..5d71326a5c --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/src/main/resources/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/conf.properties b/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/conf.properties new file mode 100644 index 0000000000..a29e6ef1c0 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/conf.properties @@ -0,0 +1,2 @@ +com.docdoku.server.viewers.ooHome=/opt/openoffice.org3 +com.docdoku.server.viewers.ooPort=8100 \ No newline at end of file diff --git a/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/document_viewer.mustache b/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/document_viewer.mustache new file mode 100644 index 0000000000..a4fbd84e85 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/document_viewer.mustache @@ -0,0 +1,11 @@ +
+ \ No newline at end of file diff --git a/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/fonts/DejaVuSans-Bold.ttf b/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/fonts/DejaVuSans-Bold.ttf new file mode 100644 index 0000000000..1f22f07c99 Binary files /dev/null and b/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/fonts/DejaVuSans-Bold.ttf differ diff --git a/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/fonts/DejaVuSans.ttf b/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/fonts/DejaVuSans.ttf new file mode 100644 index 0000000000..5267218852 Binary files /dev/null and b/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/fonts/DejaVuSans.ttf differ diff --git a/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/localization/TitleBlockGenerator.properties b/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/localization/TitleBlockGenerator.properties new file mode 100644 index 0000000000..f43a59fcc6 --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/localization/TitleBlockGenerator.properties @@ -0,0 +1,21 @@ +date.format=yyyy/MM/dd + +iteration=Current Iteration +iteration.version=Version +iteration.iteration=Iteration +iteration.date=Date +iteration.author=Author +iteration.Notes=Notes + +attributes=Attributes +attributes.name=Name +attributes.value=Value + +lifecycle=Life cycle state +lifecycle.task=Task +lifecycle.date=Date +lifecycle.author=Author +lifecycle.comments=Comments +lifecycle.signature=Signature + +signature.error=Can't render signature \ No newline at end of file diff --git a/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/localization/TitleBlockGenerator_en.properties b/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/localization/TitleBlockGenerator_en.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/localization/TitleBlockGenerator_fr.properties b/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/localization/TitleBlockGenerator_fr.properties new file mode 100644 index 0000000000..998badcd6c --- /dev/null +++ b/docdoku-server/docdoku-server-office-doc/src/main/resources/com/docdoku/server/viewers/localization/TitleBlockGenerator_fr.properties @@ -0,0 +1,21 @@ +date.format=dd/MM/yyyy + +iteration=It\u00e9ration en cours +iteration.version=Version +iteration.iteration=It\u00e9ration +iteration.date=Date +iteration.author=Auteur +iteration.Notes=Notes + +attributes=Attributs +attributes.name=Nom +attributes.value=Valeur + +lifecycle=Etat du cycle de vie +lifecycle.task=T\u00e2che +lifecycle.date=Date +lifecycle.author=Auteur +lifecycle.comments=Commentaires +lifecycle.signature=Signature + +signature.error=- \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/.gitignore b/docdoku-server/docdoku-server-rest/.gitignore new file mode 100644 index 0000000000..ce1bcacab8 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/.gitignore @@ -0,0 +1 @@ +src/main/api/client/swagger.json \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/pom.xml b/docdoku-server/docdoku-server-rest/pom.xml new file mode 100644 index 0000000000..00a48ff7b5 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/pom.xml @@ -0,0 +1,142 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-rest + war + docdoku-server-rest Java EE 7 Restful web services + + + + + com.docdoku + docdoku-common + ${project.version} + provided + + + + + javax + javaee-api + 7.0 + provided + + + + + com.google.guava + guava + 16.0 + + + net.sf.dozer + dozer + 5.4.0 + + + java3d + vecmath + 1.3.1 + + + org.apache.poi + poi + 3.9 + + + org.apache.poi + poi-ooxml + 3.9 + + + org.bitbucket.b_c + jose4j + 0.5.1 + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + + + io.swagger + swagger-jersey2-jaxrs + 1.5.9 + + + javax.ws.rs + jsr311-api + + + + + com.brsanthu + migbase64 + 2.2 + + + org.glassfish.jersey.media + jersey-media-json-jackson + 2.22.2 + + + + + junit + junit + 4.11 + test + + + org.glassfish + javax.json + 1.0.4 + jar + test + + + org.mockito + mockito-all + 1.9.5 + test + + + + + + ${project.artifactId} + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + + + src/test/resources + + + src/test/resources/com/docdoku/server/rest/file/* + + + src/test/resources/com/docdoku/server/rest/part/* + + + + + diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/export/ExcelGenerator.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/export/ExcelGenerator.java new file mode 100644 index 0000000000..4d5c44573b --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/export/ExcelGenerator.java @@ -0,0 +1,444 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.export; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.PathDataIteration; +import com.docdoku.core.document.DocumentLink; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.InstanceAttributeDescriptor; +import com.docdoku.core.meta.InstanceListOfValuesAttribute; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartLinkList; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.query.QueryContext; +import com.docdoku.core.query.QueryField; +import com.docdoku.core.query.QueryResultRow; +import com.docdoku.core.util.Tools; +import com.docdoku.server.helpers.LangHelper; +import com.docdoku.server.rest.collections.QueryResult; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import java.io.File; +import java.io.FileOutputStream; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Chadid Asmae + */ +public class ExcelGenerator { + + private static final Logger LOGGER = Logger.getLogger(ExcelGenerator.class.getName()); + + private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + private SimpleDateFormat attributeDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + public File generateXLSResponse(QueryResult queryResult, Locale locale, String baseURL) { + File excelFile = new File("export_parts.xls"); + //Blank workbook + XSSFWorkbook workbook = new XSSFWorkbook(); + + //Create a blank sheet + XSSFSheet sheet = workbook.createSheet("Parts Data"); + + String header = String.join(";", queryResult.getQuery().getSelects()); + String[] columns = header.split(";"); + + Map data = new HashMap<>(); + String[] headerFormatted = createXLSHeaderRow(header, columns, locale); + data.put(1, headerFormatted); + + Map commentsData = new HashMap<>(); + String[] headerComments = createXLSHeaderRowComments(header, columns); + commentsData.put(1, headerComments); + + List selects = queryResult.getQuery().getSelects(); + int i = 1; + for (QueryResultRow row : queryResult.getRows()) { + i++; + data.put(i, createXLSRow(selects, row, baseURL)); + commentsData.put(i, createXLSRowComments(selects, row)); + } + + //Iterate over data and write to sheet + Set keyset = data.keySet(); + int rownum = 0; + + for (Integer key : keyset) { + + Row row = sheet.createRow(rownum++); + String[] objArr = data.get(key); + int cellnum = 0; + for (String obj : objArr) { + Cell cell = row.createCell(cellnum++); + cell.setCellValue(obj); + } + + CreationHelper factory = workbook.getCreationHelper(); + Drawing drawing = sheet.createDrawingPatriarch(); + String[] commentsObjArr = commentsData.get(key); + cellnum = 0; + for (String commentsObj : commentsObjArr) { + if (commentsObj.length() > 0) { + Cell cell = row.getCell(cellnum) != null ? row.getCell(cellnum) : row.createCell(cellnum); + + // When the comment box is visible, have it show in a 1x3 space + ClientAnchor anchor = factory.createClientAnchor(); + anchor.setCol1(cell.getColumnIndex()); + anchor.setCol2(cell.getColumnIndex()+1); + anchor.setRow1(row.getRowNum()); + anchor.setRow2(row.getRowNum()+1); + + Comment comment = drawing.createCellComment(anchor); + RichTextString str = factory.createRichTextString(commentsObj); + comment.setString(str); + + // Assign the comment to the cell + cell.setCellComment(comment); + } + cellnum++; + } + } + + // Define header style + Font headerFont = workbook.createFont(); + headerFont.setBoldweight(Font.BOLDWEIGHT_BOLD); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setFontName("Courier New"); + headerFont.setItalic(true); + headerFont.setColor(IndexedColors.WHITE.getIndex()); + CellStyle headerStyle = workbook.createCellStyle(); + headerStyle.setFont(headerFont); + headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); + headerStyle.setFillPattern(CellStyle.SOLID_FOREGROUND); + + // Set header style + for (int j=0; j selects, QueryResultRow row, String baseURL) { + List data = new ArrayList<>(); + PartRevision part = row.getPartRevision(); + PartIteration lastCheckedInIteration = part.getLastCheckedInIteration(); + PartIteration lastIteration = part.getLastIteration(); + QueryContext context = row.getContext(); + + for (String select : selects) { + + switch (select) { + case QueryField.CTX_PRODUCT_ID: + String productId = context != null ? context.getConfigurationItemId() : ""; + data.add(productId); + break; + case QueryField.CTX_SERIAL_NUMBER: + String serialNumber = context != null ? context.getSerialNumber() : ""; + data.add(serialNumber != null ? serialNumber : ""); + break; + case QueryField.PART_MASTER_NUMBER: + data.add(part.getPartNumber()); + break; + case QueryField.PART_MASTER_NAME: + String sName = part.getPartName(); + data.add(sName != null ? sName : ""); + break; + case QueryField.PART_MASTER_TYPE: + String sType = part.getType(); + data.add(sType != null ? sType : ""); + break; + case QueryField.PART_REVISION_MODIFICATION_DATE: + data.add((lastIteration != null && lastIteration.getModificationDate() != null) ? simpleDateFormat.format(lastIteration.getModificationDate()) : ""); + break; + case QueryField.PART_REVISION_CREATION_DATE: + data.add((part.getCreationDate() != null) ? simpleDateFormat.format(part.getCreationDate()) : ""); + break; + case QueryField.PART_REVISION_CHECKOUT_DATE: + data.add((part.getCheckOutDate() != null) ? simpleDateFormat.format(part.getCheckOutDate()) : ""); + break; + case QueryField.PART_REVISION_CHECKIN_DATE: + data.add((lastCheckedInIteration != null && lastCheckedInIteration.getCheckInDate() != null) ? simpleDateFormat.format(lastCheckedInIteration.getCheckInDate()) : ""); + break; + case QueryField.PART_REVISION_VERSION: + data.add(part.getVersion() != null ? part.getVersion() : ""); + break; + case QueryField.PART_REVISION_LIFECYCLE_STATE: + data.add(part.getLifeCycleState() != null ? part.getLifeCycleState() : ""); + break; + case QueryField.PART_REVISION_STATUS: + data.add(part.getStatus().toString()); + break; + case QueryField.AUTHOR_LOGIN: + User user = part.getAuthor(); + data.add(user.getLogin()); + break; + case QueryField.AUTHOR_NAME: + User userAuthor = part.getAuthor(); + data.add(userAuthor.getName()); + break; + case QueryField.CTX_DEPTH: + data.add(row.getDepth() + ""); + break; + case QueryField.CTX_AMOUNT: + data.add(row.getAmount() + ""); + break; + case QueryField.PART_ITERATION_LINKED_DOCUMENTS: + StringBuilder sb = new StringBuilder(); + if (lastCheckedInIteration != null) { + Set linkedDocuments = lastCheckedInIteration.getLinkedDocuments(); + for (DocumentLink documentLink : linkedDocuments) { + DocumentRevision targetDocument = documentLink.getTargetDocument(); + sb.append(baseURL + "/documents/" + targetDocument.getWorkspaceId() + "/" + targetDocument.getId() + "/" + targetDocument.getVersion() + " "); + } + } + data.add(sb.toString()); + break; + + case QueryField.CTX_P2P_SOURCE: + Map> sources = row.getSources(); + String sourcePartLinksAsString = Tools.getPartLinksAsExcelString(sources); + data.add(sourcePartLinksAsString); + break; + + case QueryField.CTX_P2P_TARGET: + Map> targets = row.getTargets(); + String targetPartLinksAsString = Tools.getPartLinksAsExcelString(targets); + data.add(targetPartLinksAsString); + break; + + default: + if (select.startsWith(QueryField.PART_REVISION_ATTRIBUTES_PREFIX)) { + String attributeSelectType = select.substring(0, select.indexOf(".")).substring(QueryField.PART_REVISION_ATTRIBUTES_PREFIX.length()); + String attributeSelectName = select.substring(select.indexOf(".") + 1); + String attributeValue = ""; + StringBuilder sbattr = new StringBuilder(); + + if (lastIteration != null) { + List attributes = lastIteration.getInstanceAttributes(); + if (attributes != null) { + for (InstanceAttribute attribute : attributes) { + InstanceAttributeDescriptor attributeDescriptor = new InstanceAttributeDescriptor(attribute); + if (attributeDescriptor.getName().equals(attributeSelectName) + && attributeDescriptor.getStringType().equals(attributeSelectType)) { + + attributeValue = attribute.getValue() + ""; + if (attributeDescriptor.getType() == InstanceAttributeDescriptor.Type.DATE) { + attributeValue = attribute.getValue() != null ? attributeDateFormat.format(attribute.getValue()) : ""; + } else if (attribute instanceof InstanceListOfValuesAttribute) { + attributeValue = ((InstanceListOfValuesAttribute) attribute).getSelectedName(); + } + sbattr.append(attributeValue + "|"); + } + } + } + } + String content = sbattr.toString().trim(); + if (content.length() > 0) { + content = content.substring(0, content.lastIndexOf("|")); + } + data.add(content); + } + if (select.startsWith(QueryField.PATH_DATA_ATTRIBUTES_PREFIX)) { + String attributeSelectType = select.substring(0, select.indexOf(".")).substring(QueryField.PATH_DATA_ATTRIBUTES_PREFIX.length()); + String attributeSelectName = select.substring(select.indexOf(".") + 1); + String attributeValue = ""; + PathDataIteration pdi = row.getPathDataIteration(); + StringBuilder sbpdattr = new StringBuilder(); + + if (pdi != null) { + List attributes = pdi.getInstanceAttributes(); + if (attributes != null) { + for (InstanceAttribute attribute : attributes) { + InstanceAttributeDescriptor attributeDescriptor = new InstanceAttributeDescriptor(attribute); + if (attributeDescriptor.getName().equals(attributeSelectName) + && attributeDescriptor.getStringType().equals(attributeSelectType)) { + + attributeValue = attribute.getValue() + ""; + if (attributeDescriptor.getType() == InstanceAttributeDescriptor.Type.DATE) { + attributeValue = attribute.getValue() != null ? attributeDateFormat.format(attribute.getValue()) : ""; + } else if (attribute instanceof InstanceListOfValuesAttribute) { + attributeValue = ((InstanceListOfValuesAttribute) attribute).getSelectedName(); + } + sbpdattr.append(attributeValue + "|"); + } + } + } + } + String content = sbpdattr.toString().trim(); + if (content.length() > 0) { + content = content.substring(0, content.lastIndexOf("|")); + } + data.add(content); + } + } + + } + + String rowData = String.join(";", data); + return rowData.split(";"); + } + + private String[] createXLSRowComments(List selects, QueryResultRow row) { + List commentsData = new ArrayList<>(); + PartRevision part = row.getPartRevision(); + PartIteration lastIteration = part.getLastIteration(); + + for (String select : selects) { + + if (select.equals(QueryField.CTX_SERIAL_NUMBER)) { + String path = row.getPath(); + if (path != null && !path.isEmpty()) { + commentsData.add(path); + } + + } else if (select.startsWith(QueryField.PART_REVISION_ATTRIBUTES_PREFIX)) { + String attributeSelectType = select.substring(0, select.indexOf(".")).substring(QueryField.PART_REVISION_ATTRIBUTES_PREFIX.length()); + String attributeSelectName = select.substring(select.indexOf(".") + 1); + StringBuilder commentsSbattr = new StringBuilder(); + + if (lastIteration != null) { + List attributes = lastIteration.getInstanceAttributes(); + if (attributes != null) { + for (InstanceAttribute attribute : attributes) { + InstanceAttributeDescriptor attributeDescriptor = new InstanceAttributeDescriptor(attribute); + + if (attributeDescriptor.getName().equals(attributeSelectName) + && attributeDescriptor.getStringType().equals(attributeSelectType)) { + commentsSbattr.append(attribute.getId() + "|"); + } + } + } + } + + String commentsContent = commentsSbattr.toString().trim(); + if (commentsContent.length() > 0) { + commentsContent = commentsContent.substring(0, commentsContent.lastIndexOf("|")); + } + commentsData.add(commentsContent); + + } else if (select.startsWith(QueryField.PATH_DATA_ATTRIBUTES_PREFIX)) { + String attributeSelectType = select.substring(0, select.indexOf(".")).substring(QueryField.PATH_DATA_ATTRIBUTES_PREFIX.length()); + String attributeSelectName = select.substring(select.indexOf(".") + 1); + PathDataIteration pdi = row.getPathDataIteration(); + StringBuilder commentsSbpattr = new StringBuilder(); + + if (pdi != null) { + List attributes = pdi.getInstanceAttributes(); + if (attributes != null) { + for (InstanceAttribute attribute : attributes) { + InstanceAttributeDescriptor attributeDescriptor = new InstanceAttributeDescriptor(attribute); + + if (attributeDescriptor.getName().equals(attributeSelectName) + && attributeDescriptor.getStringType().equals(attributeSelectType)) { + commentsSbpattr.append(attribute.getId() + "|"); + } + } + } + } + + String commentsContent = commentsSbpattr.toString().trim(); + if (commentsContent.length() > 0) { + commentsContent = commentsContent.substring(0, commentsContent.lastIndexOf("|")); + } + commentsData.add(commentsContent); + + } else { + commentsData.add(""); + } + + } + + String commentsRowData = String.join(";", commentsData); + return commentsRowData.split(";"); + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/BasicFilter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/BasicFilter.java new file mode 100644 index 0000000000..154403e415 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/BasicFilter.java @@ -0,0 +1,69 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.filters; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.xml.bind.DatatypeConverter; +import java.io.IOException; + + +public class BasicFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + + if(!FilterUtils.isAuthenticated(request)){ + + HttpServletRequest httpRequest = (HttpServletRequest) request; + + String authHeaderVal = httpRequest.getHeader("Authorization"); + + if(authHeaderVal != null && authHeaderVal.startsWith("Basic")) { + + String authorization = httpRequest.getHeader("Authorization"); + String[] splitAuthorization = authorization.split(" "); + if(splitAuthorization.length == 2){ + byte[] decoded = DatatypeConverter.parseBase64Binary(splitAuthorization[1]); + String credentials = new String(decoded, "US-ASCII"); + String[] splitCredentials = credentials.split(":"); + String userLogin = splitCredentials[0]; + String userPassword = splitCredentials[1]; + httpRequest.getSession(); + httpRequest.login(userLogin, userPassword); + FilterUtils.authenticate(request); + } + } + } + + chain.doFilter(request,response); + + } + + @Override + public void destroy() { + + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/CORSFilter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/CORSFilter.java new file mode 100644 index 0000000000..1e40c0f43f --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/CORSFilter.java @@ -0,0 +1,40 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.filters; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.ext.Provider; +import java.io.IOException; + +@Provider +public class CORSFilter implements ContainerResponseFilter { + + @Override + public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext cres) throws IOException { + cres.getHeaders().add("Access-Control-Allow-Origin", "*"); + cres.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization"); + cres.getHeaders().add("Access-Control-Allow-Credentials", "true"); + cres.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD"); + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/FilterUtils.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/FilterUtils.java new file mode 100644 index 0000000000..bdb151d8db --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/FilterUtils.java @@ -0,0 +1,46 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.filters; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class FilterUtils { + + public static void authenticate(ServletRequest request){ + request.setAttribute("authenticated",true); + } + + public static boolean isAuthenticated(ServletRequest request){ + Object authenticated = request.getAttribute("authenticated"); + if(authenticated != null){ + return (boolean) authenticated; + } + return false; + } + + public static void sendUnauthorized(ServletResponse pResponse) throws IOException { + HttpServletResponse response = (HttpServletResponse) pResponse; + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/GuestProxy.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/GuestProxy.java new file mode 100644 index 0000000000..6bdfa91e7e --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/GuestProxy.java @@ -0,0 +1,165 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.filters; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.document.DocumentIterationKey; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartIterationKey; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.product.PartRevisionKey; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.core.services.IDocumentResourceGetterManagerLocal; +import com.docdoku.core.services.IProductInstanceManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RunAs; +import javax.ejb.LocalBean; +import javax.ejb.Stateless; +import javax.inject.Inject; +import java.io.InputStream; + +@DeclareRoles(UserGroupMapping.GUEST_PROXY_ROLE_ID) +@RunAs(UserGroupMapping.GUEST_PROXY_ROLE_ID) +@LocalBean +@Stateless +public class GuestProxy{ + + @Inject + private IProductManagerLocal productService; + + @Inject + private IProductInstanceManagerLocal productInstanceManagerLocal; + + @Inject + private IDocumentManagerLocal documentService; + + @Inject + private IDocumentResourceGetterManagerLocal documentResourceGetterService; + + public PartRevision getPublicPartRevision(PartRevisionKey partRevisionKey) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + PartRevision partRevision = productService.getPartRevision(partRevisionKey); + return partRevision.isPublicShared() ? partRevision : null; + } + + public DocumentRevision getPublicDocumentRevision(DocumentRevisionKey documentRevisionKey) throws NotAllowedException, WorkspaceNotFoundException, UserNotFoundException, DocumentRevisionNotFoundException, UserNotActiveException, AccessRightException { + DocumentRevision documentRevision = documentService.getDocumentRevision(documentRevisionKey); + return documentRevision.isPublicShared() ? documentRevision : null; + } + + public BinaryResource getPublicBinaryResourceForDocument(String fullName) + throws AccessRightException, NotAllowedException, EntityNotFoundException, UserNotActiveException{ + + BinaryResource binaryResource = documentService.getBinaryResource(fullName); + String workspaceId = binaryResource.getWorkspaceId(); + String documentMasterId = binaryResource.getHolderId(); + String documentVersion = binaryResource.getHolderRevision(); + DocumentRevision documentRevision = documentService.getDocumentRevision(new DocumentRevisionKey(workspaceId,documentMasterId,documentVersion)); + + return documentRevision.isPublicShared() ? binaryResource : null; + } + + public BinaryResource getPublicBinaryResourceForPart(String fullName) + throws AccessRightException, NotAllowedException, EntityNotFoundException, UserNotActiveException{ + + BinaryResource binaryResource = productService.getBinaryResource(fullName); + String workspaceId = binaryResource.getWorkspaceId(); + String partNumber = binaryResource.getHolderId(); + String partVersion = binaryResource.getHolderRevision(); + PartRevision partRevision = productService.getPartRevision(new PartRevisionKey(workspaceId, partNumber, partVersion)); + + return partRevision.isPublicShared() ? binaryResource:null; + } + + +/* + + + public BinaryResource getPublicBinaryResourceForDocument(DocumentRevisionKey docRK, String fullName) + throws AccessRightException, NotAllowedException, EntityNotFoundException, UserNotActiveException, LoginException{ + + DocumentRevision documentRevision = documentService.getDocumentRevision(docRK); + if(documentRevision.isPublicShared()){ + return documentService.getBinaryResource(fullName); + }else{ + return null; + } + + } + + public BinaryResource getPublicBinaryResourceForPart(PartRevisionKey partK, String fullName) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, LoginException, FileNotFoundException, NotAllowedException, AccessRightException { + getPublicPartRevision(partK); + return productService.getBinaryResource(fullName); + } + + public DocumentIteration findDocumentIterationByBinaryResource(BinaryResource binaryResource) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + return documentService.findDocumentIterationByBinaryResource(binaryResource); + } + + public User whoAmI() { + return new User(); + } + + public BinaryResource getBinaryResourceForSharedDocument(String fullName) throws AccessRightException, NotAllowedException, WorkspaceNotFoundException, UserNotFoundException, FileNotFoundException, UserNotActiveException { + return documentService.getBinaryResource(fullName); + } + + public BinaryResource getBinaryResourceForSharedPart(String fullName) throws AccessRightException, NotAllowedException, WorkspaceNotFoundException, UserNotFoundException, FileNotFoundException, UserNotActiveException { + return productService.getBinaryResource(fullName); + } + +*/ + + public BinaryResource getBinaryResourceForSharedDocument(String fullName) throws AccessRightException, NotAllowedException, WorkspaceNotFoundException, UserNotFoundException, FileNotFoundException, UserNotActiveException { + return documentService.getBinaryResource(fullName); + } + + public BinaryResource getBinaryResourceForSharedPart(String fullName) throws AccessRightException, NotAllowedException, WorkspaceNotFoundException, UserNotFoundException, FileNotFoundException, UserNotActiveException { + return productService.getBinaryResource(fullName); + } + + public boolean canAccess(DocumentIterationKey docIKey) throws EntityNotFoundException, UserNotActiveException{ + return documentService.canAccess(docIKey); + } + public boolean canAccess(PartIterationKey partIKey) throws EntityNotFoundException, UserNotActiveException{ + return productService.canAccess(partIKey); + } + + public InputStream getDocumentConvertedResource(String outputFormat, BinaryResource binaryResource) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ConvertedResourceException { + return documentResourceGetterService.getDocumentConvertedResource(outputFormat, binaryResource); + } + + public InputStream getPartConvertedResource(String outputFormat, BinaryResource binaryResource) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ConvertedResourceException { + return documentResourceGetterService.getPartConvertedResource(outputFormat, binaryResource); + } + + public BinaryResource getBinaryResourceForProducInstance(String fullName) throws UserNotActiveException, WorkspaceNotFoundException, UserNotFoundException, FileNotFoundException, NotAllowedException, AccessRightException { + return productInstanceManagerLocal.getBinaryResource(fullName); + } + + public BinaryResource getBinaryResourceForPathData(String fullName) throws UserNotActiveException, WorkspaceNotFoundException, UserNotFoundException, FileNotFoundException, NotAllowedException, AccessRightException { + return productInstanceManagerLocal.getPathDataBinaryResource(fullName); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/JwtFilter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/JwtFilter.java new file mode 100644 index 0000000000..a79327dbbc --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/JwtFilter.java @@ -0,0 +1,106 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.filters; + +import com.docdoku.core.common.Account; +import com.docdoku.core.exceptions.AccountNotFoundException; +import com.docdoku.core.services.IAccountManagerLocal; +import com.docdoku.server.jwt.RsaJsonWebKeyFactory; +import org.jose4j.jwk.RsaJsonWebKey; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.consumer.InvalidJwtException; +import org.jose4j.jwt.consumer.JwtConsumer; +import org.jose4j.jwt.consumer.JwtConsumerBuilder; + +import javax.inject.Inject; +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + + +public class JwtFilter implements Filter { + + private static final Logger LOGGER = Logger.getLogger(JwtFilter.class.getName()); + + @Inject + private IAccountManagerLocal accountManager; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + + if(!FilterUtils.isAuthenticated(request)) { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + + String authHeaderVal = httpRequest.getHeader("Authorization"); + + if (authHeaderVal != null && authHeaderVal.startsWith("Bearer")) { + String authorization = httpRequest.getHeader("Authorization"); + String[] splitAuthorization = authorization.split(" "); + if (splitAuthorization.length == 2) { + String jwt = splitAuthorization[1]; + Account account = validateToken(jwt); + if (account != null) { + // TODO log user in : login module ? + httpRequest.getSession(); + FilterUtils.authenticate(request); + } + } + } + } + + chain.doFilter(request, response); + } + + @Override + public void destroy() { + + } + + private Account validateToken(String jwt) { + RsaJsonWebKey rsaJsonWebKey = RsaJsonWebKeyFactory.createKey(); + JwtConsumer jwtConsumer = new JwtConsumerBuilder() + .setRequireSubject() + .setVerificationKey(rsaJsonWebKey.getKey()) + .build(); + + Account account = null; + + try { + JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt); + String userLogin = (String) jwtClaims.getClaimValue("sub"); + account = accountManager.getAccount(userLogin); + } catch (InvalidJwtException e) { + LOGGER.log(Level.SEVERE,null,e); + } catch (AccountNotFoundException e) { + LOGGER.log(Level.SEVERE, null, e); + } + + return account; + + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/PublicFilter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/PublicFilter.java new file mode 100644 index 0000000000..b75001b75c --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/PublicFilter.java @@ -0,0 +1,104 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.filters; + +import com.docdoku.core.services.IAccountManagerLocal; +import com.docdoku.core.services.IContextManagerLocal; +import com.docdoku.core.services.IOrganizationManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; + +import javax.ejb.EJB; +import javax.inject.Inject; +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.regex.Pattern; + +public class PublicFilter implements Filter { + + private String[] publicPaths; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IContextManagerLocal contextManager; + + @Inject + private IAccountManagerLocal accountManager; + + @EJB + private IOrganizationManagerLocal organizationManager; + + @Override + public void init(FilterConfig filterConfig) { + String parameter=filterConfig.getInitParameter("publicPaths"); + if(parameter !=null) { + publicPaths = parameter.split(","); + for(int i=0;i< publicPaths.length;i++) { + boolean endLess=false; + if(publicPaths[i].endsWith("/**")) { + publicPaths[i]= publicPaths[i].substring(0, publicPaths[i].length()-2); + endLess=true; + } + publicPaths[i] = publicPaths[i].replace("*", "[^/]+?"); + if(endLess) { + publicPaths[i] += ".*"; + } + } + } else { + publicPaths = null; + } + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + + if(!FilterUtils.isAuthenticated(request)){ + HttpServletRequest httpRequest = (HttpServletRequest) request; + if(isPublicPath(httpRequest)) { + FilterUtils.authenticate(request); + } + } + + chain.doFilter(request, response); + } + + private boolean isPublicPath(HttpServletRequest httpRequest){ + String path = httpRequest.getRequestURI(); + if(path!=null && publicPaths !=null){ + for(String excludedPath: publicPaths){ + if(Pattern.matches(excludedPath, path)) { + return true; + } + } + } + return false; + } + + @Override + public void destroy() { + // Nothing to do + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/RequestFilter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/RequestFilter.java new file mode 100644 index 0000000000..62f62f6103 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/RequestFilter.java @@ -0,0 +1,49 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.filters; + +import javax.servlet.*; +import java.io.IOException; + +/** + * Last filter in chain + * + */ +public class RequestFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + if(FilterUtils.isAuthenticated(servletRequest)){ + filterChain.doFilter(servletRequest,servletResponse); + }else{ + FilterUtils.sendUnauthorized(servletResponse); + } + } + + @Override + public void destroy() { + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/SessionFilter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/SessionFilter.java new file mode 100644 index 0000000000..b516b6c518 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/filters/SessionFilter.java @@ -0,0 +1,54 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.filters; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +public class SessionFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + + if(!FilterUtils.isAuthenticated(request)){ + HttpServletRequest httpRequest = (HttpServletRequest) request; + String remoteUser = httpRequest.getRemoteUser(); + if(remoteUser != null && !remoteUser.isEmpty()){ + FilterUtils.authenticate(request); + } + } + + chain.doFilter(request,response); + + } + + @Override + public void destroy() { + // Nothing to do + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/helpers/LangHelper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/helpers/LangHelper.java new file mode 100644 index 0000000000..dad0d6aeb2 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/helpers/LangHelper.java @@ -0,0 +1,37 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.helpers; + +import java.util.Locale; +import java.util.ResourceBundle; + +public class LangHelper { + + private static final String BUNDLE_NAME = "com.docdoku.server.i18n.LocalStrings"; + + private LangHelper() {} + + public static String getLocalizedMessage(String key, Locale locale) { + ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_NAME, locale); + return bundle.getString(key); + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/helpers/Streams.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/helpers/Streams.java new file mode 100644 index 0000000000..5fba6878d1 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/helpers/Streams.java @@ -0,0 +1,44 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.helpers; + +import java.io.Closeable; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + *@author morgan + */ +public class Streams { + + private static final Logger LOGGER = Logger.getLogger(Streams.class.getName()); + + public static void close(Closeable closeable){ + if(closeable != null){ + try { + closeable.close(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, null, e); + } + } + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/http/WebSessionListener.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/http/WebSessionListener.java new file mode 100644 index 0000000000..2a0253e2f6 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/http/WebSessionListener.java @@ -0,0 +1,57 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.http; + +import com.docdoku.server.mainchannel.MainChannelApplication; + +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Arthur FRIN + * @version 1.0, 03/06/14 + * @since V2.0 + */ +public class WebSessionListener implements HttpSessionListener { + private static final Logger LOGGER = Logger.getLogger(WebSessionListener.class.getName()); + + //Notification that a session was created. + @Override + public void sessionCreated(HttpSessionEvent httpSessionCreatedEvent) { + // Nothing to do + } + + //Notification that a session is about to be invalidated. + @Override + public void sessionDestroyed(HttpSessionEvent httpSessionDestroyedEvent) { + HttpSession httpSession = httpSessionDestroyedEvent.getSession(); + String remoteUser = (String)httpSession.getAttribute("remoteUser"); + // Remote User can be null on unauthenticated http session + if(remoteUser!=null){ + LOGGER.log(Level.FINE, " [MainChannelApplication] Session destroy for a remote user."); + MainChannelApplication.sessionDestroyed(remoteUser); + } + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/jwt/JWTokenFactory.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/jwt/JWTokenFactory.java new file mode 100644 index 0000000000..4c299b2cea --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/jwt/JWTokenFactory.java @@ -0,0 +1,63 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.jwt; + +import com.docdoku.core.common.Account; +import org.jose4j.jwk.RsaJsonWebKey; +import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.lang.JoseException; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class JWTokenFactory { + + private static final Logger LOGGER = Logger.getLogger(JWTokenFactory.class.getName()); + private static RsaJsonWebKey key; + private static final String ALG = AlgorithmIdentifiers.RSA_USING_SHA256; + + private JWTokenFactory() { + } + + public static String createToken(Account account) { + + RsaJsonWebKey rsaJsonWebKey = RsaJsonWebKeyFactory.createKey(); + + JwtClaims claims = new JwtClaims(); + claims.setSubject(account.getLogin()); + + JsonWebSignature jws = new JsonWebSignature(); + jws.setPayload(claims.toJson()); + jws.setKey(rsaJsonWebKey.getPrivateKey()); + jws.setAlgorithmHeaderValue(ALG); + + try { + return jws.getCompactSerialization(); + } catch (JoseException ex) { + LOGGER.log(Level.SEVERE, null, ex); + } + + return null; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/jwt/RsaJsonWebKeyFactory.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/jwt/RsaJsonWebKeyFactory.java new file mode 100644 index 0000000000..f09b41ea53 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/jwt/RsaJsonWebKeyFactory.java @@ -0,0 +1,51 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.jwt; + + +import org.jose4j.jwk.RsaJsonWebKey; +import org.jose4j.jwk.RsaJwkGenerator; +import org.jose4j.lang.JoseException; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class RsaJsonWebKeyFactory { + + private static final Logger LOGGER = Logger.getLogger(RsaJsonWebKeyFactory.class.getName()); + private static final int KEY_SIZE = 2048; + private static RsaJsonWebKey key; + + private RsaJsonWebKeyFactory() { + } + + public static RsaJsonWebKey createKey(){ + if(key == null){ + try { + key = RsaJwkGenerator.generateJwk(KEY_SIZE); + } catch (JoseException ex) { + LOGGER.log(Level.SEVERE, null, ex); + } + } + return key; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/CollaborativeRoomController.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/CollaborativeRoomController.java new file mode 100644 index 0000000000..d2f6844c4a --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/CollaborativeRoomController.java @@ -0,0 +1,208 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel; + +import com.docdoku.server.mainchannel.collaborative.CollaborativeRoom; +import com.docdoku.server.mainchannel.module.ChatMessage; +import com.docdoku.server.mainchannel.module.CollaborativeMessage; +import com.docdoku.server.mainchannel.util.ChannelMessagesType; + +import javax.json.JsonObject; +import javax.websocket.Session; + +/** + * A controller for manage a room of Collaboration Module + * + * @author Arthur FRIN + * @version 1.0, 30/06/14 + * @since V2.0 + */ +public class CollaborativeRoomController { + + private CollaborativeRoomController(){ + super(); + } + + + public static void broadcastNewContext(CollaborativeRoom room){ + String master = room.getMasterName(); + CollaborativeMessage message = new CollaborativeMessage(ChannelMessagesType.COLLABORATIVE_CONTEXT,room.getKey(),room.getContext(),master); + MainChannelDispatcher.sendToAllUserChannels(master,message); + for(Session slave : room.getSlaves()){ + String remoteUser = slave.getUserPrincipal().getName(); + message.setRemoteUser(remoteUser); + MainChannelDispatcher.send(slave, message); + } + } + + public static void processCreate(Session session, String callerLogin){ + CollaborativeRoom room = new CollaborativeRoom(session); + CollaborativeMessage message = new CollaborativeMessage(ChannelMessagesType.COLLABORATIVE_CREATE,room.getKey(),null,callerLogin); + MainChannelDispatcher.send(session, message); + } + + public static void processInvite(String callerLogin, String invitedUser, CollaborativeRoom room, String context, String url){ + if (room.getMasterName().equals(callerLogin) && room.findUserSession(invitedUser)==null) { + // the master sent the invitation + // the user is not already in the room + if (!room.getPendingUsers().contains(invitedUser)) { + // the user is not yet in the pending list, add him. + room.addPendingUser(invitedUser); + } + // Chat message + ChatMessage chatMessage = new ChatMessage(ChannelMessagesType.CHAT_MESSAGE,invitedUser); + String invite = "/invite " + url + "/room/" + room.getKey(); + chatMessage.setMessage(invite); + chatMessage.setContext(context); + chatMessage.setRemoteUser(callerLogin); + chatMessage.setSender(callerLogin); + MainChannelDispatcher.sendToAllUserChannels(invitedUser,chatMessage); + + CollaborativeRoomController.broadcastNewContext(room); + } + } + + public static void processJoin(Session callerSession, String callerLogin, CollaborativeRoom room, CollaborativeMessage collaborativeMessage){ + if (room == null){ + //Room not found + return; + } + + // Master + if ("".equals(room.getMasterName()) && room.getLastMaster().equals(callerLogin)) { + // if the room has no master, allow the last master to take the lead + room.setMaster(callerSession); + CollaborativeRoomController.sendAllCommands(callerSession,room); + //MainChannelDispatcher.send(callerSession, collaborativeMessage); // notify the user that he joined the room + CollaborativeRoomController.broadcastNewContext(room); + return; + } + + // User + if (!room.removePendingUser(callerLogin)){ + // if the user is not in the pending list reject him + CollaborativeMessage message = new CollaborativeMessage(ChannelMessagesType.COLLABORATIVE_KICK_NOT_INVITED,room.getKey(),null,callerLogin); + MainChannelDispatcher.send(callerSession, message); + } else { + room.addSlave(callerSession); + CollaborativeRoomController.sendAllCommands(callerSession,room); + //MainChannelDispatcher.send(callerSession, collaborativeMessage); // notify the user that he joined the room + CollaborativeRoomController.broadcastNewContext(room); + + } + } + + public static void removeSessionFromCollaborativeRoom(Session userSession) { + + for (CollaborativeRoom room : CollaborativeRoom.getAllCollaborativeRooms()) { + if (room.getSlaves().contains(userSession)){ + CollaborativeRoomController.processExit(userSession, userSession.getUserPrincipal().getName(), room); + } + if (room.getMaster()==userSession){ + CollaborativeRoomController.processExit(userSession,userSession.getUserPrincipal().getName(),room); + } + } + } + + public static void sendAllCommands(Session user, CollaborativeRoom room){ + CollaborativeMessage mess = new CollaborativeMessage(ChannelMessagesType.COLLABORATIVE_JOIN, room.getKey(), room.getCommands(), user.getUserPrincipal().getName()); + MainChannelDispatcher.send(user, mess); + } + + public static void processCommands(String callerLogin, CollaborativeRoom room, CollaborativeMessage collaborativeMessage){ + if (room.getMasterName().equals(callerLogin)) { + // the master sent the invitation + // save camera infos + JsonObject command = collaborativeMessage.getMessageBroadcast(); + room.saveCommand(command); + for (Session slave : room.getSlaves()) { + MainChannelDispatcher.send(slave, collaborativeMessage); + } + } + } + + public static void processExit(Session session, String callerLogin, CollaborativeRoom room){ + if (room.getMasterName().equals(callerLogin)) { + // exit for the master + room.setMaster(null); + room.setLastMaster(callerLogin); + } else { + room.getSlaves().remove(session); + // add the user in the pending list + if (!room.getPendingUsers().contains(callerLogin)) { + room.addPendingUser(callerLogin); + } + } + CollaborativeRoomController.broadcastNewContext(room); + } + + public static void processKill(String callerLogin, CollaborativeRoom room){ + if (room.getMasterName().equals(callerLogin)) { + // the master sent the invitation + String roomKey = room.getKey(); + CollaborativeMessage kickMessage; + for (Session slave : room.getSlaves()) { + String remoteUser = slave.getUserPrincipal().getName(); + kickMessage = new CollaborativeMessage(ChannelMessagesType.COLLABORATIVE_KICK_USER, roomKey, null, remoteUser); + MainChannelDispatcher.send(slave, kickMessage); + } + room.delete(); + } + } + + public static void processGiveHand(String callerLogin, String promotedUser, CollaborativeRoom room){ + if (room.getMasterName().equals(callerLogin)) { + // the master sent the invitation + room.addSlave(room.getMaster()); + Session userSession = room.findUserSession(promotedUser); + if(room.removeSlave(userSession)){ + room.setMaster(userSession); + CollaborativeRoomController.broadcastNewContext(room); + } + } + } + public static void processKickUser(String callerLogin, String kickedUser, CollaborativeRoom room, CollaborativeMessage collaborativeMessage){ + if (room.getMasterName().equals(callerLogin)) { + // the master sent the invitation + Session userSession = room.findUserSession(kickedUser); + if(room.removeSlave(userSession)) { + // user has been removed + MainChannelDispatcher.send(userSession, collaborativeMessage); + CollaborativeRoomController.broadcastNewContext(room); + } + } + } + public static void processWithdrawInvitation(String callerLogin, String pendingUser, CollaborativeRoom room, String context){ + if (room.getMasterName().equals(callerLogin)) { + // the master sent the invitation + room.removePendingUser(pendingUser); + // Chat message + ChatMessage chatMessage = new ChatMessage(ChannelMessagesType.CHAT_MESSAGE, pendingUser); + chatMessage.setMessage("/withdrawInvitation"); + chatMessage.setContext(context); + chatMessage.setRemoteUser(callerLogin); + chatMessage.setSender(callerLogin); + MainChannelDispatcher.sendToAllUserChannels(pendingUser, chatMessage); + + CollaborativeRoomController.broadcastNewContext(room); + } + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/MainChannelApplication.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/MainChannelApplication.java new file mode 100644 index 0000000000..0409539c0d --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/MainChannelApplication.java @@ -0,0 +1,355 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel; + +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.mainchannel.collaborative.CollaborativeRoom; +import com.docdoku.server.mainchannel.module.*; +import com.docdoku.server.mainchannel.util.ChannelMessagesBuilder; +import com.docdoku.server.mainchannel.util.ChannelMessagesType; +import com.docdoku.server.mainchannel.util.Room; + +import javax.ejb.Stateless; +import javax.inject.Inject; +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +@Stateless +@ServerEndpoint(value = "/ws", decoders = {MessageDecoder.class}, encoders = {WebRTCMessageEncoder.class, StatusMessageEncoder.class, ChatMessageEncoder.class, CollaborativeMessageEncoder.class}) +public class MainChannelApplication { + + @Inject + private IUserManagerLocal userManager; + + // Users WebSockets Map : > + private static final ConcurrentMap> CHANNELS = new ConcurrentHashMap<>(); + + private static final Logger LOGGER = Logger.getLogger(MainChannelApplication.class.getName()); + + public static boolean hasChannels(String userLogin) { + return CHANNELS!=null + && userLogin !=null + && CHANNELS.get(userLogin) != null + && !CHANNELS.get(userLogin).isEmpty(); + } + + public static Map getUserChannels(String userLogin) { + return CHANNELS.get(userLogin); + } + + + public static void sessionDestroyed(String userLogin) { + if(userLogin!=null && hasChannels(userLogin)) { + Map sessionMap = CHANNELS.get(userLogin); + List sessionList = new ArrayList<>(sessionMap.values()); + + for (Session session : sessionList) { + try { + sessionMap.get(session.getId()).close(); + } catch (IOException e) { + Logger.getLogger(MainChannelApplication.class.getName()).log(Level.INFO, null, e); + } + } + } + } + + @OnError + public void error(Session session, Throwable error) { + LOGGER.log(Level.SEVERE, null, error); + closeSession(session); + } + + @OnClose + public void close(Session session, CloseReason reason) { + LOGGER.log(Level.FINE, null, reason); + closeSession(session); + } + + @OnOpen + public void open(Session session) { + + Principal userPrincipal = session.getUserPrincipal(); + String callerLogin = userPrincipal.getName(); + String sessionId = session.getId(); + + if (callerLogin != null && sessionId != null) { + + // Store the webSocket in the user socket Map + // If user does not have a channels map yet then we create his channels map + if (CHANNELS.get(callerLogin) == null) { + CHANNELS.put(callerLogin, new HashMap()); + } + + // Store the session in the user channels hashMap + CHANNELS.get(callerLogin).put(sessionId, session); + + // Send him a welcome message + MainChannelDispatcher.send(session, ChannelMessagesBuilder.buildWelcomeMessage(callerLogin)); + } + } + + @OnMessage + public void message(Session session, AbstractMessage message) { + Principal userPrincipal = session.getUserPrincipal(); + String callerLogin = userPrincipal.getName(); + + if(!(message instanceof CollaborativeMessage)){ + // Exit if remote user is null or caller tries to join himself + if (message.getRemoteUser() == null || message.getRemoteUser().equals(callerLogin)) { + return; + } + // Exit if caller is not allowed to reach callee (business) + if (!callerIsAllowToReachCallee(callerLogin, message.getRemoteUser())) { + return; + } + } + + WebRTCMessage webRTC; + CollaborativeRoom room; + switch (message.getType()) { + case ChannelMessagesType.USER_STATUS: + StatusMessage status = (StatusMessage) message; + process(session, status); + break; + case ChannelMessagesType.WEBRTC_INVITE: + webRTC = (WebRTCMessage) message; + processWebRTCInvite(session, callerLogin, webRTC); + break; + case ChannelMessagesType.WEBRTC_ACCEPT: + webRTC = (WebRTCMessage) message; + processWebRTCAccept(session, callerLogin, webRTC); + break; + case ChannelMessagesType.WEBRTC_REJECT: + webRTC = (WebRTCMessage) message; + processWebRTCReject(callerLogin, webRTC); + break; + case ChannelMessagesType.WEBRTC_HANGUP: + webRTC = (WebRTCMessage) message; + processWebRTCHangup(session, callerLogin, webRTC); + break; + case ChannelMessagesType.WEBRTC_ANSWER: + case ChannelMessagesType.WEBRTC_OFFER: + case ChannelMessagesType.WEBRTC_CANDIDATE: + case ChannelMessagesType.WEBRTC_BYE: + webRTC = (WebRTCMessage) message; + processP2P(session, callerLogin, webRTC); + break; + case ChannelMessagesType.CHAT_MESSAGE: + ChatMessage chat = (ChatMessage) message; + process(session, callerLogin, chat); + break; + + case ChannelMessagesType.COLLABORATIVE_CREATE: + CollaborativeRoomController.processCreate(session, callerLogin); + break; + case ChannelMessagesType.COLLABORATIVE_INVITE: + CollaborativeMessage inviteMessage = (CollaborativeMessage) message; + room = CollaborativeRoom.getByKeyName(inviteMessage.getKey()); + String invitedUser = inviteMessage.getRemoteUser(); + String context = inviteMessage.getMessageBroadcast().getString("context"); + String url = inviteMessage.getMessageBroadcast().getString("url"); + if (callerIsAllowToReachCallee(callerLogin,invitedUser)) { + CollaborativeRoomController.processInvite(callerLogin, invitedUser, room, context, url); + } + break; + case ChannelMessagesType.COLLABORATIVE_JOIN: + CollaborativeMessage joinMessage = (CollaborativeMessage) message; + room = CollaborativeRoom.getByKeyName(joinMessage.getKey()); + CollaborativeRoomController.processJoin(session, callerLogin, room, joinMessage); + break; + case ChannelMessagesType.COLLABORATIVE_COMMANDS: + CollaborativeMessage commandsMessage = (CollaborativeMessage) message; + room = CollaborativeRoom.getByKeyName(commandsMessage.getKey()); + CollaborativeRoomController.processCommands(callerLogin, room, commandsMessage); + break; + case ChannelMessagesType.COLLABORATIVE_EXIT: + CollaborativeMessage exitMessage = (CollaborativeMessage) message; + room = CollaborativeRoom.getByKeyName(exitMessage.getKey()); + CollaborativeRoomController.processExit(session, callerLogin, room); + break; + case ChannelMessagesType.COLLABORATIVE_KILL: + CollaborativeMessage killMessage = (CollaborativeMessage) message; + room = CollaborativeRoom.getByKeyName(killMessage.getKey()); + CollaborativeRoomController.processKill(callerLogin, room); + break; + case ChannelMessagesType.COLLABORATIVE_GIVE_HAND: + CollaborativeMessage giveHandMessage = (CollaborativeMessage) message; + room = CollaborativeRoom.getByKeyName(giveHandMessage.getKey()); + String promotedUser = giveHandMessage.getRemoteUser(); + CollaborativeRoomController.processGiveHand(callerLogin, promotedUser, room); + break; + case ChannelMessagesType.COLLABORATIVE_KICK_USER: + CollaborativeMessage kickUserMessage = (CollaborativeMessage) message; + room = CollaborativeRoom.getByKeyName(kickUserMessage.getKey()); + String kickedUser = kickUserMessage.getRemoteUser(); + CollaborativeRoomController.processKickUser(callerLogin, kickedUser, room, kickUserMessage); + break; + case ChannelMessagesType.COLLABORATIVE_WITHDRAW_INVITATION: + CollaborativeMessage withdrawInvitationMessage = (CollaborativeMessage) message; + room = CollaborativeRoom.getByKeyName(withdrawInvitationMessage.getKey()); + String messageContext = withdrawInvitationMessage.getMessageBroadcast().getString("context"); + String pendingUser = withdrawInvitationMessage.getRemoteUser(); + CollaborativeRoomController.processWithdrawInvitation(callerLogin, pendingUser, room, messageContext); + break; + default: + break; + } + + + } + + + private void closeSession(Session session) { + Principal userPrincipal = session.getUserPrincipal(); + + String userLogin = userPrincipal.getName(); + String sessionId = session.getId(); + + if (sessionId != null && userLogin != null) { + // find whom the session belongs + Room.removeUserFromAllRoom(userLogin); + CollaborativeRoomController.removeSessionFromCollaborativeRoom(session); + // remove the session from the user hash map + Map userChannels = CHANNELS.get(userLogin); + if(userChannels != null) { + userChannels.remove(sessionId); + } + // clean from memory when no more channel left + if(!hasChannels(userLogin)){ + CHANNELS.remove(userLogin); + } + } + } + + private boolean callerIsAllowToReachCallee(String caller, String callee) { + // Allow users to communicate only if they have a common workspace + return userManager.hasCommonWorkspace(caller, callee); + } + + private void processWebRTCHangup(Session session, String callerLogin, WebRTCMessage webRTC) { + Room room = Room.getByKeyName(webRTC.getRoomKey()); + + if (room != null) { + Session otherSession = room.getOtherUserSession(session); + room.removeUserSession(session); + MainChannelDispatcher.send(otherSession, new WebRTCMessage(ChannelMessagesType.WEBRTC_HANGUP, callerLogin, webRTC.getRoomKey(), null, null, 0, null)); + } + } + + private void processWebRTCReject(String callerLogin, WebRTCMessage webRTC) { + Room room = Room.getByKeyName(webRTC.getRoomKey()); + if (room != null) { + // send "room reject event" to caller, to remove invitations in other tabs if any + MainChannelDispatcher.sendToAllUserChannels(callerLogin, new WebRTCMessage(ChannelMessagesType.WEBRTC_ROOM_REJECT_EVENT, null, webRTC.getRoomKey(), webRTC.getReason(), null, room.getOccupancy(), callerLogin)); + Session otherSession = room.getUserSession(webRTC.getRemoteUser()); + if (otherSession != null) { + MainChannelDispatcher.send(otherSession, new WebRTCMessage(ChannelMessagesType.WEBRTC_REJECT, callerLogin, webRTC.getRoomKey(), webRTC.getReason(), null, 0, null)); + } + } + } + + private void processWebRTCAccept(Session session, String callerLogin, WebRTCMessage webRTC) { + Room room = Room.getByKeyName(webRTC.getRoomKey()); + + if (room != null && room.hasUser(webRTC.getRemoteUser())) { + + room.addUserSession(session); + // send room join event to caller (all channels to remove invitations if any) + MainChannelDispatcher.sendToAllUserChannels(callerLogin, new WebRTCMessage(ChannelMessagesType.WEBRTC_ROOM_JOIN_EVENT, callerLogin, webRTC.getRoomKey(), null, null, room.getOccupancy(), callerLogin)); + + // send room join event to the other user in room + Session otherSession = room.getOtherUserSession(session); + if (otherSession != null) { + MainChannelDispatcher.send(otherSession, new WebRTCMessage(ChannelMessagesType.WEBRTC_ACCEPT, callerLogin, webRTC.getRoomKey(), null, null, 0, null)); + } + } + } + + private void processWebRTCInvite(Session session, String callerLogin, WebRTCMessage webRTC) { + String roomKey = callerLogin + "-" + webRTC.getRemoteUser(); + if (!MainChannelApplication.hasChannels(webRTC.getRemoteUser())) { + MainChannelDispatcher.send(session, new WebRTCMessage(ChannelMessagesType.WEBRTC_REJECT, null, roomKey, WebRTCMessage.WEBRTC_OFFLINE, null, 0, webRTC.getRemoteUser())); + return; + } + Room room = Room.getByKeyName(roomKey); + if (room == null) { + room = new Room(roomKey); + } + //else : multiple invitations, caller is spamming or something goes wrong. + // the room is ready to receive user sessions. + // add the caller session in the room + room.addUserSession(session); + + // send room join event to caller session (single channel) + MainChannelDispatcher.send(session, new WebRTCMessage(ChannelMessagesType.WEBRTC_ROOM_JOIN_EVENT, null, roomKey, null, null, room.getOccupancy(), callerLogin)); + + // send invitation to the remote user sessions (all channels) + MainChannelDispatcher.sendToAllUserChannels(webRTC.getRemoteUser(), new WebRTCMessage(ChannelMessagesType.WEBRTC_INVITE, callerLogin, roomKey, null, webRTC.getContext(), room.getOccupancy(), null)); + } + + private void processP2P(Session session, String callerLogin, WebRTCMessage webRTC) { + // webRTC P2P signaling messages + // These messages are forwarded to the remote peer(s) in the room + Room room = Room.getByKeyName(webRTC.getRoomKey()); + if (room != null && room.hasUser(callerLogin)) { + // forward the message to the other peer + Session otherSession = room.getOtherUserSession(session); + + // on bye message, remove the user from the room + if (ChannelMessagesType.WEBRTC_BYE.equals(webRTC.getType())) { + room.removeUserSession(session); + } + + if (otherSession != null) { + MainChannelDispatcher.send(otherSession, webRTC); + } + } + + } + + private void process(Session session, StatusMessage status) { + if (!MainChannelApplication.hasChannels(status.getRemoteUser())) { + MainChannelDispatcher.send(session, new StatusMessage(ChannelMessagesType.USER_STATUS, status.getRemoteUser(), StatusMessage.USER_STATUS_OFFLINE)); + } else { + MainChannelDispatcher.send(session, new StatusMessage(ChannelMessagesType.USER_STATUS, status.getRemoteUser(), StatusMessage.USER_STATUS_ONLINE)); + } + } + + private void process(Session session, String callerLogin, ChatMessage chat) { + if (!MainChannelApplication.hasChannels(chat.getRemoteUser())) { + MainChannelDispatcher.send(session, new ChatMessage(ChannelMessagesType.CHAT_MESSAGE, chat.getRemoteUser(), "", "", chat.getContext(), ChatMessage.CHAT_MESSAGE_UNREACHABLE)); + } else { + MainChannelDispatcher.sendToAllUserChannels(callerLogin, new ChatMessage(ChannelMessagesType.CHAT_MESSAGE_ACK, chat.getRemoteUser(), callerLogin, chat.getMessage(), chat.getContext(), null)); + MainChannelDispatcher.sendToAllUserChannels(chat.getRemoteUser(), new ChatMessage(ChannelMessagesType.CHAT_MESSAGE, callerLogin, callerLogin, chat.getMessage(), chat.getContext(), null)); + } + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/MainChannelDispatcher.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/MainChannelDispatcher.java new file mode 100644 index 0000000000..653d929b51 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/MainChannelDispatcher.java @@ -0,0 +1,109 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel; + +import com.docdoku.server.mainchannel.module.ChatMessage; +import com.docdoku.server.mainchannel.module.CollaborativeMessage; +import com.docdoku.server.mainchannel.module.StatusMessage; +import com.docdoku.server.mainchannel.module.WebRTCMessage; + +import javax.websocket.Session; +import java.util.Collection; + +public final class MainChannelDispatcher { + + private MainChannelDispatcher(){ + } + + /* Send a message to multiple channels */ + public static void sendToAllUserChannels(String userLogin, ChatMessage message){ + if(userLogin != null && !"".equals(userLogin) && MainChannelApplication.getUserChannels(userLogin) != null) { + Collection sessions = MainChannelApplication.getUserChannels(userLogin).values(); + for(Session session:sessions){ + send(session, message); + } + } + } + + /* Send a message to multiple channels */ + public static void sendToAllUserChannels(String userLogin, WebRTCMessage message){ + if(userLogin != null && !"".equals(userLogin) && MainChannelApplication.getUserChannels(userLogin) != null) { + Collection sessions = MainChannelApplication.getUserChannels(userLogin).values(); + for(Session session:sessions){ + send(session, message); + } + } + } + + /* Send a message to multiple channels */ + public static void sendToAllUserChannels(String userLogin, CollaborativeMessage message){ + if(userLogin != null && !"".equals(userLogin) && MainChannelApplication.getUserChannels(userLogin) != null) { + Collection sessions = MainChannelApplication.getUserChannels(userLogin).values(); + for(Session session:sessions){ + send(session, message); + } + } + } + + + /* Send a message to single channel */ + public static boolean send(Session session, String message){ + if (session != null) { + session.getAsyncRemote().sendText(message); + return true; + } + return false; + } + /* Send a message to single channel */ + public static boolean send(Session session, StatusMessage status){ + if (session != null) { + session.getAsyncRemote().sendObject(status); + return true; + } + return false; + } + + /* Send a message to single channel */ + public static boolean send(Session session, ChatMessage message){ + if (session != null) { + session.getAsyncRemote().sendObject(message); + return true; + } + return false; + } + + /* Send a message to single channel */ + public static boolean send(Session session, WebRTCMessage message){ + if (session != null) { + session.getAsyncRemote().sendObject(message); + return true; + } + return false; + } + + public static boolean send(Session session, CollaborativeMessage message) { + if (session != null) { + session.getAsyncRemote().sendObject(message); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/collaborative/CollaborativeRoom.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/collaborative/CollaborativeRoom.java new file mode 100644 index 0000000000..3613c0a65b --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/collaborative/CollaborativeRoom.java @@ -0,0 +1,205 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.collaborative; + + +import javax.json.*; +import javax.websocket.Session; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A room of Collaboration Module + * + * @author Arthur FRIN + * @version 1.0, 30/06/14 + * @since V2.0 + */ +public class CollaborativeRoom { + private static final ConcurrentMap DB = new ConcurrentHashMap<>(); + private String key; + private Session master; + private List slaves; + private List pendingUsers; + private Date creationDate; + private JsonObjectBuilder saveJsonCommands; + private String lastMaster; + + public CollaborativeRoom(Session master) { + this.key = UUID.randomUUID().toString(); + this.master = master; + this.pendingUsers = new LinkedList<>(); + this.creationDate = new Date(); + this.slaves = new LinkedList<>(); + this.lastMaster = getMasterName(); + this.saveJsonCommands = Json.createObjectBuilder(); + put(); + } + + /** Retrieve a {@link com.docdoku.server.mainchannel.collaborative.CollaborativeRoom} instance from database */ + public static CollaborativeRoom getByKeyName(String roomKey) { + if(roomKey==null) { + return null; + } + return DB.get(roomKey); + } + + public static Set getAllCollaborativeRooms() { + + return new HashSet<>(DB.values()); + } + + + public String getLastMaster() { + return lastMaster; + } + + public void setLastMaster(String lastMaster) { + this.lastMaster = lastMaster; + } + + @Override + public String toString() { + return this.getContext().toString(); + } + + public JsonObject getContext() { + JsonArrayBuilder contextSlaves = Json.createArrayBuilder(); + for (Session s : this.getSlaves()) { + contextSlaves.add(s.getUserPrincipal().getName()); + } + + JsonArrayBuilder contextPendingUsers = Json.createArrayBuilder(); + for (String s : this.getPendingUsers()) { + contextPendingUsers.add(s); + } + + return Json.createObjectBuilder() + .add("master", this.getMasterName()) + .add("lastMaster", this.getLastMaster()) + .add("users", contextSlaves) + .add("pendingUsers", contextPendingUsers).build(); + } + + /** Store current instance into database */ + public void put() { + DB.put(key, this); + } + + /** Delete/Remove current {@link com.docdoku.server.mainchannel.util.Room} instance from database */ + public void delete() { + if (key != null) { + DB.remove(key); + key = null; + } + } + + public Session getMaster() { + return master; + } + + public String getMasterName() { + return (master==null)?"":master.getUserPrincipal().getName(); + } + + public void setMaster(Session master) { + this.master = master; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public List getSlaves() { + return slaves; + } + + public void addSlave(Session slave) { + this.slaves.add(slave); + } + + public boolean removeSlave(Session slave) { + return this.slaves.remove(slave); + } + + public List getPendingUsers() { + return pendingUsers; + } + + public void addPendingUser(String user){ + this.pendingUsers.add(user); + } + + public boolean removePendingUser(String user){ + return this.pendingUsers.remove(user); + } + + public Date getCreationDate() { + return (creationDate!=null) ? (Date) creationDate.clone() : null; + } + + public Session findUserSession(String user){ + Session userSession = null; + for (Session s : this.getSlaves()) { + if (s.getUserPrincipal().getName().equals(user)) { + userSession = s; + } + } + return userSession; + } + + public JsonObject getCommands() { + return saveJsonCommands.build(); + } + + public void saveCommand(JsonObject command) { + final String cameraInfosField = "cameraInfos"; + final String smartPath = "smartPath"; + final String editedObjects = "editedObjects"; + final String colourEditedMeshes = "colourEditedObjects"; + final String explode = "explode"; + final String clipping = "clipping"; + final String measures = "measures"; + + if (command.containsKey(cameraInfosField)) { + saveJsonCommands.add(cameraInfosField,command.getJsonObject(cameraInfosField)); + } else if (command.containsKey(smartPath)) { + JsonValue path = command.getJsonArray(smartPath); + path = ((JsonArray) path).isEmpty() ? JsonValue.NULL : path; + saveJsonCommands.add(smartPath, path); + } else if (command.containsKey(editedObjects)) { + saveJsonCommands.add(editedObjects,command.getJsonArray(editedObjects)); + } else if (command.containsKey(colourEditedMeshes)) { + saveJsonCommands.add(colourEditedMeshes,command.getBoolean(colourEditedMeshes)); + } else if (command.containsKey(explode)) { + saveJsonCommands.add(explode,command.getString(explode)); + } else if (command.containsKey(clipping)) { + saveJsonCommands.add(clipping,command.getString(clipping)); + }else if (command.containsKey(measures)) { + saveJsonCommands.add(measures,command.getJsonArray(measures)); + } + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/AbstractMessage.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/AbstractMessage.java new file mode 100644 index 0000000000..0609011c2b --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/AbstractMessage.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.module; + +import java.io.Serializable; + +public class AbstractMessage implements Serializable{ + protected String type; + protected String remoteUser; + + public AbstractMessage(){ + + } + public AbstractMessage(String type, String remoteUser) { + this.type = type; + this.remoteUser = remoteUser; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getRemoteUser() { + return remoteUser; + } + + public void setRemoteUser(String remoteUser) { + this.remoteUser = remoteUser; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/ChatMessage.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/ChatMessage.java new file mode 100644 index 0000000000..72352503c6 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/ChatMessage.java @@ -0,0 +1,90 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.module; + + + +public class ChatMessage extends AbstractMessage{ + + + public static final String CHAT_MESSAGE_UNREACHABLE = "UNREACHABLE"; + + private String sender; + private String message; + private String context; + private String error; + + public ChatMessage(){ + } + + public ChatMessage(String type, String remoteUser){ + super(type,remoteUser); + } + public ChatMessage(String type, String remoteUser, String sender, String message, String context, String error) { + super(type,remoteUser); + this.sender = sender; + this.message = message; + this.context = context != null ? context : ""; + this.error = error; + } + + @Override + public String getType() { + return type; + } + + @Override + public void setType(String type) { + this.type = type; + } + + public String getSender() { + return sender; + } + + public void setSender(String sender) { + this.sender = sender; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getContext() { + return context; + } + + public void setContext(String context) { + this.context = context; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/ChatMessageEncoder.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/ChatMessageEncoder.java new file mode 100644 index 0000000000..763b13d6e6 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/ChatMessageEncoder.java @@ -0,0 +1,57 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.module; + + +import javax.json.Json; +import javax.json.JsonObjectBuilder; +import javax.websocket.EncodeException; +import javax.websocket.Encoder; +import javax.websocket.EndpointConfig; + + +public class ChatMessageEncoder implements Encoder.Text { + @Override + public String encode(ChatMessage chatMessage) throws EncodeException { + JsonObjectBuilder b = Json.createObjectBuilder() + .add("type", chatMessage.getType()) + .add("remoteUser", chatMessage.getRemoteUser()) + .add("sender",chatMessage.getSender()) + .add("message", chatMessage.getMessage()) + .add("context",chatMessage.getContext()); + + if(chatMessage.getError()!=null) + b.add("error", chatMessage.getError()); + + return b.build().toString(); + + } + + @Override + public void init(EndpointConfig endpointConfig) { + // Nothing to do + } + + @Override + public void destroy() { + // Nothing to do + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/CollaborativeMessage.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/CollaborativeMessage.java new file mode 100644 index 0000000000..ba7b721b6e --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/CollaborativeMessage.java @@ -0,0 +1,60 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.module; + +import javax.json.JsonObject; + +public class CollaborativeMessage extends AbstractMessage{ + + private String key; + private JsonObject messageBroadcast; + + public CollaborativeMessage(String type, String remoteUser) { + super(type,remoteUser); + } + + public CollaborativeMessage(String type, String key, JsonObject messageBroadcast, String remoteUser) { + super(type,remoteUser); + this.messageBroadcast = messageBroadcast; + this.key = key; + } + + public JsonObject getMessageBroadcast() { + return messageBroadcast; + } + + public void setKey(String key) { + this.key = key; + } + + public void setMessageBroadcast(JsonObject messageBroadcast) { + this.messageBroadcast = messageBroadcast; + } + + @Override + public String getType() { + return type; + } + + public String getKey() { + return key; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/CollaborativeMessageEncoder.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/CollaborativeMessageEncoder.java new file mode 100644 index 0000000000..fff7b2464c --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/CollaborativeMessageEncoder.java @@ -0,0 +1,59 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.module; + + +import javax.json.Json; +import javax.json.JsonObjectBuilder; +import javax.json.JsonValue; +import javax.websocket.EncodeException; +import javax.websocket.Encoder; +import javax.websocket.EndpointConfig; + + +public class CollaborativeMessageEncoder implements Encoder.Text { + + @Override + public String encode(CollaborativeMessage collaborativeMessage) throws EncodeException { + JsonObjectBuilder b = Json.createObjectBuilder() + .add("type", collaborativeMessage.getType()) + .add("remoteUser", collaborativeMessage.getRemoteUser()) + .add("key", collaborativeMessage.getKey()); + if(collaborativeMessage.getMessageBroadcast()==null){ + b.add("messageBroadcast", JsonValue.NULL); + } else { + b.add("messageBroadcast", collaborativeMessage.getMessageBroadcast()); + } + String a = b.build().toString(); + return a; + + } + + @Override + public void init(EndpointConfig endpointConfig) { + // Nothing to do + } + + @Override + public void destroy() { + // Nothing to do + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/MessageDecoder.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/MessageDecoder.java new file mode 100644 index 0000000000..c93ddeb27f --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/MessageDecoder.java @@ -0,0 +1,122 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.module; + + +import com.docdoku.server.mainchannel.util.ChannelMessagesType; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonValue; +import javax.websocket.DecodeException; +import javax.websocket.Decoder; +import javax.websocket.EndpointConfig; +import java.io.StringReader; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class MessageDecoder implements Decoder.Text{ + + @Override + public AbstractMessage decode(String s) throws DecodeException { + JsonObject jsObj = Json.createReader(new StringReader(s)).readObject(); + String remoteUser = jsObj.getString("remoteUser"); + String type = jsObj.getString("type"); + JsonValue jsonValue = jsObj.get("context"); + String context = JsonValue.NULL.equals(jsonValue) ? null : jsonValue.toString(); + AbstractMessage message=null; + switch(type){ + + case ChannelMessagesType.USER_STATUS: + StatusMessage statusMsg=new StatusMessage(type, remoteUser); + String status = jsObj.containsKey("status")?jsObj.getString("status"):null; + statusMsg.setStatus(status); + message=statusMsg; + break; + case ChannelMessagesType.CHAT_MESSAGE: + ChatMessage chatMsg=new ChatMessage(type, remoteUser); + String contentMessage = jsObj.containsKey("message")?jsObj.getString("message"):null; + chatMsg.setContext(context); + chatMsg.setMessage(contentMessage); + message=chatMsg; + break; + case ChannelMessagesType.WEBRTC_INVITE: + case ChannelMessagesType.WEBRTC_ACCEPT: + case ChannelMessagesType.WEBRTC_REJECT: + case ChannelMessagesType.WEBRTC_HANGUP: + case ChannelMessagesType.WEBRTC_ANSWER: + case ChannelMessagesType.WEBRTC_BYE: + case ChannelMessagesType.WEBRTC_CANDIDATE: + case ChannelMessagesType.WEBRTC_OFFER: + WebRTCMessage webRTCMsg=new WebRTCMessage(type,remoteUser); + String roomKey = jsObj.containsKey("roomKey")?jsObj.getString("roomKey"):null; + String reason = jsObj.containsKey("reason")?jsObj.getString("reason"):null; + String sdp = jsObj.containsKey("sdp")?jsObj.getString("sdp"):null; + String id = jsObj.containsKey("id")?jsObj.getString("id"):null; + String candidate = jsObj.containsKey("candidate")?jsObj.getString("candidate"):null; + Integer label = jsObj.containsKey("label")?jsObj.getInt("label"):null; + webRTCMsg.setContext(context); + webRTCMsg.setReason(reason); + webRTCMsg.setRoomKey(roomKey); + webRTCMsg.setSignals(sdp,id,candidate,label); + message=webRTCMsg; + break; + case ChannelMessagesType.COLLABORATIVE_CREATE: + case ChannelMessagesType.COLLABORATIVE_INVITE: + case ChannelMessagesType.COLLABORATIVE_COMMANDS: + case ChannelMessagesType.COLLABORATIVE_CONTEXT: + case ChannelMessagesType.COLLABORATIVE_EXIT: + case ChannelMessagesType.COLLABORATIVE_GIVE_HAND: + case ChannelMessagesType.COLLABORATIVE_JOIN: + case ChannelMessagesType.COLLABORATIVE_KICK_USER: + case ChannelMessagesType.COLLABORATIVE_KICK_NOT_INVITED: + case ChannelMessagesType.COLLABORATIVE_KILL: + case ChannelMessagesType.COLLABORATIVE_WITHDRAW_INVITATION: + CollaborativeMessage collaborativeMessage = new CollaborativeMessage(type,remoteUser); + String key = jsObj.containsKey("key")?jsObj.getString("key"):null; + JsonObject messageBroadcast = jsObj.containsKey("messageBroadcast")?jsObj.getJsonObject("messageBroadcast"):null; + collaborativeMessage.setMessageBroadcast(messageBroadcast); + collaborativeMessage.setKey(key); + message=collaborativeMessage; + break; + default : + Logger.getLogger(MessageDecoder.class.getName()).log(Level.WARNING, "Type of message not recognized."+type); + break; + } + return message; + } + + @Override + public boolean willDecode(String s) { + JsonObject jsObj = Json.createReader(new StringReader(s)).readObject(); + return jsObj.containsKey("type") && jsObj.containsKey("remoteUser"); + } + + @Override + public void init(EndpointConfig endpointConfig) { + // Nothing to do + } + + @Override + public void destroy() { + // Nothing to do + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/StatusMessage.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/StatusMessage.java new file mode 100644 index 0000000000..5650a8f225 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/StatusMessage.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.module; + + + +public class StatusMessage extends AbstractMessage{ + + private String status; + + public static final String USER_STATUS_OFFLINE = "OFFLINE"; + public static final String USER_STATUS_ONLINE = "ONLINE"; + + public StatusMessage(){ + + } + + public StatusMessage(String type, String remoteUser){ + super(type,remoteUser); + } + public StatusMessage(String type, String remoteUser, String status) { + super(type,remoteUser); + this.status = status; + } + + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/StatusMessageEncoder.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/StatusMessageEncoder.java new file mode 100644 index 0000000000..eadc878096 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/StatusMessageEncoder.java @@ -0,0 +1,50 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.module; + + +import javax.json.Json; +import javax.websocket.EncodeException; +import javax.websocket.Encoder; +import javax.websocket.EndpointConfig; + + +public class StatusMessageEncoder implements Encoder.Text { + + @Override + public String encode(StatusMessage statusMessage) throws EncodeException { + return Json.createObjectBuilder() + .add("type", statusMessage.getType()) + .add("remoteUser", statusMessage.getRemoteUser()) + .add("status", statusMessage.getStatus()).build().toString(); + + } + + @Override + public void init(EndpointConfig endpointConfig) { + // Nothing to do + } + + @Override + public void destroy() { + // Nothing to do + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/WebRTCMessage.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/WebRTCMessage.java new file mode 100644 index 0000000000..3b89b8176a --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/WebRTCMessage.java @@ -0,0 +1,129 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.module; + + + +public class WebRTCMessage extends AbstractMessage{ + + public static final String WEBRTC_OFFLINE = "OFFLINE"; + + private String roomKey; + private String reason; + private String context; + private Integer roomOccupancy; + private String userLogin; + private String sdp; + private Integer label; + private String id; + private String candidate; + + public WebRTCMessage(){ + + } + + public WebRTCMessage(String type, String remoteUser){ + super(type,remoteUser); + } + public WebRTCMessage(String type, String remoteUser, String roomKey, String reason, String context, Integer roomOccupancy, String userLogin) { + super(type,remoteUser); + this.roomKey = roomKey; + this.reason = reason; + this.context = context; + this.roomOccupancy = roomOccupancy; + this.userLogin = userLogin; + } + + + public String getRoomKey() { + return roomKey; + } + public void setRoomKey(String roomKey) { + this.roomKey = roomKey; + } + + public String getReason() { + return reason; + } + public void setReason(String reason) { + this.reason = reason; + } + + public String getContext() { + return context; + } + public void setContext(String context) { + this.context = context; + } + + public Integer getRoomOccupancy() { + return roomOccupancy; + } + public void setRoomOccupancy(Integer roomOccupancy) { + this.roomOccupancy = roomOccupancy; + } + + public String getUserLogin() { + return userLogin; + } + public void setUserLogin(String userLogin) { + this.userLogin = userLogin; + } + + public static String getWebrtcOffline() { + return WEBRTC_OFFLINE; + } + + public String getSdp() { + return sdp; + } + public void setSdp(String sdp) { + this.sdp = sdp; + } + + public Integer getLabel() { + return label; + } + public void setLabel(Integer label) { + this.label = label; + } + + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + + public String getCandidate() { + return candidate; + } + public void setCandidate(String candidate) { + this.candidate = candidate; + } + + public void setSignals(String sdp, String id, String candidate, Integer label){ + this.sdp=sdp; + this.id=id; + this.candidate=candidate; + this.label=label; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/WebRTCMessageEncoder.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/WebRTCMessageEncoder.java new file mode 100644 index 0000000000..a83a83dce9 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/module/WebRTCMessageEncoder.java @@ -0,0 +1,79 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.module; + +import javax.json.Json; +import javax.json.JsonObjectBuilder; +import javax.websocket.EncodeException; +import javax.websocket.Encoder; +import javax.websocket.EndpointConfig; + + +public class WebRTCMessageEncoder implements Encoder.Text { + + @Override + public String encode(WebRTCMessage webRTCMessage) throws EncodeException { + JsonObjectBuilder jsonObject = Json.createObjectBuilder(); + jsonObject.add("type", webRTCMessage.getType()); + if(webRTCMessage.getRemoteUser()!=null){ + jsonObject.add("remoteUser", webRTCMessage.getRemoteUser()); + } + if(webRTCMessage.getRoomKey()!=null){ + jsonObject.add("roomKey", webRTCMessage.getRoomKey()); + } + if(webRTCMessage.getReason()!=null){ + jsonObject.add("reason", webRTCMessage.getReason()); + } + if(webRTCMessage.getContext()!=null){ + jsonObject.add("context", webRTCMessage.getContext()); + } + if(webRTCMessage.getRoomOccupancy()!=null){ + jsonObject.add("roomOccupancy", webRTCMessage.getRoomOccupancy()); + } + if(webRTCMessage.getUserLogin()!=null){ + jsonObject.add("userLogin", webRTCMessage.getUserLogin()); + } + if(webRTCMessage.getSdp()!=null){ + jsonObject.add("sdp", webRTCMessage.getSdp()); + } + if(webRTCMessage.getId()!=null){ + jsonObject.add("id", webRTCMessage.getId()); + } + if(webRTCMessage.getCandidate()!=null){ + jsonObject.add("candidate", webRTCMessage.getCandidate()); + } + if(webRTCMessage.getLabel()!=null){ + jsonObject.add("label", webRTCMessage.getLabel()); + } + + return jsonObject.build().toString(); + } + + @Override + public void init(EndpointConfig endpointConfig) { + // Nothing to do + } + + @Override + public void destroy() { + // Nothing to do + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/util/ChannelMessagesBuilder.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/util/ChannelMessagesBuilder.java new file mode 100644 index 0000000000..9e4d5a810b --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/util/ChannelMessagesBuilder.java @@ -0,0 +1,38 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.util; + +import javax.json.Json; + +public class ChannelMessagesBuilder { + + private ChannelMessagesBuilder() { + } + + // Peer declaration + public static String buildWelcomeMessage(String userLogin) { + return Json.createObjectBuilder() + .add("type", "listen") + .add("text", "welcome " + userLogin).build().toString(); + } + + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/util/ChannelMessagesType.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/util/ChannelMessagesType.java new file mode 100644 index 0000000000..2837398ce5 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/util/ChannelMessagesType.java @@ -0,0 +1,57 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.util; + +public class ChannelMessagesType { + + public static final String USER_STATUS = "USER_STATUS"; + + public static final String CHAT_MESSAGE = "CHAT_MESSAGE"; + public static final String CHAT_MESSAGE_ACK = "CHAT_MESSAGE_ACK"; + + public static final String WEBRTC_INVITE = "WEBRTC_INVITE"; + public static final String WEBRTC_ACCEPT = "WEBRTC_ACCEPT"; + public static final String WEBRTC_REJECT = "WEBRTC_REJECT"; + public static final String WEBRTC_HANGUP = "WEBRTC_HANGUP"; + public static final String WEBRTC_ROOM_JOIN_EVENT = "WEBRTC_ROOM_JOIN_EVENT"; + public static final String WEBRTC_ROOM_REJECT_EVENT = "WEBRTC_ROOM_REJECT_EVENT"; + + public static final String WEBRTC_OFFER = "offer"; + public static final String WEBRTC_ANSWER = "answer"; + public static final String WEBRTC_BYE = "bye"; + public static final String WEBRTC_CANDIDATE = "candidate"; + + public static final String COLLABORATIVE_CREATE = "COLLABORATIVE_CREATE"; + public static final String COLLABORATIVE_INVITE = "COLLABORATIVE_INVITE"; + public static final String COLLABORATIVE_JOIN = "COLLABORATIVE_JOIN"; + public static final String COLLABORATIVE_CONTEXT = "COLLABORATIVE_CONTEXT"; + public static final String COLLABORATIVE_COMMANDS = "COLLABORATIVE_COMMANDS"; + public static final String COLLABORATIVE_EXIT = "COLLABORATIVE_EXIT"; + public static final String COLLABORATIVE_KILL = "COLLABORATIVE_KILL"; + public static final String COLLABORATIVE_GIVE_HAND = "COLLABORATIVE_GIVE_HAND"; + public static final String COLLABORATIVE_KICK_USER = "COLLABORATIVE_KICK_USER"; + public static final String COLLABORATIVE_KICK_NOT_INVITED= "COLLABORATIVE_KICK_NOT_INVITED"; + public static final String COLLABORATIVE_WITHDRAW_INVITATION = "COLLABORATIVE_WITHDRAW_INVITATION"; + + private ChannelMessagesType() { + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/util/Room.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/util/Room.java new file mode 100644 index 0000000000..cf675526ca --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/mainchannel/util/Room.java @@ -0,0 +1,232 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.util; + +import javax.websocket.Session; +import java.security.Principal; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + + +public class Room { + + private static final ConcurrentMap DB = new ConcurrentHashMap<>(); + + private String keyName; + private Session userSession1; + private Session userSession2; + + public Room(String roomKey) { + keyName = roomKey; + put(); + } + + public String getUser1Login(){ + Principal userPrincipal = userSession1.getUserPrincipal(); + return userPrincipal.getName(); + } + public String getUser2Login(){ + Principal userPrincipal = userSession2.getUserPrincipal(); + return userPrincipal.getName(); + } + + /** Retrieve a {@link com.docdoku.server.mainchannel.util.Room} instance from database */ + public static Room getByKeyName(String roomKey) { + if(roomKey==null) { + return null; + } + return DB.get(roomKey); + } + + /** @return a {@link String} representation of this room */ + @Override + public String toString() { + String str = "["; + if (userSession1 != null) { + str += getUser1Login(); + } + if (userSession2 != null) { + str += ", " + getUser2Login(); + } + str += "]"; + return str; + } + + /** @return number of participant in this room */ + public int getOccupancy() { + int occupancy = 0; + if (userSession1 != null) { + occupancy += 1; + } + if (userSession2 != null) { + occupancy += 1; + } + return occupancy; + } + + /** @return the name of the other participant, null if none */ + public Session getOtherUserSession(Session userSession) { + if (userSession.equals(userSession1)) { + return userSession2; + } else if (userSession.equals(userSession2)) { + return userSession1; + } else { + return null; + } + } + + /** @return true if one the participant is named as the input parameter, false otherwise */ + public boolean hasUser(String user) { + + if(user != null) { + if(userSession1 != null && user.equals(getUser1Login())){ + return true; + } + + if(userSession2 != null && user.equals(getUser2Login())){ + return true; + } + + } + return false; + } + + /** @return true if one the participant is named as the input parameter, false otherwise */ + public Session getUserSession(String user) { + + if(user != null) { + if(userSession1 != null && user.equals(getUser1Login())){ + return userSession1; + } + + if(userSession2 != null && user.equals(getUser2Login())){ + return userSession2; + } + + } + return null; + } + + /** Removed a participant form current room */ + public void removeUser(String user) { + + if(user != null) { + + if(userSession1 != null && user.equals(getUser1Login())){ + removeUserSession(userSession1); + } + + if(userSession2 != null && user.equals(getUser2Login())){ + removeUserSession(userSession2); + } + + } + + } + + /** Add a new participant to this room + * @return if participant is found */ + public boolean addUserSession(Session userSession) { + boolean success = true; + + // avoid a user to be added in the room many times. + if(userSession != null && (userSession.equals(userSession1) || userSession.equals(userSession2))){ + return true; + } + + if (userSession1 == null) { + userSession1 = userSession; + } else if (userSession2 == null) { + userSession2 = userSession; + } else { + // room is full, shouldn't happen + success = false; + } + + return success; + } + + /** Removed a participant form current room */ + public void removeUserSession(Session userSession) { + if (userSession != null && userSession.equals(userSession2)) { + userSession2 = null; + } + + if (userSession != null && userSession.equals(userSession1)) { + // Todo check why this if always true + if (userSession1 != null) { + userSession1 = userSession2; + userSession2 = null; + } else { + userSession1 = null; + } + } + + // auto delete ? + if (getOccupancy() > 0) { + put(); + } else { + delete(); + } + + } + + /**@return the key of this room. */ + public String key() { + return keyName; + } + + /** Store current instance into database */ + public void put() { + DB.put(keyName, this); + } + + /** Delete/Remove current {@link com.docdoku.server.mainchannel.util.Room} instance from database */ + public void delete() { + if (keyName != null) { + DB.remove(keyName); + keyName = null; + } + } + + public Session getSessionForUserLogin(String userLogin){ + if (userSession1 != null && userLogin.equals(getUser1Login())){ + return userSession1; + } else if (userSession2 != null && userLogin.equals(getUser2Login())){ + return userSession2; + } + + return null; + } + + public static void removeUserFromAllRoom(String callerLogin) { + Set> roomsEntries = new HashSet<>(DB.entrySet()); + for (Map.Entry entry : roomsEntries) { + Session session = entry.getValue().getSessionForUserLogin(callerLogin); + if (session != null) { + entry.getValue().removeUserSession(session); + } + } + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/AccountResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/AccountResource.java new file mode 100644 index 0000000000..3775f45cb8 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/AccountResource.java @@ -0,0 +1,157 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IAccountManagerLocal; +import com.docdoku.core.services.IContextManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.jwt.JWTokenFactory; +import com.docdoku.server.rest.dto.AccountDTO; +import com.docdoku.server.rest.dto.GCMAccountDTO; +import com.docdoku.server.rest.dto.WorkspaceDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +@RequestScoped +@Path("accounts") +@Api(value = "accounts", description = "Operations about accounts") +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID,UserGroupMapping.ADMIN_ROLE_ID}) +@RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID,UserGroupMapping.ADMIN_ROLE_ID}) +public class AccountResource { + + @Inject + private IAccountManagerLocal accountManager; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IContextManagerLocal contextManager; + + private Mapper mapper; + + public AccountResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @Path("/me") + @ApiOperation(value = "Get authenticated user's account", response = AccountDTO.class) + @Produces(MediaType.APPLICATION_JSON) + public AccountDTO getAccount() throws AccountNotFoundException { + Account account = accountManager.getMyAccount(); + AccountDTO accountDTO = mapper.map(account, AccountDTO.class); + if(contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID)){ + accountDTO.setAdmin(true); + } + return accountDTO; + } + + @PUT + @Path("/me") + @ApiOperation(value = "Update user's account", response = AccountDTO.class) + @Produces(MediaType.APPLICATION_JSON) + public AccountDTO updateAccount(@ApiParam(required = true,value = "Updated account") AccountDTO accountDTO) throws AccountNotFoundException { + Account account = accountManager.updateAccount(accountDTO.getName(), accountDTO.getEmail(), accountDTO.getLanguage(), accountDTO.getNewPassword(), accountDTO.getTimeZone()); + return mapper.map(account,AccountDTO.class); + } + + @POST + @Path("/create") + @ApiOperation(value = "Create user's account", response = AccountDTO.class) + @Produces(MediaType.APPLICATION_JSON) + public Response createAccount(@Context HttpServletRequest request, @ApiParam(required = true,value = "Account to create") AccountDTO accountDTO) throws AccountAlreadyExistsException, CreationException { + Account account = accountManager.createAccount(accountDTO.getLogin(), accountDTO.getName(), accountDTO.getEmail(), accountDTO.getLanguage(), accountDTO.getNewPassword(), accountDTO.getTimeZone()); + HttpSession session = request.getSession(); + try { + request.login(accountDTO.getLogin(), accountDTO.getNewPassword()); + return Response.ok() + .entity(mapper.map(account, AccountDTO.class)) + .header("jwt", JWTokenFactory.createToken(account)) + .build(); + } catch (ServletException e) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } + + @GET + @Path("/workspaces") + @ApiOperation(value = "Get workspaces where authenticated user is active", response = WorkspaceDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getWorkspaces() { + Workspace[] workspaces = userManager.getWorkspacesWhereCallerIsActive(); + + List workspaceDTOs = new ArrayList<>(); + for (Workspace workspace : workspaces) { + workspaceDTOs.add(mapper.map(workspace, WorkspaceDTO.class)); + } + + return Response.ok(new GenericEntity>((List) workspaceDTOs) { + }).build(); + + } + + @PUT + @Path("gcm") + @ApiOperation(value = "Update GCM account for authenticated user", response = Response.class, code = 200) + @Consumes(MediaType.APPLICATION_JSON) + public Response setGCMAccount(@ApiParam(required = true, value = "GCM account to set") GCMAccountDTO data) + throws EntityAlreadyExistsException, AccountNotFoundException, CreationException { + accountManager.setGCMAccount(data.getGcmId()); + return Response.ok().build(); + } + + + @DELETE + @Path("gcm") + @ApiOperation(value = "Update GCM account for authenticated user", response = Response.class, code = 200) + public Response deleteGCMAccount() throws EntityNotFoundException { + accountManager.deleteGCMAccount(); + return Response.ok().build(); + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/AdminResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/AdminResource.java new file mode 100644 index 0000000000..1cd9db02e0 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/AdminResource.java @@ -0,0 +1,183 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest; + +import com.docdoku.core.common.Workspace; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.core.services.IWorkspaceManagerLocal; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.Serializable; + + +@RequestScoped +@Api(value = "admin", description = "Admin resources") +@Path("admin") +@DeclareRoles(UserGroupMapping.ADMIN_ROLE_ID) +@RolesAllowed(UserGroupMapping.ADMIN_ROLE_ID) +public class AdminResource implements Serializable { + + @Inject + private IWorkspaceManagerLocal workspaceService; + + @Inject + private IDocumentManagerLocal documentService; + + @Inject + private IProductManagerLocal productService; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IWorkspaceManagerLocal workspaceManager; + + public AdminResource(){ + + } + + @GET + @Path("disk-usage-stats") + @ApiOperation(value = "Get disk usage stats", response = JsonObject.class) + @Produces(MediaType.APPLICATION_JSON) + public JsonObject getDiskSpaceUsageStats() throws AccountNotFoundException { + + JsonObjectBuilder diskUsage = Json.createObjectBuilder(); + + Workspace[] allWorkspaces = userManager.getAdministratedWorkspaces(); + + for(Workspace workspace:allWorkspaces){ + long workspaceDiskUsage = workspaceService.getDiskUsageInWorkspace(workspace.getId()); + diskUsage.add(workspace.getId(), workspaceDiskUsage); + } + + return diskUsage.build(); + + } + @GET + @Path("users-stats") + @ApiOperation(value = "Get users stats", response = JsonObject.class) + @Produces(MediaType.APPLICATION_JSON) + public JsonObject getUsersStats() throws AccountNotFoundException, WorkspaceNotFoundException, AccessRightException, UserNotFoundException, UserNotActiveException { + + JsonObjectBuilder userStats = Json.createObjectBuilder(); + + Workspace[] allWorkspaces = userManager.getAdministratedWorkspaces(); + + for(Workspace workspace:allWorkspaces){ + int userCount = userManager.getUsers(workspace.getId()).length; + userStats.add(workspace.getId(), userCount); + } + + return userStats.build(); + + } + @GET + @Path("documents-stats") + @ApiOperation(value = "Get documents stats", response = JsonObject.class) + @Produces(MediaType.APPLICATION_JSON) + public JsonObject getDocumentsStats() throws AccountNotFoundException, WorkspaceNotFoundException, AccessRightException { + + JsonObjectBuilder docStats = Json.createObjectBuilder(); + + Workspace[] allWorkspaces = userManager.getAdministratedWorkspaces(); + + for(Workspace workspace:allWorkspaces){ + int documentsCount = documentService.getTotalNumberOfDocuments(workspace.getId()); + docStats.add(workspace.getId(), documentsCount); + } + + return docStats.build(); + + } + @GET + @Path("products-stats") + @ApiOperation(value = "Get products stats", response = JsonObject.class) + @Produces(MediaType.APPLICATION_JSON) + public JsonObject getProductsStats() throws AccountNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + + JsonObjectBuilder productsStats = Json.createObjectBuilder(); + + Workspace[] allWorkspaces = userManager.getAdministratedWorkspaces(); + + for(Workspace workspace:allWorkspaces){ + int productsCount = productService.getConfigurationItems(workspace.getId()).size(); + productsStats.add(workspace.getId(), productsCount); + } + + return productsStats.build(); + + } + + @GET + @Path("parts-stats") + @ApiOperation(value = "Get parts stats", response = JsonObject.class) + @Produces(MediaType.APPLICATION_JSON) + public JsonObject getPartsStats() throws AccountNotFoundException, AccessRightException, WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + + JsonObjectBuilder partsStats = Json.createObjectBuilder(); + + Workspace[] allWorkspaces = userManager.getAdministratedWorkspaces(); + + for(Workspace workspace:allWorkspaces){ + int productsCount = productService.getTotalNumberOfParts(workspace.getId()); + partsStats.add(workspace.getId(), productsCount); + } + + return partsStats.build(); + } + + + @PUT + @ApiOperation(value = "Synchronize index for workspace") + @Path("index/{workspaceId}") + public Response indexWorkspace(@PathParam("workspaceId") String workspaceId){ + workspaceManager.synchronizeIndexer(workspaceId); + return Response.ok().build(); + + } + + @PUT + @ApiOperation(value = "Synchronize index for all workspaces") + @Path("index-all") + public Response indexAllWorkspaces() throws AccountNotFoundException { + Workspace[] administratedWorkspaces = userManager.getAdministratedWorkspaces(); + for(Workspace workspace : administratedWorkspaces){ + workspaceManager.synchronizeIndexer(workspace.getId()); + } + return Response.ok().build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/AttributesResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/AttributesResource.java new file mode 100644 index 0000000000..a49742d891 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/AttributesResource.java @@ -0,0 +1,106 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.exceptions.UserNotActiveException; +import com.docdoku.core.exceptions.UserNotFoundException; +import com.docdoku.core.exceptions.WorkspaceNotFoundException; +import com.docdoku.core.meta.InstanceAttributeDescriptor; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.server.rest.dto.InstanceAttributeDescriptorDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Morgan Guimard + */ + +@RequestScoped +@Api(hidden = true, value = "attributes", description = "Operations about attributes") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class AttributesResource { + + @Inject + private IProductManagerLocal productManager; + + private Mapper mapper; + + public AttributesResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @Path("part-iterations") + @ApiOperation(value = "Get parts attributes list for given workspace", + response = InstanceAttributeDescriptorDTO.class, + responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getPartIterationsAttributes(@PathParam("workspaceId") String workspaceId) + throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + List attributes = productManager.getPartIterationsInstanceAttributesInWorkspace(workspaceId); + List dtos = new ArrayList<>(); + for (InstanceAttributeDescriptor descriptor : attributes) { + dtos.add(mapper.map(descriptor, InstanceAttributeDescriptorDTO.class)); + } + + return Response.ok(new GenericEntity>((List) dtos) { + }).build(); + } + + @GET + @Path("path-data") + @ApiOperation(value = "Get path data attributes list for given workspace", + response = InstanceAttributeDescriptorDTO.class, + responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getPathDataAttributes(@PathParam("workspaceId") String workspaceId) + throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + List attributes = productManager.getPathDataInstanceAttributesInWorkspace(workspaceId); + List dtos = new ArrayList<>(); + for (InstanceAttributeDescriptor descriptor : attributes) { + dtos.add(mapper.map(descriptor, InstanceAttributeDescriptorDTO.class)); + } + return Response.ok(new GenericEntity>((List) dtos) { + }).build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/AuthResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/AuthResource.java new file mode 100644 index 0000000000..b34ca9fda5 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/AuthResource.java @@ -0,0 +1,114 @@ +package com.docdoku.server.rest; + +import com.docdoku.core.common.Account; +import com.docdoku.core.exceptions.AccountNotFoundException; +import com.docdoku.core.exceptions.PasswordRecoveryRequestNotFoundException; +import com.docdoku.core.services.IAccountManagerLocal; +import com.docdoku.core.services.IContextManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.jwt.JWTokenFactory; +import com.docdoku.server.rest.dto.AccountDTO; +import com.docdoku.server.rest.dto.LoginRequestDTO; +import com.docdoku.server.rest.dto.PasswordRecoverDTO; +import com.docdoku.server.rest.dto.PasswordRecoveryRequestDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.logging.Logger; + +@RequestScoped +@Path("auth") +@Api(value = "auth", description = "Operations about authentication") +public class AuthResource { + + @Inject + private IAccountManagerLocal accountManager; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IContextManagerLocal contextManager; + + private static final Logger LOGGER = Logger.getLogger(AuthResource.class.getName()); + private Mapper mapper; + + public AuthResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @POST + @Path("/login") + @ApiOperation(value = "Try to authenticate", response = AccountDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response login(@Context HttpServletRequest request, @ApiParam(required = true,value = "Login request") LoginRequestDTO loginRequestDTO) throws AccountNotFoundException { + + if (request.getUserPrincipal() != null){ + try { + request.logout(); + } catch (ServletException e) { + e.printStackTrace(); + } + } + + HttpSession session = request.getSession(); + + try { + request.login(loginRequestDTO.getLogin(),loginRequestDTO.getPassword()); + Account account = accountManager.getAccount(loginRequestDTO.getLogin()); + return Response.ok() + .entity(mapper.map(account,AccountDTO.class)) + .header("jwt", JWTokenFactory.createToken(account)) + .build(); + } catch (ServletException e) { + return Response.status(Response.Status.FORBIDDEN).build(); + } + } + + @POST + @Path("/recovery") + @ApiOperation(value = "Send password recovery request", response = Response.class) + @Produces(MediaType.APPLICATION_JSON) + public Response sendPasswordRecovery(PasswordRecoveryRequestDTO passwordRecoveryRequestDTO) throws AccountNotFoundException { + String login = passwordRecoveryRequestDTO.getLogin(); + Account account = accountManager.getAccount(login); + userManager.createPasswordRecoveryRequest(account); + return Response.ok().build(); + } + + @POST + @Path("/recover") + @ApiOperation(value = "Recover password", response = Response.class) + @Produces(MediaType.APPLICATION_JSON) + public Response sendPasswordRecover(PasswordRecoverDTO passwordRecoverDTO) throws PasswordRecoveryRequestNotFoundException { + userManager.recoverPassword(passwordRecoverDTO.getUuid(),passwordRecoverDTO.getNewPassword()); + return Response.ok().build(); + } + + @GET + @Path("/logout") + @ApiOperation(value = "Log out connected user", response = Response.class) + @Produces(MediaType.APPLICATION_JSON) + public Response logout(@Context HttpServletRequest request) throws ServletException { + request.logout(); + return Response.ok().build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ChangeIssuesResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ChangeIssuesResource.java new file mode 100644 index 0000000000..740331ca4b --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ChangeIssuesResource.java @@ -0,0 +1,329 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.change.ChangeIssue; +import com.docdoku.core.document.DocumentIterationKey; +import com.docdoku.core.exceptions.AccessRightException; +import com.docdoku.core.exceptions.EntityConstraintException; +import com.docdoku.core.exceptions.EntityNotFoundException; +import com.docdoku.core.exceptions.UserNotActiveException; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.product.PartIterationKey; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IChangeManagerLocal; +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.rest.dto.change.ChangeIssueDTO; +import com.docdoku.server.rest.dto.change.ChangeItemDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.*; + +@RequestScoped +@Api(hidden = true, value = "issues", description = "Operations about issues") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class ChangeIssuesResource { + + @Inject + private IChangeManagerLocal changeManager; + + private Mapper mapper; + + public ChangeIssuesResource() { + + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get issues for given parameters", + response = ChangeIssueDTO.class, + responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getIssues(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + List changeIssues = changeManager.getChangeIssues(workspaceId); + List changeIssueDTOs = new ArrayList<>(); + for (ChangeIssue issue : changeIssues) { + ChangeIssueDTO changeIssueDTO = mapper.map(issue, ChangeIssueDTO.class); + changeIssueDTO.setWritable(changeManager.isChangeItemWritable(issue)); + changeIssueDTOs.add(changeIssueDTO); + } + return Response.ok(new GenericEntity>((List) changeIssueDTOs) { + }).build(); + } + + @POST + @ApiOperation(value = "Create issue", + response = ChangeIssueDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeItemDTO createIssue(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Change issue to create") ChangeIssueDTO changeIssueDTO) + throws EntityNotFoundException, AccessRightException { + ChangeIssue changeIssue = changeManager.createChangeIssue(workspaceId, + changeIssueDTO.getName(), + changeIssueDTO.getDescription(), + changeIssueDTO.getInitiator(), + changeIssueDTO.getPriority(), + changeIssueDTO.getAssignee(), + changeIssueDTO.getCategory()); + ChangeIssueDTO ret = mapper.map(changeIssue, ChangeIssueDTO.class); + ret.setWritable(true); + return ret; + } + + @GET + @ApiOperation(value = "Search issue with given reference", + response = ChangeIssueDTO.class, + responseContainer = "List") + @Path("link") + @Produces(MediaType.APPLICATION_JSON) + public ChangeIssueDTO[] searchIssuesToLink(@PathParam("workspaceId") String workspaceId, + @QueryParam("q") String q) + throws EntityNotFoundException, UserNotActiveException { + int maxResults = 8; + List issues = changeManager.getIssuesWithReference(workspaceId, q, maxResults); + List issueDTOs = new ArrayList<>(); + for (ChangeIssue issue : issues) { + ChangeIssueDTO changeIssueDTO = mapper.map(issue, ChangeIssueDTO.class); + changeIssueDTO.setWritable(changeManager.isChangeItemWritable(issue)); + issueDTOs.add(changeIssueDTO); + } + return issueDTOs.toArray(new ChangeIssueDTO[issueDTOs.size()]); + } + + @GET + @ApiOperation(value = "Get issue", + response = ChangeIssueDTO.class) + @Produces(MediaType.APPLICATION_JSON) + @Path("{issueId}") + public ChangeItemDTO getIssue(@PathParam("workspaceId") String workspaceId, + @PathParam("issueId") int issueId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + ChangeIssue changeIssue = changeManager.getChangeIssue(workspaceId, issueId); + ChangeIssueDTO changeIssueDTO = mapper.map(changeIssue, ChangeIssueDTO.class); + changeIssueDTO.setWritable(changeManager.isChangeItemWritable(changeIssue)); + return changeIssueDTO; + } + + @PUT + @ApiOperation(value = "Update issue", + response = ChangeIssueDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("{issueId}") + public ChangeItemDTO updateIssue(@PathParam("workspaceId") String workspaceId, + @PathParam("issueId") int issueId, + @ApiParam(required = true, value = "Change issue to update") ChangeIssueDTO pChangeIssueDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + ChangeIssue changeIssue = changeManager.updateChangeIssue(issueId, + workspaceId, + pChangeIssueDTO.getDescription(), + pChangeIssueDTO.getPriority(), + pChangeIssueDTO.getAssignee(), + pChangeIssueDTO.getCategory()); + ChangeIssueDTO changeIssueDTO = mapper.map(changeIssue, ChangeIssueDTO.class); + changeIssueDTO.setWritable(changeManager.isChangeItemWritable(changeIssue)); + return changeIssueDTO; + } + + @DELETE + @ApiOperation(value = "Delete issue", + response = ChangeIssueDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Path("{issueId}") + public Response removeIssue(@PathParam("issueId") int issueId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, EntityConstraintException { + changeManager.deleteChangeIssue(issueId); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Update tags attached to an issue", + response = ChangeIssueDTO.class) + @Path("{issueId}/tags") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeItemDTO saveTags(@PathParam("workspaceId") String workspaceId, + @PathParam("issueId") int issueId, + @ApiParam(required = true, value = "Tag list to add") TagListDTO tagListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + + List tagDTOs = tagListDTO.getTags(); + String[] tagsLabel = new String[tagDTOs.size()]; + for (int i = 0; i < tagDTOs.size(); i++) { + tagsLabel[i] = tagDTOs.get(i).getLabel(); + } + + ChangeIssue changeIssue = changeManager.saveChangeIssueTags(workspaceId, issueId, tagsLabel); + ChangeIssueDTO changeIssueDTO = mapper.map(changeIssue, ChangeIssueDTO.class); + changeIssueDTO.setWritable(changeManager.isChangeItemWritable(changeIssue)); + return changeIssueDTO; + } + + @POST + @ApiOperation(value = "Attached a new tag to an issue", + response = ChangeIssueDTO.class) + @Path("{issueId}/tags") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeItemDTO addTag(@PathParam("workspaceId") String workspaceId, + @PathParam("issueId") int issueId, + @ApiParam(required = true, value = "Tag list to add") TagListDTO tagListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + ChangeIssue changeIssue = changeManager.getChangeIssue(workspaceId, issueId); + Set tags = changeIssue.getTags(); + Set tagLabels = new HashSet<>(); + + for (TagDTO tagDTO : tagListDTO.getTags()) { + tagLabels.add(tagDTO.getLabel()); + } + + for (Tag tag : tags) { + tagLabels.add(tag.getLabel()); + } + + changeIssue = changeManager.saveChangeIssueTags(workspaceId, issueId, tagLabels.toArray(new String[tagLabels.size()])); + ChangeIssueDTO changeIssueDTO = mapper.map(changeIssue, ChangeIssueDTO.class); + changeIssueDTO.setWritable(changeManager.isChangeItemWritable(changeIssue)); + return changeIssueDTO; + } + + @DELETE + @ApiOperation(value = "Delete a tag attached to an issue", + response = ChangeIssueDTO.class) + @Path("{issueId}/tags/{tagName}") + public Response removeTags(@PathParam("workspaceId") String workspaceId, + @PathParam("issueId") int issueId, + @PathParam("tagName") String tagName) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + changeManager.removeChangeIssueTag(workspaceId, issueId, tagName); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Attach a document to an issue", + response = ChangeIssueDTO.class) + @Path("{issueId}/affected-documents") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeItemDTO saveAffectedDocuments(@PathParam("workspaceId") String workspaceId, + @PathParam("issueId") int issueId, + @ApiParam(required = true, value = "Document list to save as affected") DocumentIterationListDTO documentListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + + List documentIterationDTOs = documentListDTO.getDocuments(); + DocumentIterationKey[] links = createDocumentIterationKeys(documentIterationDTOs); + + ChangeIssue changeIssue = changeManager.saveChangeIssueAffectedDocuments(workspaceId, issueId, links); + ChangeIssueDTO changeIssueDTO = mapper.map(changeIssue, ChangeIssueDTO.class); + changeIssueDTO.setWritable(changeManager.isChangeItemWritable(changeIssue)); + return changeIssueDTO; + } + + @PUT + @ApiOperation(value = "Attach a part to an issue", + response = ChangeIssueDTO.class) + @Path("{issueId}/affected-parts") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeItemDTO saveAffectedParts(@PathParam("workspaceId") String workspaceId, + @PathParam("issueId") int issueId, + @ApiParam(required = true, value = "Part list to save as affected") PartIterationListDTO partIterationListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + + List partIterationDTOs = partIterationListDTO.getParts(); + PartIterationKey[] links = createPartIterationKeys(partIterationDTOs); + + ChangeIssue changeIssue = changeManager.saveChangeIssueAffectedParts(workspaceId, issueId, links); + ChangeIssueDTO changeIssueDTO = mapper.map(changeIssue, ChangeIssueDTO.class); + changeIssueDTO.setWritable(changeManager.isChangeItemWritable(changeIssue)); + return changeIssueDTO; + } + + @PUT + @ApiOperation(value = "Update ACL of an issue", + response = Response.class) + @Path("{issueId}/acl") + @Consumes(MediaType.APPLICATION_JSON) + public Response updateACL(@PathParam("workspaceId") String pWorkspaceId, + @PathParam("issueId") int issueId, + @ApiParam(required = true, value = "ACL rules to set") ACLDTO acl) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + if (!acl.getGroupEntries().isEmpty() || !acl.getUserEntries().isEmpty()) { + + Map userEntries = new HashMap<>(); + Map groupEntries = new HashMap<>(); + + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries.put(entry.getKey(), entry.getValue().name()); + } + + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + groupEntries.put(entry.getKey(), entry.getValue().name()); + } + + changeManager.updateACLForChangeIssue(pWorkspaceId, issueId, userEntries, groupEntries); + } else { + changeManager.removeACLFromChangeIssue(pWorkspaceId, issueId); + } + return Response.ok().build(); + } + + + private DocumentIterationKey[] createDocumentIterationKeys(List dtos) { + DocumentIterationKey[] data = new DocumentIterationKey[dtos.size()]; + int i = 0; + for (DocumentIterationDTO dto : dtos) { + data[i++] = new DocumentIterationKey(dto.getWorkspaceId(), dto.getDocumentMasterId(), dto.getVersion(), dto.getIteration()); + } + + return data; + } + + private PartIterationKey[] createPartIterationKeys(List dtos) { + PartIterationKey[] data = new PartIterationKey[dtos.size()]; + int i = 0; + for (PartIterationDTO dto : dtos) { + data[i++] = new PartIterationKey(dto.getWorkspaceId(), dto.getNumber(), dto.getVersion(), dto.getIteration()); + } + + return data; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ChangeItemsResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ChangeItemsResource.java new file mode 100644 index 0000000000..ac3eb02cee --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ChangeItemsResource.java @@ -0,0 +1,74 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.security.UserGroupMapping; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.Path; + +@RequestScoped +@Api(hidden = true, value = "change-items", description = "Operations about change items") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class ChangeItemsResource { + + @Inject + private ChangeIssuesResource issues; + @Inject + private ChangeRequestsResource requests; + @Inject + private ChangeOrdersResource orders; + @Inject + private MilestonesResource milestones; + + public ChangeItemsResource() { + } + + @Path("/issues") + @ApiOperation(value = "SubResource : ChangeIssuesResource") + public ChangeIssuesResource issues() { + return issues; + } + + @Path("/requests") + @ApiOperation(value = "SubResource : ChangeRequestsResource") + public ChangeRequestsResource requests() { + return requests; + } + + @Path("/orders") + @ApiOperation(value = "SubResource : ChangeOrdersResource") + public ChangeOrdersResource orders() { + return orders; + } + + @Path("/milestones") + @ApiOperation(value = "SubResource : MilestonesResource") + public MilestonesResource milestones() { + return milestones; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ChangeOrdersResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ChangeOrdersResource.java new file mode 100644 index 0000000000..6d1c1bf505 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ChangeOrdersResource.java @@ -0,0 +1,339 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.change.ChangeOrder; +import com.docdoku.core.document.DocumentIterationKey; +import com.docdoku.core.exceptions.AccessRightException; +import com.docdoku.core.exceptions.EntityNotFoundException; +import com.docdoku.core.exceptions.UserNotActiveException; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.product.PartIterationKey; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IChangeManagerLocal; +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.rest.dto.change.ChangeItemDTO; +import com.docdoku.server.rest.dto.change.ChangeOrderDTO; +import com.docdoku.server.rest.dto.change.ChangeRequestDTO; +import com.docdoku.server.rest.dto.change.ChangeRequestListDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.*; + +@RequestScoped +@Api(hidden = true, value = "orders", description = "Operations about orders") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class ChangeOrdersResource { + + @Inject + private IChangeManagerLocal changeManager; + + private Mapper mapper; + + public ChangeOrdersResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get orders for given parameters", + response = ChangeOrderDTO.class, + responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getOrders(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + List changeOrders = changeManager.getChangeOrders(workspaceId); + List changeOrderDTOs = new ArrayList<>(); + for (ChangeOrder order : changeOrders) { + ChangeOrderDTO changeOrderDTO = mapper.map(order, ChangeOrderDTO.class); + changeOrderDTO.setWritable(changeManager.isChangeItemWritable(order)); + changeOrderDTOs.add(changeOrderDTO); + } + return Response.ok(new GenericEntity>((List) changeOrderDTOs) { + }).build(); + } + + @POST + @ApiOperation(value = "Create order", + response = ChangeOrderDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeOrderDTO createOrder(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Change order to create") ChangeOrderDTO changeOrderDTO) + throws EntityNotFoundException, AccessRightException { + ChangeOrder changeOrder = changeManager.createChangeOrder(workspaceId, + changeOrderDTO.getName(), + changeOrderDTO.getDescription(), + changeOrderDTO.getMilestoneId(), + changeOrderDTO.getPriority(), + changeOrderDTO.getAssignee(), + changeOrderDTO.getCategory()); + ChangeOrderDTO ret = mapper.map(changeOrder, ChangeOrderDTO.class); + ret.setWritable(true); + return ret; + } + + @GET + @ApiOperation(value = "Get order", + response = ChangeOrderDTO.class) + @Produces(MediaType.APPLICATION_JSON) + @Path("{orderId}") + public ChangeOrderDTO getOrder(@PathParam("workspaceId") String workspaceId, + @PathParam("orderId") int orderId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + ChangeOrder changeOrder = changeManager.getChangeOrder(workspaceId, orderId); + ChangeOrderDTO changeOrderDTO = mapper.map(changeOrder, ChangeOrderDTO.class); + changeOrderDTO.setWritable(changeManager.isChangeItemWritable(changeOrder)); + return changeOrderDTO; + } + + @PUT + @ApiOperation(value = "Update order", + response = ChangeOrderDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("{orderId}") + public ChangeOrderDTO updateOrder(@PathParam("workspaceId") String workspaceId, + @PathParam("orderId") int orderId, + @ApiParam(required = true, value = "Change order to update") ChangeOrderDTO pChangeOrderDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + ChangeOrder changeOrder = changeManager.updateChangeOrder(orderId, + workspaceId, + pChangeOrderDTO.getDescription(), + pChangeOrderDTO.getMilestoneId(), + pChangeOrderDTO.getPriority(), + pChangeOrderDTO.getAssignee(), + pChangeOrderDTO.getCategory()); + ChangeOrderDTO changeOrderDTO = mapper.map(changeOrder, ChangeOrderDTO.class); + changeOrderDTO.setWritable(changeManager.isChangeItemWritable(changeOrder)); + return changeOrderDTO; + } + + @DELETE + @ApiOperation(value = "Delete order", + response = Response.class) + @Consumes(MediaType.APPLICATION_JSON) + @Path("{orderId}") + public Response removeOrder(@PathParam("orderId") int orderId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + changeManager.deleteChangeOrder(orderId); + return Response.ok().build(); + } + + + @PUT + @ApiOperation(value = "Update tag attached to order", + response = ChangeOrderDTO.class) + @Path("{orderId}/tags") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeOrderDTO saveTags(@PathParam("workspaceId") String workspaceId, + @PathParam("orderId") int orderId, + @ApiParam(required = true, value = "Tag list to add") TagListDTO tagListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + List tagDTOs = tagListDTO.getTags(); + String[] tagsLabel = new String[tagDTOs.size()]; + for (int i = 0; i < tagDTOs.size(); i++) { + tagsLabel[i] = tagDTOs.get(i).getLabel(); + } + + ChangeOrder changeOrder = changeManager.saveChangeOrderTags(workspaceId, orderId, tagsLabel); + ChangeOrderDTO changeOrderDTO = mapper.map(changeOrder, ChangeOrderDTO.class); + changeOrderDTO.setWritable(changeManager.isChangeItemWritable(changeOrder)); + return changeOrderDTO; + } + + @POST + @ApiOperation(value = "Add new tag to order", + response = ChangeOrderDTO.class) + @Path("{orderId}/tags") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeItemDTO addTag(@PathParam("workspaceId") String workspaceId, + @PathParam("orderId") int orderId, + @ApiParam(required = true, value = "Tag list to add") TagListDTO tagListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + ChangeOrder changeOrder = changeManager.getChangeOrder(workspaceId, orderId); + Set tags = changeOrder.getTags(); + Set tagLabels = new HashSet<>(); + + for (TagDTO tagDTO : tagListDTO.getTags()) { + tagLabels.add(tagDTO.getLabel()); + } + + for (Tag tag : tags) { + tagLabels.add(tag.getLabel()); + } + + changeOrder = changeManager.saveChangeOrderTags(workspaceId, orderId, tagLabels.toArray(new String[tagLabels.size()])); + ChangeOrderDTO changeOrderDTO = mapper.map(changeOrder, ChangeOrderDTO.class); + changeOrderDTO.setWritable(changeManager.isChangeItemWritable(changeOrder)); + return changeOrderDTO; + + } + + @DELETE + @ApiOperation(value = "Delete tag attached to order", + response = Response.class) + @Path("{orderId}/tags/{tagName}") + public Response removeTags(@PathParam("workspaceId") String workspaceId, + @PathParam("orderId") int orderId, + @PathParam("tagName") String tagName) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + changeManager.removeChangeOrderTag(workspaceId, orderId, tagName); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Attach document to order", + response = ChangeOrderDTO.class) + @Path("{orderId}/affected-documents") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeItemDTO saveAffectedDocuments(@PathParam("workspaceId") String workspaceId, + @PathParam("orderId") int orderId, + @ApiParam(required = true, value = "Documents to save as affected") DocumentIterationListDTO documentIterationListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + + List documentIterationDTOs = documentIterationListDTO.getDocuments(); + DocumentIterationKey[] links = createDocumentIterationKeys(documentIterationDTOs); + + ChangeOrder changeOrder = changeManager.saveChangeOrderAffectedDocuments(workspaceId, orderId, links); + ChangeOrderDTO changeOrderDTO = mapper.map(changeOrder, ChangeOrderDTO.class); + changeOrderDTO.setWritable(changeManager.isChangeItemWritable(changeOrder)); + return changeOrderDTO; + } + + @PUT + @ApiOperation(value = "Attach part to order", + response = ChangeOrderDTO.class) + @Path("{orderId}/affected-parts") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeItemDTO saveAffectedParts(@PathParam("workspaceId") String workspaceId, + @PathParam("orderId") int orderId, + @ApiParam(required = true, value = "Parts to save as affected") PartIterationListDTO partIterationListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + + List partIterationDTOs = partIterationListDTO.getParts(); + PartIterationKey[] links = createPartIterationKeys(partIterationDTOs); + + ChangeOrder changeOrder = changeManager.saveChangeOrderAffectedParts(workspaceId, orderId, links); + ChangeOrderDTO changeOrderDTO = mapper.map(changeOrder, ChangeOrderDTO.class); + changeOrderDTO.setWritable(changeManager.isChangeItemWritable(changeOrder)); + return changeOrderDTO; + } + + @PUT + @ApiOperation(value = "Attach request to order", + response = ChangeOrderDTO.class) + @Path("{orderId}/affected-requests") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeItemDTO saveAffectedRequests(@PathParam("workspaceId") String workspaceId, + @PathParam("orderId") int orderId, + @ApiParam(required = true, value = "Change requests to save as affected") ChangeRequestListDTO changeRequestListDTOs) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + int[] links; + List changeRequestDTOs = changeRequestListDTOs.getRequests(); + if (changeRequestDTOs != null) { + int i = 0; + links = new int[changeRequestDTOs.size()]; + for (ChangeRequestDTO changeRequestDTO : changeRequestDTOs) { + links[i++] = changeRequestDTO.getId(); + } + } else { + links = new int[0]; + } + + ChangeOrder changeOrder = changeManager.saveChangeOrderAffectedRequests(workspaceId, orderId, links); + ChangeOrderDTO changeOrderDTO = mapper.map(changeOrder, ChangeOrderDTO.class); + changeOrderDTO.setWritable(changeManager.isChangeItemWritable(changeOrder)); + return changeOrderDTO; + } + + @PUT + @ApiOperation(value = "Update ACL of the order", + response = Response.class) + @Path("{orderId}/acl") + @Consumes(MediaType.APPLICATION_JSON) + public Response updateACL(@PathParam("workspaceId") String pWorkspaceId, + @PathParam("orderId") int orderId, + @ApiParam(required = true, value = "ACL rules to set") ACLDTO acl) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + if (!acl.getGroupEntries().isEmpty() || !acl.getUserEntries().isEmpty()) { + + Map userEntries = new HashMap<>(); + Map groupEntries = new HashMap<>(); + + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries.put(entry.getKey(), entry.getValue().name()); + } + + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + groupEntries.put(entry.getKey(), entry.getValue().name()); + } + + changeManager.updateACLForChangeOrder(pWorkspaceId, orderId, userEntries, groupEntries); + } else { + changeManager.removeACLFromChangeOrder(pWorkspaceId, orderId); + } + return Response.ok().build(); + } + + + private DocumentIterationKey[] createDocumentIterationKeys(List dtos) { + DocumentIterationKey[] data = new DocumentIterationKey[dtos.size()]; + int i = 0; + for (DocumentIterationDTO dto : dtos) { + data[i++] = new DocumentIterationKey(dto.getWorkspaceId(), dto.getDocumentMasterId(), dto.getVersion(), dto.getIteration()); + } + + return data; + } + + private PartIterationKey[] createPartIterationKeys(List dtos) { + PartIterationKey[] data = new PartIterationKey[dtos.size()]; + int i = 0; + for (PartIterationDTO dto : dtos) { + data[i++] = new PartIterationKey(dto.getWorkspaceId(), dto.getNumber(), dto.getVersion(), dto.getIteration()); + } + + return data; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ChangeRequestsResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ChangeRequestsResource.java new file mode 100644 index 0000000000..83319ed848 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ChangeRequestsResource.java @@ -0,0 +1,361 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.change.ChangeRequest; +import com.docdoku.core.document.DocumentIterationKey; +import com.docdoku.core.exceptions.AccessRightException; +import com.docdoku.core.exceptions.EntityConstraintException; +import com.docdoku.core.exceptions.EntityNotFoundException; +import com.docdoku.core.exceptions.UserNotActiveException; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.product.PartIterationKey; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IChangeManagerLocal; +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.rest.dto.change.ChangeIssueDTO; +import com.docdoku.server.rest.dto.change.ChangeIssueListDTO; +import com.docdoku.server.rest.dto.change.ChangeItemDTO; +import com.docdoku.server.rest.dto.change.ChangeRequestDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.*; + +@RequestScoped +@Api(hidden = true, value = "requests", description = "Operations about requests") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class ChangeRequestsResource { + + @Inject + private IChangeManagerLocal changeManager; + + private Mapper mapper; + + public ChangeRequestsResource() { + + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get requests for given parameters", + response = ChangeRequestDTO.class, + responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getRequests(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + List changeRequests = changeManager.getChangeRequests(workspaceId); + List changeRequestDTOs = new ArrayList<>(); + for (ChangeRequest request : changeRequests) { + ChangeRequestDTO changeRequestDTO = mapper.map(request, ChangeRequestDTO.class); + changeRequestDTO.setWritable(changeManager.isChangeItemWritable(request)); + changeRequestDTOs.add(changeRequestDTO); + } + return Response.ok(new GenericEntity>((List) changeRequestDTOs) { + }).build(); + } + + @POST + @ApiOperation(value = "Create request", + response = ChangeRequestDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeRequestDTO createRequest(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Change request to create") ChangeRequestDTO changeRequestDTO) + throws EntityNotFoundException, AccessRightException { + ChangeRequest changeRequest = changeManager.createChangeRequest(workspaceId, + changeRequestDTO.getName(), + changeRequestDTO.getDescription(), + changeRequestDTO.getMilestoneId(), + changeRequestDTO.getPriority(), + changeRequestDTO.getAssignee(), + changeRequestDTO.getCategory()); + ChangeRequestDTO ret = mapper.map(changeRequest, ChangeRequestDTO.class); + ret.setWritable(true); + return ret; + } + + @GET + @ApiOperation(value = "Search request for given references", + response = ChangeRequestDTO.class, + responseContainer = "List") + @Path("link") + @Produces(MediaType.APPLICATION_JSON) + public ChangeRequestDTO[] searchRequestsToLink(@PathParam("workspaceId") String workspaceId, + @QueryParam("q") String q) + throws EntityNotFoundException, UserNotActiveException { + int maxResults = 8; + List requests = changeManager.getRequestsWithReference(workspaceId, q, maxResults); + List requestDTOs = new ArrayList<>(); + for (ChangeRequest request : requests) { + ChangeRequestDTO changeRequestDTO = mapper.map(request, ChangeRequestDTO.class); + changeRequestDTO.setWritable(changeManager.isChangeItemWritable(request)); + requestDTOs.add(changeRequestDTO); + } + return requestDTOs.toArray(new ChangeRequestDTO[requestDTOs.size()]); + } + + @GET + @ApiOperation(value = "Get request", + response = ChangeRequestDTO.class) + @Produces(MediaType.APPLICATION_JSON) + @Path("{requestId}") + public ChangeRequestDTO getRequest(@PathParam("workspaceId") String workspaceId, + @PathParam("requestId") int requestId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + ChangeRequest changeRequest = changeManager.getChangeRequest(workspaceId, requestId); + ChangeRequestDTO changeRequestDTO = mapper.map(changeRequest, ChangeRequestDTO.class); + changeRequestDTO.setWritable(changeManager.isChangeItemWritable(changeRequest)); + return changeRequestDTO; + } + + @PUT + @ApiOperation(value = "Update request", + response = ChangeRequestDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("{requestId}") + public ChangeRequestDTO updateRequest(@PathParam("workspaceId") String workspaceId, + @PathParam("requestId") int requestId, + @ApiParam(required = true, value = "Request to update") ChangeRequestDTO pChangeRequestDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + ChangeRequest changeRequest = changeManager.updateChangeRequest(requestId, + workspaceId, + pChangeRequestDTO.getDescription(), + pChangeRequestDTO.getMilestoneId(), + pChangeRequestDTO.getPriority(), + pChangeRequestDTO.getAssignee(), + pChangeRequestDTO.getCategory()); + ChangeRequestDTO changeRequestDTO = mapper.map(changeRequest, ChangeRequestDTO.class); + changeRequestDTO.setWritable(changeManager.isChangeItemWritable(changeRequest)); + return changeRequestDTO; + } + + @DELETE + @ApiOperation(value = "Delete request", + response = ChangeRequestDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Path("{requestId}") + public Response removeRequest(@PathParam("workspaceId") String workspaceId, + @PathParam("requestId") int requestId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, EntityConstraintException { + changeManager.deleteChangeRequest(workspaceId, requestId); + return Response.ok().build(); + } + + + @PUT + @ApiOperation(value = "Update tag attached to a request", + response = ChangeRequestDTO.class) + @Path("{requestId}/tags") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeRequestDTO saveTags(@PathParam("workspaceId") String workspaceId, + @PathParam("requestId") int requestId, + @ApiParam(required = true, value = "Tag list to add") TagListDTO tagListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + List tagDTOs = tagListDTO.getTags(); + String[] tagsLabel = new String[tagDTOs.size()]; + for (int i = 0; i < tagDTOs.size(); i++) { + tagsLabel[i] = tagDTOs.get(i).getLabel(); + } + + ChangeRequest changeRequest = changeManager.saveChangeRequestTags(workspaceId, requestId, tagsLabel); + ChangeRequestDTO changeRequestDTO = mapper.map(changeRequest, ChangeRequestDTO.class); + changeRequestDTO.setWritable(changeManager.isChangeItemWritable(changeRequest)); + return changeRequestDTO; + } + + @POST + @ApiOperation(value = "Attach a new tag to a request", + response = ChangeRequestDTO.class) + @Path("{requestId}/tags") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeItemDTO addTag(@PathParam("workspaceId") String workspaceId, + @PathParam("requestId") int requestId, + @ApiParam(required = true, value = "Tag list to add") TagListDTO tagListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + ChangeRequest changeRequest = changeManager.getChangeRequest(workspaceId, requestId); + Set tags = changeRequest.getTags(); + Set tagLabels = new HashSet<>(); + + for (TagDTO tagDTO : tagListDTO.getTags()) { + tagLabels.add(tagDTO.getLabel()); + } + + for (Tag tag : tags) { + tagLabels.add(tag.getLabel()); + } + + changeRequest = changeManager.saveChangeRequestTags(workspaceId, requestId, tagLabels.toArray(new String[tagLabels.size()])); + ChangeRequestDTO changeRequestDTO = mapper.map(changeRequest, ChangeRequestDTO.class); + changeRequestDTO.setWritable(changeManager.isChangeItemWritable(changeRequest)); + return changeRequestDTO; + + } + + @DELETE + @ApiOperation(value = "Delete tag attached to a request", + response = Response.class) + @Path("{requestId}/tags/{tagName}") + public Response removeTags(@PathParam("workspaceId") String workspaceId, + @PathParam("requestId") int requestId, + @PathParam("tagName") String tagName) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + changeManager.removeChangeRequestTag(workspaceId, requestId, tagName); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Attach document to a request", + response = ChangeRequestDTO.class) + @Path("{requestId}/affected-documents") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeItemDTO saveAffectedDocuments(@PathParam("workspaceId") String workspaceId, + @PathParam("requestId") int requestId, + @ApiParam(required = true, value = "Document list to save as affected") DocumentIterationListDTO documentIterationListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + + List documentIterationDTOs = documentIterationListDTO.getDocuments(); + DocumentIterationKey[] links = createDocumentIterationKeys(documentIterationDTOs); + + ChangeRequest changeRequest = changeManager.saveChangeRequestAffectedDocuments(workspaceId, requestId, links); + ChangeRequestDTO changeRequestDTO = mapper.map(changeRequest, ChangeRequestDTO.class); + changeRequestDTO.setWritable(changeManager.isChangeItemWritable(changeRequest)); + return changeRequestDTO; + } + + @PUT + @ApiOperation(value = "Attach part to a request", + response = ChangeRequestDTO.class) + @Path("{requestId}/affected-parts") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeItemDTO saveAffectedParts(@PathParam("workspaceId") String workspaceId, + @PathParam("requestId") int requestId, + @ApiParam(required = true, value = "Parts to save as affected") PartIterationListDTO partIterationListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + + List partIterationDTOs = partIterationListDTO.getParts(); + PartIterationKey[] links = createPartIterationKeys(partIterationDTOs); + + ChangeRequest changeRequest = changeManager.saveChangeRequestAffectedParts(workspaceId, requestId, links); + ChangeRequestDTO changeRequestDTO = mapper.map(changeRequest, ChangeRequestDTO.class); + changeRequestDTO.setWritable(changeManager.isChangeItemWritable(changeRequest)); + return changeRequestDTO; + } + + @PUT + @ApiOperation(value = "Attach issue to a request", + response = ChangeRequestDTO.class) + @Path("{requestId}/affected-issues") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ChangeItemDTO saveAffectedIssues(@PathParam("workspaceId") String workspaceId, + @PathParam("requestId") int requestId, + @ApiParam(required = true, value = "Change issues to save as affected") ChangeIssueListDTO changeIssueListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + int[] links; + List changeIssueDTOs = changeIssueListDTO.getIssues(); + if (changeIssueDTOs != null) { + int i = 0; + links = new int[changeIssueDTOs.size()]; + for (ChangeIssueDTO changeIssueDTO : changeIssueDTOs) { + links[i++] = changeIssueDTO.getId(); + } + } else { + links = new int[0]; + } + + ChangeRequest changeRequest = changeManager.saveChangeRequestAffectedIssues(workspaceId, requestId, links); + ChangeRequestDTO changeRequestDTO = mapper.map(changeRequest, ChangeRequestDTO.class); + changeRequestDTO.setWritable(changeManager.isChangeItemWritable(changeRequest)); + return changeRequestDTO; + } + + @PUT + @ApiOperation(value = "Update ACL of a request", + response = Response.class) + @Path("{requestId}/acl") + @Consumes(MediaType.APPLICATION_JSON) + public Response updateACL(@PathParam("workspaceId") String pWorkspaceId, + @PathParam("requestId") int requestId, + @ApiParam(required = true, value = "ACL rules to set") ACLDTO acl) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + if (!acl.getGroupEntries().isEmpty() || !acl.getUserEntries().isEmpty()) { + + Map userEntries = new HashMap<>(); + Map groupEntries = new HashMap<>(); + + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries.put(entry.getKey(), entry.getValue().name()); + } + + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + groupEntries.put(entry.getKey(), entry.getValue().name()); + } + + changeManager.updateACLForChangeRequest(pWorkspaceId, requestId, userEntries, groupEntries); + } else { + changeManager.removeACLFromChangeRequest(pWorkspaceId, requestId); + } + return Response.ok().build(); + } + + private DocumentIterationKey[] createDocumentIterationKeys(List dtos) { + DocumentIterationKey[] data = new DocumentIterationKey[dtos.size()]; + int i = 0; + for (DocumentIterationDTO dto : dtos) { + data[i++] = new DocumentIterationKey(dto.getWorkspaceId(), dto.getDocumentMasterId(), dto.getVersion(), dto.getIteration()); + } + + return data; + } + + private PartIterationKey[] createPartIterationKeys(List dtos) { + PartIterationKey[] data = new PartIterationKey[dtos.size()]; + int i = 0; + for (PartIterationDTO dto : dtos) { + data[i++] = new PartIterationKey(dto.getWorkspaceId(), dto.getNumber(), dto.getVersion(), dto.getIteration()); + } + + return data; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/CheckedOutDocumentResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/CheckedOutDocumentResource.java new file mode 100644 index 0000000000..39c03050c5 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/CheckedOutDocumentResource.java @@ -0,0 +1,83 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.UserNotActiveException; +import com.docdoku.core.exceptions.UserNotFoundException; +import com.docdoku.core.exceptions.WorkspaceNotFoundException; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.server.rest.dto.DocumentRevisionDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@RequestScoped +@Api(hidden = true, value = "checked-out-documents", description = "Operations about checked out documents") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class CheckedOutDocumentResource { + + @Inject + private IDocumentManagerLocal documentService; + + private Mapper mapper; + + public CheckedOutDocumentResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @Path("{checkoutUser}/documents") + @ApiOperation(value = "Get documents checked out by caller", response = DocumentRevisionDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO[] getDocumentsCheckedOutByUser(@PathParam("workspaceId") String workspaceId) throws WorkspaceNotFoundException, UserNotActiveException, UserNotFoundException { + DocumentRevision[] docRs = documentService.getCheckedOutDocumentRevisions(workspaceId); + DocumentRevisionDTO[] docRsDTOs = new DocumentRevisionDTO[docRs.length]; + + for (int i = 0; i < docRs.length; i++) { + docRsDTOs[i] = mapper.map(docRs[i], DocumentRevisionDTO.class); + docRsDTOs[i].setPath(docRs[i].getLocation().getCompletePath()); + docRsDTOs[i] = Tools.createLightDocumentRevisionDTO(docRsDTOs[i]); + docRsDTOs[i].setIterationSubscription(documentService.isUserIterationChangeEventSubscribedForGivenDocument(workspaceId, docRs[i])); + docRsDTOs[i].setStateSubscription(documentService.isUserStateChangeEventSubscribedForGivenDocument(workspaceId, docRs[i])); + } + + return docRsDTOs; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/DocumentBaselinesResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/DocumentBaselinesResource.java new file mode 100644 index 0000000000..9a6674f958 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/DocumentBaselinesResource.java @@ -0,0 +1,188 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.configuration.DocumentBaseline; +import com.docdoku.core.exceptions.AccessRightException; +import com.docdoku.core.exceptions.EntityNotFoundException; +import com.docdoku.core.exceptions.UserNotActiveException; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDocumentBaselineManagerLocal; +import com.docdoku.server.rest.dto.baseline.DocumentBaselineDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ + +@RequestScoped +@Api(hidden = true, value = "baselines", description = "Operations about document baselines") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class DocumentBaselinesResource { + + private static final Logger LOGGER = Logger.getLogger(DocumentBaselinesResource.class.getName()); + @Inject + private IDocumentBaselineManagerLocal documentBaselineService; + private Mapper mapper; + + public DocumentBaselinesResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + + /** + * Get all document baselines of a specific workspace + * + * @param workspaceId The id of the specific workspace + * @return The list of baselines + */ + @GET + @ApiOperation(value = "Get baselines", + response = DocumentBaselineDTO.class, + responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getBaselines(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + List documentBaselines; + documentBaselines = documentBaselineService.getBaselines(workspaceId); + List baselinesDTO = new ArrayList<>(); + for (DocumentBaseline documentBaseline : documentBaselines) { + DocumentBaselineDTO documentBaselineDTO = mapper.map(documentBaseline, DocumentBaselineDTO.class); + documentBaselineDTO.setWorkspaceId(workspaceId); + baselinesDTO.add(documentBaselineDTO); + } + return Response.ok(new GenericEntity>((List) baselinesDTO) { + }).build(); + } + + /** + * Create a baseline + * + * @param workspaceId The current workspace + * @param documentBaselineDTO Description of the baseline to create + * @return Reponse of the transaction + */ + @POST + @ApiOperation(value = "Create baseline", response = DocumentBaselineDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + public Response createBaseline(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Document baseline to create") DocumentBaselineDTO documentBaselineDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + DocumentBaseline baseline = documentBaselineService.createBaseline(workspaceId, documentBaselineDTO.getName(), documentBaselineDTO.getDescription()); + DocumentBaselineDTO baselineDTO = mapper.map(baseline, DocumentBaselineDTO.class); + return prepareCreateResponse(baselineDTO); + } + + /** + * Try to put a document baseline in a response + * + * @param baselineDTO The document baseline to add + * @return The reponse with the document baseline + */ + private Response prepareCreateResponse(DocumentBaselineDTO baselineDTO) { + try { + return Response.created(URI.create(URLEncoder.encode(String.valueOf(baselineDTO.getId()), "UTF-8"))).entity(baselineDTO).build(); + } catch (UnsupportedEncodingException ex) { + LOGGER.log(Level.WARNING, null, ex); + return Response.ok().build(); + } + } + + /** + * Delete a specific document baseline + * + * @param baselineId The id of the specific document baseline + * @return A response if the baseline was deleted + */ + @DELETE + @ApiOperation(value = "Delete a baseline", response = Response.class) + @Path("{baselineId}") + @Consumes(MediaType.APPLICATION_JSON) + public Response deleteBaseline(@PathParam("baselineId") int baselineId) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + documentBaselineService.deleteBaseline(baselineId); + return Response.ok().build(); + } + + /** + * Get a specific document baseline ( with document list and folder list ) + * + * @param workspaceId The workspace of the specific baseline + * @param baselineId The id of the specific document baseline + * @return The specif baseline + */ + @GET + @ApiOperation(value = "Get baseline", response = DocumentBaselineDTO.class) + @Path("{baselineId}") + @Produces(MediaType.APPLICATION_JSON) + public DocumentBaselineDTO getBaseline(@PathParam("workspaceId") String workspaceId, + @PathParam("baselineId") int baselineId) + throws EntityNotFoundException, UserNotActiveException { +// DocumentBaseline documentBaseline = documentBaselineService.getBaseline(baselineId); + DocumentBaselineDTO baselineDTO = getBaselineLight(workspaceId, baselineId); +// List folderDTOs = Tools.mapBaselinedFoldersToFolderDTO(documentBaseline); +// baselineDTO.setBaselinedFolders(folderDTOs); + return baselineDTO; + } + + /** + * Get a specific document baseline + * + * @param workspaceId The workspace of the specific baseline + * @param baselineId The id of the specific document baseline + * @return The specif baseline + */ + @GET + @ApiOperation(value = "Get baseline light format", response = DocumentBaselineDTO.class) + @Path("{baselineId}-light") + @Produces(MediaType.APPLICATION_JSON) + public DocumentBaselineDTO getBaselineLight(@PathParam("workspaceId") String workspaceId, + @PathParam("baselineId") int baselineId) + throws EntityNotFoundException, UserNotActiveException { + DocumentBaseline documentBaseline = documentBaselineService.getBaseline(baselineId); + return mapper.map(documentBaseline, DocumentBaselineDTO.class); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/DocumentResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/DocumentResource.java new file mode 100644 index 0000000000..0b8ab2be97 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/DocumentResource.java @@ -0,0 +1,689 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.common.*; +import com.docdoku.core.configuration.DocumentConfigSpec; +import com.docdoku.core.configuration.PathDataMaster; +import com.docdoku.core.configuration.ProductInstanceMaster; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentIterationKey; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartLink; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.ACLUserEntry; +import com.docdoku.core.security.ACLUserGroupEntry; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDocumentConfigSpecManagerLocal; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.core.services.IDocumentWorkflowManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.core.sharing.SharedDocument; +import com.docdoku.core.workflow.Workflow; +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.rest.dto.product.ProductInstanceMasterDTO; +import com.docdoku.server.rest.util.ConfigSpecHelper; +import com.docdoku.server.rest.util.InstanceAttributeFactory; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.*; + +@RequestScoped +@Api(hidden = true, value = "document", description = "Operations about document") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class DocumentResource { + + @Inject + private IDocumentManagerLocal documentService; + + @Inject + private IProductManagerLocal productService; + + @Inject + private IDocumentWorkflowManagerLocal documentWorkflowService; + + @Inject + private IDocumentConfigSpecManagerLocal documentConfigSpecService; + + private Mapper mapper; + + public DocumentResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get document", response = DocumentRevisionDTO.class) + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO getDocumentRevision(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @QueryParam("configSpec") String configSpecType) + throws EntityNotFoundException, AccessRightException, NotAllowedException, UserNotActiveException { + DocumentRevision docR; + DocumentRevisionKey documentRevisionKey = new DocumentRevisionKey(workspaceId, documentId, documentVersion); + if (configSpecType == null || ConfigSpecHelper.BASELINE_UNDEFINED.equals(configSpecType) || ConfigSpecHelper.BASELINE_LATEST.equals(configSpecType)) { + docR = documentService.getDocumentRevision(documentRevisionKey); + } else { + DocumentConfigSpec configSpec = ConfigSpecHelper.getConfigSpec(workspaceId, configSpecType, documentConfigSpecService); + docR = documentConfigSpecService.getFilteredDocumentRevision(documentRevisionKey, configSpec); + } + + DocumentRevisionDTO docRsDTO = mapper.map(docR, DocumentRevisionDTO.class); + docRsDTO.setPath(docR.getLocation().getCompletePath()); + docRsDTO.setRoutePath(docR.getLocation().getRoutePath()); + + if (configSpecType == null || ConfigSpecHelper.BASELINE_UNDEFINED.equals(configSpecType) || ConfigSpecHelper.BASELINE_LATEST.equals(configSpecType)) { + setDocumentRevisionDTOWorkflow(docR, docRsDTO); + docRsDTO.setIterationSubscription(documentService.isUserIterationChangeEventSubscribedForGivenDocument(workspaceId, docR)); + docRsDTO.setStateSubscription(documentService.isUserStateChangeEventSubscribedForGivenDocument(workspaceId, docR)); + } else { + docRsDTO.setWorkflow(null); + docRsDTO.setTags(null); + } + return docRsDTO; + } + + private void setDocumentRevisionDTOWorkflow(DocumentRevision documentRevision, DocumentRevisionDTO documentRevisionDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + Workflow currentWorkflow = documentWorkflowService.getCurrentWorkflow(documentRevision.getKey()); + if (currentWorkflow != null) { + documentRevisionDTO.setWorkflow(mapper.map(currentWorkflow, WorkflowDTO.class)); + documentRevisionDTO.setLifeCycleState(currentWorkflow.getLifeCycleState()); + } + } + + @PUT + @ApiOperation(value = "Checkin document", response = DocumentRevisionDTO.class) + @Path("/checkin") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO checkInDocument(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @ApiParam(name = "body") String body) + throws NotAllowedException, EntityNotFoundException, ESServerException, AccessRightException, UserNotActiveException { + DocumentRevision docR = documentService.checkInDocument(new DocumentRevisionKey(workspaceId, documentId, documentVersion)); + DocumentRevisionDTO docRsDTO = mapper.map(docR, DocumentRevisionDTO.class); + docRsDTO.setPath(docR.getLocation().getCompletePath()); + return docRsDTO; + } + + @PUT + @ApiOperation(value = "Checkout document", response = DocumentRevisionDTO.class) + @Path("/checkout") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO checkOutDocument(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @ApiParam(name = "body") String body) + throws EntityNotFoundException, NotAllowedException, CreationException, AccessRightException, UserNotActiveException, EntityAlreadyExistsException { + DocumentRevision docR = documentService.checkOutDocument(new DocumentRevisionKey(workspaceId, documentId, documentVersion)); + DocumentRevisionDTO docRsDTO = mapper.map(docR, DocumentRevisionDTO.class); + docRsDTO.setPath(docR.getLocation().getCompletePath()); + docRsDTO.setLifeCycleState(docR.getLifeCycleState()); + return docRsDTO; + } + + @PUT + @ApiOperation(value = "Undo checkout document", response = DocumentRevisionDTO.class) + @Path("/undocheckout") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO undoCheckOutDocument(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @ApiParam(name = "body") String body) + throws EntityNotFoundException, NotAllowedException, UserNotActiveException, AccessRightException { + DocumentRevision docR = documentService.undoCheckOutDocument(new DocumentRevisionKey(workspaceId, documentId, documentVersion)); + DocumentRevisionDTO docRsDTO = mapper.map(docR, DocumentRevisionDTO.class); + docRsDTO.setPath(docR.getLocation().getCompletePath()); + docRsDTO.setLifeCycleState(docR.getLifeCycleState()); + return docRsDTO; + } + + @PUT + @ApiOperation(value = "Move document to folder", response = DocumentRevisionDTO.class) + @Path("/move") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO moveDocument(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @ApiParam(required = true, value = "Document revision to move") DocumentCreationDTO docCreationDTO) + throws EntityNotFoundException, NotAllowedException, AccessRightException, UserNotActiveException { + String parentFolderPath = docCreationDTO.getPath(); + String newCompletePath = Tools.stripTrailingSlash(parentFolderPath); + DocumentRevisionKey docRsKey = new DocumentRevisionKey(workspaceId, documentId, documentVersion); + DocumentRevision movedDocumentRevision = documentService.moveDocumentRevision(newCompletePath, docRsKey); + DocumentRevisionDTO documentRevisionDTO = mapper.map(movedDocumentRevision, DocumentRevisionDTO.class); + documentRevisionDTO.setPath(movedDocumentRevision.getLocation().getCompletePath()); + documentRevisionDTO.setLifeCycleState(movedDocumentRevision.getLifeCycleState()); + return documentRevisionDTO; + } + + @PUT + @ApiOperation(value = "Subscribe to notifications on change events", response = Response.class) + @Path("/notification/iterationChange/subscribe") + public Response subscribeToIterationChangeEvent(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion) + throws EntityNotFoundException, AccessRightException, NotAllowedException, UserNotActiveException { + documentService.subscribeToIterationChangeEvent(new DocumentRevisionKey(workspaceId, documentId, documentVersion)); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Unsubscribe from notifications on change events", response = Response.class) + @Path("/notification/iterationChange/unsubscribe") + public Response unSubscribeToIterationChangeEvent(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + documentService.unsubscribeToIterationChangeEvent(new DocumentRevisionKey(workspaceId, documentId, documentVersion)); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Subscribe to notifications on state events", response = Response.class) + @Path("/notification/stateChange/subscribe") + public Response subscribeToStateChangeEvent(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion) + throws EntityNotFoundException, AccessRightException, NotAllowedException, UserNotActiveException { + documentService.subscribeToStateChangeEvent(new DocumentRevisionKey(workspaceId, documentId, documentVersion)); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Unsubscribe to notifications on state events", response = Response.class) + @Path("/notification/stateChange/unsubscribe") + public Response unsubscribeToStateChangeEvent(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + documentService.unsubscribeToStateChangeEvent(new DocumentRevisionKey(workspaceId, documentId, documentVersion)); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Update document iteration", response = DocumentIterationDTO.class) + @Path("/iterations/{docIteration}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public DocumentIterationDTO updateDocumentIteration(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @PathParam("docIteration") String docIteration, + @ApiParam(required = true, value = "Document iteration to update") DocumentIterationDTO documentIterationDTO) + throws EntityNotFoundException, NotAllowedException, AccessRightException, UserNotActiveException { + String pRevisionNote = documentIterationDTO.getRevisionNote(); + int pIteration = Integer.parseInt(docIteration); + + List linkedDocs = documentIterationDTO.getLinkedDocuments(); + DocumentRevisionKey[] links = null; + String[] documentLinkComments = null; + if (linkedDocs != null) { + documentLinkComments = new String[linkedDocs.size()]; + links = createDocumentRevisionKeys(linkedDocs); + int i = 0; + for (DocumentRevisionDTO docRevisionForLink : linkedDocs) { + String comment = docRevisionForLink.getCommentLink(); + if (comment == null) { + comment = ""; + } + documentLinkComments[i++] = comment; + } + } + + List instanceAttributes = documentIterationDTO.getInstanceAttributes(); + List attributes = null; + if (instanceAttributes != null) { + attributes = new InstanceAttributeFactory().createInstanceAttributes(instanceAttributes); + } + + DocumentRevision docR = documentService.updateDocument(new DocumentIterationKey(workspaceId, documentId, documentVersion, pIteration), pRevisionNote, attributes, links, documentLinkComments); + return mapper.map(docR.getLastIteration(), DocumentIterationDTO.class); + } + + @PUT + @ApiOperation(value = "Create a new version of the document", response = DocumentRevisionDTO.class, responseContainer = "List") + @Path("/newVersion") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO[] createNewVersion(@PathParam("workspaceId") String pWorkspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @ApiParam(required = true, value = "New version of document to create") DocumentCreationDTO docCreationDTO) + throws EntityNotFoundException, EntityAlreadyExistsException, NotAllowedException, AccessRightException, CreationException, UserNotActiveException { + String pTitle = docCreationDTO.getTitle(); + String pDescription = docCreationDTO.getDescription(); + String pWorkflowModelId = docCreationDTO.getWorkflowModelId(); + RoleMappingDTO[] roleMappingDTOs = docCreationDTO.getRoleMapping(); + ACLDTO acl = docCreationDTO.getAcl(); + + ACLUserEntry[] userEntries = null; + ACLUserGroupEntry[] userGroupEntries = null; + if (acl != null) { + userEntries = new ACLUserEntry[acl.getUserEntries().size()]; + userGroupEntries = new ACLUserGroupEntry[acl.getGroupEntries().size()]; + int i = 0; + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries[i] = new ACLUserEntry(); + userEntries[i].setPrincipal(new User(new Workspace(pWorkspaceId), new Account(entry.getKey()))); + userEntries[i++].setPermission(ACL.Permission.valueOf(entry.getValue().name())); + } + i = 0; + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + userGroupEntries[i] = new ACLUserGroupEntry(); + userGroupEntries[i].setPrincipal(new UserGroup(new Workspace(pWorkspaceId), entry.getKey())); + userGroupEntries[i++].setPermission(ACL.Permission.valueOf(entry.getValue().name())); + } + } + + Map> userRoleMapping = new HashMap<>(); + Map> groupRoleMapping = new HashMap<>(); + + if (roleMappingDTOs != null) { + for (RoleMappingDTO roleMappingDTO : roleMappingDTOs) { + userRoleMapping.put(roleMappingDTO.getRoleName(), roleMappingDTO.getUserLogins()); + groupRoleMapping.put(roleMappingDTO.getRoleName(), roleMappingDTO.getGroupIds()); + } + } + + DocumentRevision[] docR = documentService.createDocumentRevision(new DocumentRevisionKey(pWorkspaceId, documentId, documentVersion), pTitle, pDescription, pWorkflowModelId, userEntries, userGroupEntries, userRoleMapping, groupRoleMapping); + DocumentRevisionDTO[] dtos = new DocumentRevisionDTO[docR.length]; + + for (int i = 0; i < docR.length; i++) { + dtos[i] = mapper.map(docR[i], DocumentRevisionDTO.class); + dtos[i].setPath(docR[i].getLocation().getCompletePath()); + dtos[i].setLifeCycleState(docR[i].getLifeCycleState()); + dtos[i] = Tools.createLightDocumentRevisionDTO(dtos[i]); + dtos[i].setIterationSubscription(documentService.isUserIterationChangeEventSubscribedForGivenDocument(pWorkspaceId, docR[i])); + dtos[i].setStateSubscription(documentService.isUserStateChangeEventSubscribedForGivenDocument(pWorkspaceId, docR[i])); + } + + return dtos; + } + + @PUT + @ApiOperation(value = "Release document", response = DocumentRevisionDTO.class) + @Path("/release") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO releaseDocumentRevision(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @ApiParam(name = "body", defaultValue = "") String body) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException { + + DocumentRevisionKey revisionKey = new DocumentRevisionKey(workspaceId, documentId, documentVersion); + DocumentRevision docR = documentService.releaseDocumentRevision(revisionKey); + return mapper.map(docR, DocumentRevisionDTO.class); + } + + @PUT + @ApiOperation(value = "Set document as obsolete", response = DocumentRevisionDTO.class) + @Path("/obsolete") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO markDocumentRevisionAsObsolete(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @ApiParam(name = "body", defaultValue = "") String body) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException { + + DocumentRevisionKey revisionKey = new DocumentRevisionKey(workspaceId, documentId, documentVersion); + DocumentRevision docR = documentService.markDocumentRevisionAsObsolete(revisionKey); + return mapper.map(docR, DocumentRevisionDTO.class); + } + + @PUT + @ApiOperation(value = "Set the tags of the document", response = DocumentRevisionDTO.class) + @Path("/tags") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO saveDocTags(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @ApiParam(required = true, value = "Tag list to save") TagListDTO tagListDTO) + throws EntityNotFoundException, NotAllowedException, ESServerException, AccessRightException, UserNotActiveException { + List tagDTOs = tagListDTO.getTags(); + String[] tagsLabel = new String[tagDTOs.size()]; + for (int i = 0; i < tagDTOs.size(); i++) { + tagsLabel[i] = tagDTOs.get(i).getLabel(); + } + + DocumentRevision documentRevision = documentService.saveTags(new DocumentRevisionKey(workspaceId, documentId, documentVersion), tagsLabel); + DocumentRevisionDTO documentRevisionDTO = mapper.map(documentRevision, DocumentRevisionDTO.class); + documentRevisionDTO.setPath(documentRevision.getLocation().getCompletePath()); + documentRevisionDTO.setLifeCycleState(documentRevision.getLifeCycleState()); + + return documentRevisionDTO; + } + + @POST + @ApiOperation(value = "Add tags to document", response = Response.class) + @Path("/tags") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response addDocTag(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @ApiParam(required = true, value = "Tag list to add") TagListDTO tagListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException, ESServerException { + + DocumentRevisionKey docRPK = new DocumentRevisionKey(workspaceId, documentId, documentVersion); + DocumentRevision docR = documentService.getDocumentRevision(docRPK); + Set tags = docR.getTags(); + Set tagLabels = new HashSet<>(); + + for (TagDTO tagDTO : tagListDTO.getTags()) { + tagLabels.add(tagDTO.getLabel()); + } + + for (Tag tag : tags) { + tagLabels.add(tag.getLabel()); + } + + documentService.saveTags(docRPK, tagLabels.toArray(new String[tagLabels.size()])); + return Response.ok().build(); + } + + @DELETE + @ApiOperation(value = "Remove tags from document", response = Response.class) + @Path("/tags/{tagName}") + public Response removeDocTags(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @PathParam("tagName") String tagName) + throws EntityNotFoundException, NotAllowedException, AccessRightException, UserNotActiveException, ESServerException { + documentService.removeTag(new DocumentRevisionKey(workspaceId, documentId, documentVersion), tagName); + return Response.ok().build(); + } + + @ApiOperation(value = "Delete the document", response = Response.class) + @DELETE + public Response deleteDocument(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion) + throws EntityNotFoundException, NotAllowedException, AccessRightException, UserNotActiveException, ESServerException, EntityConstraintException { + documentService.deleteDocumentRevision(new DocumentRevisionKey(workspaceId, documentId, documentVersion)); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Rename attached files of document", response = FileDTO.class) + @Path("/iterations/{docIteration}/files/{fileName}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public FileDTO renameAttachedFile(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @PathParam("docIteration") int docIteration, + @PathParam("fileName") String fileName, + @ApiParam(required = true, value = "File to rename") FileDTO fileDTO) + throws EntityNotFoundException, NotAllowedException, AccessRightException, UserNotActiveException, FileAlreadyExistsException, CreationException, StorageException { + String fileFullName = workspaceId + "/documents/" + documentId + "/" + documentVersion + "/" + docIteration + "/" + fileName; + BinaryResource binaryResource = documentService.renameFileInDocument(fileFullName, fileDTO.getShortName()); + return new FileDTO(true, binaryResource.getFullName(), binaryResource.getName()); + } + + @DELETE + @ApiOperation(value = "Remove attached file from document", response = Response.class) + @Path("/iterations/{docIteration}/files/{fileName}") + public Response removeAttachedFile(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @PathParam("docIteration") int docIteration, + @PathParam("fileName") String fileName) + throws EntityNotFoundException, NotAllowedException, AccessRightException, UserNotActiveException { + String fileFullName = workspaceId + "/documents/" + documentId + "/" + documentVersion + "/" + docIteration + "/" + fileName; + documentService.removeFileFromDocument(fileFullName); + return Response.ok().build(); + } + + @POST + @ApiOperation(value = "Create a shared document", response = SharedDocumentDTO.class) + @Path("share") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response createSharedDocument(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @ApiParam(required = true, value = "Shared document to create") SharedDocumentDTO pSharedDocumentDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException { + + String password = pSharedDocumentDTO.getPassword(); + Date expireDate = pSharedDocumentDTO.getExpireDate(); + + SharedDocument sharedDocument = documentService.createSharedDocument(new DocumentRevisionKey(workspaceId, documentId, documentVersion), password, expireDate); + SharedDocumentDTO sharedDocumentDTO = mapper.map(sharedDocument, SharedDocumentDTO.class); + return Response.ok().entity(sharedDocumentDTO).build(); + } + + + @PUT + @ApiOperation(value = "Publish a document", response = Response.class) + @Path("publish") + @Consumes(MediaType.APPLICATION_JSON) + public Response publishDocumentRevision(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException { + documentService.setDocumentPublicShared(new DocumentRevisionKey(workspaceId, documentId, documentVersion), true); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Unpublish a document", response = Response.class) + @Path("unpublish") + @Consumes(MediaType.APPLICATION_JSON) + public Response unPublishDocumentRevision(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException { + documentService.setDocumentPublicShared(new DocumentRevisionKey(workspaceId, documentId, documentVersion), false); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Update document's ACL", response = Response.class) + @Path("acl") + @Consumes(MediaType.APPLICATION_JSON) + public Response updateACL(@PathParam("workspaceId") String pWorkspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @ApiParam(required = true, value = "ACL rules to set") ACLDTO acl) + throws EntityNotFoundException, AccessRightException, UserNotActiveException, NotAllowedException { + DocumentRevisionKey documentRevisionKey = new DocumentRevisionKey(pWorkspaceId, documentId, documentVersion); + + if (!acl.getGroupEntries().isEmpty() || !acl.getUserEntries().isEmpty()) { + + Map userEntries = new HashMap<>(); + Map groupEntries = new HashMap<>(); + + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries.put(entry.getKey(), entry.getValue().name()); + } + + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + groupEntries.put(entry.getKey(), entry.getValue().name()); + } + + documentService.updateDocumentACL(pWorkspaceId, documentRevisionKey, userEntries, groupEntries); + } else { + documentService.removeACLFromDocumentRevision(documentRevisionKey); + } + return Response.ok().build(); + } + + @GET + @ApiOperation(value = "Get document's aborted workflows", response = WorkflowDTO.class, responseContainer = "List") + @Path("aborted-workflows") + @Produces(MediaType.APPLICATION_JSON) + public Response getAbortedWorkflows(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + Workflow[] abortedWorkflows = documentWorkflowService.getAbortedWorkflow(new DocumentRevisionKey(workspaceId, documentId, documentVersion)); + List abortedWorkflowsDTO = new ArrayList<>(); + + for (Workflow abortedWorkflow : abortedWorkflows) { + abortedWorkflowsDTO.add(mapper.map(abortedWorkflow, WorkflowDTO.class)); + } + + Collections.sort(abortedWorkflowsDTO); + + return Response.ok(new GenericEntity>((List) abortedWorkflowsDTO) { + }).build(); + } + + @GET + @ApiOperation(value = "Get inverse documents links", response = DocumentRevisionDTO.class, responseContainer = "List") + @Path("{iteration}/inverse-document-link") + @Produces(MediaType.APPLICATION_JSON) + public Response getInverseDocumentLinks(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @PathParam("iteration") int iteration, + @QueryParam("configSpec") String configSpecType) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, DocumentRevisionNotFoundException, DocumentIterationNotFoundException { + DocumentRevisionKey docKey = new DocumentRevisionKey(workspaceId, documentId, documentVersion); + List documents = documentService.getInverseDocumentsLink(docKey); + Set dtos = new HashSet<>(); + for (DocumentIteration doc : documents) { + dtos.add(new DocumentRevisionDTO(doc.getWorkspaceId(), doc.getDocumentMasterId(), doc.getTitle(), doc.getVersion())); + } + + return Response.ok(new GenericEntity>((List) new ArrayList<>(dtos)) { + }).build(); + } + + @GET + @ApiOperation(value = "Get inverse parts links", response = PartRevisionDTO.class, responseContainer = "List") + @Path("{iteration}/inverse-part-link") + @Produces(MediaType.APPLICATION_JSON) + public Response getInversePartsLinks(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @PathParam("iteration") int iteration, + @QueryParam("configSpec") String configSpecType) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, PartIterationNotFoundException, DocumentRevisionNotFoundException { + DocumentRevisionKey docKey = new DocumentRevisionKey(workspaceId, documentId, documentVersion); + List parts = productService.getInversePartsLink(docKey); + Set dtos = new HashSet<>(); + for (PartIteration part : parts) { + dtos.add(new PartRevisionDTO(workspaceId, part.getNumber(), part.getPartName(), part.getVersion())); + } + + return Response.ok(new GenericEntity>((List) new ArrayList<>(dtos)) { + }).build(); + } + + @GET + @ApiOperation(value = "Get inverse product instances links", response = ProductInstanceMasterDTO.class, responseContainer = "List") + @Path("{iteration}/inverse-product-instances-link") + @Produces(MediaType.APPLICATION_JSON) + public Response getInverseProductInstancesLinks(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @PathParam("iteration") int iteration, + @QueryParam("configSpec") String configSpecType) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, PartIterationNotFoundException, DocumentRevisionNotFoundException { + DocumentRevisionKey docKey = new DocumentRevisionKey(workspaceId, documentId, documentVersion); + Set productInstanceMasterList = productService.getInverseProductInstancesLink(docKey); + Set dtos = new HashSet<>(); + for (ProductInstanceMaster productInstanceMaster : productInstanceMasterList) { + dtos.add(mapper.map(productInstanceMaster, ProductInstanceMasterDTO.class)); + } + return Response.ok(new GenericEntity>((List) new ArrayList<>(dtos)) { + }).build(); + } + + @GET + @ApiOperation(value = "Get inverse path data links", response = PathDataMasterDTO.class, responseContainer = "List") + @Path("{iteration}/inverse-path-data-link") + @Produces(MediaType.APPLICATION_JSON) + public Response getInversePathDataLinks(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion, + @PathParam("iteration") int iteration, + @QueryParam("configSpec") String configSpecType) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, PartIterationNotFoundException, DocumentRevisionNotFoundException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException { + DocumentRevisionKey docKey = new DocumentRevisionKey(workspaceId, documentId, documentVersion); + Set pathDataMasters = productService.getInversePathDataLink(docKey); + + Set dtos = new HashSet<>(); + for (PathDataMaster pathDataMaster : pathDataMasters) { + PathDataMasterDTO dto = mapper.map(pathDataMaster, PathDataMasterDTO.class); + ProductInstanceMaster productInstanceMaster = productService.findProductByPathMaster(workspaceId, pathDataMaster); + + LightPartLinkListDTO partLinksList = new LightPartLinkListDTO(); + List path = productService.decodePath(productInstanceMaster.getInstanceOf().getKey(), pathDataMaster.getPath()); + for (PartLink partLink : path) { + partLinksList.getPartLinks().add(new LightPartLinkDTO(partLink)); + } + dto.setPartLinksList(partLinksList); + + dto.setSerialNumber(productInstanceMaster.getSerialNumber()); + dtos.add(dto); + } + + return Response.ok(new GenericEntity>((List) new ArrayList<>(dtos)) { + }).build(); + + } + + private DocumentRevisionKey[] createDocumentRevisionKeys(List dtos) { + DocumentRevisionKey[] data = new DocumentRevisionKey[dtos.size()]; + int i = 0; + for (DocumentRevisionDTO dto : dtos) { + data[i++] = new DocumentRevisionKey(dto.getWorkspaceId(), dto.getDocumentMasterId(), dto.getVersion()); + } + + return data; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/DocumentTemplateResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/DocumentTemplateResource.java new file mode 100644 index 0000000000..3795ddd6b2 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/DocumentTemplateResource.java @@ -0,0 +1,232 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.document.DocumentMasterTemplate; +import com.docdoku.core.document.DocumentMasterTemplateKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.rest.util.InstanceAttributeFactory; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Yassine Belouad + */ + +@RequestScoped +@Api(hidden = true, value = "document-templates", description = "Operations about document templates") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class DocumentTemplateResource { + + @Inject + private IDocumentManagerLocal documentService; + + private Mapper mapper; + + public DocumentTemplateResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get document templates", response = DocumentMasterTemplateDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public DocumentMasterTemplateDTO[] getDocumentMasterTemplates(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + + DocumentMasterTemplate[] documentMasterTemplates = documentService.getDocumentMasterTemplates(workspaceId); + DocumentMasterTemplateDTO[] dtos = new DocumentMasterTemplateDTO[documentMasterTemplates.length]; + + for (int i = 0; i < documentMasterTemplates.length; i++) { + dtos[i] = mapper.map(documentMasterTemplates[i], DocumentMasterTemplateDTO.class); + } + + return dtos; + } + + @GET + @ApiOperation(value = "Get document template", response = DocumentMasterTemplateDTO.class) + @Path("{templateId}") + @Produces(MediaType.APPLICATION_JSON) + public DocumentMasterTemplateDTO getDocumentMasterTemplate(@PathParam("workspaceId") String workspaceId, + @PathParam("templateId") String templateId) + throws EntityNotFoundException, UserNotActiveException { + + DocumentMasterTemplate documentMasterTemplate = documentService.getDocumentMasterTemplate(new DocumentMasterTemplateKey(workspaceId, templateId)); + return mapper.map(documentMasterTemplate, DocumentMasterTemplateDTO.class); + } + + @GET + @ApiOperation(value = "Generate document template id", response = TemplateGeneratedIdDTO.class) + @Path("{templateId}/generate_id") + @Produces(MediaType.APPLICATION_JSON) + public TemplateGeneratedIdDTO generateDocumentMasterId(@PathParam("workspaceId") String workspaceId, + @PathParam("templateId") String templateId) + throws EntityNotFoundException, UserNotActiveException { + + String generateId = documentService.generateId(workspaceId, templateId); + return new TemplateGeneratedIdDTO(generateId); + } + + @POST + @ApiOperation(value = "Create document template", response = DocumentMasterTemplateDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public DocumentMasterTemplateDTO createDocumentMasterTemplate(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Document master template to create") DocumentTemplateCreationDTO templateCreationDTO) + throws EntityNotFoundException, EntityAlreadyExistsException, AccessRightException, NotAllowedException, CreationException { + + String id = templateCreationDTO.getReference(); + String documentType = templateCreationDTO.getDocumentType(); + String workflowModelId = templateCreationDTO.getWorkflowModelId(); + String mask = templateCreationDTO.getMask(); + boolean idGenerated = templateCreationDTO.isIdGenerated(); + boolean attributesLocked = templateCreationDTO.isAttributesLocked(); + + List attributeTemplates = templateCreationDTO.getAttributeTemplates(); + String[] lovNames = new String[attributeTemplates.size()]; + for (int i = 0; i < attributeTemplates.size(); i++) + lovNames[i] = attributeTemplates.get(i).getLovName(); + + DocumentMasterTemplate template = documentService.createDocumentMasterTemplate(workspaceId, id, documentType, workflowModelId, mask, new InstanceAttributeFactory().createInstanceAttributeTemplateFromDTO(attributeTemplates), lovNames, idGenerated, attributesLocked); + DocumentMasterTemplateDTO response = mapper.map(template, DocumentMasterTemplateDTO.class); + return response; + } + + @PUT + @ApiOperation(value = "Update document template", response = DocumentMasterTemplateDTO.class) + @Path("{templateId}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public DocumentMasterTemplateDTO updateDocumentMasterTemplate(@PathParam("workspaceId") String workspaceId, + @PathParam("templateId") String templateId, + @ApiParam(required = true, value = "Document master template to update") DocumentMasterTemplateDTO documentMasterTemplateDTO) + throws EntityNotFoundException, AccessRightException, UserNotActiveException, NotAllowedException { + + String documentType = documentMasterTemplateDTO.getDocumentType(); + String workflowModelId = documentMasterTemplateDTO.getWorkflowModelId(); + String mask = documentMasterTemplateDTO.getMask(); + boolean idGenerated = documentMasterTemplateDTO.isIdGenerated(); + boolean attributesLocked = documentMasterTemplateDTO.isAttributesLocked(); + + List attributeTemplates = documentMasterTemplateDTO.getAttributeTemplates(); + String[] lovNames = new String[attributeTemplates.size()]; + for (int i = 0; i < attributeTemplates.size(); i++) + lovNames[i] = attributeTemplates.get(i).getLovName(); + + DocumentMasterTemplate template = documentService.updateDocumentMasterTemplate(new DocumentMasterTemplateKey(workspaceId, templateId), documentType, workflowModelId, mask, new InstanceAttributeFactory().createInstanceAttributeTemplateFromDTO(attributeTemplates), lovNames, idGenerated, attributesLocked); + return mapper.map(template, DocumentMasterTemplateDTO.class); + } + + @PUT + @ApiOperation(value = "Update document template ACL", response = Response.class) + @Path("{templateId}/acl") + @Consumes(MediaType.APPLICATION_JSON) + public Response updateDocumentMasterTemplateACL(@PathParam("workspaceId") String workspaceId, + @PathParam("templateId") String templateId, + @ApiParam(required = true, value = "ACL rules to set") ACLDTO acl) + throws EntityNotFoundException, AccessRightException, UserNotActiveException, NotAllowedException { + + if (!acl.getGroupEntries().isEmpty() || !acl.getUserEntries().isEmpty()) { + + Map userEntries = new HashMap<>(); + Map groupEntries = new HashMap<>(); + + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries.put(entry.getKey(), entry.getValue().name()); + } + + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + groupEntries.put(entry.getKey(), entry.getValue().name()); + } + + documentService.updateACLForDocumentMasterTemplate(workspaceId, templateId, userEntries, groupEntries); + } else { + documentService.removeACLFromDocumentMasterTemplate(workspaceId, templateId); + } + + return Response.ok().build(); + } + + @DELETE + @ApiOperation(value = "Delete document template", response = Response.class) + @Path("{templateId}") + public Response deleteDocumentMasterTemplate(@PathParam("workspaceId") String workspaceId, @PathParam("templateId") String templateId) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + documentService.deleteDocumentMasterTemplate(new DocumentMasterTemplateKey(workspaceId, templateId)); + return Response.ok().build(); + } + + @DELETE + @ApiOperation(value = "Remove attached file from document template", response = Response.class) + @Path("{templateId}/files/{fileName}") + @Consumes(MediaType.APPLICATION_JSON) + public Response removeAttachedFile(@PathParam("workspaceId") String workspaceId, + @PathParam("templateId") String templateId, + @PathParam("fileName") String fileName) + throws EntityNotFoundException, AccessRightException, UserNotActiveException, StorageException { + + String fileFullName = workspaceId + "/document-templates/" + templateId + "/" + fileName; + + documentService.removeFileFromTemplate(fileFullName); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Rename attached file in document template", response = FileDTO.class) + @Path("{templateId}/files/{fileName}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public FileDTO renameAttachedFile(@PathParam("workspaceId") String workspaceId, + @PathParam("templateId") String templateId, + @PathParam("fileName") String fileName, + @ApiParam(required = true, value = "File to rename") FileDTO fileDTO) + throws UserNotActiveException, WorkspaceNotFoundException, CreationException, UserNotFoundException, FileNotFoundException, NotAllowedException, AccessRightException, FileAlreadyExistsException, StorageException { + String fileFullName = workspaceId + "/document-templates/" + templateId + "/" + fileName; + BinaryResource binaryResource = documentService.renameFileInTemplate(fileFullName, fileDTO.getShortName()); + return new FileDTO(true, binaryResource.getFullName(), binaryResource.getName()); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/DocumentsResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/DocumentsResource.java new file mode 100644 index 0000000000..8a2bc7ac59 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/DocumentsResource.java @@ -0,0 +1,218 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest; + +import com.docdoku.core.configuration.DocumentConfigSpec; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.query.DocumentSearchQuery; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDocumentConfigSpecManagerLocal; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.core.services.IDocumentWorkflowManagerLocal; +import com.docdoku.server.rest.dto.CountDTO; +import com.docdoku.server.rest.dto.DocumentRevisionDTO; +import com.docdoku.server.rest.util.ConfigSpecHelper; +import com.docdoku.server.rest.util.SearchQueryParser; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.UriInfo; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +@RequestScoped +@Api(hidden = true, value = "documents", description = "Operations about documents") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class DocumentsResource { + + private static final Logger LOGGER = Logger.getLogger(DocumentsResource.class.getName()); + @Inject + private IDocumentManagerLocal documentService; + @Inject + private IDocumentConfigSpecManagerLocal documentConfigSpecService; + @Inject + private IDocumentWorkflowManagerLocal documentWorkflowService; + @Inject + private DocumentBaselinesResource baselinesResource; + @Inject + private DocumentResource documentResource; + private Mapper mapper; + + public DocumentsResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @ApiOperation(value = "SubResource : DocumentResource") + @Path("{documentId: [^/].*}-{documentVersion:[A-Z]+}") + public DocumentResource getDocumentResource() { + return documentResource; + } + + @GET + @ApiOperation(value = "Get documents in workspace", response = DocumentRevisionDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO[] getDocumentsInWorkspace(@PathParam("workspaceId") String workspaceId, + @QueryParam("start") int start, + @QueryParam("configSpec") String configSpecType) + throws UserNotActiveException, ESServerException, WorkspaceNotFoundException, UserNotFoundException, BaselineNotFoundException, DocumentRevisionNotFoundException { + + int maxResult = 20; + + DocumentRevision[] docRs; + if (configSpecType == null || ConfigSpecHelper.BASELINE_UNDEFINED.equals(configSpecType) || ConfigSpecHelper.BASELINE_LATEST.equals(configSpecType)) { + docRs = documentService.getAllDocumentsInWorkspace(workspaceId, start, maxResult); + } else { + DocumentConfigSpec configSpec = ConfigSpecHelper.getConfigSpec(workspaceId, configSpecType, documentConfigSpecService); + docRs = documentConfigSpecService.getFilteredDocuments(workspaceId, configSpec, start, maxResult); + } + DocumentRevisionDTO[] docRsDTOs = new DocumentRevisionDTO[docRs.length]; + + for (int i = 0; i < docRs.length; i++) { + docRsDTOs[i] = mapper.map(docRs[i], DocumentRevisionDTO.class); + docRsDTOs[i].setPath(docRs[i].getLocation().getCompletePath()); + docRsDTOs[i] = Tools.createLightDocumentRevisionDTO(docRsDTOs[i]); + docRsDTOs[i].setIterationSubscription(documentService.isUserIterationChangeEventSubscribedForGivenDocument(workspaceId, docRs[i])); + docRsDTOs[i].setStateSubscription(documentService.isUserStateChangeEventSubscribedForGivenDocument(workspaceId, docRs[i])); + } + + return docRsDTOs; + } + + + @GET + @ApiOperation(value = "Search documents", response = DocumentRevisionDTO.class, responseContainer = "List") + @Path("search") + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO[] searchDocumentRevision(@Context UriInfo uri, + @PathParam("workspaceId") String workspaceId, + @QueryParam("q") String q) throws EntityNotFoundException, UserNotActiveException, ESServerException { + MultivaluedMap params = uri.getQueryParameters(); + String configSpecType = params.containsKey("configSpec") ? params.get("configSpec").get(0) : null; + + DocumentSearchQuery documentSearchQuery = SearchQueryParser.parseDocumentStringQuery(workspaceId, params); + + DocumentRevision[] docRs; + if (configSpecType == null || ConfigSpecHelper.BASELINE_UNDEFINED.equals(configSpecType) || ConfigSpecHelper.BASELINE_LATEST.equals(configSpecType)) { + docRs = documentService.searchDocumentRevisions(documentSearchQuery); + } else { + DocumentConfigSpec configSpec = ConfigSpecHelper.getConfigSpec(workspaceId, configSpecType, documentConfigSpecService); + docRs = documentConfigSpecService.searchFilteredDocuments(workspaceId, configSpec, documentSearchQuery); + } + DocumentRevisionDTO[] docRsDTOs = new DocumentRevisionDTO[docRs.length]; + + for (int i = 0; i < docRs.length; i++) { + docRsDTOs[i] = mapper.map(docRs[i], DocumentRevisionDTO.class); + docRsDTOs[i].setPath(docRs[i].getLocation().getCompletePath()); + docRsDTOs[i] = Tools.createLightDocumentRevisionDTO(docRsDTOs[i]); + docRsDTOs[i].setIterationSubscription(documentService.isUserIterationChangeEventSubscribedForGivenDocument(workspaceId, docRs[i])); + docRsDTOs[i].setStateSubscription(documentService.isUserStateChangeEventSubscribedForGivenDocument(workspaceId, docRs[i])); + } + + return docRsDTOs; + } + + @GET + @ApiOperation(value = "Count documents", response = CountDTO.class) + @Path("count") + @Produces(MediaType.APPLICATION_JSON) + public CountDTO getDocumentsInWorkspaceCount(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + return new CountDTO(documentService.getDocumentsInWorkspaceCount(Tools.stripTrailingSlash(workspaceId))); + } + + + @GET + @ApiOperation(value = "Get checked out documents", response = DocumentRevisionDTO.class, responseContainer = "List") + @Path("checkedout") + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO[] getCheckedOutDocuments(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + + DocumentRevision[] checkedOutDocumentRevisions = documentService.getCheckedOutDocumentRevisions(workspaceId); + DocumentRevisionDTO[] documentRevisionDTOs = new DocumentRevisionDTO[checkedOutDocumentRevisions.length]; + + for (int i = 0; i < checkedOutDocumentRevisions.length; i++) { + documentRevisionDTOs[i] = mapper.map(checkedOutDocumentRevisions[i], DocumentRevisionDTO.class); + documentRevisionDTOs[i].setPath(checkedOutDocumentRevisions[i].getLocation().getCompletePath()); + documentRevisionDTOs[i] = Tools.createLightDocumentRevisionDTO(documentRevisionDTOs[i]); + documentRevisionDTOs[i].setIterationSubscription(documentService.isUserIterationChangeEventSubscribedForGivenDocument(workspaceId, checkedOutDocumentRevisions[i])); + documentRevisionDTOs[i].setStateSubscription(documentService.isUserStateChangeEventSubscribedForGivenDocument(workspaceId, checkedOutDocumentRevisions[i])); + } + + return documentRevisionDTOs; + } + + @GET + @ApiOperation(value = "Count checked out documents", response = DocumentRevisionDTO.class, responseContainer = "List") + @Path("countCheckedOut") + @Produces(MediaType.APPLICATION_JSON) + public CountDTO countCheckedOutDocs(@PathParam("workspaceId") String workspaceId) + throws WorkspaceNotFoundException, UserNotActiveException, UserNotFoundException { + return new CountDTO(documentService.getCheckedOutDocumentRevisions(workspaceId).length); + } + + + @GET + @ApiOperation(value = "searchDocumentRevisionsToLink : todo doc", response = DocumentRevisionDTO.class, responseContainer = "List") + @Path("doc_revs") + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO[] searchDocumentRevisionsToLink(@PathParam("workspaceId") String workspaceId, + @QueryParam("q") String q, + @QueryParam("l") int limit) + throws EntityNotFoundException, UserNotActiveException { + + int maxResults = limit == 0 ? 15 : limit; + DocumentRevision[] docRs = documentService.getDocumentRevisionsWithReferenceOrTitle(workspaceId, q, maxResults); + + List docRevDTOS = new ArrayList<>(); + for (DocumentRevision docR : docRs) { + DocumentRevisionDTO docRevDTO = new DocumentRevisionDTO(docR.getWorkspaceId(), docR.getDocumentMasterId(), docR.getTitle(), docR.getVersion()); + docRevDTOS.add(docRevDTO); + } + + return docRevDTOS.toArray(new DocumentRevisionDTO[docRevDTOS.size()]); + } + + @ApiOperation(value = "SubResource : DocumentBaselinesResource") + @Path("baselines") + public DocumentBaselinesResource getAllBaselines(@PathParam("workspaceId") String workspaceId) { + return baselinesResource; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/FileResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/FileResource.java new file mode 100644 index 0000000000..a07480db2d --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/FileResource.java @@ -0,0 +1,90 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.server.rest.file.*; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.Path; + +@RequestScoped +@Api(value = "files", description = "Operations about files") +@Path("files") +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) +public class FileResource { + + @Inject + private DocumentBinaryResource documentBinaryResource; + + @Inject + private PartBinaryResource partBinaryResource; + + @Inject + private DocumentTemplateBinaryResource documentTemplateBinaryResource; + + @Inject + private PartTemplateBinaryResource partTemplateBinaryResource; + + @Inject + private ProductInstanceBinaryResource productInstanceBinaryResource; + + public FileResource() { + } + + @ApiOperation(value = "documents") + @Path("/{workspaceId}/documents/{documentId}/{version}") + public DocumentBinaryResource documentFile() { + return documentBinaryResource; + } + + @ApiOperation(value = "parts") + @Path("/{workspaceId}/parts/{partNumber}/{version}") + public PartBinaryResource partFile() { + return partBinaryResource; + } + + @ApiOperation(value = "document-templates") + @Path("/{workspaceId}/document-templates/{templateId}/") + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + public DocumentTemplateBinaryResource documentTemplateFile() { + return documentTemplateBinaryResource; + } + + @ApiOperation(value = "part-templates") + @Path("/{workspaceId}/part-templates/{templateId}/") + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + public PartTemplateBinaryResource partTemplateFile() { + return partTemplateBinaryResource; + } + + @ApiOperation(value = "product-instances") + @Path("/{workspaceId}/product-instances/{serialNumber}/{ciId}/") + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + public ProductInstanceBinaryResource productInstanceFile() { + return productInstanceBinaryResource; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/FolderResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/FolderResource.java new file mode 100644 index 0000000000..eb39bd2b1c --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/FolderResource.java @@ -0,0 +1,383 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.configuration.DocumentConfigSpec; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.document.Folder; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.ACLUserEntry; +import com.docdoku.core.security.ACLUserGroupEntry; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDocumentConfigSpecManagerLocal; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.rest.util.ConfigSpecHelper; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +@RequestScoped +@Api(hidden = true, value = "folders", description = "Operations about folders") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class FolderResource { + + private static final Logger LOGGER = Logger.getLogger(FolderResource.class.getName()); + @Inject + private IDocumentManagerLocal documentService; + @Inject + private IDocumentConfigSpecManagerLocal documentConfigSpecService; + private Mapper mapper; + + public FolderResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @Path("{folderId}/documents/") + @ApiOperation(value = "Get documents in folder", response = DocumentRevisionDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO[] getDocumentsWithGivenFolderIdAndWorkspaceId( + @PathParam("workspaceId") String workspaceId, + @PathParam("folderId") String folderId, + @QueryParam("configSpec") String configSpecType) + throws EntityNotFoundException, UserNotActiveException { + + String decodedCompletePath = getPathFromUrlParams(workspaceId, folderId); + DocumentRevision[] docRs; + if (configSpecType == null || ConfigSpecHelper.BASELINE_UNDEFINED.equals(configSpecType) || ConfigSpecHelper.BASELINE_LATEST.equals(configSpecType)) { + docRs = documentService.findDocumentRevisionsByFolder(decodedCompletePath); + } else { + DocumentConfigSpec configSpec = ConfigSpecHelper.getConfigSpec(workspaceId, configSpecType, documentConfigSpecService); + docRs = documentConfigSpecService.getFilteredDocumentsByFolder(workspaceId, configSpec, decodedCompletePath); + } + DocumentRevisionDTO[] docRsDTOs = new DocumentRevisionDTO[docRs.length]; + + for (int i = 0; i < docRs.length; i++) { + docRsDTOs[i] = mapper.map(docRs[i], DocumentRevisionDTO.class); + docRsDTOs[i].setPath(docRs[i].getLocation().getCompletePath()); + docRsDTOs[i] = Tools.createLightDocumentRevisionDTO(docRsDTOs[i]); + if (configSpecType == null || ConfigSpecHelper.BASELINE_UNDEFINED.equals(configSpecType) || ConfigSpecHelper.BASELINE_LATEST.equals(configSpecType)) { + docRsDTOs[i].setLifeCycleState(docRs[i].getLifeCycleState()); + docRsDTOs[i].setIterationSubscription(documentService.isUserIterationChangeEventSubscribedForGivenDocument(workspaceId, docRs[i])); + docRsDTOs[i].setStateSubscription(documentService.isUserStateChangeEventSubscribedForGivenDocument(workspaceId, docRs[i])); + } else { + docRsDTOs[i].setWorkflow(null); + docRsDTOs[i].setTags(null); + } + } + + return docRsDTOs; + } + + @POST + @Path("{folderId}/documents/") + @ApiOperation(value = "Create document", response = DocumentRevisionDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response createDocumentMasterInFolder( + @PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Document to create") DocumentCreationDTO docCreationDTO, + @PathParam("folderId") String folderId) + throws EntityNotFoundException, EntityAlreadyExistsException, NotAllowedException, CreationException, AccessRightException { + + String pDocMID = docCreationDTO.getReference(); + String pTitle = docCreationDTO.getTitle(); + String pDescription = docCreationDTO.getDescription(); + + String decodedCompletePath = getPathFromUrlParams(workspaceId, folderId); + + String pWorkflowModelId = docCreationDTO.getWorkflowModelId(); + RoleMappingDTO[] roleMappingDTOs = docCreationDTO.getRoleMapping(); + String pDocMTemplateId = docCreationDTO.getTemplateId(); + + ACLDTO acl = docCreationDTO.getAcl(); + + ACLUserEntry[] userEntries = null; + ACLUserGroupEntry[] userGroupEntries = null; + if (acl != null) { + userEntries = new ACLUserEntry[acl.getUserEntries().size()]; + userGroupEntries = new ACLUserGroupEntry[acl.getGroupEntries().size()]; + int i = 0; + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries[i] = new ACLUserEntry(); + userEntries[i].setPrincipal(new User(new Workspace(workspaceId), new Account(entry.getKey()))); + userEntries[i++].setPermission(ACL.Permission.valueOf(entry.getValue().name())); + } + i = 0; + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + userGroupEntries[i] = new ACLUserGroupEntry(); + userGroupEntries[i].setPrincipal(new UserGroup(new Workspace(workspaceId), entry.getKey())); + userGroupEntries[i++].setPermission(ACL.Permission.valueOf(entry.getValue().name())); + } + } + + Map> userRoleMapping = new HashMap<>(); + Map> groupRoleMapping = new HashMap<>(); + + if (roleMappingDTOs != null) { + for (RoleMappingDTO roleMappingDTO : roleMappingDTOs) { + userRoleMapping.put(roleMappingDTO.getRoleName(), roleMappingDTO.getUserLogins()); + groupRoleMapping.put(roleMappingDTO.getRoleName(), roleMappingDTO.getGroupIds()); + } + } + DocumentRevision createdDocRs = documentService.createDocumentMaster(decodedCompletePath, pDocMID, pTitle, pDescription, pDocMTemplateId, pWorkflowModelId, userEntries, userGroupEntries, userRoleMapping, groupRoleMapping); + + DocumentRevisionDTO docRsDTO = mapper.map(createdDocRs, DocumentRevisionDTO.class); + docRsDTO.setPath(createdDocRs.getLocation().getCompletePath()); + docRsDTO.setLifeCycleState(createdDocRs.getLifeCycleState()); + + try { + return Response.created(URI.create(URLEncoder.encode(pDocMID + "-" + createdDocRs.getVersion(), "UTF-8"))).entity(docRsDTO).build(); + } catch (UnsupportedEncodingException ex) { + LOGGER.log(Level.WARNING, null, ex); + return Response.ok().build(); + } + } + + private String getPathFromUrlParams(String workspaceId, String folderId) { + return folderId == null ? Tools.stripTrailingSlash(workspaceId) : Tools.stripTrailingSlash(FolderDTO.replaceColonWithSlash(folderId)); + } + + /** + * Retrieves representation of folders located at the root of the given workspace + * + * @param workspaceId The current workspace id + * @return The array of folders + */ + @GET + @ApiOperation(value = "Get root folders", response = FolderDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public FolderDTO[] getRootFolders(@PathParam("workspaceId") String workspaceId, + @QueryParam("configSpec") String configSpecType) + throws EntityNotFoundException, UserNotActiveException { + String completePath = Tools.stripTrailingSlash(workspaceId); + return getFolders(workspaceId, completePath, true, configSpecType); + } + + @GET + @ApiOperation(value = "Get sub folders", response = FolderDTO.class, responseContainer = "List") + @Path("{completePath}/folders") + @Produces(MediaType.APPLICATION_JSON) + public FolderDTO[] getSubFolders(@PathParam("workspaceId") String workspaceId, + @PathParam("completePath") String folderId, + @QueryParam("configSpec") String configSpecType) + throws EntityNotFoundException, UserNotActiveException { + String decodedCompletePath = FolderDTO.replaceColonWithSlash(folderId); + String completePath = Tools.stripTrailingSlash(decodedCompletePath); + return getFolders(workspaceId, completePath, false, configSpecType); + } + + private FolderDTO[] getFolders(String workspaceId, String completePath, boolean rootFolder, String configSpecType) + throws EntityNotFoundException, UserNotActiveException { + String[] folderNames; + if (configSpecType == null || "latest".equals(configSpecType)) { + folderNames = documentService.getFolders(completePath); + } else { + DocumentConfigSpec cs = ConfigSpecHelper.getConfigSpec(workspaceId, configSpecType, documentConfigSpecService); + folderNames = documentConfigSpecService.getFilteredFolders(workspaceId, cs, completePath); + } + + FolderDTO[] folderDTOs = new FolderDTO[folderNames.length]; + + for (int i = 0; i < folderNames.length; i++) { + String completeFolderPath; + if (rootFolder) { + completeFolderPath = workspaceId + "/" + folderNames[i]; + } else { + completeFolderPath = completePath + "/" + folderNames[i]; + } + + String encodedFolderId = FolderDTO.replaceSlashWithColon(completeFolderPath); + + folderDTOs[i] = new FolderDTO(); + folderDTOs[i].setPath(completePath); + folderDTOs[i].setName(folderNames[i]); + folderDTOs[i].setId(encodedFolderId); + + } + + return folderDTOs; + } + + /** + * PUT method for updating or creating an instance of FolderResource + */ + @PUT + @ApiOperation(value = "Rename a folder", response = FolderDTO.class) + @Path("{folderId}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public FolderDTO renameFolder(@PathParam("workspaceId") String workspaceId, + @PathParam("folderId") String folderPath, + @ApiParam(value = "Folder with new name", required = true) FolderDTO folderDTO) + throws EntityNotFoundException, EntityAlreadyExistsException, NotAllowedException, AccessRightException, CreationException { + + String decodedCompletePath = FolderDTO.replaceColonWithSlash(folderPath); + String completePath = Tools.stripTrailingSlash(decodedCompletePath); + String destParentFolder = FolderDTO.extractParentFolder(completePath); + String folderName = folderDTO.getName(); + + documentService.moveFolder(completePath, destParentFolder, folderName); + + String completeRenamedFolderId = destParentFolder + '/' + folderName; + String encodedRenamedFolderId = FolderDTO.replaceSlashWithColon(completeRenamedFolderId); + + FolderDTO renamedFolderDTO = new FolderDTO(); + renamedFolderDTO.setPath(destParentFolder); + renamedFolderDTO.setName(folderName); + renamedFolderDTO.setId(encodedRenamedFolderId); + + return renamedFolderDTO; + } + + /** + * PUT method for moving folder into an other + */ + @PUT + @ApiOperation(value = "Move a folder", response = FolderDTO.class) + @Path("{folderId}/move") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public FolderDTO moveFolder(@PathParam("workspaceId") String workspaceId, + @PathParam("folderId") String folderPath, + @ApiParam(required = true, value = "Folder to move") FolderDTO folderDTO) + throws EntityNotFoundException, EntityAlreadyExistsException, NotAllowedException, AccessRightException, CreationException { + + String decodedCompletePath = FolderDTO.replaceColonWithSlash(folderPath); + String completePath = Tools.stripTrailingSlash(decodedCompletePath); + + String destParentFolder = FolderDTO.replaceColonWithSlash(folderDTO.getId()); + String folderName = Tools.stripLeadingSlash(FolderDTO.extractName(completePath)); + + documentService.moveFolder(completePath, destParentFolder, folderName); + + String completeRenamedFolderId = destParentFolder + '/' + folderName; + String encodedRenamedFolderId = FolderDTO.replaceSlashWithColon(completeRenamedFolderId); + + FolderDTO renamedFolderDTO = new FolderDTO(); + renamedFolderDTO.setPath(destParentFolder); + renamedFolderDTO.setName(folderName); + renamedFolderDTO.setId(encodedRenamedFolderId); + + return renamedFolderDTO; + } + + @POST + @ApiOperation(value = "Create a sub folder", response = FolderDTO.class) + @Path("{parentFolderPath}/folders") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public FolderDTO createSubFolder(@PathParam("workspaceId") String workspaceId, + @PathParam("parentFolderPath") String parentFolderPath, + @ApiParam(value = "Folder to create", required = true) FolderDTO folder) + throws EntityNotFoundException, EntityAlreadyExistsException, NotAllowedException, AccessRightException, UserNotActiveException, CreationException { + + String decodedCompletePath = FolderDTO.replaceColonWithSlash(parentFolderPath); + + String folderName = folder.getName(); + return createFolder(decodedCompletePath, folderName); + } + + @POST + @ApiOperation(value = "Create root folder", response = FolderDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public FolderDTO createRootFolder(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Folder to create") FolderDTO folder) + throws EntityNotFoundException, EntityAlreadyExistsException, NotAllowedException, AccessRightException, UserNotActiveException, CreationException { + + String folderName = folder.getName(); + return createFolder(workspaceId, folderName); + } + + /** + * DELETE method for deleting an instance of FolderResource + * + * @param completePath the folder path + * @return the array of the documents that have also been deleted + */ + @DELETE + @ApiOperation(value = "Delete root folder", response = Response.class) + @Path("{folderId}") + @Produces(MediaType.APPLICATION_JSON) + public Response deleteRootFolder(@PathParam("workspaceId") String workspaceId, + @PathParam("folderId") String completePath) + throws EntityNotFoundException, NotAllowedException, AccessRightException, UserNotActiveException, ESServerException, EntityConstraintException { + + deleteFolder(completePath); + return Response.status(Response.Status.OK).build(); + } + + private DocumentRevisionKey[] deleteFolder(String pCompletePath) + throws EntityNotFoundException, ESServerException, AccessRightException, NotAllowedException, EntityConstraintException, UserNotActiveException { + + String decodedCompletePath = FolderDTO.replaceColonWithSlash(pCompletePath); + String completePath = Tools.stripTrailingSlash(decodedCompletePath); + return documentService.deleteFolder(completePath); + } + + private FolderDTO createFolder(String pCompletePath, String pFolderName) + throws EntityNotFoundException, EntityAlreadyExistsException, CreationException, AccessRightException, NotAllowedException { + Folder createdFolder = documentService.createFolder(pCompletePath, pFolderName); + + String completeCreatedFolderPath = createdFolder.getCompletePath() + '/' + createdFolder.getShortName(); + String encodedFolderId = FolderDTO.replaceSlashWithColon(completeCreatedFolderPath); + + FolderDTO createdFolderDTOs = new FolderDTO(); + createdFolderDTOs.setPath(createdFolder.getCompletePath()); + createdFolderDTOs.setName(createdFolder.getShortName()); + createdFolderDTOs.setId(encodedFolderId); + + return createdFolderDTOs; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/HTMLViewerResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/HTMLViewerResource.java new file mode 100644 index 0000000000..a31a94e56e --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/HTMLViewerResource.java @@ -0,0 +1,149 @@ +package com.docdoku.server.rest; + + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.product.PartRevisionKey; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.core.services.IFileViewerManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.core.services.IShareManagerLocal; +import com.docdoku.core.sharing.SharedDocument; +import com.docdoku.core.sharing.SharedEntity; +import com.docdoku.core.sharing.SharedPart; +import com.docdoku.server.filters.GuestProxy; +import com.docdoku.server.rest.exceptions.ExpiredLinkException; +import com.docdoku.server.rest.exceptions.UnmatchingUuidException; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.Date; + +@RequestScoped +@Api(value = "viewer", description = "Operations about html viewer") +@Path("viewer") +public class HTMLViewerResource { + + @Inject + private IFileViewerManagerLocal viewerManager; + + @Inject + private IProductManagerLocal productManager; + + @Inject + private IDocumentManagerLocal documentManager; + + @Inject + private IShareManagerLocal shareManager; + + @Inject + private GuestProxy guestProxy; + + public HTMLViewerResource() { + } + + @GET + @Produces(MediaType.TEXT_HTML) + @ApiOperation(value = "Get html viewer for document") + public Response getHtmlViewerForFile(@QueryParam("uuid") final String uuid, @QueryParam("fileName") final String fileName) throws AccessRightException, NotAllowedException, EntityNotFoundException, UserNotActiveException, ExpiredLinkException, UnmatchingUuidException { + if (uuid != null && !uuid.isEmpty()) { + SharedEntity sharedEntity = shareManager.findSharedEntityForGivenUUID(uuid); + BinaryResource sharedResource = checkUuidValidity(sharedEntity, fileName); + return Response.ok().entity(viewerManager.getHtmlForViewer(sharedResource, uuid)).build(); + } + + String holderType = BinaryResource.parseHolderType(fileName); + if ("documents".equals(holderType)) { + return getDocumentHTMLViewer(fileName); + } else if ("parts".equals(holderType)) { + return getPartHTMLViewer(fileName); + } else { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + } + + + private Response getPartHTMLViewer(String fileName) throws NotAllowedException, AccessRightException, UserNotActiveException, EntityNotFoundException, ExpiredLinkException, UnmatchingUuidException { + + BinaryResource binaryResource = guestProxy.getPublicBinaryResourceForPart(fileName); + if (binaryResource != null) { + return Response.ok().entity(viewerManager.getHtmlForViewer(binaryResource, null)).build(); + } else { + binaryResource = productManager.getBinaryResource(fileName); + return Response.ok().entity(viewerManager.getHtmlForViewer(binaryResource, null)).build(); + } + } + + public Response getDocumentHTMLViewer(String fileName) throws NotAllowedException, AccessRightException, UserNotActiveException, EntityNotFoundException { + BinaryResource binaryResource = guestProxy.getPublicBinaryResourceForDocument(fileName); + if (binaryResource != null) { + return Response.ok().entity(viewerManager.getHtmlForViewer(binaryResource, null)).build(); + } else { + binaryResource = documentManager.getBinaryResource(fileName); + return Response.ok().entity(viewerManager.getHtmlForViewer(binaryResource, null)).build(); + } + } + + + private BinaryResource checkUuidValidity(SharedEntity sharedEntity, String fileName) + throws UnmatchingUuidException, NotAllowedException, WorkspaceNotFoundException, AccessRightException, FileNotFoundException, UserNotFoundException, UserNotActiveException, ExpiredLinkException { + + // Compare types + String holderType = BinaryResource.parseHolderType(fileName); + if ("parts".equals(holderType) && !(sharedEntity instanceof SharedPart) || + "documents".equals(holderType) && !(sharedEntity instanceof SharedDocument)) { + throw new UnmatchingUuidException(); + } + + checkUuidExpiredDate(sharedEntity); + + String workspaceId = BinaryResource.parseWorkspaceId(fileName); + String holderId = BinaryResource.parseHolderId(fileName); + String holderRevision = BinaryResource.parseHolderRevision(fileName); + Integer holderIteration = BinaryResource.parseHolderIteration(fileName); + + if ("parts".equals(holderType)){ + PartRevisionKey partRPK = new PartRevisionKey(workspaceId, holderId, holderRevision); + PartRevision partRevision = ((SharedPart) sharedEntity).getPartRevision(); + PartIteration lastCheckedInIteration = partRevision.getLastCheckedInIteration(); + if(partRevision.getKey().equals(partRPK) && (null != lastCheckedInIteration && lastCheckedInIteration.getIteration() <= holderIteration)){ + return guestProxy.getBinaryResourceForSharedPart(fileName); + }else{ + throw new UnmatchingUuidException(); + } + } + if ("documents".equals(holderType)){ + DocumentRevisionKey docRPK = new DocumentRevisionKey(workspaceId, holderId, holderRevision); + DocumentRevision documentRevision = ((SharedDocument) sharedEntity).getDocumentRevision(); + DocumentIteration lastCheckedInIteration = documentRevision.getLastCheckedInIteration(); + if(documentRevision.getKey().equals(docRPK) && (null != lastCheckedInIteration && lastCheckedInIteration.getIteration() <= holderIteration)){ + return guestProxy.getBinaryResourceForSharedDocument(fileName); + }else{ + throw new UnmatchingUuidException(); + } + } + throw new UnmatchingUuidException(); + } + + private void checkUuidExpiredDate(SharedEntity sharedEntity) throws ExpiredLinkException { + // Check shared entity expired + if (sharedEntity.getExpireDate() != null && sharedEntity.getExpireDate().getTime() < new Date().getTime()) { + shareManager.deleteSharedEntityIfExpired(sharedEntity); + throw new ExpiredLinkException(); + } + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/LOVResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/LOVResource.java new file mode 100644 index 0000000000..34f1048553 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/LOVResource.java @@ -0,0 +1,149 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.exceptions.*; +import com.docdoku.core.meta.ListOfValues; +import com.docdoku.core.meta.ListOfValuesKey; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.ILOVManagerLocal; +import com.docdoku.server.rest.dto.ListOfValuesDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +/** + * @author lebeaujulien on 03/03/15. + */ + +@RequestScoped +@Api(hidden = true, value = "listOfValues", description = "Operations about ListOfValues") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class LOVResource { + + @Inject + private ILOVManagerLocal lovManager; + + private Mapper mapper; + + public LOVResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get a list of ListOfValues for given parameters", + response = ListOfValuesDTO.class, + responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getLOVs(@PathParam("workspaceId") String workspaceId) + throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + List lovsDTO = new ArrayList<>(); + List lovs = lovManager.findLOVFromWorkspace(workspaceId); + + for (ListOfValues lov : lovs) { + ListOfValuesDTO lovDTO = mapper.map(lov, ListOfValuesDTO.class); + lovDTO.setDeletable(lovManager.isLOVDeletable(new ListOfValuesKey(lov.getWorkspaceId(), lov.getName()))); + lovsDTO.add(lovDTO); + } + return Response.ok(new GenericEntity>((List) lovsDTO) { + }).build(); + } + + @POST + @ApiOperation(value = "Create ListOfValues", + response = ListOfValuesDTO.class, + responseContainer = "List") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response createLOV(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "LOV to create") ListOfValuesDTO lovDTO) + throws ListOfValuesAlreadyExistsException, CreationException, UnsupportedEncodingException, UserNotFoundException, AccessRightException, UserNotActiveException, WorkspaceNotFoundException { + ListOfValues lov = mapper.map(lovDTO, ListOfValues.class); + lovManager.createLov(workspaceId, lov.getName(), lov.getValues()); + return Response.created(URI.create(URLEncoder.encode(lov.getName(), "UTF-8"))).entity(lovDTO).build(); + } + + @GET + @ApiOperation(value = "Get the ListOfValues from the given parameters", + response = ListOfValuesDTO.class) + @Path("/{name}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ListOfValuesDTO getLOV(@PathParam("workspaceId") String workspaceId, + @PathParam("name") String name) + throws ListOfValuesNotFoundException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + ListOfValuesKey lovKey = new ListOfValuesKey(workspaceId, name); + ListOfValues lov = lovManager.findLov(lovKey); + return mapper.map(lov, ListOfValuesDTO.class); + } + + @PUT + @Path("/{name}") + @ApiOperation(value = "Update the ListOfValues", + response = ListOfValuesDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ListOfValuesDTO updateLOV(@PathParam("workspaceId") String workspaceId, + @PathParam("name") String name, + @ApiParam(required = true, value = "LOV to update") ListOfValuesDTO lovDTO) + throws ListOfValuesNotFoundException, ListOfValuesAlreadyExistsException, CreationException, UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException { + ListOfValuesKey lovKey = new ListOfValuesKey(workspaceId, name); + ListOfValues lov = mapper.map(lovDTO, ListOfValues.class); + + ListOfValues newLovUpdated = lovManager.updateLov(lovKey, lov.getName(), workspaceId, lov.getValues()); + return mapper.map(newLovUpdated, ListOfValuesDTO.class); + } + + @DELETE + @Path("/{name}") + @ApiOperation(value = "Delete the ListOfValues", + response = Response.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response deleteLOV(@PathParam("workspaceId") String workspaceId, + @PathParam("name") String name) + throws ListOfValuesNotFoundException, UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, EntityConstraintException { + ListOfValuesKey lovKey = new ListOfValuesKey(workspaceId, name); + lovManager.deleteLov(lovKey); + return Response.ok().build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/LanguagesResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/LanguagesResource.java new file mode 100644 index 0000000000..bd7f476010 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/LanguagesResource.java @@ -0,0 +1,60 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.security.UserGroupMapping; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@RequestScoped +@Path("languages") +@Api(value = "languages", description = "Operations about languages") +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID,UserGroupMapping.ADMIN_ROLE_ID}) +@RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID,UserGroupMapping.ADMIN_ROLE_ID}) +public class LanguagesResource { + + private final static String[] SUPPORTED_LANGUAGES = {"fr","en"}; + + public LanguagesResource() { + } + + @GET + @ApiOperation(value = "Get languages", response = String.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getLanguages() { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + for(String lang: SUPPORTED_LANGUAGES){ + arrayBuilder.add(lang); + } + return Response.ok().entity(arrayBuilder.build()).build(); + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/LayerResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/LayerResource.java new file mode 100644 index 0000000000..d59a253a8d --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/LayerResource.java @@ -0,0 +1,167 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.exceptions.AccessRightException; +import com.docdoku.core.exceptions.EntityNotFoundException; +import com.docdoku.core.exceptions.UserNotActiveException; +import com.docdoku.core.product.ConfigurationItemKey; +import com.docdoku.core.product.Layer; +import com.docdoku.core.product.Marker; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.server.rest.dto.LayerDTO; +import com.docdoku.server.rest.dto.MarkerDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.List; +import java.util.Set; + +/** + * @author Florent Garin + */ + +@RequestScoped +@Api(hidden = true, value = "layers", description = "Operations about layers") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class LayerResource { + + @Inject + private IProductManagerLocal productService; + + public LayerResource() { + } + + @GET + @ApiOperation(value = "Get layers", response = LayerDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public LayerDTO[] getLayersInProduct(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId) + throws EntityNotFoundException, UserNotActiveException { + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + List layers = productService.getLayers(ciKey); + LayerDTO[] layerDTOs = new LayerDTO[layers.size()]; + for (int i = 0; i < layers.size(); i++) { + layerDTOs[i] = new LayerDTO(layers.get(i).getId(), layers.get(i).getName(), layers.get(i).getColor()); + } + return layerDTOs; + } + + + @POST + @ApiOperation(value = "Create layers", response = LayerDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public LayerDTO createLayer(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @ApiParam(required = true, value = "Layer to create") LayerDTO layer) + throws EntityNotFoundException, AccessRightException { + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + Layer l = productService.createLayer(ciKey, layer.getName(), layer.getColor()); + return new LayerDTO(l.getId(), l.getName(), l.getColor()); + } + + @PUT + @ApiOperation(value = "Update layer", response = LayerDTO.class) + @Path("{layerId}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public LayerDTO updateLayer(@PathParam("layerId") int layerId, + @PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @ApiParam(required = true, value = "Layer to update") LayerDTO layer) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + Layer l = productService.updateLayer(ciKey, layerId, layer.getName(), layer.getColor()); + return new LayerDTO(l.getId(), l.getName(), l.getColor()); + } + + @DELETE + @ApiOperation(value = "Delete layer", response = Response.class) + @Path("{layerId}") + @Produces(MediaType.APPLICATION_JSON) + public Response deleteLayer(@PathParam("layerId") int layerId, + @PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + productService.deleteLayer(workspaceId, layerId); + return Response.ok().build(); + } + + @GET + @ApiOperation(value = "Get markers", response = MarkerDTO.class, responseContainer = "List") + @Path("{layerId}/markers") + @Produces(MediaType.APPLICATION_JSON) + public MarkerDTO[] getMarkersInLayer(@PathParam("workspaceId") String workspaceId, + @PathParam("layerId") int layerId) + throws EntityNotFoundException, UserNotActiveException { + + Layer layer = productService.getLayer(layerId); + Set markers = layer.getMarkers(); + Marker[] markersArray = markers.toArray(new Marker[markers.size()]); + MarkerDTO[] markersDTO = new MarkerDTO[markers.size()]; + for (int i = 0; i < markersArray.length; i++) { + markersDTO[i] = new MarkerDTO(markersArray[i].getId(), markersArray[i].getTitle(), markersArray[i].getDescription(), markersArray[i].getX(), markersArray[i].getY(), markersArray[i].getZ()); + } + return markersDTO; + } + + @POST + @ApiOperation(value = "Create marker", response = MarkerDTO.class) + @Path("{layerId}/markers") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public MarkerDTO createMarker(@PathParam("workspaceId") String workspaceId, + @PathParam("layerId") int layerId, + @ApiParam(required = true, value = "Marker to create") MarkerDTO markerDTO) + throws EntityNotFoundException, AccessRightException { + + Marker marker = productService.createMarker(layerId, markerDTO.getTitle(), markerDTO.getDescription(), markerDTO.getX(), markerDTO.getY(), markerDTO.getZ()); + return new MarkerDTO(marker.getId(), marker.getTitle(), marker.getDescription(), marker.getX(), marker.getY(), marker.getZ()); + } + + @DELETE + @ApiOperation(value = "Delete marker", response = Response.class) + @Path("{layerId}/markers/{markerId}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response deleteMarker(@PathParam("workspaceId") String workspaceId, + @PathParam("layerId") int layerId, + @PathParam("markerId") int markerId) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + productService.deleteMarker(layerId, markerId); + return Response.status(Response.Status.OK).build(); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/MilestonesResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/MilestonesResource.java new file mode 100644 index 0000000000..c5c2462c0b --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/MilestonesResource.java @@ -0,0 +1,217 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.change.ChangeOrder; +import com.docdoku.core.change.ChangeRequest; +import com.docdoku.core.change.Milestone; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IChangeManagerLocal; +import com.docdoku.server.rest.dto.ACLDTO; +import com.docdoku.server.rest.dto.change.ChangeOrderDTO; +import com.docdoku.server.rest.dto.change.ChangeRequestDTO; +import com.docdoku.server.rest.dto.change.MilestoneDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RequestScoped +@Api(hidden = true, value = "milestones", description = "Operations about milestones") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class MilestonesResource { + + @Inject + private IChangeManagerLocal changeManager; + + private Mapper mapper; + + public MilestonesResource() { + + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get milestones for given parameters", + response = MilestoneDTO.class, + responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getMilestones(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + List milestones = changeManager.getMilestones(workspaceId); + List milestoneDTOs = new ArrayList<>(); + for (Milestone milestone : milestones) { + MilestoneDTO milestoneDTO = mapper.map(milestone, MilestoneDTO.class); + milestoneDTO.setWritable(changeManager.isMilestoneWritable(milestone)); + milestoneDTO.setNumberOfRequests(changeManager.getNumberOfRequestByMilestone(milestone.getWorkspaceId(), milestone.getId())); + milestoneDTO.setNumberOfOrders(changeManager.getNumberOfOrderByMilestone(milestone.getWorkspaceId(), milestone.getId())); + milestoneDTOs.add(milestoneDTO); + } + return Response.ok(new GenericEntity>((List) milestoneDTOs) { + }).build(); + } + + @POST + @ApiOperation(value = "Create milestone", + response = MilestoneDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public MilestoneDTO createMilestone(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Milestone to create") MilestoneDTO milestoneDTO) + throws EntityNotFoundException, AccessRightException, EntityAlreadyExistsException { + Milestone milestone = changeManager.createMilestone(workspaceId, milestoneDTO.getTitle(), milestoneDTO.getDescription(), milestoneDTO.getDueDate()); + milestoneDTO = mapper.map(milestone, MilestoneDTO.class); + milestoneDTO.setWritable(true); + return milestoneDTO; + } + + @GET + @ApiOperation(value = "Get milestone", + response = MilestoneDTO.class) + @Produces(MediaType.APPLICATION_JSON) + @Path("{milestoneId}") + public MilestoneDTO getMilestone(@PathParam("workspaceId") String workspaceId, + @PathParam("milestoneId") int milestoneId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + Milestone milestone = changeManager.getMilestone(workspaceId, milestoneId); + MilestoneDTO milestoneDTO = mapper.map(milestone, MilestoneDTO.class); + milestoneDTO.setWritable(changeManager.isMilestoneWritable(milestone)); + milestoneDTO.setNumberOfRequests(changeManager.getNumberOfRequestByMilestone(milestone.getWorkspaceId(), milestone.getId())); + milestoneDTO.setNumberOfOrders(changeManager.getNumberOfOrderByMilestone(milestone.getWorkspaceId(), milestone.getId())); + return milestoneDTO; + } + + @PUT + @ApiOperation(value = "Update milestone", + response = MilestoneDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("{milestoneId}") + public MilestoneDTO updateMilestone(@PathParam("workspaceId") String workspaceId, + @PathParam("milestoneId") int milestoneId, + @ApiParam(required = true, value = "Milestone to update") MilestoneDTO pMilestoneDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + Milestone milestone = changeManager.updateMilestone(milestoneId, workspaceId, pMilestoneDTO.getTitle(), pMilestoneDTO.getDescription(), pMilestoneDTO.getDueDate()); + MilestoneDTO milestoneDTO = mapper.map(milestone, MilestoneDTO.class); + milestoneDTO.setWritable(changeManager.isMilestoneWritable(milestone)); + milestoneDTO.setNumberOfRequests(changeManager.getNumberOfRequestByMilestone(milestone.getWorkspaceId(), milestone.getId())); + milestoneDTO.setNumberOfOrders(changeManager.getNumberOfOrderByMilestone(milestone.getWorkspaceId(), milestone.getId())); + return milestoneDTO; + } + + @DELETE + @ApiOperation(value = "Delete milestone", + response = Response.class) + @Consumes(MediaType.APPLICATION_JSON) + @Path("{milestoneId}") + public Response removeMilestone(@PathParam("workspaceId") String workspaceId, + @PathParam("milestoneId") int milestoneId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, EntityConstraintException { + changeManager.deleteMilestone(workspaceId, milestoneId); + return Response.ok().build(); + } + + @GET + @ApiOperation(value = "Get requests for the given milestone", + response = ChangeRequestDTO.class, + responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + @Path("{milestoneId}/requests") + public Response getRequestsByMilestone(@PathParam("workspaceId") String workspaceId, + @PathParam("milestoneId") int milestoneId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + List changeRequests = changeManager.getChangeRequestsByMilestone(workspaceId, milestoneId); + List changeRequestDTOs = new ArrayList<>(); + for (ChangeRequest changeRequest : changeRequests) { + changeRequestDTOs.add(mapper.map(changeRequest, ChangeRequestDTO.class)); + } + return Response.ok(new GenericEntity>((List) changeRequestDTOs) { + }).build(); + } + + @GET + @ApiOperation(value = "Get orders for the given milestone", + response = ChangeOrderDTO.class, + responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + @Path("{milestoneId}/orders") + public Response getOrdersByMilestone(@PathParam("workspaceId") String workspaceId, + @PathParam("milestoneId") int milestoneId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + List changeOrders = changeManager.getChangeOrdersByMilestone(workspaceId, milestoneId); + List changeOrderDTOs = new ArrayList<>(); + for (ChangeOrder changeOrder : changeOrders) { + changeOrderDTOs.add(mapper.map(changeOrder, ChangeOrderDTO.class)); + } + return Response.ok(new GenericEntity>((List) changeOrderDTOs) { + }).build(); + } + + @PUT + @ApiOperation(value = "Update ACL of the milestone", + response = Response.class) + @Path("{milestoneId}/acl") + @Consumes(MediaType.APPLICATION_JSON) + public Response updateACL(@PathParam("workspaceId") String pWorkspaceId, + @PathParam("milestoneId") int milestoneId, + @ApiParam(required = true, value = "ACL rules to set") ACLDTO acl) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + if (!acl.getGroupEntries().isEmpty() || !acl.getUserEntries().isEmpty()) { + + Map userEntries = new HashMap<>(); + Map groupEntries = new HashMap<>(); + + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries.put(entry.getKey(), entry.getValue().name()); + } + + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + groupEntries.put(entry.getKey(), entry.getValue().name()); + } + + changeManager.updateACLForMilestone(pWorkspaceId, milestoneId, userEntries, groupEntries); + } else { + changeManager.removeACLFromMilestone(pWorkspaceId, milestoneId); + } + return Response.ok().build(); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ModificationNotificationResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ModificationNotificationResource.java new file mode 100644 index 0000000000..258148af35 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ModificationNotificationResource.java @@ -0,0 +1,74 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.exceptions.AccessRightException; +import com.docdoku.core.exceptions.PartRevisionNotFoundException; +import com.docdoku.core.exceptions.UserNotFoundException; +import com.docdoku.core.exceptions.WorkspaceNotFoundException; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.server.rest.dto.ModificationNotificationDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * @author Florent Garin + */ + +@RequestScoped +@Api(hidden = true, value = "modification-notifications", description = "Operations about modification notifications") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class ModificationNotificationResource { + + @Inject + private IProductManagerLocal productService; + + public ModificationNotificationResource() { + } + + @PUT + @ApiOperation(value = "Acknowledge modification notification", response = Response.class) + @Path("/{notificationId}") + @Consumes(MediaType.APPLICATION_JSON) + public Response acknowledgeNotification(@PathParam("workspaceId") String workspaceId, + @PathParam("notificationId") int notificationId, + @ApiParam(required = true, value = "Modification notification to acknowledge") ModificationNotificationDTO notificationDTO) + throws UserNotFoundException, AccessRightException, PartRevisionNotFoundException, WorkspaceNotFoundException { + + productService.updateModificationNotification(workspaceId, notificationId, notificationDTO.getAckComment()); + return Response.ok().build(); + } + + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/OrganizationResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/OrganizationResource.java new file mode 100644 index 0000000000..b547c59d9f --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/OrganizationResource.java @@ -0,0 +1,36 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest; + +import com.docdoku.core.security.UserGroupMapping; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.ws.rs.Path; + +@RequestScoped +@Path("organizations") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class OrganizationResource { + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/PartResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/PartResource.java new file mode 100644 index 0000000000..c5220aee0e --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/PartResource.java @@ -0,0 +1,760 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.change.ModificationNotification; +import com.docdoku.core.common.*; +import com.docdoku.core.configuration.PSFilter; +import com.docdoku.core.configuration.ProductBaseline; +import com.docdoku.core.configuration.ProductInstanceMaster; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.InstanceAttributeTemplate; +import com.docdoku.core.meta.Tag; +import com.docdoku.core.product.*; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.ACLUserEntry; +import com.docdoku.core.security.ACLUserGroupEntry; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IConverterManagerLocal; +import com.docdoku.core.services.IProductInstanceManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.core.sharing.SharedPart; +import com.docdoku.core.workflow.Workflow; +import com.docdoku.server.rest.collections.VirtualInstanceCollection; +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.rest.dto.baseline.ProductBaselineDTO; +import com.docdoku.server.rest.dto.product.ProductInstanceMasterDTO; +import com.docdoku.server.rest.util.InstanceAttributeFactory; +import io.swagger.annotations.*; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.*; + +@RequestScoped +@Api(hidden = true, value = "part", description = "Operation about single parts") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class PartResource { + + @Inject + private IProductManagerLocal productService; + + @Inject + private IProductInstanceManagerLocal productInstanceService; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private IConverterManagerLocal converterService; + + private Mapper mapper; + + public PartResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get part revision", response = PartRevisionDTO.class) + @Produces(MediaType.APPLICATION_JSON) + public Response getPartRevision(@PathParam("workspaceId") String pWorkspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + PartRevisionKey revisionKey = new PartRevisionKey(pWorkspaceId, partNumber, partVersion); + PartRevision partRevision = productService.getPartRevision(revisionKey); + PartRevisionDTO partRevisionDTO = Tools.mapPartRevisionToPartDTO(partRevision); + + PartIterationKey iterationKey = new PartIterationKey(revisionKey, partRevision.getLastIterationNumber()); + List notifications = productService.getModificationNotifications(iterationKey); + List notificationDTOs = Tools.mapModificationNotificationsToModificationNotificationDTO(notifications); + partRevisionDTO.setNotifications(notificationDTOs); + + return Response.ok(partRevisionDTO).build(); + } + + @GET + @ApiOperation(value = "Get product instance where part is in use", response = ProductInstanceMasterDTO.class, responseContainer = "List") + @Path("/used-by-product-instance-masters") + @Produces(MediaType.APPLICATION_JSON) + public Response getProductInstanceMasters(@PathParam("workspaceId") String pWorkspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + + PartRevisionKey revisionKey = new PartRevisionKey(pWorkspaceId, partNumber, partVersion); + PartRevision partRevision = productService.getPartRevision(revisionKey); + List productInstanceMasters = productInstanceService.getProductInstanceMasters(partRevision); + List productInstanceMasterDTOs = new ArrayList<>(); + + for (ProductInstanceMaster productInstanceMaster : productInstanceMasters) { + ProductInstanceMasterDTO productInstanceMasterDTO = mapper.map(productInstanceMaster, ProductInstanceMasterDTO.class); + productInstanceMasterDTO.setProductInstanceIterations(null); + productInstanceMasterDTO.setConfigurationItemId(productInstanceMaster.getInstanceOf().getId()); + productInstanceMasterDTOs.add(productInstanceMasterDTO); + } + return Response.ok(new GenericEntity>((List) productInstanceMasterDTOs) { + }).build(); + } + + @GET + @ApiOperation(value = "Get part revisions where use as component", response = PartRevisionDTO.class, responseContainer = "List") + @Path("/used-by-as-component") + @Produces(MediaType.APPLICATION_JSON) + public Response getUsedByAsComponent(@PathParam("workspaceId") String pWorkspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + + List partIterations = productService.getUsedByAsComponent(new PartRevisionKey(pWorkspaceId, partNumber, partVersion)); + + Set partRevisions = new HashSet<>(); + + for (PartIteration partIteration : partIterations) { + partRevisions.add(partIteration.getPartRevision()); + } + List partRevisionDTOs = getPartRevisionDTO(partRevisions); + + return Response.ok(new GenericEntity>((List) partRevisionDTOs) { + }).build(); + } + + @GET + @ApiOperation(value = "Get part revisions where use as substitute", response = PartRevisionDTO.class, responseContainer = "List") + @Path("/used-by-as-substitute") + @Produces(MediaType.APPLICATION_JSON) + public Response getUsedByAsSubstitute(@PathParam("workspaceId") String pWorkspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + + List partIterations = productService.getUsedByAsSubstitute(new PartRevisionKey(pWorkspaceId, partNumber, partVersion)); + + Set partRevisions = new HashSet<>(); + + for (PartIteration partIteration : partIterations) { + partRevisions.add(partIteration.getPartRevision()); + } + + List partRevisionDTOs = getPartRevisionDTO(partRevisions); + return Response.ok(new GenericEntity>((List) partRevisionDTOs) { + }).build(); + } + + @PUT + @ApiOperation(value = "Update part iteration", response = PartRevisionDTO.class) + @Path("/iterations/{partIteration}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response updatePartIteration(@PathParam("workspaceId") String pWorkspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @PathParam("partIteration") int partIteration, + @ApiParam(required = true, value = "Part iteration to update") PartIterationDTO data) + throws EntityNotFoundException, EntityAlreadyExistsException, AccessRightException, UserNotActiveException, NotAllowedException, CreationException, EntityConstraintException { + + PartRevisionKey revisionKey = new PartRevisionKey(pWorkspaceId, partNumber, partVersion); + PartRevision partRevision = productService.getPartRevision(revisionKey); + + PartIterationKey pKey = new PartIterationKey(revisionKey, partIteration); + + List instanceAttributes = data.getInstanceAttributes(); + List attributes = null; + InstanceAttributeFactory instanceAttributeFactory = new InstanceAttributeFactory(); + if (instanceAttributes != null) { + attributes = instanceAttributeFactory.createInstanceAttributes(instanceAttributes); + } + + List instanceAttributeTemplates = data.getInstanceAttributeTemplates(); + List attributeTemplates = null; + if (instanceAttributeTemplates != null) { + attributeTemplates = instanceAttributeFactory.createInstanceAttributeTemplateFromDTO(instanceAttributeTemplates); + } + + String[] lovNames = new String[instanceAttributeTemplates.size()]; + for (int i = 0; i < instanceAttributeTemplates.size(); i++) + lovNames[i] = instanceAttributeTemplates.get(i).getLovName(); + + List components = data.getComponents(); + List newComponents = null; + if (components != null) { + newComponents = createComponents(pWorkspaceId, components); + } + + List linkedDocs = data.getLinkedDocuments(); + DocumentRevisionKey[] links = null; + String[] documentLinkComments = null; + if (linkedDocs != null) { + documentLinkComments = new String[linkedDocs.size()]; + links = createDocumentRevisionKey(linkedDocs); + int i = 0; + for (DocumentRevisionDTO docRevisionForLink : linkedDocs) { + String comment = docRevisionForLink.getCommentLink(); + if (comment == null) { + comment = ""; + } + documentLinkComments[i++] = comment; + } + } + + PartIteration.Source sameSource = partRevision.getIteration(partIteration).getSource(); + + PartRevision partRevisionUpdated = productService.updatePartIteration(pKey, data.getIterationNote(), sameSource, newComponents, attributes, attributeTemplates, links, documentLinkComments, lovNames); + + PartRevisionDTO partRevisionDTO = Tools.mapPartRevisionToPartDTO(partRevisionUpdated); + return Response.ok(partRevisionDTO).build(); + } + + @GET + @ApiOperation(value = "Get conversion status", response = ConversionDTO.class) + @Path("/iterations/{partIteration}/conversion") + @Produces(MediaType.APPLICATION_JSON) + public ConversionDTO getConversionStatus(@PathParam("workspaceId") String pWorkspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @PathParam("partIteration") int partIteration) + throws UserNotActiveException, PartRevisionNotFoundException, WorkspaceNotFoundException, UserNotFoundException, PartIterationNotFoundException, AccessRightException { + PartIterationKey partIPK = new PartIterationKey(pWorkspaceId, partNumber, partVersion, partIteration); + Conversion conversion = productService.getConversion(partIPK); + if (conversion != null) { + return mapper.map(conversion, ConversionDTO.class); + } + return null; + } + + @PUT + @ApiOperation(value = "Retry conversion", response = Response.class) + @Path("/iterations/{partIteration}/conversion") + public Response retryConversion(@PathParam("workspaceId") String pWorkspaceId, @PathParam("partNumber") String partNumber, @PathParam("partVersion") String partVersion, @PathParam("partIteration") int iteration) throws UserNotActiveException, PartRevisionNotFoundException, WorkspaceNotFoundException, UserNotFoundException, PartIterationNotFoundException, AccessRightException, NotAllowedException { + + PartIterationKey partIPK = new PartIterationKey(pWorkspaceId, partNumber, partVersion, iteration); + PartIteration partIteration = productService.getPartIteration(partIPK); + BinaryResource nativeCADFile = partIteration.getNativeCADFile(); + if (nativeCADFile != null) { + try { + converterService.convertCADFileToOBJ(partIPK, nativeCADFile); + return Response.ok().build(); + } catch (Exception e) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } + return Response.status(Response.Status.BAD_REQUEST).build(); + } + + @PUT + @ApiOperation(value = "Checkin part", response = PartRevisionDTO.class) + @Path("/checkin") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public PartRevisionDTO checkIn(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @ApiParam(name = "body", defaultValue = "") String body) + throws EntityNotFoundException, ESServerException, AccessRightException, NotAllowedException, EntityConstraintException, UserNotActiveException { + + PartRevisionKey revisionKey = new PartRevisionKey(workspaceId, partNumber, partVersion); + PartRevision partRevision = productService.checkInPart(revisionKey); + return Tools.mapPartRevisionToPartDTO(partRevision); + } + + @PUT + @ApiOperation(value = "Checkout part", response = PartRevisionDTO.class) + @Path("/checkout") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public PartRevisionDTO checkOut(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @ApiParam(name = "body") String body) + throws EntityNotFoundException, EntityAlreadyExistsException, CreationException, AccessRightException, NotAllowedException, UserNotActiveException { + + PartRevisionKey revisionKey = new PartRevisionKey(workspaceId, partNumber, partVersion); + PartRevision partRevision = productService.checkOutPart(revisionKey); + return Tools.mapPartRevisionToPartDTO(partRevision); + } + + @PUT + @ApiOperation(value = "Undo checkout part", response = PartRevisionDTO.class) + @Path("/undocheckout") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public PartRevisionDTO undoCheckOut(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @ApiParam(name = "body") String body) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException { + + PartRevisionKey revisionKey = new PartRevisionKey(workspaceId, partNumber, partVersion); + PartRevision partRevision = productService.undoCheckOutPart(revisionKey); + return Tools.mapPartRevisionToPartDTO(partRevision); + } + + @PUT + @ApiOperation(value = "Update part ACL", response = Response.class) + @Path("/acl") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response updateACL(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @ApiParam(required = true, value = "ACL rules to set") ACLDTO acl) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + PartRevisionKey revisionKey = new PartRevisionKey(workspaceId, partNumber, partVersion); + + if (!acl.getGroupEntries().isEmpty() || !acl.getUserEntries().isEmpty()) { + Map userEntries = new HashMap<>(); + Map groupEntries = new HashMap<>(); + + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries.put(entry.getKey(), entry.getValue().name()); + } + + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + groupEntries.put(entry.getKey(), entry.getValue().name()); + } + + productService.updatePartRevisionACL(workspaceId, revisionKey, userEntries, groupEntries); + + } else { + productService.removeACLFromPartRevision(revisionKey); + } + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Create new part version", response = Response.class) + @Path("/newVersion") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response createNewVersion(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @ApiParam(required = true, value = "New version of part to create") PartCreationDTO partCreationDTO) + throws EntityNotFoundException, EntityAlreadyExistsException, CreationException, AccessRightException, NotAllowedException { + + RoleMappingDTO[] roleMappingDTOs = partCreationDTO.getRoleMapping(); + ACLDTO acl = partCreationDTO.getAcl(); + PartRevisionKey revisionKey = new PartRevisionKey(workspaceId, partNumber, partVersion); + String description = partCreationDTO.getDescription(); + String workflowModelId = partCreationDTO.getWorkflowModelId(); + + ACLUserEntry[] userEntries = null; + ACLUserGroupEntry[] userGroupEntries = null; + if (acl != null) { + userEntries = new ACLUserEntry[acl.getUserEntries().size()]; + userGroupEntries = new ACLUserGroupEntry[acl.getGroupEntries().size()]; + int i = 0; + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries[i] = new ACLUserEntry(); + userEntries[i].setPrincipal(new User(new Workspace(workspaceId), new Account(entry.getKey()))); + userEntries[i++].setPermission(ACL.Permission.valueOf(entry.getValue().name())); + } + i = 0; + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + userGroupEntries[i] = new ACLUserGroupEntry(); + userGroupEntries[i].setPrincipal(new UserGroup(new Workspace(workspaceId), entry.getKey())); + userGroupEntries[i++].setPermission(ACL.Permission.valueOf(entry.getValue().name())); + } + } + + Map> userRoleMapping = new HashMap<>(); + Map> groupRoleMapping = new HashMap<>(); + + if (roleMappingDTOs != null) { + for (RoleMappingDTO roleMappingDTO : roleMappingDTOs) { + userRoleMapping.put(roleMappingDTO.getRoleName(), roleMappingDTO.getUserLogins()); + groupRoleMapping.put(roleMappingDTO.getRoleName(), roleMappingDTO.getGroupIds()); + } + } + + productService.createPartRevision(revisionKey, description, workflowModelId, userEntries, userGroupEntries, userRoleMapping, groupRoleMapping); + + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Release part", response = PartRevisionDTO.class) + @Path("/release") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public PartRevisionDTO releasePartRevision(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @ApiParam(name = "body", defaultValue = "") String body) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException { + + PartRevisionKey revisionKey = new PartRevisionKey(workspaceId, partNumber, partVersion); + PartRevision partRevision = productService.releasePartRevision(revisionKey); + return Tools.mapPartRevisionToPartDTO(partRevision); + } + + @PUT + @ApiOperation(value = "Set part as obsolete", response = PartRevisionDTO.class) + @Path("/obsolete") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public PartRevisionDTO markPartRevisionAsObsolete(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @ApiParam(name = "body", defaultValue = "") String body) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException { + + PartRevisionKey revisionKey = new PartRevisionKey(workspaceId, partNumber, partVersion); + PartRevision partRevision = productService.markPartRevisionAsObsolete(revisionKey); + return Tools.mapPartRevisionToPartDTO(partRevision); + } + + @DELETE + @ApiOperation(value = "Delete part", response = Response.class) + @Produces(MediaType.APPLICATION_JSON) + public Response deletePartRevision(@PathParam("workspaceId") String workspaceId, @PathParam("partNumber") String partNumber, @PathParam("partVersion") String partVersion) + throws EntityNotFoundException, UserNotActiveException, EntityConstraintException, ESServerException, AccessRightException { + + PartRevisionKey revisionKey = new PartRevisionKey(workspaceId, partNumber, partVersion); + productService.deletePartRevision(revisionKey); + return Response.ok().build(); + } + + @DELETE + @ApiOperation(value = "Remove file from part iteration", response = Response.class) + @Consumes(MediaType.APPLICATION_JSON) + @Path("/iterations/{partIteration}/files/{subType}/{fileName}") + public Response removeFile(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @PathParam("partIteration") int partIteration, + @PathParam("subType") String subType, + @PathParam("fileName") String fileName) + throws EntityNotFoundException, UserNotActiveException { + PartIterationKey partIKey = new PartIterationKey(workspaceId, partNumber, partVersion, partIteration); + String fileFullName = workspaceId + "/parts/" + partNumber + "/" + partVersion + "/" + partIteration + "/" + subType + "/" + fileName; + productService.removeFileInPartIteration(partIKey, subType, fileFullName); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Remove attached file from part iteration", response = Response.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("/iterations/{partIteration}/files/{subType}/{fileName}") + public FileDTO renameAttachedFile(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @PathParam("partIteration") int partIteration, + @PathParam("subType") String subType, + @PathParam("fileName") String fileName, + @ApiParam(required = true, value = "File to rename") FileDTO fileDTO) + throws UserNotActiveException, WorkspaceNotFoundException, CreationException, UserNotFoundException, FileNotFoundException, NotAllowedException, FileAlreadyExistsException, StorageException { + String fileFullName = workspaceId + "/parts/" + partNumber + "/" + partVersion + "/" + partIteration + "/" + subType + "/" + fileName; + BinaryResource binaryResource = productService.renameFileInPartIteration(subType, fileFullName, fileDTO.getShortName()); + return new FileDTO(true, binaryResource.getFullName(), binaryResource.getName()); + } + + @POST + @ApiOperation(value = "Create a new shared part", response = SharedPartDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("/share") + public Response createSharedPart(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @ApiParam(required = true, value = "Shared part to create") SharedPartDTO pSharedPartDTO) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + String password = pSharedPartDTO.getPassword(); + Date expireDate = pSharedPartDTO.getExpireDate(); + + SharedPart sharedPart = productService.createSharedPart(new PartRevisionKey(workspaceId, partNumber, partVersion), password, expireDate); + SharedPartDTO sharedPartDTO = mapper.map(sharedPart, SharedPartDTO.class); + return Response.ok().entity(sharedPartDTO).build(); + } + + @PUT + @ApiOperation(value = "Publish part revision", response = Response.class) + @Consumes(MediaType.APPLICATION_JSON) + @Path("/publish") + public Response publishPartRevision(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + productService.setPublicSharedPart(new PartRevisionKey(workspaceId, partNumber, partVersion),true); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Unpublish part revision", response = Response.class) + @Consumes(MediaType.APPLICATION_JSON) + @Path("/unpublish") + public Response unPublishPartRevision(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + productService.setPublicSharedPart(new PartRevisionKey(workspaceId, partNumber, partVersion),false); + return Response.ok().build(); + } + + @GET + @ApiOperation(value = "Get part's aborted workflows", response = WorkflowDTO.class, responseContainer = "List") + @Path("/aborted-workflows") + @Produces(MediaType.APPLICATION_JSON) + public Response getAbortedWorkflows(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + PartRevisionKey revisionKey = new PartRevisionKey(workspaceId, partNumber, partVersion); + PartRevision partRevision = productService.getPartRevision(revisionKey); + + List abortedWorkflows = partRevision.getAbortedWorkflows(); + List abortedWorkflowsDTO = new ArrayList<>(); + + for (Workflow abortedWorkflow : abortedWorkflows) { + abortedWorkflowsDTO.add(mapper.map(abortedWorkflow, WorkflowDTO.class)); + } + return Response.ok(new GenericEntity>((List) abortedWorkflowsDTO) { + }).build(); + } + + @PUT + @ApiOperation(value = "Save part's tags", response = PartRevisionDTO.class) + @Path("/tags") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public PartRevisionDTO savePartTags(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @ApiParam(required = true, value = "Tag list to add") TagListDTO tagListDTO) + throws EntityNotFoundException, NotAllowedException, ESServerException, AccessRightException, UserNotActiveException { + + List tagDTOs = tagListDTO.getTags(); + String[] tagLabels = new String[tagDTOs.size()]; + + for (int i = 0; i < tagDTOs.size(); i++) { + tagLabels[i] = tagDTOs.get(i).getLabel(); + } + PartRevisionKey revisionKey = new PartRevisionKey(workspaceId, partNumber, partVersion); + + PartRevision partRevision = productService.saveTags(revisionKey, tagLabels); + PartRevisionDTO partRevisionDTO = mapper.map(partRevision, PartRevisionDTO.class); + + return partRevisionDTO; + } + + @POST + @ApiOperation(value = "Add tags to part", response = Response.class) + @Path("/tags") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response addPartTag(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @ApiParam(required = true, value = "Tag list to add") TagListDTO tagListDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException, ESServerException { + + PartRevisionKey revisionKey = new PartRevisionKey(workspaceId, partNumber, partVersion); + PartRevision partRevision = productService.getPartRevision(revisionKey); + Set tags = partRevision.getTags(); + Set tagLabels = new HashSet<>(); + + for (TagDTO tagDTO : tagListDTO.getTags()) { + tagLabels.add(tagDTO.getLabel()); + } + + for (Tag tag : tags) { + tagLabels.add(tag.getLabel()); + } + + productService.saveTags(revisionKey, tagLabels.toArray(new String[tagLabels.size()])); + return Response.ok().build(); + } + + @DELETE + @ApiOperation(value = "Delete tags from part", response = Response.class) + @Path("/tags/{tagName}") + public Response removePartTags(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @PathParam("tagName") String tagName) + throws EntityNotFoundException, NotAllowedException, AccessRightException, UserNotActiveException, ESServerException { + productService.removeTag(new PartRevisionKey(workspaceId, partNumber, partVersion), tagName); + return Response.ok().build(); + } + + + @GET + @ApiOperation(value = "Get instances", response = VirtualInstanceCollection.class) + @Path("/instances") + @Produces(MediaType.APPLICATION_JSON) + public Response getInstances(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + PartRevision partRevision = productService.getPartRevision(new PartRevisionKey(workspaceId, partNumber, partVersion)); + PSFilter filter = productService.getLatestCheckedInPSFilter(workspaceId); + VirtualInstanceCollection virtualInstanceCollection = new VirtualInstanceCollection(partRevision, filter); + return Response.ok().entity(virtualInstanceCollection).build(); + } + + @GET + @ApiOperation(value = "Get baselines where part revision is involved", response = ProductBaselineDTO.class, responseContainer = "List") + @Path("/baselines") + @Produces(MediaType.APPLICATION_JSON) + public Response getBaselinesWherePartRevisionHasIterations(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException { + + List baselines = productService.findBaselinesWherePartRevisionHasIterations(new PartRevisionKey(workspaceId, partNumber, partVersion)); + List productBaselineDTOs = new ArrayList<>(); + for(ProductBaseline baseline :baselines){ + productBaselineDTOs.add(mapper.map(baseline,ProductBaselineDTO.class)); + } + return Response.ok(new GenericEntity>((List) productBaselineDTOs) { + }).build(); + } + + + public List createComponents(String workspaceId, List pComponents) + throws EntityNotFoundException, EntityAlreadyExistsException, AccessRightException, NotAllowedException, CreationException, UserNotActiveException { + + List components = new ArrayList<>(); + for (PartUsageLinkDTO partUsageLinkDTO : pComponents) { + + PartMaster component = findOrCreatePartMaster(workspaceId, partUsageLinkDTO.getComponent()); + + if (component != null) { + PartUsageLink partUsageLink = new PartUsageLink(); + + List cadInstances = new ArrayList<>(); + List partSubstituteLinks = new ArrayList<>(); + + if (partUsageLinkDTO.getCadInstances() != null) { + for (CADInstanceDTO cadInstanceDTO : partUsageLinkDTO.getCadInstances()) { + CADInstance cadInstance = mapper.map(cadInstanceDTO, CADInstance.class); + cadInstance.setRotationMatrix(new RotationMatrix(cadInstanceDTO.getMatrix())); + if (cadInstance.getRotationType() == null) { + cadInstance.setRotationType(CADInstance.RotationType.ANGLE); + } + cadInstances.add(cadInstance); + + } + } else if (partUsageLinkDTO.getUnit() == null || partUsageLinkDTO.getUnit().isEmpty()) { + for (double i = 0; i < partUsageLinkDTO.getAmount(); i++) { + cadInstances.add(new CADInstance(0, 0, 0, 0, 0, 0)); + } + } else { + cadInstances.add(new CADInstance(0, 0, 0, 0, 0, 0)); + } + for (PartSubstituteLinkDTO substituteLinkDTO : partUsageLinkDTO.getSubstitutes()) { + PartMaster substitute = findOrCreatePartMaster(workspaceId, substituteLinkDTO.getSubstitute()); + if (substitute != null) { + PartSubstituteLink partSubstituteLink = mapper.map(substituteLinkDTO, PartSubstituteLink.class); + List subCADInstances = new ArrayList<>(); + if (substituteLinkDTO.getCadInstances() != null) { + for (CADInstanceDTO cadInstanceDTO : substituteLinkDTO.getCadInstances()) { + subCADInstances.add(mapper.map(cadInstanceDTO, CADInstance.class)); + } + } else if (substituteLinkDTO.getUnit() == null || substituteLinkDTO.getUnit().isEmpty()) { + for (double i = 0; i < substituteLinkDTO.getAmount(); i++) { + subCADInstances.add(new CADInstance(0, 0, 0, 0, 0, 0)); + } + } else { + subCADInstances.add(new CADInstance(0, 0, 0, 0, 0, 0)); + } + partSubstituteLink.setCadInstances(subCADInstances); + partSubstituteLink.setSubstitute(substitute); + partSubstituteLinks.add(partSubstituteLink); + } + } + partUsageLink.setComponent(component); + partUsageLink.setAmount(partUsageLinkDTO.getAmount()); + partUsageLink.setComment(partUsageLinkDTO.getComment()); + partUsageLink.setReferenceDescription(partUsageLinkDTO.getReferenceDescription()); + partUsageLink.setCadInstances(cadInstances); + partUsageLink.setUnit(partUsageLinkDTO.getUnit()); + partUsageLink.setOptional(partUsageLinkDTO.isOptional()); + partUsageLink.setSubstitutes(partSubstituteLinks); + partUsageLink.setId(partUsageLinkDTO.getId()); + components.add(partUsageLink); + } + + } + + return components; + + } + + public PartMaster findOrCreatePartMaster(String workspaceId, ComponentDTO componentDTO) + throws EntityNotFoundException, EntityAlreadyExistsException, NotAllowedException, UserNotActiveException, AccessRightException, CreationException { + String componentNumber = componentDTO.getNumber(); + PartMasterKey partMasterKey = new PartMasterKey(workspaceId, componentNumber); + if (productService.partMasterExists(partMasterKey)) { + return new PartMaster(userManager.getWorkspace(workspaceId), componentNumber); + } else { + return productService.createPartMaster(workspaceId, componentDTO.getNumber(), componentDTO.getName(), componentDTO.isStandardPart(), null, componentDTO.getDescription(), null, null, null, null, null); + } + } + + private DocumentRevisionKey[] createDocumentRevisionKey(List dtos) { + DocumentRevisionKey[] data = new DocumentRevisionKey[dtos.size()]; + int i = 0; + for (DocumentRevisionDTO dto : dtos) { + data[i++] = new DocumentRevisionKey(dto.getWorkspaceId(), dto.getDocumentMasterId(), dto.getVersion()); + } + return data; + } + + private List getPartRevisionDTO(Set partRevisions) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException { + List partRevisionDTOs = new ArrayList<>(); + + for (PartRevision partRevision : partRevisions) { + if (!productService.canAccess(partRevision.getKey())) { + continue; + } + PartRevisionDTO partRevisionDTO = mapper.map(partRevision, PartRevisionDTO.class); + partRevisionDTO.setNumber(partRevision.getPartNumber()); + partRevisionDTO.setPartKey(partRevision.getPartNumber() + "-" + partRevision.getVersion()); + partRevisionDTO.setName(partRevision.getPartMaster().getName()); + partRevisionDTO.setStandardPart(partRevision.getPartMaster().isStandardPart()); + partRevisionDTOs.add(partRevisionDTO); + } + return partRevisionDTOs; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/PartTemplateResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/PartTemplateResource.java new file mode 100644 index 0000000000..e75ae62f0f --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/PartTemplateResource.java @@ -0,0 +1,248 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.product.PartMasterTemplate; +import com.docdoku.core.product.PartMasterTemplateKey; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.rest.util.InstanceAttributeFactory; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Morgan Guimard + */ +@RequestScoped +@Api(hidden = true, value = "part-templates", description = "Operations about part templates") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class PartTemplateResource { + + @Inject + private IProductManagerLocal productService; + + private Mapper mapper; + + public PartTemplateResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get part master templates", response = PartMasterTemplateDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public PartMasterTemplateDTO[] getPartMasterTemplates(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + + + PartMasterTemplate[] partMasterTemplates = productService.getPartMasterTemplates(workspaceId); + PartMasterTemplateDTO[] partMasterTemplateDTOs = new PartMasterTemplateDTO[partMasterTemplates.length]; + + for (int i = 0; i < partMasterTemplates.length; i++) { + partMasterTemplateDTOs[i] = mapper.map(partMasterTemplates[i], PartMasterTemplateDTO.class); + } + + return partMasterTemplateDTOs; + } + + @GET + @ApiOperation(value = "Get part master template", response = PartMasterTemplateDTO.class) + @Path("{templateId}") + @Produces(MediaType.APPLICATION_JSON) + public PartMasterTemplateDTO getPartMasterTemplate(@PathParam("workspaceId") String workspaceId, + @PathParam("templateId") String templateId) + throws EntityNotFoundException, UserNotActiveException { + + PartMasterTemplate partMasterTemplate = productService.getPartMasterTemplate(new PartMasterTemplateKey(workspaceId, templateId)); + return mapper.map(partMasterTemplate, PartMasterTemplateDTO.class); + } + + @GET + @ApiOperation(value = "Generate part master template id", response = TemplateGeneratedIdDTO.class) + @Path("{templateId}/generate_id") + @Produces(MediaType.APPLICATION_JSON) + public TemplateGeneratedIdDTO generatePartMasterTemplateId(@PathParam("workspaceId") String workspaceId, + @PathParam("templateId") String templateId) + throws EntityNotFoundException, UserNotActiveException { + + String generatedId = productService.generateId(workspaceId, templateId); + return new TemplateGeneratedIdDTO(generatedId); + } + + @POST + @ApiOperation(value = "Crate part master template", response = PartMasterTemplateDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public PartMasterTemplateDTO createPartMasterTemplate(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Part master template to create") PartTemplateCreationDTO templateCreationDTO) + throws EntityNotFoundException, EntityAlreadyExistsException, CreationException, AccessRightException, NotAllowedException { + + String id = templateCreationDTO.getReference(); + String partType = templateCreationDTO.getPartType(); + String workflowModelId = templateCreationDTO.getWorkflowModelId(); + String mask = templateCreationDTO.getMask(); + boolean idGenerated = templateCreationDTO.isIdGenerated(); + boolean attributesLocked = templateCreationDTO.isAttributesLocked(); + + List attributeTemplates = templateCreationDTO.getAttributeTemplates(); + String[] lovNames = new String[attributeTemplates.size()]; + for (int i = 0; i < attributeTemplates.size(); i++) { + lovNames[i] = attributeTemplates.get(i).getLovName(); + } + + List attributeInstanceTemplates = templateCreationDTO.getAttributeInstanceTemplates(); + String[] instanceLovNames = new String[attributeInstanceTemplates.size()]; + for (int i = 0; i < attributeInstanceTemplates.size(); i++) { + instanceLovNames[i] = attributeInstanceTemplates.get(i).getLovName(); + } + InstanceAttributeFactory attributeFactory = new InstanceAttributeFactory(); + PartMasterTemplate template = productService.createPartMasterTemplate(workspaceId, id, partType, workflowModelId, mask, attributeFactory.createInstanceAttributeTemplateFromDTO(attributeTemplates), lovNames, attributeFactory.createInstanceAttributeTemplateFromDTO(attributeInstanceTemplates), instanceLovNames, idGenerated, attributesLocked); + return mapper.map(template, PartMasterTemplateDTO.class); + } + + @PUT + @ApiOperation(value = "Update part master template", response = PartMasterTemplateDTO.class) + @Path("{templateId}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public PartMasterTemplateDTO updatePartMasterTemplate(@PathParam("workspaceId") String workspaceId, + @PathParam("templateId") String templateId, + @ApiParam(required = true, value = "Part master template to update") PartMasterTemplateDTO partMasterTemplateDTO) + throws EntityNotFoundException, AccessRightException, UserNotActiveException, NotAllowedException { + + String partType = partMasterTemplateDTO.getPartType(); + String mask = partMasterTemplateDTO.getMask(); + String workflowModelId = partMasterTemplateDTO.getWorkflowModelId(); + boolean idGenerated = partMasterTemplateDTO.isIdGenerated(); + boolean attributesLocked = partMasterTemplateDTO.isAttributesLocked(); + + List attributeTemplates = partMasterTemplateDTO.getAttributeTemplates(); + String[] lovNames = new String[attributeTemplates.size()]; + for (int i = 0; i < attributeTemplates.size(); i++) { + lovNames[i] = attributeTemplates.get(i).getLovName(); + } + + + List attributeInstanceTemplates = partMasterTemplateDTO.getAttributeInstanceTemplates(); + String[] instanceLovNames = new String[attributeInstanceTemplates.size()]; + for (int i = 0; i < attributeInstanceTemplates.size(); i++) { + instanceLovNames[i] = attributeInstanceTemplates.get(i).getLovName(); + } + InstanceAttributeFactory attributeFactory = new InstanceAttributeFactory(); + PartMasterTemplate template = productService.updatePartMasterTemplate(new PartMasterTemplateKey(workspaceId, templateId), partType, workflowModelId, mask, attributeFactory.createInstanceAttributeTemplateFromDTO(attributeTemplates), lovNames, attributeFactory.createInstanceAttributeTemplateFromDTO(attributeInstanceTemplates), instanceLovNames, idGenerated, attributesLocked); + return mapper.map(template, PartMasterTemplateDTO.class); + } + + @PUT + @ApiOperation(value = "Update part master template ACL", response = Response.class) + @Path("{templateId}/acl") + @Consumes(MediaType.APPLICATION_JSON) + public Response updatePartMasterTemplateACL(@PathParam("workspaceId") String workspaceId, + @PathParam("templateId") String templateId, + @ApiParam(required = true, value = "ACL rules to set") ACLDTO acl) + throws EntityNotFoundException, AccessRightException, UserNotActiveException, NotAllowedException { + + if (!acl.getGroupEntries().isEmpty() || !acl.getUserEntries().isEmpty()) { + + Map userEntries = new HashMap<>(); + Map groupEntries = new HashMap<>(); + + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries.put(entry.getKey(), entry.getValue().name()); + } + + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + groupEntries.put(entry.getKey(), entry.getValue().name()); + } + + productService.updateACLForPartMasterTemplate(workspaceId, templateId, userEntries, groupEntries); + } else { + productService.removeACLFromPartMasterTemplate(workspaceId, templateId); + } + + return Response.ok().build(); + } + + @DELETE + @ApiOperation(value = "Delete part master template", response = Response.class) + @Path("{templateId}") + public Response deletePartMasterTemplate(@PathParam("workspaceId") String workspaceId, + @PathParam("templateId") String templateId) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + productService.deletePartMasterTemplate(new PartMasterTemplateKey(workspaceId, templateId)); + return Response.ok().build(); + } + + @DELETE + @ApiOperation(value = "Remove attached file from part master template", response = Response.class) + @Path("{templateId}/files/{fileName}") + @Consumes(MediaType.APPLICATION_JSON) + public Response removeAttachedFile(@PathParam("workspaceId") String workspaceId, + @PathParam("templateId") String templateId, + @PathParam("fileName") String fileName) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + String fileFullName = workspaceId + "/part-templates/" + templateId + "/" + fileName; + productService.removeFileFromTemplate(fileFullName); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Rename attached file in part master template", response = Response.class) + @Path("{templateId}/files/{fileName}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public FileDTO renameAttachedFile(@PathParam("workspaceId") String workspaceId, + @PathParam("templateId") String templateId, + @PathParam("fileName") String fileName, + @ApiParam(required = true, value = "File to rename") FileDTO fileDTO) + throws UserNotActiveException, WorkspaceNotFoundException, CreationException, UserNotFoundException, FileNotFoundException, AccessRightException, FileAlreadyExistsException, StorageException, NotAllowedException { + + String fileFullName = workspaceId + "/part-templates/" + templateId + "/" + fileName; + BinaryResource binaryResource = productService.renameFileInTemplate(fileFullName, fileDTO.getShortName()); + return new FileDTO(true, binaryResource.getFullName(), binaryResource.getName()); + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/PartsResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/PartsResource.java new file mode 100644 index 0000000000..467612e502 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/PartsResource.java @@ -0,0 +1,538 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.change.ModificationNotification; +import com.docdoku.core.common.Account; +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.configuration.PSFilter; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.product.*; +import com.docdoku.core.query.PartSearchQuery; +import com.docdoku.core.query.Query; +import com.docdoku.core.query.QueryResultRow; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.ACLUserEntry; +import com.docdoku.core.security.ACLUserGroupEntry; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IImporterManagerLocal; +import com.docdoku.core.services.IPSFilterManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.core.util.FileIO; +import com.docdoku.server.export.ExcelGenerator; +import com.docdoku.server.rest.collections.QueryResult; +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.rest.file.util.BinaryResourceUpload; +import com.docdoku.server.rest.util.SearchQueryParser; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.ejb.EJB; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.Part; +import javax.ws.rs.*; +import javax.ws.rs.core.*; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.nio.file.Files; +import java.util.*; + +@RequestScoped +@Api(hidden = true, value = "parts", description = "Operation about parts") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class PartsResource { + + @Inject + private IProductManagerLocal productService; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private PartResource partResource; + + @Inject + private IPSFilterManagerLocal filterService; + + @EJB + private IImporterManagerLocal importerService; + private Mapper mapper; + + public PartsResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @ApiOperation(value = "SubResource : PartResource") + @Path("{partNumber: [^/].*}-{partVersion:[A-Z]+}") + @Produces(MediaType.APPLICATION_JSON) + public PartResource getPartResource() { + return partResource; + } + + @GET + @ApiOperation(value = "Get part revisions", response = PartRevisionDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getPartRevisions(@PathParam("workspaceId") String workspaceId, + @QueryParam("start") int start, + @QueryParam("length") int length) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + List partRevisions = productService.getPartRevisions(Tools.stripTrailingSlash(workspaceId), start, length); + List partRevisionDTOs = new ArrayList<>(); + + for (PartRevision partRevision : partRevisions) { + PartRevisionDTO partRevisionDTO = Tools.mapPartRevisionToPartDTO(partRevision); + + List notificationDTOs = getModificationNotificationDTOs(partRevision); + partRevisionDTO.setNotifications(notificationDTOs); + + partRevisionDTOs.add(partRevisionDTO); + } + return Response.ok(new GenericEntity>((List) partRevisionDTOs) { + }).build(); + } + + @GET + @ApiOperation(value = "Count part revisions", response = CountDTO.class) + @Path("count") + @Produces(MediaType.APPLICATION_JSON) + public CountDTO getTotalNumberOfParts(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + return new CountDTO(productService.getTotalNumberOfParts(Tools.stripTrailingSlash(workspaceId))); + } + + @GET + @ApiOperation(value = "Get part revisions", response = PartRevisionDTO.class, responseContainer = "List") + @Path("tags/{tagId}") + @Produces(MediaType.APPLICATION_JSON) + public Response getPartRevisions(@PathParam("workspaceId") String workspaceId, + @PathParam("tagId") String tagId) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + PartRevision[] partRevisions = productService.findPartRevisionsByTag(Tools.stripTrailingSlash(workspaceId), tagId); + List partRevisionDTOs = new ArrayList<>(); + + for (PartRevision partRevision : partRevisions) { + PartRevisionDTO partRevisionDTO = Tools.mapPartRevisionToPartDTO(partRevision); + + List notificationDTOs = getModificationNotificationDTOs(partRevision); + partRevisionDTO.setNotifications(notificationDTOs); + + partRevisionDTOs.add(partRevisionDTO); + } + return Response.ok(new GenericEntity>((List) partRevisionDTOs) { + }).build(); + } + + @GET + @ApiOperation(value = "Search part revisions", response = PartRevisionDTO.class, responseContainer = "List") + @Path("search") + @Produces(MediaType.APPLICATION_JSON) + public Response searchPartRevisions(@Context UriInfo uri, + @PathParam("workspaceId") String workspaceId, + @QueryParam("q") String q) + throws EntityNotFoundException, ESServerException, UserNotActiveException, AccessRightException { + + PartSearchQuery partSearchQuery = SearchQueryParser.parsePartStringQuery(workspaceId, uri.getQueryParameters()); + + List partRevisions = productService.searchPartRevisions(partSearchQuery); + List partRevisionDTOs = new ArrayList<>(); + + for (PartRevision partRevision : partRevisions) { + PartRevisionDTO partRevisionDTO = Tools.mapPartRevisionToPartDTO(partRevision); + + List notificationDTOs = getModificationNotificationDTOs(partRevision); + partRevisionDTO.setNotifications(notificationDTOs); + + partRevisionDTOs.add(partRevisionDTO); + } + + return Response.ok(new GenericEntity>((List) partRevisionDTOs) { + }).build(); + } + + @GET + @ApiOperation(value = "Get custom queries", response = QueryDTO.class, responseContainer = "List") + @Path("queries") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response getCustomQueries(@PathParam("workspaceId") String workspaceId) + throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + List queries = productService.getQueries(workspaceId); + List queryDTOs = new ArrayList<>(); + for (Query query : queries) { + queryDTOs.add(mapper.map(query, QueryDTO.class)); + } + return Response.ok(new GenericEntity>((List) queryDTOs) { + }).build(); + } + + @POST + @ApiOperation(value = "Run custom queries", response = QueryResult.class, responseContainer = "List") + @Path("queries") + @Consumes(MediaType.APPLICATION_JSON) + @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM}) + public Response runCustomQuery(@PathParam("workspaceId") String workspaceId, + @QueryParam("save") boolean save, + @QueryParam("export") String exportType, + @ApiParam(required = true, value = "Query to run") QueryDTO queryDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, CreationException, QueryAlreadyExistsException, EntityConstraintException, NotAllowedException { + Query query = mapper.map(queryDTO, Query.class); + QueryResult queryResult = getQueryResult(workspaceId, query, exportType); + + if (save) { + productService.createQuery(workspaceId, query); + } + + return Response.ok(new GenericEntity((QueryResult) queryResult) { + }).build(); + } + + + @GET + @ApiOperation(value = "Filter part master with config spec", response = PartIterationDTO.class) + @Path("{partNumber}/filter/{baselineId}") + @Produces(MediaType.APPLICATION_JSON) + public Response filterPartMasterInBaseline(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("baselineId") String baselineId) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, BaselineNotFoundException, PartMasterNotFoundException { + + PSFilter filter = filterService.getBaselinePSFilter(Integer.valueOf(baselineId)); + PartMaster partMaster = productService.getPartMaster(new PartMasterKey(workspaceId, partNumber)); + List partIterations = filter.filter(partMaster); + if(!partIterations.isEmpty()){ + return Response.ok().entity(Tools.mapPartIterationToPartIterationDTO(partIterations.get(0))).build(); + }else{ + return Response.status(Response.Status.NOT_FOUND).build(); + } + } + + @GET + @ApiOperation(value = "Export custom query", response = Response.class) + @Path("queries/{queryId}/format/{export}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces("application/vnd.ms-excel") + public Response exportCustomQuery(@Context HttpServletRequest request, + @PathParam("workspaceId") String workspaceId, + @PathParam("queryId") String queryId, + @PathParam("export") String exportType) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, CreationException, QueryAlreadyExistsException, EntityConstraintException, NotAllowedException { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + Locale locale = new Locale(user != null ? user.getLanguage() : "en"); + Query query = productService.loadQuery(workspaceId, Integer.valueOf(queryId)); + QueryResult queryResult = getQueryResult(workspaceId, query, exportType); + String url = request.getRequestURL().toString(); + String baseURL = url.substring(0, url.length() - request.getRequestURI().length()) + request.getContextPath(); + return makeQueryResponse(queryResult, locale, baseURL); + } + + private QueryResult getQueryResult(String workspaceId, Query query, String pExportType) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, EntityConstraintException, BaselineNotFoundException, ProductInstanceMasterNotFoundException, NotAllowedException, ConfigurationItemNotFoundException, PartMasterNotFoundException { + List partRevisions = productService.searchPartRevisions(workspaceId, query); + QueryResult queryResult = new QueryResult(partRevisions, query); + if (query.hasContext()) { + List rows = productService.filterProductBreakdownStructure(workspaceId, query); + queryResult.mergeRows(rows); + } + String exportType = pExportType != null ? pExportType : "JSON"; + queryResult.setExportType(QueryResult.ExportType.valueOf(exportType)); + return queryResult; + } + + public Response makeQueryResponse(QueryResult queryResult, Locale locale, String baseURL) { + ExcelGenerator excelGenerator = new ExcelGenerator(); + String contentType = "application/vnd.ms-excel"; + String contentDisposition = "attachment; filename=export_parts.xls"; + Response.ResponseBuilder responseBuilder = Response.ok((Object) excelGenerator.generateXLSResponse(queryResult, locale, baseURL)); + responseBuilder + .header("Content-Type", contentType) + .header("Content-Disposition", contentDisposition); + + return responseBuilder.build(); + } + + @DELETE + @ApiOperation(value = "Delete custom query", response = Response.class) + @Path("queries/{queryId}") + @Produces(MediaType.APPLICATION_JSON) + public Response deleteQuery(@PathParam("workspaceId") String workspaceId, + @PathParam("queryId") int queryId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + productService.deleteQuery(workspaceId, queryId); + return Response.ok().build(); + } + + @GET + @ApiOperation(value = "Get checked out part revisions", response = PartRevisionDTO.class, responseContainer = "List") + @Path("checkedout") + @Produces(MediaType.APPLICATION_JSON) + public Response getCheckedOutPartRevisions(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + PartRevision[] checkedOutPartRevisions = productService.getCheckedOutPartRevisions(workspaceId); + List partRevisionDTOs = new ArrayList<>(); + + for (PartRevision partRevision : checkedOutPartRevisions) { + PartRevisionDTO partRevisionDTO = Tools.mapPartRevisionToPartDTO(partRevision); + + List notificationDTOs = getModificationNotificationDTOs(partRevision); + partRevisionDTO.setNotifications(notificationDTOs); + + partRevisionDTOs.add(partRevisionDTO); + } + + return Response.ok(new GenericEntity>((List) partRevisionDTOs) { + }).build(); + } + + @GET + @ApiOperation(value = "Count checked out part revisions", response = CountDTO.class) + @Path("countCheckedOut") + @Produces(MediaType.APPLICATION_JSON) + public CountDTO getCheckedOutNumberOfItems(@PathParam("workspaceId") String workspaceId) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, AccountNotFoundException { + return new CountDTO(productService.getCheckedOutPartRevisions(workspaceId).length); + } + + @GET + @ApiOperation(value = "Search part numbers", response = LightPartMasterDTO.class, responseContainer = "List") + @Path("numbers") + @Produces(MediaType.APPLICATION_JSON) + public Response searchPartNumbers(@PathParam("workspaceId") String workspaceId, + @QueryParam("q") String q) + throws EntityNotFoundException, AccessRightException { + + String search = "%" + q + "%"; + List partMasters = productService.findPartMasters(Tools.stripTrailingSlash(workspaceId), search, search, 8); + List partsMastersDTO = new ArrayList<>(); + for (PartMaster p : partMasters) { + LightPartMasterDTO lightPartMasterDTO = new LightPartMasterDTO(p.getNumber(), p.getName()); + partsMastersDTO.add(lightPartMasterDTO); + } + + return Response.ok(new GenericEntity>((List) partsMastersDTO) { + }).build(); + } + + + @POST + @ApiOperation(value = "Create new part", response = PartRevisionDTO.class) + @Produces(MediaType.APPLICATION_JSON) + public PartRevisionDTO createNewPart(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Part to create") PartCreationDTO partCreationDTO) + throws EntityNotFoundException, EntityAlreadyExistsException, CreationException, AccessRightException, NotAllowedException { + + String pWorkflowModelId = partCreationDTO.getWorkflowModelId(); + RoleMappingDTO[] roleMappingDTOs = partCreationDTO.getRoleMapping(); + + Map> userRoleMapping = new HashMap<>(); + Map> groupRoleMapping = new HashMap<>(); + + if (roleMappingDTOs != null) { + for (RoleMappingDTO roleMappingDTO : roleMappingDTOs) { + userRoleMapping.put(roleMappingDTO.getRoleName(), roleMappingDTO.getUserLogins()); + groupRoleMapping.put(roleMappingDTO.getRoleName(), roleMappingDTO.getGroupIds()); + } + } + + ACLDTO acl = partCreationDTO.getAcl(); + ACLUserEntry[] userEntries = null; + ACLUserGroupEntry[] userGroupEntries = null; + if (acl != null) { + userEntries = new ACLUserEntry[acl.getUserEntries().size()]; + userGroupEntries = new ACLUserGroupEntry[acl.getGroupEntries().size()]; + int i = 0; + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries[i] = new ACLUserEntry(); + userEntries[i].setPrincipal(new User(new Workspace(workspaceId), new Account(entry.getKey()))); + userEntries[i++].setPermission(ACL.Permission.valueOf(entry.getValue().name())); + } + i = 0; + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + userGroupEntries[i] = new ACLUserGroupEntry(); + userGroupEntries[i].setPrincipal(new UserGroup(new Workspace(workspaceId), entry.getKey())); + userGroupEntries[i++].setPermission(ACL.Permission.valueOf(entry.getValue().name())); + } + } + + PartMaster partMaster = productService.createPartMaster(workspaceId, partCreationDTO.getNumber(), partCreationDTO.getName(), partCreationDTO.isStandardPart(), pWorkflowModelId, partCreationDTO.getDescription(), partCreationDTO.getTemplateId(), userEntries, userGroupEntries, userRoleMapping, groupRoleMapping); + return Tools.mapPartRevisionToPartDTO(partMaster.getLastRevision()); + } + + @GET + @ApiOperation(value = "Search documents last iteration to link", response = PartIterationDTO.class, responseContainer = "List") + @Path("parts_last_iter") + @Produces(MediaType.APPLICATION_JSON) + public PartIterationDTO[] searchDocumentsLastIterationToLink(@PathParam("workspaceId") String workspaceId, @QueryParam("q") String q, @QueryParam("l") int limit) + throws EntityNotFoundException, UserNotActiveException { + + int maxResults = limit == 0 ? 15 : limit; + PartRevision[] partRs = productService.getPartRevisionsWithReferenceOrName(workspaceId, q, maxResults); + + List partsLastIter = new ArrayList<>(); + for (PartRevision partR : partRs) { + PartIteration partLastIter = partR.getLastIteration(); + if (partLastIter != null) { + partsLastIter.add(new PartIterationDTO(partLastIter.getWorkspaceId(), partLastIter.getPartName(), partLastIter.getPartNumber(), partLastIter.getPartVersion(), partLastIter.getIteration())); + } + } + + return partsLastIter.toArray(new PartIterationDTO[partsLastIter.size()]); + } + + @POST + @ApiOperation(value = "Import part attributes from file", response = Response.class) + @Path("import") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public Response importAttributes(@Context HttpServletRequest request, + @PathParam("workspaceId") String workspaceId, + @QueryParam("autoCheckout") boolean autoCheckout, + @QueryParam("autoCheckin") boolean autoCheckin, + @QueryParam("permissiveUpdate") boolean permissiveUpdate, + @QueryParam("revisionNote") String revisionNote) + throws Exception { + + Collection parts = request.getParts(); + + if (parts.isEmpty() || parts.size() > 1) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + + Part part = parts.iterator().next(); + String name = FileIO.getFileNameWithoutExtension(part.getSubmittedFileName()); + String extension = FileIO.getExtension(part.getSubmittedFileName()); + + File importFile = Files.createTempFile("part-" + name, "-import.tmp" + (extension == null ? "" : "." + extension)).toFile(); + BinaryResourceUpload.uploadBinary(new BufferedOutputStream(new FileOutputStream(importFile)), part); + importerService.importIntoParts(workspaceId, importFile, name + "." + extension, revisionNote, autoCheckout, autoCheckin, permissiveUpdate); + + importFile.deleteOnExit(); + + return Response.noContent().build(); + } + + @GET + @ApiOperation(value = "Get current imports", response = ImportDTO.class, responseContainer = "List") + @Path("imports/{filename}") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public List getImports(@PathParam("workspaceId") String workspaceId, @PathParam("filename") String filename) + throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + + List imports = productService.getImports(workspaceId, filename); + List importDTOs = new ArrayList<>(); + for (Import i : imports) { + importDTOs.add(mapper.map(i, ImportDTO.class)); + } + return importDTOs; + } + + @GET + @ApiOperation(value = "Get import", response = ImportDTO.class) + @Path("import/{importId}") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public ImportDTO getImport(@PathParam("workspaceId") String workspaceId, @PathParam("importId") String importId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, AccessRightException { + Import anImport = productService.getImport(workspaceId, importId); + return mapper.map(anImport, ImportDTO.class); + } + + @DELETE + @ApiOperation(value = "Delete import", response = Response.class) + @Path("import/{importId}") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public Response deleteImport(@PathParam("workspaceId") String workspaceId, @PathParam("importId") String importId) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException { + productService.removeImport(workspaceId, importId); + return Response.noContent().build(); + } + + @POST + @Path("importPreview") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public List getImportPreview(@Context HttpServletRequest request, + @PathParam("workspaceId") String workspaceId, + @QueryParam("autoCheckout") boolean autoCheckout, + @QueryParam("autoCheckin") boolean autoCheckin, + @QueryParam("permissiveUpdate") boolean permissiveUpdate) + throws Exception { + + Collection parts = request.getParts(); + + if(parts.isEmpty() || parts.size() > 1){ + return null; + } + + Part part = parts.iterator().next(); + String name = FileIO.getFileNameWithoutExtension(part.getSubmittedFileName()); + String extension = FileIO.getExtension(part.getSubmittedFileName()); + + File importFile = Files.createTempFile("part-" + name, "-import.tmp" + (extension==null?"":"." + extension)).toFile(); + BinaryResourceUpload.uploadBinary(new BufferedOutputStream(new FileOutputStream(importFile)), part); + ImportPreview importPreview = importerService.dryRunImportIntoParts(workspaceId,importFile,name+"."+extension,autoCheckout, autoCheckin, permissiveUpdate); + + importFile.deleteOnExit(); + + List result = new ArrayList<>(); + for(PartRevision partRevision : importPreview.getPartRevsToCheckout()){ + result.add(mapper.map(partRevision,LightPartRevisionDTO.class)); + } + + return result; + + } + + /** + * Return a list of ModificationNotificationDTO matching with a given PartRevision + * + * @param partRevision The specified PartRevision + * @return A list of ModificationNotificationDTO + * @throws EntityNotFoundException If an entity doesn't exist + * @throws AccessRightException If the user can not get the modification notifications + * @throws UserNotActiveException If the user is disabled + */ + private List getModificationNotificationDTOs(PartRevision partRevision) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + PartIterationKey iterationKey = new PartIterationKey(partRevision.getKey(), partRevision.getLastIterationNumber()); + List notifications = productService.getModificationNotifications(iterationKey); + return Tools.mapModificationNotificationsToModificationNotificationDTO(notifications); + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ProductBaselinesResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ProductBaselinesResource.java new file mode 100644 index 0000000000..a41fb1c875 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ProductBaselinesResource.java @@ -0,0 +1,299 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.configuration.BaselinedPart; +import com.docdoku.core.configuration.ProductBaseline; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.product.ConfigurationItemKey; +import com.docdoku.core.product.PartIterationKey; +import com.docdoku.core.product.PartLink; +import com.docdoku.core.product.PathToPathLink; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IProductBaselineManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.server.rest.dto.LightPartLinkDTO; +import com.docdoku.server.rest.dto.LightPartLinkListDTO; +import com.docdoku.server.rest.dto.LightPathToPathLinkDTO; +import com.docdoku.server.rest.dto.PathToPathLinkDTO; +import com.docdoku.server.rest.dto.baseline.BaselinedPartDTO; +import com.docdoku.server.rest.dto.baseline.ProductBaselineDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Taylor LABEJOF + */ +@RequestScoped +@Api(hidden = true, value = "product-baseline", description = "Operations about product-baseline") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class ProductBaselinesResource { + + @Inject + private IProductBaselineManagerLocal productBaselineService; + + @Inject + private IProductManagerLocal productService; + + private Mapper mapper; + + public ProductBaselinesResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get product-baseline with given configuration item", response = ProductBaselineDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getBaselines(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId) + throws UserNotActiveException, EntityNotFoundException, AccessRightException { + List productBaselines; + if (ciId != null) { + ConfigurationItemKey configurationItemKey = new ConfigurationItemKey(workspaceId, ciId); + productBaselines = productBaselineService.getBaselines(configurationItemKey); + } else { + productBaselines = productBaselineService.getAllBaselines(workspaceId); + } + List baselinesDTO = new ArrayList<>(); + for (ProductBaseline productBaseline : productBaselines) { + ProductBaselineDTO productBaselineDTO = mapper.map(productBaseline, ProductBaselineDTO.class); + productBaselineDTO.setConfigurationItemId(productBaseline.getConfigurationItem().getId()); + productBaselineDTO.setConfigurationItemLatestRevision(productBaseline.getConfigurationItem().getDesignItem().getLastRevision().getVersion()); + productBaselineDTO.setHasObsoletePartRevisions(!productBaselineService.getObsoletePartRevisionsInBaseline(workspaceId, productBaseline.getId()).isEmpty()); + baselinesDTO.add(productBaselineDTO); + } + return Response.ok(new GenericEntity>((List) baselinesDTO) { + }).build(); + } + + @POST + @ApiOperation(value = "Create product-baseline", response = ProductBaselineDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ProductBaselineDTO createBaseline(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String pCiId, + @ApiParam(required = true, value = "Product baseline to create") ProductBaselineDTO productBaselineDTO) + throws UserNotActiveException, EntityNotFoundException, NotAllowedException, AccessRightException, PartRevisionNotReleasedException, EntityConstraintException, CreationException, PathToPathLinkAlreadyExistsException { + + String ciId = (pCiId != null) ? pCiId : productBaselineDTO.getConfigurationItemId(); + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + String description = productBaselineDTO.getDescription(); + String name = productBaselineDTO.getName(); + ProductBaseline.BaselineType type = productBaselineDTO.getType(); + + List baselinedPartsDTO = productBaselineDTO.getBaselinedParts(); + List partIterationKeys = new ArrayList<>(); + for (BaselinedPartDTO part : baselinedPartsDTO) { + partIterationKeys.add(new PartIterationKey(workspaceId, part.getNumber(), part.getVersion(), part.getIteration())); + } + + ProductBaseline baseline = productBaselineService.createBaseline(ciKey, + name, type, description, partIterationKeys, productBaselineDTO.getSubstituteLinks(), productBaselineDTO.getOptionalUsageLinks()); + ProductBaselineDTO dto = mapper.map(baseline, ProductBaselineDTO.class); + dto.setConfigurationItemLatestRevision(baseline.getConfigurationItem().getDesignItem().getLastRevision().getVersion()); + dto.setHasObsoletePartRevisions(!productBaselineService.getObsoletePartRevisionsInBaseline(workspaceId, dto.getId()).isEmpty()); + + return dto; + } + + @DELETE + @ApiOperation(value = "Delete product-baseline", response = Response.class) + @Path("{baselineId}") + @Consumes(MediaType.APPLICATION_JSON) + public Response deleteBaseline(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @PathParam("baselineId") int baselineId) + throws EntityNotFoundException, AccessRightException, UserNotActiveException, EntityConstraintException { + productBaselineService.deleteBaseline(workspaceId, baselineId); + return Response.ok().build(); + } + + @GET + @ApiOperation(value = "Get product-baseline", response = ProductBaselineDTO.class) + @Path("{baselineId}") + @Produces(MediaType.APPLICATION_JSON) + public ProductBaselineDTO getBaseline(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @PathParam("baselineId") int baselineId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + ProductBaseline productBaseline = productBaselineService.getBaseline(baselineId); + ProductBaselineDTO productBaselineDTO = mapper.map(productBaseline, ProductBaselineDTO.class); + productBaselineDTO.setPathToPathLinks(getPathToPathLinksForGivenBaseline(productBaseline)); + productBaselineDTO.setConfigurationItemId(productBaseline.getConfigurationItem().getId()); + productBaselineDTO.setConfigurationItemLatestRevision(productBaseline.getConfigurationItem().getDesignItem().getLastRevision().getVersion()); + + ConfigurationItemKey ciKey = productBaseline.getConfigurationItem().getKey(); + + List substitutesParts = new ArrayList<>(); + List optionalParts = new ArrayList<>(); + + for (String path : productBaseline.getSubstituteLinks()) { + LightPartLinkListDTO lightPartLinkListDTO = new LightPartLinkListDTO(); + for (PartLink partLink : productService.decodePath(ciKey, path)) { + lightPartLinkListDTO.getPartLinks().add(new LightPartLinkDTO(partLink)); + } + substitutesParts.add(lightPartLinkListDTO); + } + for (String path : productBaseline.getOptionalUsageLinks()) { + LightPartLinkListDTO lightPartLinkListDTO = new LightPartLinkListDTO(); + for (PartLink partLink : productService.decodePath(ciKey, path)) { + lightPartLinkListDTO.getPartLinks().add(new LightPartLinkDTO(partLink)); + } + optionalParts.add(lightPartLinkListDTO); + } + + productBaselineDTO.setSubstitutesParts(substitutesParts); + productBaselineDTO.setOptionalsParts(optionalParts); + + return productBaselineDTO; + } + + @GET + @ApiOperation(value = "Get product-baseline's part", response = BaselinedPartDTO.class, responseContainer = "List") + @Path("{baselineId}/parts") + @Produces(MediaType.APPLICATION_JSON) + public Response getBaselineParts(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @PathParam("baselineId") int baselineId, + @QueryParam("q") String q) + throws EntityNotFoundException, UserNotActiveException { + int maxResults = 8; + List baselinedPartList = productBaselineService.getBaselinedPartWithReference(baselineId, q, maxResults); + + List baselinedPartDTOList = new ArrayList<>(); + for (BaselinedPart baselinedPart : baselinedPartList) { + baselinedPartDTOList.add(Tools.mapBaselinedPartToBaselinedPartDTO(baselinedPart)); + } + return Response.ok(new GenericEntity>((List) baselinedPartDTOList) { + }).build(); + } + + @GET + @ApiOperation(value = "Get product-baseline's path-to-path links", response = LightPathToPathLinkDTO.class, responseContainer = "List") + @Path("{baselineId}/path-to-path-links-types") + @Produces(MediaType.APPLICATION_JSON) + public Response getPathToPathLinkTypes(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String configurationItemId, + @PathParam("baselineId") int baselineId) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, BaselineNotFoundException { + List pathToPathLinkTypes = productBaselineService.getPathToPathLinkTypes(workspaceId, configurationItemId, baselineId); + List pathToPathLinkDTOs = new ArrayList<>(); + for (String type : pathToPathLinkTypes) { + LightPathToPathLinkDTO pathToPathLinkDTO = new LightPathToPathLinkDTO(); + pathToPathLinkDTO.setType(type); + pathToPathLinkDTOs.add(pathToPathLinkDTO); + } + return Response.ok(new GenericEntity>((List) pathToPathLinkDTOs) { + }).build(); + } + + @GET + @ApiOperation(value = "Get product-baseline's path-to-path links for given source and target", response = LightPathToPathLinkDTO.class, responseContainer = "List") + @Path("{baselineId}/path-to-path-links/source/{sourcePath}/target/{targetPath}") + @Produces(MediaType.APPLICATION_JSON) + public Response getPathToPathLinkFromSourceAndTarget(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String configurationItemId, + @PathParam("baselineId") int baselineId, + @PathParam("sourcePath") String sourcePathAsString, + @PathParam("targetPath") String targetPathAsString) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, ProductInstanceMasterNotFoundException, BaselineNotFoundException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException { + List pathToPathLinks = productBaselineService.getPathToPathLinkFromSourceAndTarget(workspaceId, configurationItemId, baselineId, sourcePathAsString, targetPathAsString); + List dtos = new ArrayList<>(); + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, configurationItemId); + + for (PathToPathLink pathToPathLink : pathToPathLinks) { + PathToPathLinkDTO pathToPathLinkDTO = mapper.map(pathToPathLink, PathToPathLinkDTO.class); + List sourceLightPartLinkDTOs = new ArrayList<>(); + + List sourcePath = productService.decodePath(ciKey, pathToPathLink.getSourcePath()); + List targetPath = productService.decodePath(ciKey, pathToPathLink.getTargetPath()); + + for (PartLink partLink : sourcePath) { + LightPartLinkDTO lightPartLinkDTO = new LightPartLinkDTO(partLink); + sourceLightPartLinkDTOs.add(lightPartLinkDTO); + } + + List targetLightPartLinkDTOs = new ArrayList<>(); + for (PartLink partLink : targetPath) { + LightPartLinkDTO lightPartLinkDTO = new LightPartLinkDTO(partLink); + targetLightPartLinkDTOs.add(lightPartLinkDTO); + } + + pathToPathLinkDTO.setSourceComponents(sourceLightPartLinkDTOs); + pathToPathLinkDTO.setTargetComponents(targetLightPartLinkDTOs); + dtos.add(pathToPathLinkDTO); + } + + return Response.ok(new GenericEntity>((List) dtos) { + }).build(); + + } + + private List getPathToPathLinksForGivenBaseline(ProductBaseline productBaseline) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException, ProductInstanceMasterNotFoundException, BaselineNotFoundException, AccessRightException { + + List pathToPathLinkTypes = productBaseline.getPathToPathLinks(); + List pathToPathLinkDTOs = new ArrayList<>(); + + for (PathToPathLink pathToPathLink : pathToPathLinkTypes) { + PathToPathLinkDTO pathToPathLinkDTO = mapper.map(pathToPathLink, PathToPathLinkDTO.class); + List sourcePath = productService.decodePath(productBaseline.getConfigurationItem().getKey(), pathToPathLink.getSourcePath()); + List targetPath = productService.decodePath(productBaseline.getConfigurationItem().getKey(), pathToPathLink.getTargetPath()); + + List sourceLightPartLinkDTOs = new ArrayList<>(); + for (PartLink partLink : sourcePath) { + LightPartLinkDTO lightPartLinkDTO = new LightPartLinkDTO(partLink); + sourceLightPartLinkDTOs.add(lightPartLinkDTO); + } + + List targetLightPartLinkDTOs = new ArrayList<>(); + for (PartLink partLink : targetPath) { + LightPartLinkDTO lightPartLinkDTO = new LightPartLinkDTO(partLink); + targetLightPartLinkDTOs.add(lightPartLinkDTO); + } + + pathToPathLinkDTO.setSourceComponents(sourceLightPartLinkDTOs); + pathToPathLinkDTO.setTargetComponents(targetLightPartLinkDTOs); + pathToPathLinkDTOs.add(pathToPathLinkDTO); + + } + return pathToPathLinkDTOs; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ProductConfigurationsResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ProductConfigurationsResource.java new file mode 100644 index 0000000000..518778d83f --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ProductConfigurationsResource.java @@ -0,0 +1,215 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.configuration.ProductConfiguration; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.product.ConfigurationItemKey; +import com.docdoku.core.product.PartLink; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IProductBaselineManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.server.rest.dto.ACLDTO; +import com.docdoku.server.rest.dto.LightPartLinkDTO; +import com.docdoku.server.rest.dto.LightPartLinkListDTO; +import com.docdoku.server.rest.dto.baseline.ProductConfigurationDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Morgan Guimard + */ +@RequestScoped +@Api(hidden = true, value = "product-configurations", description = "Operations about product configurations") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class ProductConfigurationsResource { + + @Inject + private IProductBaselineManagerLocal productBaselineService; + + @Inject + private IProductManagerLocal productService; + + private Mapper mapper; + + public ProductConfigurationsResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get all product configurations", response = ProductConfigurationDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getAllConfiguration(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId) + throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, ConfigurationItemNotFoundException { + List allProductConfigurations; + if (ciId != null) { + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + allProductConfigurations = productBaselineService.getAllProductConfigurationsByConfigurationItemId(ciKey); + } else { + allProductConfigurations = productBaselineService.getAllProductConfigurations(workspaceId); + } + List configurationDTOs = new ArrayList<>(); + for (ProductConfiguration productConfiguration : allProductConfigurations) { + ProductConfigurationDTO productConfigurationDTO = mapper.map(productConfiguration, ProductConfigurationDTO.class); + productConfigurationDTO.setConfigurationItemId(productConfiguration.getConfigurationItem().getId()); + configurationDTOs.add(productConfigurationDTO); + } + + return Response.ok(new GenericEntity>((List) configurationDTOs) { + }).build(); + + } + + @GET + @ApiOperation(value = "Get product configuration", response = ProductConfigurationDTO.class) + @Path("{productConfigurationId}") + @Produces(MediaType.APPLICATION_JSON) + public ProductConfigurationDTO getConfiguration(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @PathParam("productConfigurationId") int productConfigurationId) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ProductConfigurationNotFoundException, EntityConstraintException, NotAllowedException, AccessRightException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException, PartMasterNotFoundException { + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + ProductConfiguration productConfiguration = productBaselineService.getProductConfiguration(ciKey, productConfigurationId); + ProductConfigurationDTO productConfigurationDTO = mapper.map(productConfiguration, ProductConfigurationDTO.class); + productConfigurationDTO.setConfigurationItemId(productConfiguration.getConfigurationItem().getId()); + + List substitutesParts = new ArrayList<>(); + List optionalParts = new ArrayList<>(); + + for (String path : productConfiguration.getSubstituteLinks()) { + LightPartLinkListDTO partDTOs = new LightPartLinkListDTO(); + for (PartLink partLink : productService.decodePath(ciKey, path)) { + partDTOs.getPartLinks().add(new LightPartLinkDTO(partLink)); + } + substitutesParts.add(partDTOs); + } + + for (String path : productConfiguration.getOptionalUsageLinks()) { + LightPartLinkListDTO partDTOs = new LightPartLinkListDTO(); + for (PartLink partLink : productService.decodePath(ciKey, path)) { + partDTOs.getPartLinks().add(new LightPartLinkDTO(partLink)); + } + optionalParts.add(partDTOs); + } + + productConfigurationDTO.setSubstitutesParts(substitutesParts); + productConfigurationDTO.setOptionalsParts(optionalParts); + + return productConfigurationDTO; + } + + @POST + @ApiOperation(value = "Create product configuration", response = ProductConfigurationDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ProductConfigurationDTO createConfiguration(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String pCiId, + @ApiParam(required = true, value = "Product configuration to create") ProductConfigurationDTO pProductConfigurationDTO) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ConfigurationItemNotFoundException, CreationException, AccessRightException { + String ciId = (pCiId != null) ? pCiId : pProductConfigurationDTO.getConfigurationItemId(); + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + String description = pProductConfigurationDTO.getDescription(); + String name = pProductConfigurationDTO.getName(); + + ACLDTO acldto = pProductConfigurationDTO.getAcl(); + Map userEntries = new HashMap<>(); + Map grpEntries = new HashMap<>(); + if (acldto != null) { + userEntries = acldto.getUserEntries(); + grpEntries = acldto.getGroupEntries(); + } + + ProductConfiguration productConfiguration = productBaselineService.createProductConfiguration(ciKey, name, description, pProductConfigurationDTO.getSubstituteLinks(), pProductConfigurationDTO.getOptionalUsageLinks(), userEntries, grpEntries); + ProductConfigurationDTO productConfigurationDTO = mapper.map(productConfiguration, ProductConfigurationDTO.class); + productConfigurationDTO.setConfigurationItemId(productConfiguration.getConfigurationItem().getId()); + return productConfigurationDTO; + } + + + @PUT + @ApiOperation(value = "Update product configuration", response = Response.class) + @Path("{productConfigurationId}/acl") + @Consumes(MediaType.APPLICATION_JSON) + public Response updateConfigurationACL(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String pCiId, + @PathParam("productConfigurationId") int productConfigurationId, + @ApiParam(required = true, value = "ACL rules to set") ACLDTO acl) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ProductConfigurationNotFoundException, AccessRightException { + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, pCiId); + + if (!acl.getGroupEntries().isEmpty() || !acl.getUserEntries().isEmpty()) { + + Map userEntries = new HashMap<>(); + Map groupEntries = new HashMap<>(); + + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries.put(entry.getKey(), entry.getValue().name()); + } + + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + groupEntries.put(entry.getKey(), entry.getValue().name()); + } + + productBaselineService.updateACLForConfiguration(ciKey, productConfigurationId, userEntries, groupEntries); + } else { + productBaselineService.removeACLFromConfiguration(ciKey, productConfigurationId); + } + + return Response.ok().build(); + } + + @DELETE + @ApiOperation(value = "Delete product configuration", response = Response.class) + @Path("{productConfigurationId}") + @Produces(MediaType.APPLICATION_JSON) + public Response deleteProductConfiguration(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @PathParam("productConfigurationId") int productConfigurationId) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ProductConfigurationNotFoundException, AccessRightException { + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + productBaselineService.deleteProductConfiguration(ciKey, productConfigurationId); + return Response.ok().build(); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ProductInstancesResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ProductInstancesResource.java new file mode 100644 index 0000000000..6fe3f70418 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ProductInstancesResource.java @@ -0,0 +1,761 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.configuration.*; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.InstanceAttributeTemplate; +import com.docdoku.core.product.*; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IImporterManagerLocal; +import com.docdoku.core.services.IPSFilterManagerLocal; +import com.docdoku.core.services.IProductInstanceManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.core.util.FileIO; +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.rest.dto.baseline.ProductBaselineDTO; +import com.docdoku.server.rest.dto.product.ProductInstanceCreationDTO; +import com.docdoku.server.rest.dto.product.ProductInstanceIterationDTO; +import com.docdoku.server.rest.dto.product.ProductInstanceMasterDTO; +import com.docdoku.server.rest.file.util.BinaryResourceUpload; +import com.docdoku.server.rest.util.InstanceAttributeFactory; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.Part; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.nio.file.Files; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@RequestScoped +@Api(hidden = true, value = "product-instances", description = "Operations about product-instances") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class ProductInstancesResource { + + private static final Logger LOGGER = Logger.getLogger(ProductInstancesResource.class.getName()); + @Inject + private IProductInstanceManagerLocal productInstanceService; + @Inject + private IProductManagerLocal productService; + @Inject + private IPSFilterManagerLocal psFilterService; + @Inject + private IImporterManagerLocal importerService; + private Mapper mapper; + + public ProductInstancesResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get product-instance with given configuration item", response = ProductInstanceMasterDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getProductInstances(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId) + throws EntityNotFoundException, UserNotActiveException { + + List productInstanceMasterList; + if (ciId != null) { + ConfigurationItemKey configurationItemKey = new ConfigurationItemKey(workspaceId, ciId); + productInstanceMasterList = productInstanceService.getProductInstanceMasters(configurationItemKey); + } else { + productInstanceMasterList = productInstanceService.getProductInstanceMasters(workspaceId); + } + List dtos = new ArrayList<>(); + for (ProductInstanceMaster productInstanceMaster : productInstanceMasterList) { + ProductInstanceMasterDTO productInstanceMasterDTO = mapper.map(productInstanceMaster, ProductInstanceMasterDTO.class); + productInstanceMasterDTO.setConfigurationItemId(productInstanceMaster.getInstanceOf().getId()); + dtos.add(productInstanceMasterDTO); + } + return Response.ok(new GenericEntity>((List) dtos) { + }).build(); + } + + @POST + @ApiOperation(value = "Create product-instance", response = ProductInstanceMasterDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ProductInstanceMasterDTO createProductInstanceMaster(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Product instance master to create") ProductInstanceCreationDTO productInstanceCreationDTO) + throws EntityNotFoundException, EntityAlreadyExistsException, AccessRightException, CreationException, NotAllowedException, EntityConstraintException, UserNotActiveException { + + InstanceAttributeFactory factory = new InstanceAttributeFactory(); + ACLDTO acldto = productInstanceCreationDTO.getAcl(); + Map userEntries = new HashMap<>(); + Map grpEntries = new HashMap<>(); + List instanceAttributes = productInstanceCreationDTO.getInstanceAttributes(); + List attributes = new ArrayList<>(); + if (instanceAttributes != null) { + attributes = factory.createInstanceAttributes(instanceAttributes); + } + if (acldto != null) { + userEntries = acldto.getUserEntries(); + grpEntries = acldto.getGroupEntries(); + } + Set linkedDocs = productInstanceCreationDTO.getLinkedDocuments(); + DocumentRevisionKey[] links = null; + String[] documentLinkComments = null; + if (linkedDocs != null) { + documentLinkComments = new String[linkedDocs.size()]; + links = createDocumentRevisionKeys(linkedDocs); + int i = 0; + for (DocumentRevisionDTO docRevisionForLink : linkedDocs) { + String comment = docRevisionForLink.getCommentLink(); + if (comment == null) { + comment = ""; + } + documentLinkComments[i++] = comment; + } + } + ProductInstanceMaster productInstanceMaster = productInstanceService.createProductInstance(workspaceId, new ConfigurationItemKey(workspaceId, productInstanceCreationDTO.getConfigurationItemId()), productInstanceCreationDTO.getSerialNumber(), productInstanceCreationDTO.getBaselineId(), userEntries, grpEntries, attributes, links, documentLinkComments); + + return mapper.map(productInstanceMaster, ProductInstanceMasterDTO.class); + } + + @PUT + @ApiOperation(value = "Update product-instance", response = ProductInstanceMasterDTO.class) + @Path("{serialNumber}/iterations/{iteration}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public ProductInstanceMasterDTO updateProductInstanceMaster(@PathParam("workspaceId") String workspaceId, + @PathParam("iteration") int iteration, + @ApiParam(required = true, value = "Product instance master to create") ProductInstanceIterationDTO productInstanceCreationDTO) + throws EntityNotFoundException, EntityAlreadyExistsException, AccessRightException, CreationException, UserNotActiveException { + + InstanceAttributeFactory factory = new InstanceAttributeFactory(); + + List instanceAttributes = productInstanceCreationDTO.getInstanceAttributes(); + List attributes = new ArrayList<>(); + if (instanceAttributes != null) { + attributes = factory.createInstanceAttributes(instanceAttributes); + } + + Set linkedDocs = productInstanceCreationDTO.getLinkedDocuments(); + DocumentRevisionKey[] links = null; + String[] documentLinkComments = null; + if (linkedDocs != null) { + documentLinkComments = new String[linkedDocs.size()]; + links = createDocumentRevisionKeys(linkedDocs); + int i = 0; + for (DocumentRevisionDTO docRevisionForLink : linkedDocs) { + String comment = docRevisionForLink.getCommentLink(); + if (comment == null) { + comment = ""; + } + documentLinkComments[i++] = comment; + } + } + + ProductInstanceMaster productInstanceMaster = productInstanceService.updateProductInstance(workspaceId, iteration, productInstanceCreationDTO.getIterationNote(), new ConfigurationItemKey(workspaceId, productInstanceCreationDTO.getConfigurationItemId()), productInstanceCreationDTO.getSerialNumber(), productInstanceCreationDTO.getBasedOn().getId(), attributes, links, documentLinkComments); + + return mapper.map(productInstanceMaster, ProductInstanceMasterDTO.class); + } + + @GET + @ApiOperation(value = "Get product-instance", response = ProductInstanceMasterDTO.class) + @Path("{serialNumber}") + @Produces(MediaType.APPLICATION_JSON) + public ProductInstanceMasterDTO getProductInstance(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber) + throws EntityNotFoundException, UserNotActiveException { + + ProductInstanceMaster productInstanceMaster = productInstanceService.getProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, configurationItemId); + ProductInstanceMasterDTO dto = mapper.map(productInstanceMaster, ProductInstanceMasterDTO.class); + + + List productInstanceIterationsDTO = dto.getProductInstanceIterations(); + List productInstanceIterations = productInstanceMaster.getProductInstanceIterations(); + Iterator iterationIterator = productInstanceIterations.iterator(); + for (ProductInstanceIterationDTO productInstanceIterationDTO : productInstanceIterationsDTO) { + + List substitutesParts = new ArrayList<>(); + List optionalParts = new ArrayList<>(); + try { + productInstanceIterationDTO.setPathToPathLinks(getPathToPathLinksForGivenProductInstance(iterationIterator.next())); + } catch (AccessRightException e) { + LOGGER.log(Level.FINEST, null, e); + } + for (String path : productInstanceIterationDTO.getSubstituteLinks()) { + LightPartLinkListDTO lightPartLinkDTO = new LightPartLinkListDTO(); + for (PartLink partLink : productService.decodePath(ciKey, path)) { + lightPartLinkDTO.getPartLinks().add(new LightPartLinkDTO(partLink)); + } + substitutesParts.add(lightPartLinkDTO); + } + for (String path : productInstanceIterationDTO.getOptionalUsageLinks()) { + LightPartLinkListDTO lightPartLinkDTO = new LightPartLinkListDTO(); + for (PartLink partLink : productService.decodePath(ciKey, path)) { + lightPartLinkDTO.getPartLinks().add(new LightPartLinkDTO(partLink)); + } + optionalParts.add(lightPartLinkDTO); + } + + productInstanceIterationDTO.setSubstitutesParts(substitutesParts); + productInstanceIterationDTO.setOptionalsParts(optionalParts); + + List pathDataPaths = new ArrayList<>(); + for (PathDataMasterDTO pathDataMasterDTO : productInstanceIterationDTO.getPathDataMasterList()) { + LightPartLinkListDTO lightPartLinkListDTO = new LightPartLinkListDTO(); + for (PartLink partLink : productService.decodePath(ciKey, pathDataMasterDTO.getPath())) { + lightPartLinkListDTO.getPartLinks().add(new LightPartLinkDTO(partLink)); + } + pathDataPaths.add(lightPartLinkListDTO); + } + productInstanceIterationDTO.setPathDataPaths(pathDataPaths); + } + + return dto; + } + + @DELETE + @ApiOperation(value = "Remove attached file from product-instance", response = Response.class) + @Consumes(MediaType.APPLICATION_JSON) + @Path("{serialNumber}/iterations/{iteration}/files/{fileName}") + public Response removeAttachedFile(@PathParam("workspaceId") String workspaceId, @PathParam("iteration") int iteration, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber, @PathParam("fileName") String fileName) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + String fullName = workspaceId + "/product-instances/" + serialNumber + "/iterations/" + iteration + "/" + fileName; + productInstanceService.removeFileFromProductInstanceIteration(workspaceId, iteration, fullName, new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + return Response.ok().build(); + } + + + @PUT + @ApiOperation(value = "Update product-instance's ACL", response = Response.class) + @Path("{serialNumber}/acl") + @Consumes(MediaType.APPLICATION_JSON) + public Response updateProductInstanceACL(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber, ACLDTO acl) + throws EntityNotFoundException, AccessRightException, UserNotActiveException, NotAllowedException { + + if (!acl.getGroupEntries().isEmpty() || !acl.getUserEntries().isEmpty()) { + + Map userEntries = new HashMap<>(); + Map groupEntries = new HashMap<>(); + + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries.put(entry.getKey(), entry.getValue().name()); + } + + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + groupEntries.put(entry.getKey(), entry.getValue().name()); + } + + productInstanceService.updateACLForProductInstanceMaster(workspaceId, configurationItemId, serialNumber, userEntries, groupEntries); + } else { + productInstanceService.removeACLFromProductInstanceMaster(workspaceId, configurationItemId, serialNumber); + } + + return Response.ok().build(); + } + + @DELETE + @ApiOperation(value = "Delete product-instance", response = Response.class) + @Path("{serialNumber}") + @Consumes(MediaType.APPLICATION_JSON) + public Response deleteProductInstanceMaster(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + productInstanceService.deleteProductInstance(workspaceId, configurationItemId, serialNumber); + return Response.ok().build(); + } + + @GET + @ApiOperation(value = "Get product-instance's iterations", response = ProductInstanceIterationDTO.class, responseContainer = "List") + @Path("{serialNumber}/iterations") + @Produces(MediaType.APPLICATION_JSON) + public Response getProductInstanceIterations(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber) + throws EntityNotFoundException, UserNotActiveException { + + List productInstanceIterationList = productInstanceService.getProductInstanceIterations(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + List dtos = new ArrayList<>(); + for (ProductInstanceIteration productInstanceIteration : productInstanceIterationList) { + ProductInstanceIterationDTO productInstanceIterationDTO = mapper.map(productInstanceIteration, ProductInstanceIterationDTO.class); + dtos.add(productInstanceIterationDTO); + } + return Response.ok(new GenericEntity>((List) dtos) { + }).build(); + } + + + @GET + @ApiOperation(value = "Get product-instance's iteration", response = ProductInstanceIterationDTO.class) + @Path("{serialNumber}/iterations/{iteration}") + @Produces(MediaType.APPLICATION_JSON) + public ProductInstanceIterationDTO getProductInstanceIteration(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber, @PathParam("iteration") int iteration) + throws EntityNotFoundException, UserNotActiveException { + + ProductInstanceIteration productInstanceIteration = productInstanceService.getProductInstanceIteration(new ProductInstanceIterationKey(serialNumber, workspaceId, configurationItemId, iteration)); + return mapper.map(productInstanceIteration, ProductInstanceIterationDTO.class); + } + + @PUT + @ApiOperation(value = "Rebase product-instance", response = Response.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("{serialNumber}/rebase") + public Response rebaseProductInstance(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String configurationItemId, + @PathParam("serialNumber") String serialNumber, ProductBaselineDTO baselineDTO) throws UserNotActiveException, WorkspaceNotFoundException, BaselineNotFoundException, UserNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException, NotAllowedException, ConfigurationItemNotFoundException, PathToPathLinkAlreadyExistsException, PartMasterNotFoundException, CreationException, EntityConstraintException { + + productInstanceService.rebaseProductInstance(workspaceId, serialNumber, new ConfigurationItemKey(workspaceId, configurationItemId), baselineDTO.getId()); + return Response.ok().build(); + } + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("{serialNumber}/iterations/{iteration}/files/{fileName}") + public FileDTO renameAttachedFile(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber, @PathParam("iteration") int iteration, @PathParam("fileName") String fileName, FileDTO fileDTO) throws UserNotActiveException, WorkspaceNotFoundException, CreationException, UserNotFoundException, FileNotFoundException, NotAllowedException, FileAlreadyExistsException, ProductInstanceMasterNotFoundException, AccessRightException, StorageException { + String fullName = workspaceId + "/product-instances/" + serialNumber + "/iterations/" + iteration + "/" + fileName; + BinaryResource binaryResource = productInstanceService.renameFileInProductInstance(fullName, fileDTO.getShortName(), serialNumber, configurationItemId, iteration); + return new FileDTO(true, binaryResource.getFullName(), binaryResource.getName()); + } + + @GET + @ApiOperation(value = "Get product-instance's path-data", response = PathDataMasterDTO.class) + @Path("{serialNumber}/pathdata/{path}") + @Produces(MediaType.APPLICATION_JSON) + public PathDataMasterDTO getPathData(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber, @PathParam("path") String pathAsString) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, ProductInstanceMasterNotFoundException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException, BaselineNotFoundException { + PathDataMaster pathDataMaster = productInstanceService.getPathDataByPath(workspaceId, configurationItemId, serialNumber, pathAsString); + + PathDataMasterDTO dto = pathDataMaster == null ? new PathDataMasterDTO(pathAsString) : mapper.map(pathDataMaster, PathDataMasterDTO.class); + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, configurationItemId); + + LightPartLinkListDTO partLinksList = new LightPartLinkListDTO(); + List path = productService.decodePath(ciKey, pathAsString); + for (PartLink partLink : path) { + partLinksList.getPartLinks().add(new LightPartLinkDTO(partLink)); + } + dto.setPartLinksList(partLinksList); + + List attributesDTO = new ArrayList<>(); + List attributeTemplatesDTO = new ArrayList<>(); + PartLink partLink = path.get(path.size() - 1); + PSFilter filter = psFilterService.getPSFilter(ciKey, "pi-" + serialNumber, false); + List partIterations = filter.filter(partLink.getComponent()); + PartIteration partIteration = partIterations.get(0); + + if (partIteration != null) { + for (InstanceAttribute instanceAttribute : partIteration.getInstanceAttributes()) { + attributesDTO.add(mapper.map(instanceAttribute, InstanceAttributeDTO.class)); + } + dto.setPartAttributes(attributesDTO); + for (InstanceAttributeTemplate instanceAttributeTemplate : partIteration.getInstanceAttributeTemplates()) { + attributeTemplatesDTO.add(mapper.map(instanceAttributeTemplate, InstanceAttributeTemplateDTO.class)); + } + dto.setPartAttributeTemplates(attributeTemplatesDTO); + } + + return dto; + } + + + @PUT + @ApiOperation(value = "Rename product-instance's attached file", response = FileDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("{serialNumber}/pathdata/{pathDataId}/iterations/{iteration}/files/{fileName}") + public FileDTO renameAttachedFileInPathData(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String configurationItemId, + @PathParam("serialNumber") String serialNumber, + @PathParam("pathDataId") int pathDataId, + @PathParam("iteration") int iteration, + @PathParam("fileName") String fileName, + FileDTO fileDTO) throws UserNotActiveException, WorkspaceNotFoundException, CreationException, UserNotFoundException, FileNotFoundException, NotAllowedException, FileAlreadyExistsException, ProductInstanceMasterNotFoundException, AccessRightException, StorageException { + + String fullName = workspaceId + "/product-instances/" + serialNumber + "/pathdata/" + pathDataId + "/iterations/" + iteration + "/" + fileName; + BinaryResource binaryResource = productInstanceService.renameFileInPathData(workspaceId, configurationItemId, serialNumber, pathDataId, iteration, fullName, fileDTO.getShortName()); + return new FileDTO(true, binaryResource.getFullName(), binaryResource.getName()); + } + + @DELETE + @ApiOperation(value = "Delete product-instance's attached file", response = Response.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("{serialNumber}/pathdata/{pathDataId}/iterations/{iteration}/files/{fileName}") + public Response deleteAttachedFileInPathData(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String configurationItemId, + @PathParam("serialNumber") String serialNumber, + @PathParam("pathDataId") int pathDataId, + @PathParam("iteration") int iteration, + @PathParam("fileName") String fileName + ) throws UserNotActiveException, WorkspaceNotFoundException, CreationException, UserNotFoundException, FileNotFoundException, NotAllowedException, FileAlreadyExistsException, ProductInstanceMasterNotFoundException, AccessRightException { + + String fullName = workspaceId + "/product-instances/" + serialNumber + "/pathdata/" + pathDataId + "/iterations/" + iteration + "/" + fileName; + ProductInstanceMaster productInstanceMaster = productInstanceService.getProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, workspaceId, configurationItemId)); + productInstanceService.removeFileFromPathData(workspaceId, configurationItemId, serialNumber, pathDataId, iteration, fullName, productInstanceMaster); + return Response.ok().build(); + } + + @DELETE + @ApiOperation(value = "Delete product-instance's path-data", response = Response.class) + @Path("{serialNumber}/pathdata/{pathDataId}") + @Produces(MediaType.APPLICATION_JSON) + public Response deletePathData(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber, @PathParam("pathDataId") int pathDataId) throws UserNotActiveException, WorkspaceNotFoundException, UserNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException, NotAllowedException { + productInstanceService.deletePathData(workspaceId, configurationItemId, serialNumber, pathDataId); + return Response.ok().build(); + } + + @POST + @ApiOperation(value = "Add new path-data iteration", response = PathDataMasterDTO.class) + @Path("{serialNumber}/pathdata/{pathDataId}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public PathDataMasterDTO addNewPathDataIteration(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber, @PathParam("pathDataId") int pathDataId, PathDataIterationCreationDTO pathDataIterationCreationDTO) throws UserNotFoundException, AccessRightException, UserNotActiveException, ProductInstanceMasterNotFoundException, WorkspaceNotFoundException, NotAllowedException, PathDataAlreadyExistsException, FileAlreadyExistsException, CreationException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException, BaselineNotFoundException, PathDataMasterNotFoundException { + + InstanceAttributeFactory factory = new InstanceAttributeFactory(); + + List instanceAttributes = pathDataIterationCreationDTO.getInstanceAttributes(); + List attributes = new ArrayList<>(); + if (instanceAttributes != null) { + attributes = factory.createInstanceAttributes(instanceAttributes); + } + Set linkedDocs = pathDataIterationCreationDTO.getLinkedDocuments(); + DocumentRevisionKey[] links = null; + String[] documentLinkComments = null; + if (linkedDocs != null) { + documentLinkComments = new String[linkedDocs.size()]; + links = createDocumentRevisionKeys(linkedDocs); + int i = 0; + for (DocumentRevisionDTO docRevisionForLink : linkedDocs) { + String comment = docRevisionForLink.getCommentLink(); + if (comment == null) { + comment = ""; + } + documentLinkComments[i++] = comment; + } + } + + + PathDataMaster pathDataMaster = productInstanceService.addNewPathDataIteration(workspaceId, configurationItemId, serialNumber, pathDataId, attributes, pathDataIterationCreationDTO.getIterationNote(), links, documentLinkComments); + PathDataMasterDTO dto = mapper.map(pathDataMaster, PathDataMasterDTO.class); + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, configurationItemId); + + LightPartLinkListDTO partLinksList = new LightPartLinkListDTO(); + List path = productService.decodePath(ciKey, pathDataIterationCreationDTO.getPath()); + for (PartLink partLink : path) { + partLinksList.getPartLinks().add(new LightPartLinkDTO(partLink)); + } + dto.setPartLinksList(partLinksList); + + List attributesDTO = new ArrayList<>(); + PartLink partLink = path.get(path.size() - 1); + PSFilter filter = psFilterService.getPSFilter(ciKey, "pi-" + serialNumber, false); + List partIterations = filter.filter(partLink.getComponent()); + PartIteration partIteration = partIterations.get(0); + + if (partIteration != null) { + for (InstanceAttribute instanceAttribute : partIteration.getInstanceAttributes()) { + attributesDTO.add(mapper.map(instanceAttribute, InstanceAttributeDTO.class)); + } + dto.setPartAttributes(attributesDTO); + } + + return dto; + } + + @POST + @ApiOperation(value = "Create new path-data", response = PathDataMasterDTO.class) + @Path("{serialNumber}/pathdata/{path}/new") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public PathDataMasterDTO createPathDataMaster(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber, @PathParam("path") String pathAsString, PathDataIterationCreationDTO pathDataIterationCreationDTO) throws UserNotFoundException, AccessRightException, UserNotActiveException, ProductInstanceMasterNotFoundException, WorkspaceNotFoundException, NotAllowedException, PathDataAlreadyExistsException, FileAlreadyExistsException, CreationException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException { + + InstanceAttributeFactory factory = new InstanceAttributeFactory(); + + List instanceAttributes = pathDataIterationCreationDTO.getInstanceAttributes(); + List attributes = new ArrayList<>(); + if (instanceAttributes != null) { + attributes = factory.createInstanceAttributes(instanceAttributes); + } + + PathDataMaster pathDataMaster = productInstanceService.createPathDataMaster(workspaceId, configurationItemId, serialNumber, pathAsString, attributes, pathDataIterationCreationDTO.getIterationNote()); + + PathDataMasterDTO dto = mapper.map(pathDataMaster, PathDataMasterDTO.class); + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, configurationItemId); + + LightPartLinkListDTO partLinksList = new LightPartLinkListDTO(); + List path = productService.decodePath(ciKey, pathAsString); + for (PartLink partLink : path) { + partLinksList.getPartLinks().add(new LightPartLinkDTO(partLink)); + } + dto.setPartLinksList(partLinksList); + + return dto; + } + + @PUT + @ApiOperation(value = "Update path-data", response = PathDataMasterDTO.class) + @Path("{serialNumber}/pathdata/{pathDataId}/iterations/{iteration}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public PathDataMasterDTO updatePathData(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("iteration") int iteration, @PathParam("serialNumber") String serialNumber, @PathParam("pathDataId") int pathDataId, PathDataIterationCreationDTO pathDataIterationCreationDTO) throws UserNotFoundException, AccessRightException, UserNotActiveException, ProductInstanceMasterNotFoundException, WorkspaceNotFoundException, NotAllowedException, PathDataAlreadyExistsException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException { + + InstanceAttributeFactory factory = new InstanceAttributeFactory(); + + List instanceAttributes = pathDataIterationCreationDTO.getInstanceAttributes(); + List attributes = new ArrayList<>(); + if (instanceAttributes != null) { + attributes = factory.createInstanceAttributes(instanceAttributes); + } + + Set linkedDocs = pathDataIterationCreationDTO.getLinkedDocuments(); + DocumentRevisionKey[] links = null; + String[] documentLinkComments = null; + if (linkedDocs != null) { + documentLinkComments = new String[linkedDocs.size()]; + links = createDocumentRevisionKeys(linkedDocs); + int i = 0; + for (DocumentRevisionDTO docRevisionForLink : linkedDocs) { + String comment = docRevisionForLink.getCommentLink(); + if (comment == null) { + comment = ""; + } + documentLinkComments[i++] = comment; + } + } + + PathDataMaster pathDataMaster = productInstanceService.updatePathData(workspaceId, configurationItemId, serialNumber, pathDataIterationCreationDTO.getId(), iteration, attributes, pathDataIterationCreationDTO.getIterationNote(), links, documentLinkComments); + + + PathDataMasterDTO dto = mapper.map(pathDataMaster, PathDataMasterDTO.class); + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, configurationItemId); + + LightPartLinkListDTO partLinksList = new LightPartLinkListDTO(); + List path = productService.decodePath(ciKey, dto.getPath()); + for (PartLink partLink : path) { + partLinksList.getPartLinks().add(new LightPartLinkDTO(partLink)); + } + dto.setPartLinksList(partLinksList); + + return dto; + } + + @GET + @ApiOperation(value = "Get path-to-path link types", response = LightPathToPathLinkDTO.class, responseContainer = "List") + @Path("{serialNumber}/path-to-path-links-types") + @Produces(MediaType.APPLICATION_JSON) + public Response getPathToPathLinkTypes(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, ProductInstanceMasterNotFoundException { + List pathToPathLinkTypes = productInstanceService.getPathToPathLinkTypes(workspaceId, configurationItemId, serialNumber); + List pathToPathLinkDTOs = new ArrayList<>(); + for (String type : pathToPathLinkTypes) { + LightPathToPathLinkDTO pathToPathLinkDTO = new LightPathToPathLinkDTO(); + pathToPathLinkDTO.setType(type); + pathToPathLinkDTOs.add(pathToPathLinkDTO); + } + return Response.ok(new GenericEntity>((List) pathToPathLinkDTOs) { + }).build(); + } + + @GET + @ApiOperation(value = "Get part from path-to-path link", response = LightPartMasterDTO.class) + @Path("{serialNumber}/link-path-part/{pathPart}") + @Produces(MediaType.APPLICATION_JSON) + public LightPartMasterDTO getPartFromPathLink(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber, @PathParam("pathPart") String partPath) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, ProductInstanceMasterNotFoundException, PartUsageLinkNotFoundException, ConfigurationItemNotFoundException { + + PartMaster partMaster = productService.getPartMasterFromPath(workspaceId, configurationItemId, partPath); + LightPartMasterDTO lightPartMasterDTO = new LightPartMasterDTO(); + lightPartMasterDTO.setPartName(partMaster.getName()); + lightPartMasterDTO.setPartNumber(partMaster.getNumber()); + return lightPartMasterDTO; + + } + + @GET + @ApiOperation(value = "Get path-to-path links", response = LightPathToPathLinkDTO.class, responseContainer = "List") + @Path("{serialNumber}/path-to-path-links") + @Produces(MediaType.APPLICATION_JSON) + public Response getPathToPathLinks(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, ProductInstanceMasterNotFoundException { + List pathToPathLinkTypes = productInstanceService.getPathToPathLinks(workspaceId, configurationItemId, serialNumber); + List pathToPathLinkDTOs = new ArrayList<>(); + for (PathToPathLink pathToPathLink : pathToPathLinkTypes) { + pathToPathLinkDTOs.add(mapper.map(pathToPathLink, LightPathToPathLinkDTO.class)); + + } + return Response.ok(new GenericEntity>((List) pathToPathLinkDTOs) { + }).build(); + } + + + @GET + @ApiOperation(value = "Get path-to-path link", response = LightPathToPathLinkDTO.class) + @Path("{serialNumber}/path-to-path-links/{pathToPathLinkId}") + @Produces(MediaType.APPLICATION_JSON) + public LightPathToPathLinkDTO getPathToPathLink(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber, @PathParam("pathToPathLinkId") int pathToPathLinkId) throws UserNotActiveException, WorkspaceNotFoundException, UserNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException, PathToPathLinkNotFoundException { + PathToPathLink pathToPathLink = productInstanceService.getPathToPathLink(workspaceId, configurationItemId, serialNumber, pathToPathLinkId); + return mapper.map(pathToPathLink, LightPathToPathLinkDTO.class); + } + + @GET + @ApiOperation(value = "Get path-to-path link for given source and target", response = LightPathToPathLinkDTO.class, responseContainer = "List") + @Path("{serialNumber}/path-to-path-links/source/{sourcePath}/target/{targetPath}") + @Produces(MediaType.APPLICATION_JSON) + public Response getPathToPathLinksForGivenSourceAndTarget(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber, @PathParam("sourcePath") String sourcePathAsString, @PathParam("targetPath") String targetPathAsString) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, ProductInstanceMasterNotFoundException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException { + List pathToPathLinks = productInstanceService.getPathToPathLinkFromSourceAndTarget(workspaceId, configurationItemId, serialNumber, sourcePathAsString, targetPathAsString); + List dtos = new ArrayList<>(); + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, configurationItemId); + + for (PathToPathLink pathToPathLink : pathToPathLinks) { + PathToPathLinkDTO pathToPathLinkDTO = mapper.map(pathToPathLink, PathToPathLinkDTO.class); + List sourceLightPartLinkDTOs = new ArrayList<>(); + + List sourcePath = productService.decodePath(ciKey, pathToPathLink.getSourcePath()); + List targetPath = productService.decodePath(ciKey, pathToPathLink.getTargetPath()); + + for (PartLink partLink : sourcePath) { + LightPartLinkDTO lightPartLinkDTO = new LightPartLinkDTO(partLink); + sourceLightPartLinkDTOs.add(lightPartLinkDTO); + } + + List targetLightPartLinkDTOs = new ArrayList<>(); + for (PartLink partLink : targetPath) { + LightPartLinkDTO lightPartLinkDTO = new LightPartLinkDTO(partLink); + targetLightPartLinkDTOs.add(lightPartLinkDTO); + } + + pathToPathLinkDTO.setSourceComponents(sourceLightPartLinkDTOs); + pathToPathLinkDTO.setTargetComponents(targetLightPartLinkDTOs); + dtos.add(pathToPathLinkDTO); + } + + return Response.ok(new GenericEntity>((List) dtos) { + }).build(); + } + + @GET + @ApiOperation(value = "Get root path-to-path links", response = LightPathToPathLinkDTO.class, responseContainer = "List") + @Path("{serialNumber}/path-to-path-links-roots/{type}") + @Produces(MediaType.APPLICATION_JSON) + public Response getRootPathToPathLinks(@PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber, @PathParam("type") String type) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, ProductInstanceMasterNotFoundException { + List pathToPathLinks = productInstanceService.getRootPathToPathLinks(workspaceId, configurationItemId, serialNumber, type); + List dtos = new ArrayList<>(); + for (PathToPathLink pathToPathLink : pathToPathLinks) { + dtos.add(mapper.map(pathToPathLink, LightPathToPathLinkDTO.class)); + } + return Response.ok(new GenericEntity>((List) dtos) { + }).build(); + } + + @POST + @ApiOperation(value = "Import attribute into product-instance", response = Response.class) + @Path("import") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public Response importAttributes(@Context HttpServletRequest request, + @PathParam("workspaceId") String workspaceId, + @QueryParam("autoFreezeAfterUpdate") boolean autoFreezeAfterUpdate, + @QueryParam("permissiveUpdate") boolean permissiveUpdate, + @QueryParam("revisionNote") String revisionNote) + throws Exception { + + Collection parts = request.getParts(); + + if (parts.isEmpty() || parts.size() > 1) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + + Part part = parts.iterator().next(); + String name = FileIO.getFileNameWithoutExtension(part.getSubmittedFileName()); + String extension = FileIO.getExtension(part.getSubmittedFileName()); + + File importFile = Files.createTempFile("product-" + name, "-import.tmp" + (extension == null ? "" : "." + extension)).toFile(); + long length = BinaryResourceUpload.uploadBinary(new BufferedOutputStream(new FileOutputStream(importFile)), part); + importerService.importIntoPathData(workspaceId, importFile, name + "." + extension, revisionNote, autoFreezeAfterUpdate, permissiveUpdate); + + importFile.deleteOnExit(); + + return Response.noContent().build(); + + } + + + private DocumentRevisionKey[] createDocumentRevisionKeys(Set dtos) { + DocumentRevisionKey[] data = new DocumentRevisionKey[dtos.size()]; + int i = 0; + for (DocumentRevisionDTO dto : dtos) { + data[i++] = new DocumentRevisionKey(dto.getWorkspaceId(), dto.getDocumentMasterId(), dto.getVersion()); + } + return data; + } + + private List getPathToPathLinksForGivenProductInstance(ProductInstanceIteration productInstanceIteration) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, ProductInstanceMasterNotFoundException, PartUsageLinkNotFoundException, ConfigurationItemNotFoundException { + List pathToPathLinkTypes = productInstanceIteration.getPathToPathLinks(); + List pathToPathLinkDTOs = new ArrayList<>(); + + for (PathToPathLink pathToPathLink : pathToPathLinkTypes) { + PathToPathLinkDTO pathToPathLinkDTO = mapper.map(pathToPathLink, PathToPathLinkDTO.class); + List sourcePath = productService.decodePath(productInstanceIteration.getBasedOn().getConfigurationItem().getKey(), pathToPathLink.getSourcePath()); + List targetPath = productService.decodePath(productInstanceIteration.getBasedOn().getConfigurationItem().getKey(), pathToPathLink.getTargetPath()); + + List sourceLightPartLinkDTOs = new ArrayList<>(); + for (PartLink partLink : sourcePath) { + LightPartLinkDTO lightPartLinkDTO = new LightPartLinkDTO(partLink); + sourceLightPartLinkDTOs.add(lightPartLinkDTO); + } + + List targetLightPartLinkDTOs = new ArrayList<>(); + for (PartLink partLink : targetPath) { + LightPartLinkDTO lightPartLinkDTO = new LightPartLinkDTO(partLink); + targetLightPartLinkDTOs.add(lightPartLinkDTO); + } + + pathToPathLinkDTO.setSourceComponents(sourceLightPartLinkDTOs); + pathToPathLinkDTO.setTargetComponents(targetLightPartLinkDTOs); + pathToPathLinkDTOs.add(pathToPathLinkDTO); + } + return pathToPathLinkDTOs; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ProductResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ProductResource.java new file mode 100644 index 0000000000..7f4e6a7ad3 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/ProductResource.java @@ -0,0 +1,897 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.change.ModificationNotification; +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.CascadeResult; +import com.docdoku.core.configuration.PSFilter; +import com.docdoku.core.configuration.PathChoice; +import com.docdoku.core.configuration.ProductBaseline; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentIterationLink; +import com.docdoku.core.document.DocumentLink; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.product.*; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.ICascadeActionManagerLocal; +import com.docdoku.core.services.IPSFilterManagerLocal; +import com.docdoku.core.services.IProductBaselineManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.server.rest.collections.InstanceCollection; +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.rest.dto.baseline.BaselinedPartDTO; +import com.docdoku.server.rest.dto.baseline.PathChoiceDTO; +import com.docdoku.server.rest.util.FileDownloadTools; +import com.docdoku.server.rest.util.FileExportEntity; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.*; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Florent Garin + */ +@RequestScoped +@Api(hidden = true, value = "products", description = "Operations about products") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class ProductResource { + + private static final Logger LOGGER = Logger.getLogger(ProductResource.class.getName()); + @Inject + private IProductManagerLocal productService; + @Inject + private IProductBaselineManagerLocal productBaselineService; + @Inject + private ICascadeActionManagerLocal cascadeActionService; + @Inject + private IPSFilterManagerLocal psFilterService; + @Inject + private LayerResource layerResource; + @Inject + private ProductConfigurationsResource productConfigurationsResource; + @Inject + private ProductBaselinesResource productBaselinesResource; + @Inject + private ProductInstancesResource productInstancesResource; + private Mapper mapper; + + public ProductResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get configuration items", response = ConfigurationItemDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public ConfigurationItemDTO[] getConfigurationItems(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException, EntityConstraintException, NotAllowedException { + + String wksId = Tools.stripTrailingSlash(workspaceId); + List cis = productService.getConfigurationItems(wksId); + ConfigurationItemDTO[] dtos = new ConfigurationItemDTO[cis.size()]; + + for (int i = 0; i < cis.size(); i++) { + ConfigurationItem ci = cis.get(i); + dtos[i] = new ConfigurationItemDTO(mapper.map(ci.getAuthor(), UserDTO.class), ci.getId(), ci.getWorkspaceId(), + ci.getDescription(), ci.getDesignItem().getNumber(), ci.getDesignItem().getName(), ci.getDesignItem().getLastRevision().getVersion()); + dtos[i].setPathToPathLinks(getPathToPathLinksForGivenConfigurationItem(ci)); + // TODO : find a better way to detect modification notifications on products. Too heavy for big structures. + //dtos[i].setHasModificationNotification(productService.hasModificationNotification(ci.getKey())); + } + + return dtos; + } + + @POST + @ApiOperation(value = "Create configuration item", response = ConfigurationItemDTO.class) + @Produces(MediaType.APPLICATION_JSON) + public Response createConfigurationItem(@ApiParam(required = true, value = "Product to create") ConfigurationItemDTO configurationItemDTO) + throws EntityNotFoundException, EntityAlreadyExistsException, CreationException, AccessRightException, NotAllowedException { + + ConfigurationItem configurationItem = productService.createConfigurationItem(configurationItemDTO.getWorkspaceId(), configurationItemDTO.getId(), configurationItemDTO.getDescription(), configurationItemDTO.getDesignItemNumber()); + ConfigurationItemDTO configurationItemDTOCreated = mapper.map(configurationItem, ConfigurationItemDTO.class); + configurationItemDTOCreated.setDesignItemNumber(configurationItem.getDesignItem().getNumber()); + configurationItemDTOCreated.setDesignItemLatestVersion(configurationItem.getDesignItem().getLastRevision().getVersion()); + + try { + return Response.created(URI.create(URLEncoder.encode(configurationItemDTOCreated.getId(), "UTF-8"))).entity(configurationItemDTOCreated).build(); + } catch (UnsupportedEncodingException ex) { + LOGGER.log(Level.WARNING, null, ex); + return Response.ok().build(); + } + } + + + @GET + @ApiOperation(value = "Filter part", response = PartRevisionDTO.class, responseContainer = "List") + @Path("{ciId}/bom") + @Produces(MediaType.APPLICATION_JSON) + public PartRevisionDTO[] filterPart(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @QueryParam("configSpec") String configSpecType, + @QueryParam("path") String path, + @QueryParam("diverge") boolean diverge) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException, EntityConstraintException { + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + PSFilter filter = psFilterService.getPSFilter(ciKey, configSpecType, diverge); + List decodedPath = productService.decodePath(ciKey, path); + Component component = productService.filterProductStructure(ciKey, filter, decodedPath, 1); + + List components = component.getComponents(); + List partsRevisions = new ArrayList<>(); + for (int i = 0; i < components.size(); i++) { + PartIteration retainedIteration = components.get(i).getRetainedIteration(); + //If no iteration has been retained, then take the last revision (the first one). + PartRevision partRevision = retainedIteration == null ? components.get(i).getPartMaster().getLastRevision() : retainedIteration.getPartRevision(); + if (!productService.canAccess(partRevision.getKey())) { + continue; + } + PartRevisionDTO dto = mapper.map(partRevision, PartRevisionDTO.class); + dto.getPartIterations().clear(); + //specify the iteration only if an iteration has been retained. + if (retainedIteration != null) { + dto.getPartIterations().add(mapper.map(retainedIteration, PartIterationDTO.class)); + } + dto.setNumber(partRevision.getPartNumber()); + dto.setPartKey(partRevision.getPartNumber() + "-" + partRevision.getVersion()); + dto.setName(partRevision.getPartMaster().getName()); + dto.setStandardPart(partRevision.getPartMaster().isStandardPart()); + + List notificationDTOs = getModificationNotificationDTOs(partRevision); + dto.setNotifications(notificationDTOs); + partsRevisions.add(dto); + } + + return partsRevisions.toArray(new PartRevisionDTO[partsRevisions.size()]); + } + + @GET + @ApiOperation(value = "Filter product structure", response = ComponentDTO.class) + @Path("{ciId}/filter") + @Produces(MediaType.APPLICATION_JSON) + public ComponentDTO filterProductStructure(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @QueryParam("configSpec") String configSpecType, + @QueryParam("path") String path, + @QueryParam("depth") Integer depth, + @QueryParam("linkType") String linkType, + @QueryParam("diverge") boolean diverge) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException, EntityConstraintException { + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + PSFilter filter = psFilterService.getPSFilter(ciKey, configSpecType, diverge); + Component component; + + if (linkType == null) { + List decodedPath = productService.decodePath(ciKey, path); + component = productService.filterProductStructure(ciKey, filter, decodedPath, depth); + } else { + component = productService.filterProductStructureOnLinkType(ciKey, filter, configSpecType, path, linkType); + } + + if (component == null) { + throw new IllegalArgumentException(); + } + + String serialNumber = null; + if (configSpecType.startsWith("pi-")) { + serialNumber = configSpecType.substring(3); + } + + return createComponentDTO(component, workspaceId, ciId, serialNumber); + } + + @GET + @ApiOperation(value = "Get configuration item", response = ComponentDTO.class) + @Path("{ciId}") + @Produces(MediaType.APPLICATION_JSON) + public ConfigurationItemDTO getConfigurationItem(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException, EntityConstraintException { + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + ConfigurationItem ci = productService.getConfigurationItem(ciKey); + + ConfigurationItemDTO dto = new ConfigurationItemDTO(mapper.map(ci.getAuthor(), UserDTO.class), ci.getId(), ci.getWorkspaceId(), + ci.getDescription(), ci.getDesignItem().getNumber(), ci.getDesignItem().getName(), + ci.getDesignItem().getLastRevision().getVersion()); + dto.setPathToPathLinks(getPathToPathLinksForGivenConfigurationItem(ci)); + return dto; + } + + @DELETE + @ApiOperation(value = "Delete configuration item", response = ComponentDTO.class) + @Path("{ciId}") + @Produces(MediaType.APPLICATION_JSON) + public Response deleteConfigurationItem(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId) + throws EntityNotFoundException, NotAllowedException, AccessRightException, UserNotActiveException, EntityConstraintException { + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + productService.deleteConfigurationItem(ciKey); + return Response.ok().build(); + } + + @GET + @ApiOperation(value = "Search paths", response = PathDTO.class, responseContainer = "List") + @Path("{ciId}/paths") + @Produces(MediaType.APPLICATION_JSON) + public Response searchPaths(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @QueryParam("search") String search, + @QueryParam("configSpec") String configSpecType, + @QueryParam("diverge") boolean diverge) + throws EntityNotFoundException, UserNotActiveException, EntityConstraintException, NotAllowedException { + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + PSFilter filter = psFilterService.getPSFilter(ciKey, configSpecType, diverge); + List usagePaths = productService.findPartUsages(ciKey, filter, search); + + List pathsDTO = new ArrayList<>(); + + for (PartLink[] usagePath : usagePaths) { + String pathAsString = com.docdoku.core.util.Tools.getPathAsString(Arrays.asList(usagePath)); + pathsDTO.add(new PathDTO(pathAsString)); + } + + return Response.ok(new GenericEntity>((List) pathsDTO) { + }).build(); + } + + @ApiOperation(value = "SubResource : LayerResource") + @Path("{ciId}/layers") + public LayerResource processLayers(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId) { + return layerResource; + } + + @GET + @ApiOperation(value = "Get baseline creation path choices", response = PathChoiceDTO.class, responseContainer = "List") + @Path("{ciId}/path-choices") + @Produces(MediaType.APPLICATION_JSON) + public Response getBaselineCreationPathChoices(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @QueryParam("type") String pType) + throws ConfigurationItemNotFoundException, WorkspaceNotFoundException, UserNotActiveException, UserNotFoundException, PartMasterNotFoundException, NotAllowedException, EntityConstraintException { + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + + ProductBaseline.BaselineType type; + + if (pType == null || "LATEST".equals(pType)) { + type = ProductBaseline.BaselineType.LATEST; + } else if ("RELEASED".equals(pType)) { + type = ProductBaseline.BaselineType.RELEASED; + } else { + throw new IllegalArgumentException("Type must be either RELEASED or LATEST"); + } + + List choices = productBaselineService.getBaselineCreationPathChoices(ciKey, type); + + List pathChoiceDTOs = new ArrayList<>(); + + for (PathChoice choice : choices) { + pathChoiceDTOs.add(new PathChoiceDTO(choice)); + } + + return Response.ok(new GenericEntity>((List) pathChoiceDTOs) { + }).build(); + } + + @GET + @ApiOperation(value = "Get baseline creation version choices", response = BaselinedPartDTO.class, responseContainer = "List") + @Path("{ciId}/versions-choices") + @Produces(MediaType.APPLICATION_JSON) + public Response getBaselineCreationVersionsChoices(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId) + throws ConfigurationItemNotFoundException, WorkspaceNotFoundException, UserNotActiveException, UserNotFoundException, PartMasterNotFoundException, NotAllowedException, EntityConstraintException { + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + + List parts = productBaselineService.getBaselineCreationVersionsChoices(ciKey); + + // Serve the latest first + parts.sort(Comparator.reverseOrder()); + + Map> map = new HashMap<>(); + + for (PartIteration partIteration : parts) { + PartMaster partMaster = partIteration.getPartRevision().getPartMaster(); + if (map.get(partMaster) == null) { + map.put(partMaster, new ArrayList<>()); + } + map.get(partMaster).add(partIteration); + } + + List partsDTO = new ArrayList<>(); + + for (Map.Entry> entry : map.entrySet()) { + List availableParts = entry.getValue(); + if (availableParts.size() == 1 && !availableParts.get(0).getPartRevision().isReleased() || availableParts.size() > 1) { + partsDTO.add(new BaselinedPartDTO(availableParts)); + } + } + + return Response.ok(new GenericEntity>((List) partsDTO) { + }).build(); + } + + + @GET + @ApiOperation(value = "Get instances", response = Response.class) + @Path("{ciId}/instances") + @Produces(MediaType.APPLICATION_JSON) + public Response getInstances(@Context Request request, + @PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @QueryParam("configSpec") String configSpecType, + @QueryParam("path") String path, + @QueryParam("diverge") boolean diverge) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException { + Response.ResponseBuilder rb = fakeSimilarBehavior(request); + if (rb != null) { + return rb.build(); + } else { + CacheControl cc = new CacheControl(); + //this request is resources consuming so we cache the response for 30 minutes + cc.setMaxAge(60 * 15); + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + PSFilter filter = psFilterService.getPSFilter(ciKey, configSpecType, diverge); + List decodedPath = productService.decodePath(ciKey, path); + + List> paths = new ArrayList<>(); + + if (decodedPath != null) { + paths.add(decodedPath); + } + + InstanceCollection instanceCollection = new InstanceCollection(ciKey, filter, paths); + + return Response.ok().lastModified(new Date()).cacheControl(cc).entity(instanceCollection).build(); + } + } + + @POST + @ApiOperation(value = "Get instances for multiple paths", response = Response.class) + @Path("{ciId}/instances") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response getInstancesForMultiplePath(@Context Request request, + @PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @QueryParam("diverge") boolean diverge, + @ApiParam(required = true, value = "list of paths to filter") PathListDTO pathsDTO) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException { + + Response.ResponseBuilder rb = fakeSimilarBehavior(request); + if (rb != null) { + return rb.build(); + } else { + CacheControl cc = new CacheControl(); + //this request is resources consuming so we cache the response for 30 minutes + cc.setMaxAge(60 * 15); + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + + PSFilter filter = psFilterService.getPSFilter(ciKey, pathsDTO.getConfigSpec(), diverge); + + List> paths = new ArrayList<>(); + + for (String path : pathsDTO.getPaths()) { + List decodedPath = productService.decodePath(ciKey, path); + if (decodedPath != null) { + paths.add(decodedPath); + } + } + + InstanceCollection instanceCollection = new InstanceCollection(ciKey, filter, paths); + + return Response.ok().lastModified(new Date()).cacheControl(cc).entity(instanceCollection).build(); + } + } + + @GET + @ApiOperation(value = "Get last release of part", response = PartRevisionDTO.class) + @Path("{ciId}/releases/last") + @Produces(MediaType.APPLICATION_JSON) + public Response getLastRelease(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + + PartRevision partRevision = productService.getLastReleasePartRevision(ciKey); + PartRevisionDTO partRevisionDTO = mapper.map(partRevision, PartRevisionDTO.class); + partRevisionDTO.setNumber(partRevision.getPartNumber()); + partRevisionDTO.setPartKey(partRevision.getPartNumber() + "-" + partRevision.getVersion()); + partRevisionDTO.setName(partRevision.getPartMaster().getName()); + partRevisionDTO.setStandardPart(partRevision.getPartMaster().isStandardPart()); + + return Response.ok(partRevisionDTO).build(); + } + + @ApiOperation(value = "Get all ProductConfigurationsResource") + @Path("configurations") + public ProductConfigurationsResource getAllConfigurations(@PathParam("workspaceId") String workspaceId) { + return productConfigurationsResource; + } + + @ApiOperation(value = "Get ProductConfigurationsResource") + @Path("{ciId}/configurations") + public ProductConfigurationsResource getConfigurations(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId) { + return productConfigurationsResource; + } + + + // TODO : refactor with regex to merge 2 by 2 + + @ApiOperation(value = "Get ProductConfigurationsResource") + @Path("baselines") + public ProductBaselinesResource getAllBaselines(@PathParam("workspaceId") String workspaceId) { + return productBaselinesResource; + } + + @ApiOperation(value = "Get ProductBaselinesResource") + @Path("{ciId}/baselines") + public ProductBaselinesResource getBaselines(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId) { + return productBaselinesResource; + } + + @ApiOperation(value = "Get all ProductInstancesResource") + @Path("product-instances") + public ProductInstancesResource getAllProductInstances(@PathParam("workspaceId") String workspaceId) { + return productInstancesResource; + } + + @ApiOperation(value = "Get ProductInstancesResource") + @Path("{ciId}/product-instances") + public ProductInstancesResource getProductInstances(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId) { + return productInstancesResource; + } + // -- refactor with regex to merge 2 by 2 + + + @GET + @ApiOperation(value = "Export files", response = Response.class) + @Path("{ciId}/export-files") + public Response exportFiles(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @QueryParam("configSpecType") String configSpecType, + @QueryParam("exportNativeCADFiles") boolean exportNativeCADFiles, + @QueryParam("exportDocumentLinks") boolean exportDocumentLinks) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, BaselineNotFoundException, ProductInstanceMasterNotFoundException { + + if (configSpecType == null) { + configSpecType = "wip"; + } + + FileExportEntity fileExportEntity = new FileExportEntity(); + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + PSFilter psFilter = psFilterService.getPSFilter(ciKey, configSpecType, false); + + fileExportEntity.setPsFilter(psFilter); + fileExportEntity.setConfigurationItemKey(ciKey); + + fileExportEntity.setExportNativeCADFile(exportNativeCADFiles); + fileExportEntity.setExportDocumentLinks(exportDocumentLinks); + + if (configSpecType.startsWith("pi-")) { + String serialNumber = configSpecType.substring(3); + fileExportEntity.setSerialNumber(serialNumber); + fileExportEntity.setBaselineId(productService.loadProductBaselineForProductInstanceMaster(ciKey, serialNumber).getId()); + + } else if (!"wip".equals(configSpecType) && !"latest".equals(configSpecType) && !"released".equals(configSpecType)) { + try { + fileExportEntity.setBaselineId(Integer.parseInt(configSpecType)); + } catch (NumberFormatException e) { + LOGGER.log(Level.FINEST, null, e); + } + } + + String fileName = FileDownloadTools.getFileName(ciId + "-" + configSpecType + "-export", "zip"); + String contentDisposition = FileDownloadTools.getContentDisposition("attachment", fileName); + + return Response.ok() + .header("Content-Type", "application/download") + .header("Content-Disposition", contentDisposition) + .entity(fileExportEntity).build(); + } + + @POST + @ApiOperation(value = "Create path to path link", response = LightPathToPathLinkDTO.class) + @Path("{ciId}/path-to-path-links") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public LightPathToPathLinkDTO createPathToPathLink(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String configurationItemId, + @PathParam("serialNumber") String serialNumber, + @ApiParam(required = true, value = "Path to path link to create") LightPathToPathLinkDTO pathToPathLinkDTO) + throws PathToPathLinkAlreadyExistsException, UserNotActiveException, WorkspaceNotFoundException, CreationException, UserNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException, PathToPathCyclicException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException, NotAllowedException { + PathToPathLink pathToPathLink = productService.createPathToPathLink(workspaceId, configurationItemId, pathToPathLinkDTO.getType(), pathToPathLinkDTO.getSourcePath(), pathToPathLinkDTO.getTargetPath(), pathToPathLinkDTO.getDescription()); + return mapper.map(pathToPathLink, LightPathToPathLinkDTO.class); + } + + @PUT + @ApiOperation(value = "Update path to path link", response = LightPathToPathLinkDTO.class) + @Path("{ciId}/path-to-path-links/{pathToPathLinkId}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public LightPathToPathLinkDTO updatePathToPathLink(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String configurationItemId, + @PathParam("serialNumber") String serialNumber, + @PathParam("pathToPathLinkId") int pathToPathLinkId, + @ApiParam(required = true, value = "Path to path link to update") LightPathToPathLinkDTO pathToPathLinkDTO) + throws PathToPathLinkAlreadyExistsException, UserNotActiveException, WorkspaceNotFoundException, CreationException, UserNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException, PathToPathCyclicException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException, NotAllowedException, PathToPathLinkNotFoundException { + PathToPathLink pathToPathLink = productService.updatePathToPathLink(workspaceId, configurationItemId, pathToPathLinkId, pathToPathLinkDTO.getDescription()); + return mapper.map(pathToPathLink, LightPathToPathLinkDTO.class); + } + + @DELETE + @ApiOperation(value = "Delete path to path link", response = Response.class) + @Path("{ciId}/path-to-path-links/{pathToPathLinkId}") + public Response deletePathToPathLink(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String configurationItemId, + @PathParam("serialNumber") String serialNumber, + @PathParam("pathToPathLinkId") int pathToPathLinkId) + throws PathToPathLinkNotFoundException, UserNotActiveException, WorkspaceNotFoundException, UserNotFoundException, ProductInstanceMasterNotFoundException, AccessRightException, ConfigurationItemNotFoundException { + productService.deletePathToPathLink(workspaceId, configurationItemId, pathToPathLinkId); + return Response.ok().build(); + } + + @GET + @ApiOperation(value = "Get path to path links from source and target", response = Response.class) + @Path("{ciId}/path-to-path-links/source/{sourcePath}/target/{targetPath}") + @Produces(MediaType.APPLICATION_JSON) + public Response getPathToPathLinkFromSourceAndTarget(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String configurationItemId, + @PathParam("sourcePath") String sourcePathAsString, + @PathParam("targetPath") String targetPathAsString) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException { + List pathToPathLinks = productService.getPathToPathLinkFromSourceAndTarget(workspaceId, configurationItemId, sourcePathAsString, targetPathAsString); + List dtos = new ArrayList<>(); + + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, configurationItemId); + + for (PathToPathLink pathToPathLink : pathToPathLinks) { + PathToPathLinkDTO pathToPathLinkDTO = mapper.map(pathToPathLink, PathToPathLinkDTO.class); + List sourceLightPartLinkDTOs = new ArrayList<>(); + + List sourcePath = productService.decodePath(ciKey, pathToPathLink.getSourcePath()); + List targetPath = productService.decodePath(ciKey, pathToPathLink.getTargetPath()); + + for (PartLink partLink : sourcePath) { + LightPartLinkDTO lightPartLinkDTO = new LightPartLinkDTO(partLink); + sourceLightPartLinkDTOs.add(lightPartLinkDTO); + } + + List targetLightPartLinkDTOs = new ArrayList<>(); + for (PartLink partLink : targetPath) { + LightPartLinkDTO lightPartLinkDTO = new LightPartLinkDTO(partLink); + targetLightPartLinkDTOs.add(lightPartLinkDTO); + } + + pathToPathLinkDTO.setSourceComponents(sourceLightPartLinkDTOs); + pathToPathLinkDTO.setTargetComponents(targetLightPartLinkDTOs); + dtos.add(pathToPathLinkDTO); + } + return Response.ok(new GenericEntity>((List) dtos) { + }).build(); + } + + @GET + @ApiOperation(value = "Get path to path links types", response = LightPathToPathLinkDTO.class, responseContainer = "List") + @Path("{ciId}/path-to-path-links-types") + @Produces(MediaType.APPLICATION_JSON) + public Response getPathToPathLinkTypes(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String configurationItemId) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, ProductInstanceMasterNotFoundException, ConfigurationItemNotFoundException { + List pathToPathLinkTypes = productService.getPathToPathLinkTypes(workspaceId, configurationItemId); + List pathToPathLinkDTOs = new ArrayList<>(); + for (String type : pathToPathLinkTypes) { + LightPathToPathLinkDTO pathToPathLinkDTO = new LightPathToPathLinkDTO(); + pathToPathLinkDTO.setType(type); + pathToPathLinkDTOs.add(pathToPathLinkDTO); + } + return Response.ok(new GenericEntity>((List) pathToPathLinkDTOs) { + }).build(); + } + + @GET + @ApiOperation(value = "Decode string path", response = LightPartLinkDTO.class, responseContainer = "List") + @Path("{ciId}/decode-path/{path}") + @Produces(MediaType.APPLICATION_JSON) + public Response decodePath(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String configurationItemId, + @PathParam("path") String pathAsString) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, BaselineNotFoundException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException { + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, configurationItemId); + List path = productService.decodePath(ciKey, pathAsString); + List lightPartLinkDTOs = new ArrayList<>(); + for (PartLink partLink : path) { + LightPartLinkDTO lightPartLinkDTO = new LightPartLinkDTO(partLink); + lightPartLinkDTOs.add(lightPartLinkDTO); + } + return Response.ok(new GenericEntity>((List) lightPartLinkDTOs) { + }).build(); + } + + @GET + @ApiOperation(value = "Get document links for given part operation", response = DocumentIterationLinkDTO.class, responseContainer = "List") + @Path("{ciId}/document-links/{partNumber: [^/].*}-{partVersion:[A-Z]+}-{partIteration:[0-9]+}/{configSpec}") + @Produces(MediaType.APPLICATION_JSON) + public Response getDocumentLinksForGivenPartIteration(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String configurationItemId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion, + @PathParam("partIteration") int partIteration, + @PathParam("configSpec") String configSpec) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccessRightException, ProductInstanceMasterNotFoundException, BaselineNotFoundException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException, PartIterationNotFoundException { + + List dtos = new ArrayList<>(); + PartIterationKey partIterationKey = new PartIterationKey(workspaceId, partNumber, partVersion, partIteration); + List documentIterationLinkList = productService.getDocumentLinksAsDocumentIterations(workspaceId, configurationItemId, configSpec, partIterationKey); + for (DocumentIterationLink documentIterationLink : documentIterationLinkList) { + + DocumentIteration documentIteration = documentIterationLink.getDocumentIteration(); + DocumentLink documentLink = documentIterationLink.getDocumentLink(); + + DocumentIterationLinkDTO documentIterationLinkDTO = new DocumentIterationLinkDTO(); + documentIterationLinkDTO.setDocumentMasterId(documentIteration.getId()); + documentIterationLinkDTO.setVersion(documentIteration.getVersion()); + documentIterationLinkDTO.setTitle(documentIteration.getTitle()); + documentIterationLinkDTO.setIteration(documentIteration.getIteration()); + documentIterationLinkDTO.setWorkspaceId(documentIteration.getWorkspaceId()); + documentIterationLinkDTO.setCommentLink(documentLink.getComment()); + + dtos.add(documentIterationLinkDTO); + + } + return Response.ok(new GenericEntity>((List) dtos) { + }).build(); + } + + @PUT + @ApiOperation(value = "Cascade checkout", response = CascadeResult.class) + @Path("{ciId}/cascade-checkout") + @Produces(MediaType.APPLICATION_JSON) + public Response cascadeCheckout(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @QueryParam("configSpec") String configSpecType, + @QueryParam("path") String path) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ConfigurationItemNotFoundException, PartMasterNotFoundException, EntityConstraintException, NotAllowedException, PartUsageLinkNotFoundException { + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + CascadeResult cascadeResult = cascadeActionService.cascadeCheckout(ciKey, path); + return Response.ok(cascadeResult).build(); + } + + @PUT + @ApiOperation(value = "Cascade checkint", response = CascadeResult.class) + @Path("{ciId}/cascade-checkin") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response cascadeCheckin(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @QueryParam("configSpec") String configSpecType, + @QueryParam("path") String path, + @ApiParam(required = true, value = "Iteration note to add") IterationNoteDTO iterationNoteDTO) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ConfigurationItemNotFoundException, PartMasterNotFoundException, EntityConstraintException, NotAllowedException, PartUsageLinkNotFoundException { + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + CascadeResult cascadeResult = cascadeActionService.cascadeCheckin(ciKey, path, iterationNoteDTO.getIterationNote()); + return Response.ok(cascadeResult).build(); + } + + @PUT + @ApiOperation(value = "Cascade undo checkout", response = CascadeResult.class) + @Path("{ciId}/cascade-undocheckout") + @Produces(MediaType.APPLICATION_JSON) + public Response cascadeUndocheckout(@PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String ciId, + @QueryParam("configSpec") String configSpecType, + @QueryParam("path") String path) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ConfigurationItemNotFoundException, PartMasterNotFoundException, EntityConstraintException, NotAllowedException, PartUsageLinkNotFoundException { + ConfigurationItemKey ciKey = new ConfigurationItemKey(workspaceId, ciId); + CascadeResult cascadeResult = cascadeActionService.cascadeUndocheckout(ciKey, path); + return Response.ok(cascadeResult).build(); + } + + + /** + * Because some AS (like Glassfish) forbids the use of CacheControl + * when authenticated we use the LastModified header to fake + * a similar behavior (15 minutes of cache) + * + * @param request The incoming request + * @return Nothing if there still have cache + */ + private Response.ResponseBuilder fakeSimilarBehavior(Request request) { + Calendar cal = new GregorianCalendar(); + cal.add(Calendar.MINUTE, -15); + return request.evaluatePreconditions(cal.getTime()); + } + + private ComponentDTO createComponentDTO(Component component, String workspaceId, String configurationItemId, String serialNumber) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + + PartMaster pm = component.getPartMaster(); + PartIteration retainedIteration = component.getRetainedIteration(); + + if (retainedIteration == null) { + return null; + } + + PartRevision partR = retainedIteration.getPartRevision(); + + // Filter ACL on partR + if(!component.isVirtual() && !productService.canAccess(partR.getKey())){ + return null; + } + + List path = component.getPath(); + PartLink usageLink = path.get(path.size() - 1); + + ComponentDTO dto = new ComponentDTO(); + + dto.setPath(com.docdoku.core.util.Tools.getPathAsString(path)); + dto.setVirtual(component.isVirtual()); + dto.setNumber(pm.getNumber()); + dto.setPartUsageLinkId(usageLink.getFullId()); + dto.setName(pm.getName()); + dto.setStandardPart(pm.isStandardPart()); + dto.setAuthor(pm.getAuthor().getName()); + dto.setAuthorLogin(pm.getAuthor().getLogin()); + dto.setAmount(usageLink.getAmount()); + dto.setUnit(usageLink.getUnit()); + dto.setSubstitute(usageLink instanceof PartSubstituteLink); + dto.setVersion(retainedIteration.getVersion()); + dto.setIteration(retainedIteration.getIteration()); + dto.setReleased(partR.isReleased()); + dto.setObsolete(partR.isObsolete()); + dto.setDescription(partR.getDescription()); + dto.setPartUsageLinkReferenceDescription(usageLink.getReferenceDescription()); + dto.setOptional(usageLink.isOptional()); + + List substitutes = usageLink.getSubstitutes(); + if (substitutes != null) { + List substituteIds = new ArrayList<>(); + for (PartSubstituteLink substituteLink : substitutes) { + substituteIds.add(substituteLink.getFullId()); + } + dto.setSubstituteIds(substituteIds); + } + + List lstAttributes = new ArrayList<>(); + List components = new ArrayList<>(); + + User checkoutUser = partR.getCheckOutUser(); + if (checkoutUser != null) { + dto.setCheckOutUser(mapper.map(partR.getCheckOutUser(), UserDTO.class)); + dto.setCheckOutDate(partR.getCheckOutDate()); + } + + if (!component.isVirtual()) { + try { + productService.checkPartRevisionReadAccess(partR.getKey()); + dto.setAccessDeny(false); + dto.setLastIterationNumber(productService.getNumberOfIteration(partR.getKey())); + } catch (Exception e) { + LOGGER.log(Level.FINEST, null, e); + dto.setLastIterationNumber(-1); + dto.setAccessDeny(true); + } + } else { + dto.setAccessDeny(false); + } + + for (InstanceAttribute attr : retainedIteration.getInstanceAttributes()) { + lstAttributes.add(mapper.map(attr, InstanceAttributeDTO.class)); + } + + if (!component.isVirtual() && serialNumber != null) { + PathDataMasterDTO pathData = productInstancesResource.getPathData(workspaceId, configurationItemId, serialNumber, com.docdoku.core.util.Tools.getPathAsString(path)); + dto.setHasPathData(!pathData.getPathDataIterations().isEmpty()); + } + + for (Component subComponent : component.getComponents()) { + ComponentDTO componentDTO = createComponentDTO(subComponent, workspaceId, configurationItemId, serialNumber); + if (componentDTO != null) { + components.add(componentDTO); + } + } + + dto.setAssembly(retainedIteration.isAssembly()); + dto.setAttributes(lstAttributes); + + if (!component.isVirtual()) { + dto.setNotifications(getModificationNotificationDTOs(partR)); + } + + dto.setComponents(components); + + return dto; + } + + /** + * Return a list of ModificationNotificationDTO matching with a given PartRevision + * + * @param partRevision The specified PartRevision + * @return A list of ModificationNotificationDTO + * @throws EntityNotFoundException If an entity doesn't exist + * @throws AccessRightException If the user can not get the modification notifications + * @throws UserNotActiveException If the user is disabled + */ + private List getModificationNotificationDTOs(PartRevision partRevision) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + PartIterationKey iterationKey = new PartIterationKey(partRevision.getKey(), partRevision.getLastIterationNumber()); + List notifications = productService.getModificationNotifications(iterationKey); + return Tools.mapModificationNotificationsToModificationNotificationDTO(notifications); + } + + private List getPathToPathLinksForGivenConfigurationItem(ConfigurationItem configurationItem) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ConfigurationItemNotFoundException, PartUsageLinkNotFoundException { + List pathToPathLinkTypes = configurationItem.getPathToPathLinks(); + List pathToPathLinkDTOs = new ArrayList<>(); + + for (PathToPathLink pathToPathLink : pathToPathLinkTypes) { + PathToPathLinkDTO pathToPathLinkDTO = mapper.map(pathToPathLink, PathToPathLinkDTO.class); + List sourcePath = productService.decodePath(configurationItem.getKey(), pathToPathLink.getSourcePath()); + List targetPath = productService.decodePath(configurationItem.getKey(), pathToPathLink.getTargetPath()); + + List sourceLightPartLinkDTOs = new ArrayList<>(); + for (PartLink partLink : sourcePath) { + LightPartLinkDTO lightPartLinkDTO = new LightPartLinkDTO(partLink); + sourceLightPartLinkDTOs.add(lightPartLinkDTO); + } + + List targetLightPartLinkDTOs = new ArrayList<>(); + for (PartLink partLink : targetPath) { + LightPartLinkDTO lightPartLinkDTO = new LightPartLinkDTO(partLink); + targetLightPartLinkDTOs.add(lightPartLinkDTO); + } + + pathToPathLinkDTO.setSourceComponents(sourceLightPartLinkDTOs); + pathToPathLinkDTO.setTargetComponents(targetLightPartLinkDTOs); + pathToPathLinkDTOs.add(pathToPathLinkDTO); + } + return pathToPathLinkDTOs; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/RoleResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/RoleResource.java new file mode 100644 index 0000000000..f8669487f5 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/RoleResource.java @@ -0,0 +1,209 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + + +import com.docdoku.core.exceptions.*; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IWorkflowManagerLocal; +import com.docdoku.core.workflow.Role; +import com.docdoku.core.workflow.RoleKey; +import com.docdoku.server.rest.dto.RoleDTO; +import com.docdoku.server.rest.dto.UserDTO; +import com.docdoku.server.rest.dto.UserGroupDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Morgan Guimard + */ +@RequestScoped +@Api(hidden=true, value = "roles", description = "Operations about roles") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class RoleResource { + + private static final Logger LOGGER = Logger.getLogger(RoleResource.class.getName()); + + @Inject + private IWorkflowManagerLocal roleService; + + private Mapper mapper; + + public RoleResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(hidden = false, value = "Get roles", response = RoleDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public RoleDTO[] getRolesInWorkspace(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + + Role[] roles = roleService.getRoles(workspaceId); + RoleDTO[] rolesDTO = new RoleDTO[roles.length]; + + for (int i = 0; i < roles.length; i++) { + rolesDTO[i] = mapRoleToDTO(roles[i]); + } + + // TODO: return Response instead of RoleDTO[] + return rolesDTO; + } + + @GET + @ApiOperation(value = "Get roles in use", response = RoleDTO.class, responseContainer = "List") + @Path("inuse") + @Produces(MediaType.APPLICATION_JSON) + public RoleDTO[] getRolesInUseInWorkspace(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + + Role[] roles = roleService.getRolesInUse(workspaceId); + RoleDTO[] rolesDTO = new RoleDTO[roles.length]; + + for (int i = 0; i < roles.length; i++) { + rolesDTO[i] = mapRoleToDTO(roles[i]); + } + + // TODO: return Response instead of RoleDTO[] + return rolesDTO; + } + + + @POST + @ApiOperation(value = "Create role", response = RoleDTO.class) + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response createRole( + @PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Role to create") RoleDTO roleDTO) + throws EntityNotFoundException, EntityAlreadyExistsException, UserNotActiveException, AccessRightException, CreationException { + + List userDTOs = roleDTO.getDefaultAssignedUsers(); + List groupDTOs = roleDTO.getDefaultAssignedGroups(); + List userLogins = new ArrayList<>(); + List userGroupIds = new ArrayList<>(); + if (userDTOs != null) { + for(UserDTO userDTO:userDTOs){ + userLogins.add(userDTO.getLogin()); + } + } + if (groupDTOs != null) { + for(UserGroupDTO groupDTO:groupDTOs){ + userGroupIds.add(groupDTO.getId()); + } + } + + Role roleCreated = roleService.createRole(roleDTO.getName(), roleDTO.getWorkspaceId(), userLogins, userGroupIds); + RoleDTO roleCreatedDTO = mapRoleToDTO(roleCreated); + + try { + return Response.created(URI.create(URLEncoder.encode(roleCreatedDTO.getName(), "UTF-8"))).entity(roleCreatedDTO).build(); + } catch (UnsupportedEncodingException ex) { + LOGGER.log(Level.WARNING, null, ex); + return Response.ok().build(); + } + } + + @PUT + @ApiOperation(value = "Update role", response = RoleDTO.class) + @Path("{roleName}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response updateRole(@PathParam("workspaceId") String workspaceId, + @PathParam("roleName") String roleName, + @ApiParam(required = true, value = "Role to update") RoleDTO roleDTO) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + List userDTOs = roleDTO.getDefaultAssignedUsers(); + List groupDTOs = roleDTO.getDefaultAssignedGroups(); + List userLogins = new ArrayList<>(); + List userGroupIds = new ArrayList<>(); + if (userDTOs != null) { + for(UserDTO userDTO:userDTOs){ + userLogins.add(userDTO.getLogin()); + } + } + if (groupDTOs != null) { + for(UserGroupDTO groupDTO:groupDTOs){ + userGroupIds.add(groupDTO.getId()); + } + } + + Role roleUpdated = roleService.updateRole(new RoleKey(roleDTO.getWorkspaceId(), roleName), userLogins, userGroupIds); + RoleDTO roleUpdatedDTO = mapRoleToDTO(roleUpdated); + try { + return Response.created(URI.create(URLEncoder.encode(roleUpdatedDTO.getName(), "UTF-8"))).entity(roleUpdatedDTO).build(); + } catch (UnsupportedEncodingException ex) { + LOGGER.log(Level.WARNING, null, ex); + return Response.ok().build(); + } + } + + @DELETE + @ApiOperation(value = "Delete role", response = Response.class) + @Path("{roleName}") + @Produces(MediaType.APPLICATION_JSON) + public Response deleteRole(@PathParam("workspaceId") String workspaceId, + @PathParam("roleName") String roleName) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, EntityConstraintException { + + RoleKey roleKey = new RoleKey(workspaceId, roleName); + roleService.deleteRole(roleKey); + return Response.ok().build(); + } + + + private RoleDTO mapRoleToDTO(Role role) { + RoleDTO roleDTO = mapper.map(role, RoleDTO.class); + roleDTO.setWorkspaceId(role.getWorkspace().getId()); +// +// if (role.getDefaultAssignee() != null) { +// roleDTO.setDefaultAssignee(mapper.map(role.getDefaultAssignee(), UserDTO.class)); +// } + roleDTO.setId(role.getName()); + + return roleDTO; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/SharedResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/SharedResource.java new file mode 100644 index 0000000000..b9f4d0d145 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/SharedResource.java @@ -0,0 +1,194 @@ +package com.docdoku.server.rest; + +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.document.DocumentRevisionKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.product.PartRevisionKey; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.core.services.IShareManagerLocal; +import com.docdoku.core.sharing.SharedDocument; +import com.docdoku.core.sharing.SharedEntity; +import com.docdoku.core.sharing.SharedPart; +import com.docdoku.server.filters.GuestProxy; +import com.docdoku.server.rest.dto.DocumentRevisionDTO; +import com.docdoku.server.rest.dto.PartRevisionDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.security.MessageDigest; +import java.util.Date; + +@RequestScoped +@Api(value = "shared", description = "Operations about shared entities") +@Path("shared") +public class SharedResource { + + @Inject + private GuestProxy guestProxy; + + @Inject + private IDocumentManagerLocal documentManager; + + @Inject + private IProductManagerLocal productManager; + + @Inject + private IShareManagerLocal shareManager; + + private Mapper mapper; + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @Path("{workspaceId}/documents/{documentId}-{documentVersion}") + @ApiOperation(value = "Get document revision", response = DocumentRevisionDTO.class) + @Produces(MediaType.APPLICATION_JSON) + public Response getPublicSharedDocumentRevision(@PathParam("workspaceId") String workspaceId, + @PathParam("documentId") String documentId, + @PathParam("documentVersion") String documentVersion) + throws AccessRightException, NotAllowedException, WorkspaceNotFoundException, UserNotFoundException, + DocumentRevisionNotFoundException, UserNotActiveException { + + // Tries public + DocumentRevisionKey docKey = new DocumentRevisionKey(workspaceId, documentId, documentVersion); + DocumentRevision documentRevision = guestProxy.getPublicDocumentRevision(docKey); + + if(documentRevision == null) { + // Ties authenticated + documentRevision = documentManager.getDocumentRevision(docKey); + } + + DocumentRevisionDTO documentRevisionDTO = mapper.map(documentRevision, DocumentRevisionDTO.class); + documentRevisionDTO.setRoutePath(documentRevision.getLocation().getRoutePath()); + + if(documentRevision != null){ + return Response.ok().entity(documentRevisionDTO).build(); + }else{ + return Response.status(Response.Status.FORBIDDEN).build(); + } + + } + + @GET + @Path("{workspaceId}/parts/{partNumber}-{partVersion}") + @ApiOperation(value = "Get part revision", response = PartRevisionDTO.class) + @Produces(MediaType.APPLICATION_JSON) + public Response getPublicSharedPartRevision(@PathParam("workspaceId") String workspaceId, + @PathParam("partNumber") String partNumber, + @PathParam("partVersion") String partVersion) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, PartRevisionNotFoundException, AccessRightException { + + // Tries public + PartRevisionKey partKey = new PartRevisionKey(workspaceId, partNumber, partVersion); + PartRevision partRevision = guestProxy.getPublicPartRevision(partKey); + + if(partRevision == null) { + // Ties authenticated + partRevision = productManager.getPartRevision(partKey); + } + + if(partRevision != null){ + return Response.ok().entity(Tools.mapPartRevisionToPartDTO(partRevision)).build(); + }else{ + return Response.status(Response.Status.FORBIDDEN).build(); + } + + } + + @GET + @Path("{uuid}/documents") + @ApiOperation(value = "Get shared document", response = DocumentRevisionDTO.class) + @Produces(MediaType.APPLICATION_JSON) + public Response getDocumentWithSharedEntity(@HeaderParam("password") String password ,@PathParam("uuid") String uuid) throws SharedEntityNotFoundException { + + SharedEntity sharedEntity = shareManager.findSharedEntityForGivenUUID(uuid); + + // check if expire - delete it - send 404 + if(sharedEntity.getExpireDate() != null && sharedEntity.getExpireDate().getTime() < new Date().getTime()){ + shareManager.deleteSharedEntityIfExpired(sharedEntity); + return createExpiredEntityResponse(); + } + + if(!checkPasswordAccess(sharedEntity.getPassword(), password)){ + return createPasswordProtectedResponse(); + } + + DocumentRevision documentRevision = ((SharedDocument) sharedEntity).getDocumentRevision(); + return Response.ok().entity(mapper.map(documentRevision, DocumentRevisionDTO.class)).build(); + + } + + @GET + @Path("{uuid}/parts") + @ApiOperation(value = "Get shared part", response = PartRevisionDTO.class) + @Produces(MediaType.APPLICATION_JSON) + public Response getPartWithSharedEntity(@HeaderParam("password") String password, @PathParam("uuid") String uuid) throws SharedEntityNotFoundException { + + SharedEntity sharedEntity = shareManager.findSharedEntityForGivenUUID(uuid); + + // check if expire - delete it - send 404 + if(sharedEntity.getExpireDate() != null && sharedEntity.getExpireDate().getTime() < new Date().getTime()){ + shareManager.deleteSharedEntityIfExpired(sharedEntity); + return createExpiredEntityResponse(); + } + + if(!checkPasswordAccess(sharedEntity.getPassword(), password)){ + return createPasswordProtectedResponse(); + } + + PartRevision partRevision = ((SharedPart) sharedEntity).getPartRevision(); + return Response.ok().entity(Tools.mapPartRevisionToPartDTO(partRevision)).build(); + + } + + private boolean checkPasswordAccess(String entityPassword, String password) { + return entityPassword == null || entityPassword.isEmpty() || entityPassword.equals(md5Sum(password)); + } + + private Response createPasswordProtectedResponse() { + return Response.status(Response.Status.FORBIDDEN) + .header("Reason-Phrase", "password-protected") + .entity("{\"forbidden\":\"password-protected\"}") + .build(); + } + + private Response createExpiredEntityResponse() { + return Response.status(Response.Status.NOT_FOUND).header("Reason-Phrase", "entity-expired") + .header("Reason-Phrase", "entity-expired") + .entity("{\"forbidden\":\"entity-expired\"}") + .build(); + } + + private String md5Sum(String pText) { + byte[] digest; + try { + digest = MessageDigest.getInstance("MD5").digest(pText.getBytes()); + } catch(Exception e){ + return null; + } + StringBuffer hexString = new StringBuffer(); + for (byte aDigest : digest) { + String hex = Integer.toHexString(0xFF & aDigest); + if (hex.length() == 1) { + hexString.append("0" + hex); + } else { + hexString.append(hex); + } + } + return hexString.toString(); + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/TagResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/TagResource.java new file mode 100644 index 0000000000..792972cae4 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/TagResource.java @@ -0,0 +1,247 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.configuration.DocumentConfigSpec; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.meta.TagKey; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.ACLUserEntry; +import com.docdoku.core.security.ACLUserGroupEntry; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDocumentConfigSpecManagerLocal; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.rest.util.ConfigSpecHelper; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Yassine Belouad + */ +@RequestScoped +@Api(hidden = true, value = "tags", description = "Operations about tags") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class TagResource { + + private final static Logger LOGGER = Logger.getLogger(TagResource.class.getName()); + @Inject + private IDocumentManagerLocal documentService; + @Inject + private IDocumentConfigSpecManagerLocal documentConfigSpecService; + private Mapper mapper; + + public TagResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get tags in workspace", response = TagDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getTagsInWorkspace(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + + String[] tagsName = documentService.getTags(workspaceId); + List tagsDTO = new ArrayList<>(); + for (String tagName : tagsName) { + tagsDTO.add(new TagDTO(tagName, workspaceId)); + } + return Response.ok(new GenericEntity>((List) tagsDTO) { + }).build(); + } + + @POST + @ApiOperation(value = "Create tag in workspace", response = TagDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public TagDTO createTag(@PathParam("workspaceId") String workspaceId, + @ApiParam(value = "Tag to create", required = true) TagDTO tag) + throws EntityNotFoundException, EntityAlreadyExistsException, UserNotActiveException, AccessRightException, CreationException { + + documentService.createTag(workspaceId, tag.getLabel()); + return new TagDTO(tag.getLabel()); + } + + @POST + @ApiOperation(value = "Create tags in workspace", response = Response.class) + @Path("/multiple") + @Consumes(MediaType.APPLICATION_JSON) + public Response createTags(@PathParam("workspaceId") String workspaceId, + @ApiParam(value = "Tag list to create", required = true) TagListDTO tagList) + throws EntityNotFoundException, EntityAlreadyExistsException, UserNotActiveException, AccessRightException, CreationException { + + for (TagDTO tagDTO : tagList.getTags()) { + documentService.createTag(workspaceId, tagDTO.getLabel()); + } + return Response.ok().build(); + } + + @DELETE + @ApiOperation(value = "Delete tag in workspace", response = Response.class) + @Path("{tagId}") + public Response deleteTag(@PathParam("workspaceId") String workspaceId, + @PathParam("tagId") String tagId) + throws EntityNotFoundException, AccessRightException { + + documentService.deleteTag(new TagKey(workspaceId, tagId)); + return Response.ok().build(); + } + + @GET + @ApiOperation(value = "Get documents from given tag id", response = DocumentRevisionDTO.class, responseContainer = "List") + @Path("{tagId}/documents/") + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO[] getDocumentsWithGivenTagIdAndWorkspaceId( + @PathParam("workspaceId") String workspaceId, + @PathParam("tagId") String tagId, + @QueryParam("configSpec") String configSpecType) + throws EntityNotFoundException, UserNotActiveException { + + DocumentRevision[] docRs; + TagKey tagKey = new TagKey(workspaceId, tagId); + if (configSpecType == null || ConfigSpecHelper.BASELINE_UNDEFINED.equals(configSpecType) || ConfigSpecHelper.BASELINE_LATEST.equals(configSpecType)) { + docRs = documentService.findDocumentRevisionsByTag(tagKey); + } else { + DocumentConfigSpec configSpec = ConfigSpecHelper.getConfigSpec(workspaceId, configSpecType, documentConfigSpecService); + docRs = documentConfigSpecService.getFilteredDocumentsByTag(workspaceId, configSpec, tagKey); + } + DocumentRevisionDTO[] docRsDTOs = new DocumentRevisionDTO[docRs.length]; + + for (int i = 0; i < docRs.length; i++) { + docRsDTOs[i] = mapper.map(docRs[i], DocumentRevisionDTO.class); + docRsDTOs[i].setPath(docRs[i].getLocation().getCompletePath()); + docRsDTOs[i] = Tools.createLightDocumentRevisionDTO(docRsDTOs[i]); + if (configSpecType == null || ConfigSpecHelper.BASELINE_UNDEFINED.equals(configSpecType) || ConfigSpecHelper.BASELINE_LATEST.equals(configSpecType)) { + docRsDTOs[i].setLifeCycleState(docRs[i].getLifeCycleState()); + docRsDTOs[i].setIterationSubscription(documentService.isUserIterationChangeEventSubscribedForGivenDocument(workspaceId, docRs[i])); + docRsDTOs[i].setStateSubscription(documentService.isUserStateChangeEventSubscribedForGivenDocument(workspaceId, docRs[i])); + } else { + docRsDTOs[i].setWorkflow(null); + docRsDTOs[i].setTags(null); + } + } + + return docRsDTOs; + } + + @POST + @Path("{tagId}/documents/") + @ApiOperation(value = "Create document", response = Response.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response createDocumentMasterInRootFolderWithTag( + @PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Document to create") DocumentCreationDTO docCreationDTO, + @PathParam("tagId") String tagId, + @QueryParam("configSpec") String configSpecType) + throws CreationException, FileAlreadyExistsException, DocumentRevisionAlreadyExistsException, WorkspaceNotFoundException, UserNotFoundException, NotAllowedException, DocumentMasterAlreadyExistsException, RoleNotFoundException, FolderNotFoundException, WorkflowModelNotFoundException, AccessRightException, DocumentMasterTemplateNotFoundException, DocumentRevisionNotFoundException, UserNotActiveException, ESServerException, UserGroupNotFoundException { + + String pDocMID = docCreationDTO.getReference(); + String pTitle = docCreationDTO.getTitle(); + String pDescription = docCreationDTO.getDescription(); + + String decodedCompletePath = getPathFromUrlParams(workspaceId, workspaceId); + + String pWorkflowModelId = docCreationDTO.getWorkflowModelId(); + RoleMappingDTO[] roleMappingDTOs = docCreationDTO.getRoleMapping(); + String pDocMTemplateId = docCreationDTO.getTemplateId(); + + ACLDTO acl = docCreationDTO.getAcl(); + + ACLUserEntry[] userEntries = null; + ACLUserGroupEntry[] userGroupEntries = null; + if (acl != null) { + userEntries = new ACLUserEntry[acl.getUserEntries().size()]; + userGroupEntries = new ACLUserGroupEntry[acl.getGroupEntries().size()]; + int i = 0; + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries[i] = new ACLUserEntry(); + userEntries[i].setPrincipal(new User(new Workspace(workspaceId), new Account(entry.getKey()))); + userEntries[i++].setPermission(ACL.Permission.valueOf(entry.getValue().name())); + } + i = 0; + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + userGroupEntries[i] = new ACLUserGroupEntry(); + userGroupEntries[i].setPrincipal(new UserGroup(new Workspace(workspaceId), entry.getKey())); + userGroupEntries[i++].setPermission(ACL.Permission.valueOf(entry.getValue().name())); + } + } + + Map> userRoleMapping = new HashMap<>(); + Map> groupRoleMapping = new HashMap<>(); + + if (roleMappingDTOs != null) { + for (RoleMappingDTO roleMappingDTO : roleMappingDTOs) { + userRoleMapping.put(roleMappingDTO.getRoleName(), roleMappingDTO.getUserLogins()); + groupRoleMapping.put(roleMappingDTO.getRoleName(), roleMappingDTO.getGroupIds()); + } + } + + + DocumentRevision createdDocRs = documentService.createDocumentMaster(decodedCompletePath, pDocMID, pTitle, pDescription, pDocMTemplateId, pWorkflowModelId, userEntries, userGroupEntries, userRoleMapping, groupRoleMapping); + documentService.saveTags(createdDocRs.getKey(), new String[]{tagId}); + + DocumentRevisionDTO docRsDTO = mapper.map(createdDocRs, DocumentRevisionDTO.class); + docRsDTO.setPath(createdDocRs.getLocation().getCompletePath()); + docRsDTO.setLifeCycleState(createdDocRs.getLifeCycleState()); + + try { + return Response.created(URI.create(URLEncoder.encode(pDocMID + "-" + createdDocRs.getVersion(), "UTF-8"))).entity(docRsDTO).build(); + } catch (UnsupportedEncodingException ex) { + LOGGER.log(Level.WARNING, null, ex); + return Response.ok().build(); + } + } + + private String getPathFromUrlParams(String workspaceId, String folderId) { + return folderId == null ? Tools.stripTrailingSlash(workspaceId) : Tools.stripTrailingSlash(FolderDTO.replaceColonWithSlash(folderId)); + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/TaskResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/TaskResource.java new file mode 100644 index 0000000000..913daf8cf4 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/TaskResource.java @@ -0,0 +1,177 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.core.services.ITaskManagerLocal; +import com.docdoku.core.workflow.ActivityKey; +import com.docdoku.core.workflow.TaskKey; +import com.docdoku.core.workflow.TaskWrapper; +import com.docdoku.server.rest.dto.DocumentRevisionDTO; +import com.docdoku.server.rest.dto.TaskDTO; +import com.docdoku.server.rest.dto.TaskProcessDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Morgan Guimard + */ + +@RequestScoped +@Api(hidden = true, value = "tasks", description = "Operations about tasks") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class TaskResource { + + @Inject + private IDocumentManagerLocal documentService; + + @Inject + private ITaskManagerLocal taskManager; + + private Mapper mapper; + + public TaskResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + + @GET + @ApiOperation(value = "Get assigned tasks for given user", response = TaskDTO.class, responseContainer = "List") + @Path("{assignedUserLogin}/assigned") + @Produces(MediaType.APPLICATION_JSON) + public TaskDTO[] getAssignedTasksForGivenUser( + @PathParam("workspaceId") String workspaceId, + @PathParam("assignedUserLogin") String assignedUserLogin) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + TaskWrapper[] runningTasksForGivenUser = taskManager.getAssignedTasksForGivenUser(workspaceId, assignedUserLogin); + List taskDTOs = new ArrayList<>(); + for(TaskWrapper taskWrapper:runningTasksForGivenUser){ + TaskDTO taskDTO = mapper.map(taskWrapper.getTask(), TaskDTO.class); + taskDTO.setHolderType(taskWrapper.getHolderType()); + taskDTO.setWorkspaceId(workspaceId); + taskDTO.setHolderReference(taskWrapper.getHolderReference()); + taskDTO.setHolderVersion(taskWrapper.getHolderVersion()); + taskDTOs.add(taskDTO); + } + return taskDTOs.toArray(new TaskDTO[taskDTOs.size()]); + } + + @GET + @ApiOperation(value = "Get task", response = TaskDTO.class) + @Path("{taskId}") + @Produces(MediaType.APPLICATION_JSON) + public TaskDTO getTask( + @PathParam("workspaceId") String workspaceId, + @PathParam("taskId") String taskId) throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException, TaskNotFoundException, AccessRightException { + + String[] split = taskId.split("-"); + + int workflowId = Integer.valueOf(split[0]); + int step = Integer.valueOf(split[1]); + int task = Integer.valueOf(split[2]); + + TaskKey taskKey = new TaskKey(new ActivityKey(workflowId, step), task); + TaskWrapper taskWrapper = taskManager.getTask(workspaceId, taskKey); + + TaskDTO taskDTO = mapper.map(taskWrapper.getTask(), TaskDTO.class); + taskDTO.setHolderType(taskWrapper.getHolderType()); + taskDTO.setWorkspaceId(workspaceId); + taskDTO.setHolderReference(taskWrapper.getHolderReference()); + taskDTO.setHolderVersion(taskWrapper.getHolderVersion()); + + return taskDTO; + } + + @GET + @ApiOperation(value = "Get documents where user has assigned tasks", response = DocumentRevisionDTO.class, responseContainer = "List") + @Path("{assignedUserLogin}/documents") + @Produces(MediaType.APPLICATION_JSON) + public DocumentRevisionDTO[] getDocumentsWhereGivenUserHasAssignedTasks( + @PathParam("workspaceId") String workspaceId, + @PathParam("assignedUserLogin") String assignedUserLogin, + @QueryParam("filter") String filter) + throws EntityNotFoundException, UserNotActiveException { + + DocumentRevision[] docRs; + + if ("in_progress".equals(filter)) { + docRs = documentService.getDocumentRevisionsWithOpenedTasksForGivenUser(workspaceId, assignedUserLogin); + } else { + docRs = documentService.getDocumentRevisionsWithAssignedTasksForGivenUser(workspaceId, assignedUserLogin); + } + + List docRsDTOs = new ArrayList<>(); + + for (DocumentRevision docR : docRs) { + + DocumentRevisionDTO docDTO = mapper.map(docR, DocumentRevisionDTO.class); + docDTO.setPath(docR.getLocation().getCompletePath()); + docDTO = Tools.createLightDocumentRevisionDTO(docDTO); + docDTO.setIterationSubscription(documentService.isUserIterationChangeEventSubscribedForGivenDocument(workspaceId, docR)); + docDTO.setStateSubscription(documentService.isUserStateChangeEventSubscribedForGivenDocument(workspaceId, docR)); + docRsDTOs.add(docDTO); + + } + + return docRsDTOs.toArray(new DocumentRevisionDTO[docRsDTOs.size()]); + } + + + @PUT + @ApiOperation(value = "Approve or reject task on document", response = Response.class) + @Path("{taskId}/process") + @Consumes(MediaType.APPLICATION_JSON) + public Response processTask(@PathParam("workspaceId") String workspaceId, + @PathParam("taskId") String taskId, + @ApiParam(required = true, value = "Task process data") TaskProcessDTO taskProcessDTO) + throws EntityNotFoundException, NotAllowedException, UserNotActiveException, AccessRightException { + + String[] split = taskId.split("-"); + int workflowId = Integer.valueOf(split[0]); + int step = Integer.valueOf(split[1]); + int index = Integer.valueOf(split[2]); + + taskManager.processTask(workspaceId, new TaskKey(new ActivityKey(workflowId,step),index), taskProcessDTO.getAction().name(), taskProcessDTO.getComment(), taskProcessDTO.getSignature()); + return Response.ok().build(); + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/TimeZoneResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/TimeZoneResource.java new file mode 100644 index 0000000000..6de18cf90e --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/TimeZoneResource.java @@ -0,0 +1,69 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.security.UserGroupMapping; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.TimeZone; + +@RequestScoped +@Path("timezones") +@Api(value = "timezone", description = "Operations about timezones") +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID,UserGroupMapping.ADMIN_ROLE_ID}) +@RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID,UserGroupMapping.ADMIN_ROLE_ID}) +public class TimeZoneResource { + + private Mapper mapper; + + public TimeZoneResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get timezones", response = String.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getTimeZones() { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + for(String timeZone : TimeZone.getAvailableIDs()){ + arrayBuilder.add(timeZone); + } + return Response.ok().entity(arrayBuilder.build()).build(); + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/Tools.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/Tools.java new file mode 100644 index 0000000000..c65603f1d4 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/Tools.java @@ -0,0 +1,264 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest; + +import com.docdoku.core.change.ModificationNotification; +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; +import com.docdoku.core.configuration.BaselinedFolder; +import com.docdoku.core.configuration.BaselinedPart; +import com.docdoku.core.configuration.DocumentBaseline; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.product.*; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.ACLUserEntry; +import com.docdoku.core.security.ACLUserGroupEntry; +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.rest.dto.baseline.BaselinedDocumentDTO; +import com.docdoku.server.rest.dto.baseline.BaselinedPartDTO; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * @author Florent Garin + */ +public class Tools { + + private Tools() { + + } + + public static String stripTrailingSlash(String completePath) { + if (completePath.charAt(completePath.length() - 1) == '/') { + return completePath.substring(0, completePath.length() - 1); + } else { + return completePath; + } + } + + public static String stripLeadingSlash(String completePath) { + if (completePath.charAt(0) == '/') { + return completePath.substring(1, completePath.length()); + } else { + return completePath; + } + } + + public static DocumentRevisionDTO createLightDocumentRevisionDTO(DocumentRevisionDTO docRsDTO) { + + if (docRsDTO.getLastIteration() != null) { + DocumentIterationDTO lastIteration = docRsDTO.getLastIteration(); + List iterations = new ArrayList<>(); + iterations.add(lastIteration); + docRsDTO.setDocumentIterations(iterations); + } + + docRsDTO.setTags(null); + docRsDTO.setWorkflow(null); + + return docRsDTO; + } + + + public static ACLDTO mapACLtoACLDTO(ACL acl) { + + ACLDTO aclDTO = new ACLDTO(); + + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + ACLUserEntry aclEntry = entry.getValue(); + aclDTO.addUserEntry(aclEntry.getPrincipalLogin(), aclEntry.getPermission()); + } + + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + ACLUserGroupEntry aclEntry = entry.getValue(); + aclDTO.addGroupEntry(aclEntry.getPrincipalId(), aclEntry.getPermission()); + } + + return aclDTO; + + } + + public static List mapModificationNotificationsToModificationNotificationDTO(Collection pNotifications) { + List dtos = new ArrayList<>(); + for (ModificationNotification notification : pNotifications) { + dtos.add(mapModificationNotificationToModificationNotificationDTO(notification)); + } + return dtos; + } + + public static ModificationNotificationDTO mapModificationNotificationToModificationNotificationDTO(ModificationNotification pNotification) { + ModificationNotificationDTO dto = new ModificationNotificationDTO(); + Mapper mapper = DozerBeanMapperSingletonWrapper.getInstance(); + + UserDTO userDTO = mapper.map(pNotification.getModifiedPart().getAuthor(), UserDTO.class); + dto.setAuthor(userDTO); + + dto.setId(pNotification.getId()); + + dto.setImpactedPartNumber(pNotification.getImpactedPart().getNumber()); + dto.setImpactedPartVersion(pNotification.getImpactedPart().getVersion()); + + User ackAuthor = pNotification.getAcknowledgementAuthor(); + if (ackAuthor != null) { + UserDTO ackDTO = mapper.map(ackAuthor, UserDTO.class); + dto.setAckAuthor(ackDTO); + } + dto.setAcknowledged(pNotification.isAcknowledged()); + dto.setAckComment(pNotification.getAcknowledgementComment()); + dto.setAckDate(pNotification.getAcknowledgementDate()); + + dto.setCheckInDate(pNotification.getModifiedPart().getCheckInDate()); + dto.setIterationNote(pNotification.getModifiedPart().getIterationNote()); + + dto.setModifiedPartIteration(pNotification.getModifiedPart().getIteration()); + dto.setModifiedPartNumber(pNotification.getModifiedPart().getPartNumber()); + dto.setModifiedPartName(pNotification.getModifiedPart().getPartName()); + dto.setModifiedPartVersion(pNotification.getModifiedPart().getPartVersion()); + + return dto; + } + + public static PartRevisionDTO mapPartRevisionToPartDTO(PartRevision partRevision) { + + Mapper mapper = DozerBeanMapperSingletonWrapper.getInstance(); + + PartRevisionDTO partRevisionDTO = mapper.map(partRevision, PartRevisionDTO.class); + + partRevisionDTO.setNumber(partRevision.getPartNumber()); + partRevisionDTO.setPartKey(partRevision.getPartNumber() + "-" + partRevision.getVersion()); + partRevisionDTO.setName(partRevision.getPartMaster().getName()); + partRevisionDTO.setStandardPart(partRevision.getPartMaster().isStandardPart()); + partRevisionDTO.setType(partRevision.getPartMaster().getType()); + + if (partRevision.isObsolete()) { + partRevisionDTO.setObsoleteDate(partRevision.getObsoleteDate()); + UserDTO obsoleteUserDTO = mapper.map(partRevision.getObsoleteAuthor(), UserDTO.class); + partRevisionDTO.setObsoleteAuthor(obsoleteUserDTO); + } + + if (partRevision.getReleaseAuthor() != null) { + partRevisionDTO.setReleaseDate(partRevision.getReleaseDate()); + UserDTO releaseUserDTO = mapper.map(partRevision.getReleaseAuthor(), UserDTO.class); + partRevisionDTO.setReleaseAuthor(releaseUserDTO); + } + + List partIterationDTOs = new ArrayList<>(); + for (PartIteration partIteration : partRevision.getPartIterations()) { + partIterationDTOs.add(mapPartIterationToPartIterationDTO(partIteration)); + } + partRevisionDTO.setPartIterations(partIterationDTOs); + + if (partRevision.isCheckedOut()) { + partRevisionDTO.setCheckOutDate(partRevision.getCheckOutDate()); + UserDTO checkoutUserDTO = mapper.map(partRevision.getCheckOutUser(), UserDTO.class); + partRevisionDTO.setCheckOutUser(checkoutUserDTO); + } + + if (partRevision.hasWorkflow()) { + partRevisionDTO.setLifeCycleState(partRevision.getWorkflow().getLifeCycleState()); + partRevisionDTO.setWorkflow(mapper.map(partRevision.getWorkflow(), WorkflowDTO.class)); + } + + ACL acl = partRevision.getACL(); + if (acl != null) { + partRevisionDTO.setAcl(Tools.mapACLtoACLDTO(acl)); + } else { + partRevisionDTO.setAcl(null); + } + + + return partRevisionDTO; + } + + public static PartIterationDTO mapPartIterationToPartIterationDTO(PartIteration partIteration) { + Mapper mapper = DozerBeanMapperSingletonWrapper.getInstance(); + + List usageLinksDTO = new ArrayList<>(); + PartIterationDTO partIterationDTO = mapper.map(partIteration, PartIterationDTO.class); + + for (PartUsageLink partUsageLink : partIteration.getComponents()) { + PartUsageLinkDTO partUsageLinkDTO = mapper.map(partUsageLink, PartUsageLinkDTO.class); + List cadInstancesDTO = new ArrayList<>(); + for (CADInstance cadInstance : partUsageLink.getCadInstances()) { + CADInstanceDTO cadInstanceDTO = mapper.map(cadInstance, CADInstanceDTO.class); + cadInstanceDTO.setMatrix(cadInstance.getRotationMatrix().getValues()); + cadInstancesDTO.add(cadInstanceDTO); + } + List substituteLinkDTOs = new ArrayList<>(); + for (PartSubstituteLink partSubstituteLink : partUsageLink.getSubstitutes()) { + PartSubstituteLinkDTO substituteLinkDTO = mapper.map(partSubstituteLink, PartSubstituteLinkDTO.class); + substituteLinkDTOs.add(substituteLinkDTO); + + } + partUsageLinkDTO.setCadInstances(cadInstancesDTO); + partUsageLinkDTO.setSubstitutes(substituteLinkDTOs); + usageLinksDTO.add(partUsageLinkDTO); + } + partIterationDTO.setComponents(usageLinksDTO); + partIterationDTO.setNumber(partIteration.getPartRevision().getPartNumber()); + partIterationDTO.setVersion(partIteration.getPartRevision().getVersion()); + + if(!partIteration.getGeometries().isEmpty()){ + partIterationDTO.setGeometryFileURI("/api/files/"+partIteration.getSortedGeometries().get(0).getFullName()); + } + + return partIterationDTO; + } + + public static BaselinedPartDTO mapBaselinedPartToBaselinedPartDTO(BaselinedPart baselinedPart) { + return new BaselinedPartDTO(baselinedPart.getTargetPart()); + } + + public static List mapBaselinedDocumentsToBaselinedDocumentDTO(Collection baselinedDocuments) { + List baselinedDocumentDTOs = new ArrayList<>(); + for (DocumentIteration baselinedDocument : baselinedDocuments) { + baselinedDocumentDTOs.add(mapBaselinedDocumentToBaselinedDocumentDTO(baselinedDocument)); + } + return baselinedDocumentDTOs; + } + + public static BaselinedDocumentDTO mapBaselinedDocumentToBaselinedDocumentDTO(DocumentIteration baselineDocument) { + return new BaselinedDocumentDTO(baselineDocument); + } + + public static List mapBaselinedFoldersToFolderDTO(Collection baselinedFolders) { + List folderDTOs = new ArrayList<>(); + for (BaselinedFolder baselinedFolder : baselinedFolders) { + folderDTOs.add(mapBaselinedFolderToFolderDTO(baselinedFolder)); + } + return folderDTOs; + } + + public static List mapBaselinedFoldersToFolderDTO(DocumentBaseline documentBaseline) { + return mapBaselinedFoldersToFolderDTO(documentBaseline.getBaselinedFolders().values()); + } + + + private static FolderDTO mapBaselinedFolderToFolderDTO(BaselinedFolder baselinedFolder) { + String completePath = baselinedFolder.getCompletePath(); + return new FolderDTO(FolderDTO.extractParentFolder(completePath), FolderDTO.extractName(completePath)); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/UserResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/UserResource.java new file mode 100644 index 0000000000..8c4efb28b9 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/UserResource.java @@ -0,0 +1,106 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.exceptions.AccessRightException; +import com.docdoku.core.exceptions.EntityNotFoundException; +import com.docdoku.core.exceptions.UserNotActiveException; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.rest.dto.UserDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@RequestScoped +@Api(hidden = true, value = "users", description = "Operations about users") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class UserResource { + + + @Inject + private IUserManagerLocal userManager; + + private Mapper mapper; + + public UserResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get users", response = UserDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public UserDTO[] getUsersInWorkspace(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, AccessRightException, UserNotActiveException { + + User[] users = userManager.getUsers(workspaceId); + UserDTO[] dtos = new UserDTO[users.length]; + + for (int i = 0; i < users.length; i++) { + dtos[i] = mapper.map(users[i], UserDTO.class); + } + + return dtos; + } + + @GET + @ApiOperation(value = "Get current user details", response = UserDTO.class) + @Path("me") + @Produces(MediaType.APPLICATION_JSON) + public UserDTO whoami(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + + User user = userManager.whoAmI(workspaceId); + return mapper.map(user, UserDTO.class); + } + + @GET + @ApiOperation(value = "Get admin for workspace", response = UserDTO.class) + @Path("admin") + @Produces(MediaType.APPLICATION_JSON) + public UserDTO getAdminInWorkspace(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException { + + Workspace workspace = userManager.getWorkspace(workspaceId); + UserDTO userDTO = mapper.map(workspace.getAdmin(), UserDTO.class); + userDTO.setWorkspaceId(workspaceId); + return userDTO; + } +} + diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkflowModelResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkflowModelResource.java new file mode 100644 index 0000000000..3a993e2ba5 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkflowModelResource.java @@ -0,0 +1,186 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IWorkflowManagerLocal; +import com.docdoku.core.workflow.ActivityModel; +import com.docdoku.core.workflow.WorkflowModel; +import com.docdoku.core.workflow.WorkflowModelKey; +import com.docdoku.server.rest.dto.ACLDTO; +import com.docdoku.server.rest.dto.ActivityModelDTO; +import com.docdoku.server.rest.dto.WorkflowModelDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Morgan Guimard + */ +@RequestScoped +@Api(hidden = true, value = "workflowModels", description = "Operations about workflow models") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class WorkflowModelResource { + + @Inject + private IWorkflowManagerLocal workflowService; + + private Mapper mapper; + + public WorkflowModelResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get workflow models", response = WorkflowModelDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public WorkflowModelDTO[] getWorkflowModelsInWorkspace(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + + WorkflowModel[] workflowModels = workflowService.getWorkflowModels(workspaceId); + WorkflowModelDTO[] dtos = new WorkflowModelDTO[workflowModels.length]; + + for (int i = 0; i < workflowModels.length; i++) { + dtos[i] = mapper.map(workflowModels[i], WorkflowModelDTO.class); + } + + return dtos; + } + + @GET + @ApiOperation(value = "Get workflow model", response = WorkflowModelDTO.class) + @Path("{workflowModelId}") + @Produces(MediaType.APPLICATION_JSON) + public WorkflowModelDTO getWorkflowModelInWorkspace(@PathParam("workspaceId") String workspaceId, + @PathParam("workflowModelId") String workflowModelId) + throws EntityNotFoundException, UserNotActiveException { + + WorkflowModel workflowModel = workflowService.getWorkflowModel(new WorkflowModelKey(workspaceId, workflowModelId)); + return mapper.map(workflowModel, WorkflowModelDTO.class); + } + + @DELETE + @ApiOperation(value = "Delete workflow model", response = Response.class) + @Path("{workflowModelId}") + public Response delWorkflowModel(@PathParam("workspaceId") String workspaceId, + @PathParam("workflowModelId") String workflowModelId) + throws EntityNotFoundException, AccessRightException, UserNotActiveException, EntityConstraintException { + workflowService.deleteWorkflowModel(new WorkflowModelKey(workspaceId, workflowModelId)); + return Response.status(Response.Status.OK).build(); + } + + @PUT + @ApiOperation(value = "Update workflow model", response = WorkflowModelDTO.class) + @Path("{workflowModelId}") + @Produces(MediaType.APPLICATION_JSON) + public WorkflowModelDTO updateWorkflowModel(@PathParam("workspaceId") String workspaceId, + @PathParam("workflowModelId") String workflowModelId, + @ApiParam(required = true, value = "Workflow model to update") WorkflowModelDTO workflowModelDTOToPersist) + throws EntityNotFoundException, AccessRightException, EntityAlreadyExistsException, CreationException, UserNotActiveException, NotAllowedException { + + WorkflowModelKey workflowModelKey = new WorkflowModelKey(workspaceId, workflowModelId); + List activityModelDTOsList = workflowModelDTOToPersist.getActivityModels(); + ActivityModel[] activityModels = extractActivityModelFromDTO(activityModelDTOsList); + WorkflowModel workflowModel = workflowService.updateWorkflowModel(workflowModelKey, workflowModelDTOToPersist.getFinalLifeCycleState(), activityModels); + return mapper.map(workflowModel, WorkflowModelDTO.class); + } + + @PUT + @ApiOperation(value = "Update workflow model ACL", response = Response.class) + @Path("{workflowModelId}/acl") + @Consumes(MediaType.APPLICATION_JSON) + public Response updateWorkflowModelACL(@PathParam("workspaceId") String pWorkspaceId, + @PathParam("workflowModelId") String workflowModelId, + @ApiParam(required = true, value = "ACL rules to set") ACLDTO acl) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + if (!acl.getGroupEntries().isEmpty() || !acl.getUserEntries().isEmpty()) { + + Map userEntries = new HashMap<>(); + Map groupEntries = new HashMap<>(); + + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + userEntries.put(entry.getKey(), entry.getValue().name()); + } + + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + groupEntries.put(entry.getKey(), entry.getValue().name()); + } + + workflowService.updateACLForWorkflow(pWorkspaceId, workflowModelId, userEntries, groupEntries); + } else { + workflowService.removeACLFromWorkflow(pWorkspaceId, workflowModelId); + } + return Response.ok().build(); + } + + @POST + @ApiOperation(value = "Create workflow model", response = WorkflowModelDTO.class) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public WorkflowModelDTO createWorkflowModel(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Workflow model to create rules to set") WorkflowModelDTO workflowModelDTOToPersist) + throws EntityNotFoundException, EntityAlreadyExistsException, UserNotActiveException, NotAllowedException, AccessRightException, CreationException { + List activityModelDTOsList = workflowModelDTOToPersist.getActivityModels(); + ActivityModel[] activityModels = extractActivityModelFromDTO(activityModelDTOsList); + WorkflowModel workflowModel = workflowService.createWorkflowModel(workspaceId, workflowModelDTOToPersist.getReference(), workflowModelDTOToPersist.getFinalLifeCycleState(), activityModels); + return mapper.map(workflowModel, WorkflowModelDTO.class); + } + + private ActivityModel[] extractActivityModelFromDTO(List activityModelDTOsList) throws NotAllowedException { + Map activityModels = new HashMap<>(); + + for (int i = 0; i < activityModelDTOsList.size(); i++) { + ActivityModelDTO activityModelDTO = activityModelDTOsList.get(i); + ActivityModel activityModel = mapper.map(activityModelDTO, ActivityModel.class); + activityModels.put(activityModel.getStep(), activityModel); + + Integer relaunchStep = activityModelDTO.getRelaunchStep(); + if (relaunchStep != null && relaunchStep < i) { + ActivityModel relaunchActivity = activityModels.get(relaunchStep); + activityModel.setRelaunchActivity(relaunchActivity); + } + } + + return activityModels.values().toArray(new ActivityModel[activityModels.size()]); + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkflowResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkflowResource.java new file mode 100644 index 0000000000..faf0b43258 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkflowResource.java @@ -0,0 +1,102 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.exceptions.*; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IWorkflowManagerLocal; +import com.docdoku.core.workflow.Workflow; +import com.docdoku.server.rest.dto.WorkflowDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author Morgan Guimard + */ +@RequestScoped +@Api(hidden = true, value = "workflows", description = "Operations about workflow instances") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class WorkflowResource { + + @Inject + private IWorkflowManagerLocal workflowService; + + private Mapper mapper; + + public WorkflowResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get instantiated workflow", response = WorkflowDTO.class) + @Path("{workflowId}") + @Produces(MediaType.APPLICATION_JSON) + public WorkflowDTO getWorkflowInstance(@PathParam("workspaceId") String workspaceId, @PathParam("workflowId") int workflowId) + throws EntityNotFoundException, UserNotActiveException, AccessRightException { + Workflow workflow = workflowService.getWorkflow(workspaceId, workflowId); + return mapper.map(workflow, WorkflowDTO.class); + } + + @GET + @ApiOperation(value = "Get workflow's aborted workflows", response = WorkflowDTO.class, responseContainer = "List") + @Path("{workflowId}/aborted") + @Produces(MediaType.APPLICATION_JSON) + public Response getWorkflowAbortedWorkflows(@PathParam("workspaceId") String workspaceId, @PathParam("workflowId") int workflowId) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, WorkflowNotFoundException, AccessRightException { + Workflow[] abortedWorkflows = workflowService.getWorkflowAbortedWorkflows(workspaceId, workflowId); + + List abortedWorkflowsDTO = new ArrayList<>(); + + for (Workflow abortedWorkflow : abortedWorkflows) { + abortedWorkflowsDTO.add(mapper.map(abortedWorkflow, WorkflowDTO.class)); + } + + Collections.sort(abortedWorkflowsDTO); + + return Response.ok(new GenericEntity>((List) abortedWorkflowsDTO) { + }).build(); + + } + + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkspaceMembershipResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkspaceMembershipResource.java new file mode 100644 index 0000000000..9c0d7496cb --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkspaceMembershipResource.java @@ -0,0 +1,133 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest; + +import com.docdoku.core.exceptions.EntityNotFoundException; +import com.docdoku.core.exceptions.UserNotActiveException; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.security.WorkspaceUserGroupMembership; +import com.docdoku.core.security.WorkspaceUserMembership; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.rest.dto.WorkspaceUserGroupMemberShipDTO; +import com.docdoku.server.rest.dto.WorkspaceUserMemberShipDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Morgan Guimard + */ +@RequestScoped +@Api(hidden = true, value = "workspace-memberships", description = "Operations about workspace memberships") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class WorkspaceMembershipResource { + + @Inject + private IUserManagerLocal userManager; + + private Mapper mapper; + + public WorkspaceMembershipResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get workspace's user memberships", response = WorkspaceUserMemberShipDTO.class, responseContainer = "List") + @Path("users") + @Produces(MediaType.APPLICATION_JSON) + public WorkspaceUserMemberShipDTO[] getWorkspaceUserMemberShips(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + + WorkspaceUserMembership[] workspaceUserMemberships = userManager.getWorkspaceUserMemberships(workspaceId); + WorkspaceUserMemberShipDTO[] workspaceUserMemberShipDTO = new WorkspaceUserMemberShipDTO[workspaceUserMemberships.length]; + for (int i = 0; i < workspaceUserMemberships.length; i++) { + workspaceUserMemberShipDTO[i] = mapper.map(workspaceUserMemberships[i], WorkspaceUserMemberShipDTO.class); + } + return workspaceUserMemberShipDTO; + } + + @GET + @ApiOperation(value = "Get workspace's user membership for current user", response = WorkspaceUserMemberShipDTO.class) + @Path("users/me") + @Produces(MediaType.APPLICATION_JSON) + public WorkspaceUserMemberShipDTO getWorkspaceSpecificUserMemberShips(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + + WorkspaceUserMembership workspaceUserMemberships = userManager.getWorkspaceSpecificUserMemberships(workspaceId); + return mapper.map(workspaceUserMemberships, WorkspaceUserMemberShipDTO.class); + } + + @GET + @ApiOperation(value = "Get workspace's group membership for current user", response = WorkspaceUserGroupMemberShipDTO.class, responseContainer = "List") + @Path("usergroups") + @Produces(MediaType.APPLICATION_JSON) + public WorkspaceUserGroupMemberShipDTO[] getWorkspaceUserGroupMemberShips(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + + WorkspaceUserGroupMembership[] workspaceUserGroupMemberships = userManager.getWorkspaceUserGroupMemberships(workspaceId); + WorkspaceUserGroupMemberShipDTO[] workspaceUserGroupMemberShipDTO = new WorkspaceUserGroupMemberShipDTO[workspaceUserGroupMemberships.length]; + for (int i = 0; i < workspaceUserGroupMemberships.length; i++) { + workspaceUserGroupMemberShipDTO[i] = mapper.map(workspaceUserGroupMemberships[i], WorkspaceUserGroupMemberShipDTO.class); + } + return workspaceUserGroupMemberShipDTO; + } + + @GET + @ApiOperation(value = "Get workspace's group membership for current user", response = WorkspaceUserGroupMemberShipDTO.class, responseContainer = "List") + @Path("usergroups/me") + @Produces(MediaType.APPLICATION_JSON) + public Response getWorkspaceSpecificUserGroupMemberShips(@PathParam("workspaceId") String workspaceId) + throws EntityNotFoundException, UserNotActiveException { + + WorkspaceUserGroupMembership[] workspaceUserGroupMemberships = userManager.getWorkspaceSpecificUserGroupMemberships(workspaceId); + List workspaceUserGroupMemberShipDTO = new ArrayList<>(); + for (int i = 0; i < workspaceUserGroupMemberships.length; i++) { + if (workspaceUserGroupMemberships[i] != null) { + workspaceUserGroupMemberShipDTO.add(mapper.map(workspaceUserGroupMemberships[i], WorkspaceUserGroupMemberShipDTO.class)); + } + } + + return Response.ok(new GenericEntity>((List) workspaceUserGroupMemberShipDTO) { + }).build(); + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkspaceResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkspaceResource.java new file mode 100644 index 0000000000..b2edea75ed --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkspaceResource.java @@ -0,0 +1,683 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.common.*; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.security.WorkspaceUserGroupMembership; +import com.docdoku.core.security.WorkspaceUserMembership; +import com.docdoku.core.services.*; +import com.docdoku.server.rest.dto.*; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.ws.rs.*; +import javax.ws.rs.NotAllowedException; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.util.*; + +@RequestScoped +@Api(value = "workspaces", description = "Operations about workspaces") +@Path("workspaces") +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) +@RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.ADMIN_ROLE_ID}) +public class WorkspaceResource { + + @Inject + private DocumentsResource documents; + + @Inject + private DocumentBaselinesResource documentBaselines; + + @Inject + private FolderResource folders; + + @Inject + private DocumentTemplateResource docTemplates; + + @Inject + private PartTemplateResource partTemplates; + + @Inject + private ProductResource products; + + @Inject + private PartsResource parts; + + @Inject + private TagResource tags; + + @Inject + private CheckedOutDocumentResource checkedOutDocuments; + + @Inject + private TaskResource tasks; + + @Inject + private WorkflowResource workflowInstances; + + @Inject + private WorkflowModelResource workflowModels; + + @Inject + private WorkspaceWorkflowResource workspaceWorkflows; + + @Inject + private ChangeItemsResource changeItems; + + @Inject + private UserResource users; + + @Inject + private RoleResource roles; + + @Inject + private ModificationNotificationResource notifications; + + @Inject + private WorkspaceMembershipResource workspaceMemberships; + + @Inject + private IDocumentManagerLocal documentService; + + @Inject + private IProductManagerLocal productService; + + @Inject + private IUserManagerLocal userManager; + + @Inject + private LOVResource lov; + + @Inject + private AttributesResource attributes; + + @Inject + private IWorkspaceManagerLocal workspaceManager; + + private Mapper mapper; + + @Inject + private IAccountManagerLocal accountManager; + + @Inject + private IContextManagerLocal contextManager; + + public WorkspaceResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get workspace list for authenticated user", response = WorkspaceListDTO.class) + @Produces(MediaType.APPLICATION_JSON) + public WorkspaceListDTO getWorkspacesForConnectedUser() throws EntityNotFoundException { + + WorkspaceListDTO workspaceListDTO = new WorkspaceListDTO(); + + Workspace[] administratedWorkspaces = userManager.getAdministratedWorkspaces(); + Workspace[] allWorkspaces = userManager.getWorkspacesWhereCallerIsActive(); + + for (Workspace workspace : administratedWorkspaces) { + workspaceListDTO.addAdministratedWorkspaces(mapper.map(workspace, WorkspaceDTO.class)); + } + for (Workspace workspace : allWorkspaces) { + workspaceListDTO.addAllWorkspaces(mapper.map(workspace, WorkspaceDTO.class)); + } + return workspaceListDTO; + } + + @GET + @ApiOperation(value = "Get detailed workspace list for authenticated user", response = WorkspaceDetailsDTO.class, responseContainer = "List") + @Path("/more") + @Produces(MediaType.APPLICATION_JSON) + public Response getDetailedWorkspacesForConnectedUser() throws EntityNotFoundException { + List workspaceListDTO = new ArrayList<>(); + + for (Workspace workspace : userManager.getWorkspacesWhereCallerIsActive()) { + workspaceListDTO.add(mapper.map(workspace, WorkspaceDetailsDTO.class)); + } + return Response.ok(new GenericEntity>((List) workspaceListDTO) { + }).build(); + } + + @GET + @ApiOperation(value = "Get online users visible by current user", response = UserDTO.class, responseContainer = "List") + @Path("reachable-users") + @Produces(MediaType.APPLICATION_JSON) + public UserDTO[] getReachableUsersForCaller() + throws EntityNotFoundException { + + User[] users = userManager.getReachableUsers(); + UserDTO[] dtos = new UserDTO[users.length]; + + for (int i = 0; i < users.length; i++) { + dtos[i] = mapper.map(users[i], UserDTO.class); + } + + return dtos; + } + + @PUT + @ApiOperation(value = "Update workspace", response = WorkspaceDTO.class) + @Path("/{workspaceId}") + @Produces(MediaType.APPLICATION_JSON) + public Response updateWorkspace(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Workspace values to update") WorkspaceDTO workspaceDTO) + throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccountNotFoundException, AccessRightException { + Workspace workspace = userManager.updateWorkspace(workspaceId, workspaceDTO.getDescription(), workspaceDTO.isFolderLocked()); + return Response.ok(mapper.map(workspace, WorkspaceDTO.class)).build(); + } + + @PUT + @Path("/{workspaceId}/index") + @ApiOperation(value = "Index the workspace", response = Response.class) + @Produces(MediaType.APPLICATION_JSON) + public Response synchronizeIndexer(@PathParam("workspaceId") String workspaceId) + throws AccessRightException, UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + workspaceManager.synchronizeIndexer(workspaceId); + return Response.ok().build(); + } + + @DELETE + @ApiOperation(value = "Delete workspace", response = Response.class) + @Path("/{workspaceId}") + @Produces(MediaType.APPLICATION_JSON) + public Response deleteWorkspace(@PathParam("workspaceId") String workspaceId) { + workspaceManager.deleteWorkspace(workspaceId); + return Response.ok().build(); + } + + @GET + @ApiOperation(value = "Get user groups", response = UserGroupDTO.class, responseContainer = "List") + @Path("/{workspaceId}/user-group") + @Produces(MediaType.APPLICATION_JSON) + public UserGroupDTO[] getUserGroups(@PathParam("workspaceId") String workspaceId) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccountNotFoundException { + UserGroup[] userGroups = userManager.getUserGroups(workspaceId); + UserGroupDTO[] userGroupDTOs = new UserGroupDTO[userGroups.length]; + for(int i = 0 ; i < userGroups.length; i++){ + userGroupDTOs[i]=mapper.map(userGroups[i], UserGroupDTO.class); + } + return userGroupDTOs; + } + + @POST + @ApiOperation(value = "Create user group", response = UserGroupDTO.class) + @Path("/{workspaceId}/user-group") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public UserGroupDTO createGroup(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "UserGroup to create") UserGroupDTO userGroupDTO) + throws UserGroupAlreadyExistsException, AccessRightException, AccountNotFoundException, CreationException, WorkspaceNotFoundException { + UserGroup userGroup = userManager.createUserGroup(userGroupDTO.getId(), workspaceId); + return mapper.map(userGroup, UserGroupDTO.class); + } + + @DELETE + @ApiOperation(value = "Remove user group", response = UserGroupDTO.class) + @Path("/{workspaceId}/user-group/{groupId}") + @Produces(MediaType.APPLICATION_JSON) + public Response removeGroup(@PathParam("workspaceId") String workspaceId, @PathParam("groupId") String groupId) throws UserGroupNotFoundException, AccessRightException, EntityConstraintException, AccountNotFoundException, WorkspaceNotFoundException { + userManager.removeUserGroups(workspaceId, new String[]{groupId}); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Add user to workspace", response = Response.class) + @Path("/{workspaceId}/add-user") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response addUser(@PathParam("workspaceId") String workspaceId, + @QueryParam("group") String group, + @ApiParam(required = true, value = "User to add") UserDTO userDTO) throws WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, AccessRightException, UserGroupNotFoundException, com.docdoku.core.exceptions.NotAllowedException, AccountNotFoundException, UserAlreadyExistsException, FolderAlreadyExistsException, CreationException { + + if (group != null && !group.isEmpty()) { + userManager.addUserInGroup(new UserGroupKey(workspaceId, group), userDTO.getLogin()); + } else { + userManager.addUserInWorkspace(workspaceId, userDTO.getLogin()); + } + + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Set a new admin", response = WorkspaceDTO.class) + @Path("/{workspaceId}/admin") + @Produces(MediaType.APPLICATION_JSON) + public Response setNewAdmin(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "New admin user") UserDTO userDTO) throws AccountNotFoundException, AccessRightException, WorkspaceNotFoundException { + Workspace workspace = workspaceManager.changeAdmin(workspaceId, userDTO.getLogin()); + + return Response.ok(mapper.map(workspace, WorkspaceDTO.class)).build(); + } + + @POST + @ApiOperation(value = "Create workspace", response = WorkspaceDTO.class) + @Produces(MediaType.APPLICATION_JSON) + public WorkspaceDTO createWorkspace(@QueryParam("userLogin") String userLogin, + @ApiParam(value = "Workspace to create", required = true) WorkspaceDTO workspaceDTO) throws FolderAlreadyExistsException, UserAlreadyExistsException, WorkspaceAlreadyExistsException, CreationException, NotAllowedException, AccountNotFoundException, ESIndexNamingException, IOException, com.docdoku.core.exceptions.NotAllowedException { + Account account; + if (contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID)) { + account = accountManager.getAccount(userLogin); + } else { + account = accountManager.getMyAccount(); + } + Workspace workspace = userManager.createWorkspace(workspaceDTO.getId(), account, workspaceDTO.getDescription(), workspaceDTO.isFolderLocked()); + + return mapper.map(workspace, WorkspaceDTO.class); + } + + @PUT + @ApiOperation(value = "Set user access in workspace", response = Response.class) + @Path("/{workspaceId}/user-access") + @Produces(MediaType.APPLICATION_JSON) + public Response setUserAccess(@PathParam("workspaceId") String workspaceId, + @ApiParam(value = "User to grant access in workspace", required = true) UserDTO userDTO) throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + if (userDTO.getMembership() == null) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + WorkspaceUserMembership workspaceUserMembership = userManager.grantUserAccess(workspaceId, userDTO.getLogin(), userDTO.getMembership() == WorkspaceMembership.READ_ONLY ? true : false); + return Response.ok(mapper.map(workspaceUserMembership.getMember(), UserDTO.class)).build(); + } + + @PUT + @ApiOperation(value = "Set group access in workspace", response = Response.class) + @Path("/{workspaceId}/group-access") + @Produces(MediaType.APPLICATION_JSON) + public Response setGroupAccess(@PathParam("workspaceId") String workspaceId, + @ApiParam(value = "User to grant access in group", required = true) WorkspaceUserGroupMemberShipDTO workspaceUserGroupMemberShipDTO) + throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, UserGroupNotFoundException { + + WorkspaceUserGroupMembership membership = userManager.grantGroupAccess(workspaceId, workspaceUserGroupMemberShipDTO.getMemberId(), workspaceUserGroupMemberShipDTO.isReadOnly()); + return Response.ok(mapper.map(membership, WorkspaceUserGroupMemberShipDTO.class)).build(); + } + + @PUT + @ApiOperation(value = "Remove user from group", response = UserGroupDTO.class) + @Path("/{workspaceId}/remove-from-group/{groupId}") + @Produces(MediaType.APPLICATION_JSON) + public Response removeUserFromGroup(@PathParam("workspaceId") String workspaceId, + @PathParam("groupId") String groupId, + @ApiParam(value = "User to remove from group", required = true) UserDTO userDTO) + throws AccessRightException, UserGroupNotFoundException, AccountNotFoundException, WorkspaceNotFoundException { + UserGroup userGroup = userManager.removeUserFromGroup(new UserGroupKey(workspaceId, groupId), userDTO.getLogin()); + return Response.ok(mapper.map(userGroup, UserGroupDTO.class)).build(); + } + + @PUT + @ApiOperation(value = "Remove user from workspace", response = WorkspaceDTO.class) + @Path("/{workspaceId}/remove-from-workspace") + @Produces(MediaType.APPLICATION_JSON) + public Response removeUserFromWorkspace(@PathParam("workspaceId") String workspaceId, + @ApiParam(value = "User to remove from workspace", required = true) UserDTO userDTO) + throws UserGroupNotFoundException, AccessRightException, UserNotFoundException, NotAllowedException, AccountNotFoundException, WorkspaceNotFoundException, FolderNotFoundException, ESServerException, EntityConstraintException, DocumentRevisionNotFoundException, UserNotActiveException, com.docdoku.core.exceptions.NotAllowedException { + Workspace workspace = userManager.removeUser(workspaceId, userDTO.getLogin()); + return Response.ok(mapper.map(workspace, WorkspaceDTO.class)).build(); + } + + @PUT + @ApiOperation(value = "Enable user", response = Response.class) + @Path("/{workspaceId}/enable-user") + @Produces(MediaType.APPLICATION_JSON) + public Response enableUser(@PathParam("workspaceId") String workspaceId, + @ApiParam(value = "User to enable", required = true) UserDTO userDTO) + throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + userManager.activateUser(workspaceId, userDTO.getLogin()); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Disable user", response = Response.class) + @Path("/{workspaceId}/disable-user") + @Produces(MediaType.APPLICATION_JSON) + public Response disableUser(@PathParam("workspaceId") String workspaceId, + @ApiParam(value = "User to disable", required = true) UserDTO userDTO) + throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + userManager.passivateUser(workspaceId, userDTO.getLogin()); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Enable group", response = Response.class) + @Path("/{workspaceId}/enable-group") + @Produces(MediaType.APPLICATION_JSON) + public Response enableGroup(@PathParam("workspaceId") String workspaceId, + @ApiParam(value = "Group to enable", required = true) UserGroupDTO userGroupDTO) + throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + userManager.activateUserGroup(workspaceId, userGroupDTO.getId()); + return Response.ok().build(); + } + + @PUT + @ApiOperation(value = "Disable group", response = Response.class) + @Path("/{workspaceId}/disable-group") + @Produces(MediaType.APPLICATION_JSON) + public Response disableGroup(@PathParam("workspaceId") String workspaceId, + @ApiParam(value = "Group to disable", required = true) UserGroupDTO userGroupDTO) + throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException { + userManager.passivateUserGroup(workspaceId, userGroupDTO.getId()); + return Response.ok().build(); + } + + @GET + @ApiOperation(value = "Get groups", response = UserGroupDTO.class, responseContainer = "List") + @Path("/{workspaceId}/groups") + @Produces(MediaType.APPLICATION_JSON) + public UserGroupDTO[] getGroups(@PathParam("workspaceId") String workspaceId) + throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException { + UserGroup[] userGroups = userManager.getUserGroups(workspaceId); + UserGroupDTO[] userGroupDTOs = new UserGroupDTO[userGroups.length]; + for (int i = 0; i < userGroups.length; i++) { + userGroupDTOs[i] = mapper.map(userGroups[i],UserGroupDTO.class); + } + return userGroupDTOs; + } + + @GET + @ApiOperation(value = "Get users of group", response = UserDTO.class, responseContainer = "List") + @Path("/{workspaceId}/groups/{groupId}/users") + @Produces(MediaType.APPLICATION_JSON) + public UserDTO[] getUsersInGroup(@PathParam("workspaceId") String workspaceId, @PathParam("groupId") String groupId) + throws AccessRightException, AccountNotFoundException, WorkspaceNotFoundException, UserNotFoundException, UserNotActiveException, UserGroupNotFoundException { + UserGroup userGroup = userManager.getUserGroup(new UserGroupKey(workspaceId, groupId)); + Set users = userGroup.getUsers(); + User[] usersArray = users.toArray(new User[users.size()]); + UserDTO[] userDTOs = new UserDTO[users.size()]; + for (int i = 0; i < usersArray.length; i++) { + userDTOs[i] = mapper.map(usersArray[i], UserDTO.class); + } + return userDTOs; + } + + @GET + @ApiOperation(value = "Get stats overview for workspace", response = StatsOverviewDTO.class) + @Path("/{workspaceId}/stats-overview") + @Produces(MediaType.APPLICATION_JSON) + public StatsOverviewDTO getStatsOverview(@PathParam("workspaceId") String workspaceId) + throws WorkspaceNotFoundException, AccountNotFoundException, AccessRightException, UserNotFoundException, UserNotActiveException { + + StatsOverviewDTO statsOverviewDTO = new StatsOverviewDTO(); + + boolean admin = false; + + if(contextManager.isCallerInRole(UserGroupMapping.ADMIN_ROLE_ID)){ + admin = true; + }else { + User user = userManager.checkWorkspaceReadAccess(workspaceId); + admin = user.isAdministrator(); + } + + if(admin){ + statsOverviewDTO.setDocuments(documentService.getTotalNumberOfDocuments(workspaceId)); + statsOverviewDTO.setParts(productService.getTotalNumberOfParts(workspaceId)); + }else{ + statsOverviewDTO.setDocuments(documentService.getDocumentsInWorkspaceCount(workspaceId)); + statsOverviewDTO.setParts(productService.getPartsInWorkspaceCount(workspaceId)); + } + + statsOverviewDTO.setUsers(userManager.getUsers(workspaceId).length); + statsOverviewDTO.setProducts(productService.getConfigurationItems(workspaceId).size()); + + return statsOverviewDTO; + } + + @GET + @ApiOperation(value = "Get disk usage stats for workspace", response = DiskUsageSpaceDTO.class) + @Path("/{workspaceId}/disk-usage-stats") + @Produces(MediaType.APPLICATION_JSON) + public DiskUsageSpaceDTO getDiskSpaceUsageStats(@PathParam("workspaceId") String workspaceId) throws WorkspaceNotFoundException, AccountNotFoundException, AccessRightException { + DiskUsageSpaceDTO diskUsageSpaceDTO = new DiskUsageSpaceDTO(); + diskUsageSpaceDTO.setDocuments(documentService.getDiskUsageForDocumentsInWorkspace(workspaceId)); + diskUsageSpaceDTO.setParts(productService.getDiskUsageForPartsInWorkspace(workspaceId)); + diskUsageSpaceDTO.setDocumentTemplates(documentService.getDiskUsageForDocumentTemplatesInWorkspace(workspaceId)); + diskUsageSpaceDTO.setPartTemplates(productService.getDiskUsageForPartTemplatesInWorkspace(workspaceId)); + return diskUsageSpaceDTO; + } + + @GET + @ApiOperation(value = "Get checked out documents stats for workspace", response = JsonObject.class) + @Path("/{workspaceId}/checked-out-documents-stats") + @Produces(MediaType.APPLICATION_JSON) + public JsonObject getCheckedOutDocumentsStats(@PathParam("workspaceId") String workspaceId) throws WorkspaceNotFoundException, AccountNotFoundException, AccessRightException { + + DocumentRevision[] checkedOutDocumentRevisions = documentService.getAllCheckedOutDocumentRevisions(workspaceId); + JsonObjectBuilder statsByUserBuilder = Json.createObjectBuilder(); + + Map userArrays=new HashMap<>(); + for(DocumentRevision documentRevision : checkedOutDocumentRevisions){ + + String userLogin = documentRevision.getCheckOutUser().getLogin(); + JsonArrayBuilder userArray=userArrays.get(userLogin); + if(userArray==null) { + userArray = Json.createArrayBuilder(); + userArrays.put(userLogin, userArray); + } + userArray.add(Json.createObjectBuilder().add("date",documentRevision.getCheckOutDate().getTime()).build()); + } + + for(Map.Entry entry : userArrays.entrySet()){ + statsByUserBuilder.add(entry.getKey(),entry.getValue().build()); + } + + return statsByUserBuilder.build(); + + } + + @GET + @ApiOperation(value = "Get checked out parts stats for workspace", response = JsonObject.class) + @Path("/{workspaceId}/checked-out-parts-stats") + @Produces(MediaType.APPLICATION_JSON) + public JsonObject getCheckedOutPartsStats(@PathParam("workspaceId") String workspaceId) throws WorkspaceNotFoundException, AccountNotFoundException, AccessRightException { + + PartRevision[] checkedOutPartRevisions = productService.getAllCheckedOutPartRevisions(workspaceId); + JsonObjectBuilder statsByUserBuilder = Json.createObjectBuilder(); + + Map userArrays=new HashMap<>(); + for(PartRevision partRevision : checkedOutPartRevisions){ + + String userLogin = partRevision.getCheckOutUser().getLogin(); + JsonArrayBuilder userArray=userArrays.get(userLogin); + if(userArray==null) { + userArray = Json.createArrayBuilder(); + userArrays.put(userLogin, userArray); + } + userArray.add(Json.createObjectBuilder().add("date", partRevision.getCheckOutDate().getTime()).build()); + } + + for(Map.Entry entry : userArrays.entrySet()){ + statsByUserBuilder.add(entry.getKey(),entry.getValue().build()); + } + + return statsByUserBuilder.build(); + + } + + @GET + @ApiOperation(value = "Get user stats for workspace", response = JsonObject.class) + @Path("/{workspaceId}/users-stats") + @Produces(MediaType.APPLICATION_JSON) + public JsonObject getUsersStats(@PathParam("workspaceId") String workspaceId) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, AccountNotFoundException, AccessRightException { + + WorkspaceUserMembership[] workspaceUserMemberships = userManager.getWorkspaceUserMemberships(workspaceId); + WorkspaceUserGroupMembership[] workspaceUserGroupMemberships = userManager.getWorkspaceUserGroupMemberships(workspaceId); + + int usersCount = userManager.getUsers(workspaceId).length; + int activeUsersCount = workspaceUserMemberships.length; + int inactiveUsersCount = usersCount - activeUsersCount; + + int groupsCount = userManager.getUserGroups(workspaceId).length; + int activeGroupsCount = workspaceUserGroupMemberships.length; + int inactiveGroupsCount = groupsCount - activeGroupsCount; + + return Json.createObjectBuilder() + .add("users", usersCount) + .add("activeusers", activeUsersCount) + .add("inactiveusers", inactiveUsersCount) + .add("groups", groupsCount) + .add("activegroups", activeGroupsCount) + .add("inactivegroups", inactiveGroupsCount).build(); + } + + + // Sub resources + + @ApiOperation(value = "DocumentsResource") + @Path("/{workspaceId}/documents") + public DocumentsResource documents() { + return documents; + } + + @ApiOperation(value = "FolderResource") + @Path("/{workspaceId}/folders") + public FolderResource folders() { + return folders; + } + + @ApiOperation(value = "DocumentTemplateResource") + @Path("/{workspaceId}/document-templates") + public DocumentTemplateResource docTemplates() { + return docTemplates; + } + + @ApiOperation(value = "PartTemplateResource") + @Path("/{workspaceId}/part-templates") + public PartTemplateResource partTemplates() { + return partTemplates; + } + + @ApiOperation(value = "ProductResource") + @Path("/{workspaceId}/products") + public ProductResource products() { + return products; + } + + @ApiOperation(value = "PartsResource") + @Path("/{workspaceId}/parts") + public PartsResource parts() { + return parts; + } + + @ApiOperation(value = "TagResource") + @Path("/{workspaceId}/tags") + public TagResource tags() { + return tags; + } + + @ApiOperation(value = "CheckedOutDocumentResource") + @Path("/{workspaceId}/checkedouts") + public CheckedOutDocumentResource checkedOuts() { + return checkedOutDocuments; + } + + @ApiOperation(value = "TaskResource") + @Path("/{workspaceId}/tasks") + public TaskResource tasks() { + return tasks; + } + + @ApiOperation(value = "ModificationNotificationResource") + @Path("/{workspaceId}/notifications") + public ModificationNotificationResource notifications() { + return notifications; + } + + @ApiOperation(value = "WorkflowModelResource") + @Path("/{workspaceId}/workflow-models") + public WorkflowModelResource workflowModels() { + return workflowModels; + } + + @ApiOperation(value = "WorkflowResource") + @Path("/{workspaceId}/workflow-instances") + public WorkflowResource workflowsInstances() { + return workflowInstances; + } + + @ApiOperation(value = "WorkspaceWorkflowResource") + @Path("/{workspaceId}/workspace-workflows") + public WorkspaceWorkflowResource workspaceWorkflows() { + return workspaceWorkflows; + } + + @ApiOperation(value = "UserResource") + @Path("/{workspaceId}/users") + public UserResource users() { + return users; + } + + @ApiOperation(value = "RoleResource", hidden = false) + @Path("/{workspaceId}/roles") + public RoleResource roles() { + return roles; + } + + @ApiOperation(value = "WorkspaceMembershipResource") + @Path("/{workspaceId}/memberships") + public WorkspaceMembershipResource workspaceMemberships() { + return workspaceMemberships; + } + + @ApiOperation(value = "ChangeItemsResource") + @Path("/{workspaceId}/changes") + public ChangeItemsResource changeItems() { + return changeItems; + } + + @ApiOperation(value = "DocumentBaselinesResource") + @Path("/{workspaceId}/document-baselines") + public DocumentBaselinesResource documentBaselines() { + return documentBaselines; + } + + @ApiOperation(value = "LOVResource") + @Path("/{workspaceId}/lov") + public LOVResource lov() { + return lov; + } + + @ApiOperation(hidden = true, value = "AttributesResource") + @Path("/{workspaceId}/attributes") + public AttributesResource attributes() { + return attributes; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkspaceWorkflowResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkspaceWorkflowResource.java new file mode 100644 index 0000000000..aebdbcb3d7 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/WorkspaceWorkflowResource.java @@ -0,0 +1,129 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest; + +import com.docdoku.core.workflow.WorkspaceWorkflow; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IWorkflowManagerLocal; +import com.docdoku.server.rest.dto.RoleMappingDTO; +import com.docdoku.server.rest.dto.WorkspaceWorkflowCreationDTO; +import com.docdoku.server.rest.dto.WorkspaceWorkflowDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.annotation.PostConstruct; +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.*; + +/** + * @author Morgan Guimard + */ +@RequestScoped +@Api(hidden = true, value = "workspaceWorkflows", description = "Operations about workspace workflows") +@DeclareRoles(UserGroupMapping.REGULAR_USER_ROLE_ID) +@RolesAllowed(UserGroupMapping.REGULAR_USER_ROLE_ID) +public class WorkspaceWorkflowResource { + + @Inject + private IWorkflowManagerLocal workflowService; + + private Mapper mapper; + + public WorkspaceWorkflowResource() { + } + + @PostConstruct + public void init() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @GET + @ApiOperation(value = "Get workspace workflow list", response = WorkspaceWorkflowDTO.class, responseContainer = "List") + @Produces(MediaType.APPLICATION_JSON) + public Response getWorkspaceWorkflowList(@PathParam("workspaceId") String workspaceId) + throws UserNotFoundException, UserNotActiveException, WorkspaceNotFoundException { + + WorkspaceWorkflow[] workspaceWorkflowList = workflowService.getWorkspaceWorkflowList(workspaceId); + List workspaceWorkflowListDTO = new ArrayList<>(); + + for (WorkspaceWorkflow workspaceWorkflow : workspaceWorkflowList) { + workspaceWorkflowListDTO.add(mapper.map(workspaceWorkflow, WorkspaceWorkflowDTO.class)); + } + + return Response.ok(new GenericEntity>((List) workspaceWorkflowListDTO) { + }).build(); + } + + @GET + @ApiOperation(value = "Get workspace workflow", response = WorkspaceWorkflowDTO.class) + @Path("{workspaceWorkflowId}") + @Produces(MediaType.APPLICATION_JSON) + public WorkspaceWorkflowDTO getWorkspaceWorkflow(@PathParam("workspaceId") String workspaceId, @PathParam("workspaceWorkflowId") String workspaceWorkflowId) + throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, WorkflowNotFoundException { + WorkspaceWorkflow workspaceWorkflow = workflowService.getWorkspaceWorkflow(workspaceId, workspaceWorkflowId); + return mapper.map(workspaceWorkflow, WorkspaceWorkflowDTO.class); + } + + + @POST + @ApiOperation(value = "Instantiate workspace workflow from workflow model", response = WorkspaceWorkflowDTO.class) + @Produces(MediaType.APPLICATION_JSON) + public WorkspaceWorkflowDTO createWorkspaceWorkflow(@PathParam("workspaceId") String workspaceId, + @ApiParam(required = true, value = "Workspace workflow to create") WorkspaceWorkflowCreationDTO workflowCreationDTO) + throws RoleNotFoundException, WorkspaceNotFoundException, UserNotFoundException, AccessRightException, WorkflowModelNotFoundException, NotAllowedException, UserGroupNotFoundException { + + Map> userRoleMapping = new HashMap<>(); + Map> groupRoleMapping = new HashMap<>(); + RoleMappingDTO[] roleMappingDTOs = workflowCreationDTO.getRoleMapping(); + + if (roleMappingDTOs != null) { + for (RoleMappingDTO roleMappingDTO : roleMappingDTOs) { + userRoleMapping.put(roleMappingDTO.getRoleName(), roleMappingDTO.getUserLogins()); + groupRoleMapping.put(roleMappingDTO.getRoleName(), roleMappingDTO.getGroupIds()); + } + } + + WorkspaceWorkflow workspaceWorkflow = workflowService.instantiateWorkflow(workspaceId, workflowCreationDTO.getId(), workflowCreationDTO.getWorkflowModelId(), userRoleMapping, groupRoleMapping); + return mapper.map(workspaceWorkflow, WorkspaceWorkflowDTO.class); + } + + + @DELETE + @Path("{workspaceWorkflowId}") + @ApiOperation(value = "Delete a workspace workflow ", response = Response.class) + @Produces(MediaType.APPLICATION_JSON) + public Response deleteWorkspaceWorkflow(@PathParam("workspaceId") String workspaceId, + @PathParam("workspaceWorkflowId") String workspaceWorkflowId) throws UserNotFoundException, WorkspaceNotFoundException, AccessRightException { + workflowService.deleteWorkspaceWorkflow(workspaceId,workspaceWorkflowId); + return Response.ok().build(); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/collections/InstanceCollection.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/collections/InstanceCollection.java new file mode 100644 index 0000000000..ef6223cc51 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/collections/InstanceCollection.java @@ -0,0 +1,76 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.collections; + +import com.docdoku.core.configuration.PSFilter; +import com.docdoku.core.product.ConfigurationItemKey; +import com.docdoku.core.product.PartLink; +import com.docdoku.core.util.Tools; + +import java.util.List; + +/** + * @author Florent Garin + */ + +public class InstanceCollection { + + // Used for services call + private ConfigurationItemKey ciKey; + + // Used to walk the structure + private PSFilter filter; + + // All instances under these paths + private List> paths; + + public InstanceCollection(ConfigurationItemKey ciKey, PSFilter filter, List> paths) { + this.ciKey = ciKey; + this.filter = filter; + this.paths = paths; + } + + + public PSFilter getFilter() { + return filter; + } + + public ConfigurationItemKey getCiKey() { + return ciKey; + } + + public List> getPaths() { + return paths; + } + + public boolean isFiltered(List currentPath) { + for (List path : paths) { + if (filter(path, currentPath)) { + return true; + } + } + return false; + } + + private boolean filter(List path, List currentPath) { + return Tools.getPathAsString(currentPath).startsWith(Tools.getPathAsString(path)); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/collections/QueryResult.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/collections/QueryResult.java new file mode 100644 index 0000000000..b7fd885afc --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/collections/QueryResult.java @@ -0,0 +1,101 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.collections; + +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.query.Query; +import com.docdoku.core.query.QueryResultRow; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Morgan Guimard + */ + +public class QueryResult { + + private Query query; + private List rows = new ArrayList<>(); + + private ExportType exportType = ExportType.JSON; + + public QueryResult() { + } + + public QueryResult(Query query, List rows) { + this.query = query; + this.rows = rows; + } + + public QueryResult(List partRevisions, Query query) { + this.query = query; + for (PartRevision partRevision : partRevisions) { + rows.add(new QueryResultRow(partRevision)); + } + } + + public ExportType getExportType() { + return exportType; + } + + public void setExportType(ExportType exportType) { + this.exportType = exportType; + } + + public Query getQuery() { + return query; + } + + public void setQuery(Query query) { + this.query = query; + } + + public List getRows() { + return rows; + } + + public void setRows(List rows) { + this.rows = rows; + } + + public void mergeRows(List rows) { + if (rows != null && !rows.isEmpty()) { + List mergedRows = new ArrayList<>(); + for (QueryResultRow row : rows) { + for (QueryResultRow filteredRow : this.rows) { + if (filteredRow.getPartRevision().equals(row.getPartRevision())) { + mergedRows.add(row); + break; + } + } + } + + this.rows = mergedRows; + } + } + + public enum ExportType { + JSON, CSV, XLS + } + + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/collections/VirtualInstanceCollection.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/collections/VirtualInstanceCollection.java new file mode 100644 index 0000000000..0e164f8685 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/collections/VirtualInstanceCollection.java @@ -0,0 +1,58 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.collections; + +import com.docdoku.core.configuration.PSFilter; +import com.docdoku.core.product.PartRevision; + +/** + * @author Morgan Guimard + */ + +public class VirtualInstanceCollection { + + private PartRevision rootPart; + private PSFilter filter; + + public VirtualInstanceCollection() { + } + + public VirtualInstanceCollection(PartRevision rootPart, PSFilter filter) { + this.rootPart = rootPart; + this.filter = filter; + } + + public PartRevision getRootPart() { + return rootPart; + } + + public void setRootPart(PartRevision rootPart) { + this.rootPart = rootPart; + } + + public PSFilter getFilter() { + return filter; + } + + public void setFilter(PSFilter filter) { + this.filter = filter; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/AclDozerConverter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/AclDozerConverter.java new file mode 100644 index 0000000000..2b60749b0a --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/AclDozerConverter.java @@ -0,0 +1,68 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.converters; + +import com.docdoku.core.common.User; +import com.docdoku.core.common.UserGroup; +import com.docdoku.core.security.ACL; +import com.docdoku.core.security.ACLUserEntry; +import com.docdoku.core.security.ACLUserGroupEntry; +import com.docdoku.server.rest.dto.ACLDTO; +import org.dozer.DozerConverter; + +import java.util.Map; + + +public class AclDozerConverter extends DozerConverter { + + + public AclDozerConverter() { + super(ACL.class, ACLDTO.class); + } + + @Override + public ACLDTO convertTo(ACL acl, ACLDTO aclDTO) { + + aclDTO = new ACLDTO(); + + if (acl != null) { + + for (Map.Entry entry : acl.getUserEntries().entrySet()) { + ACLUserEntry aclEntry = entry.getValue(); + aclDTO.addUserEntry(aclEntry.getPrincipalLogin(), aclEntry.getPermission()); + } + + for (Map.Entry entry : acl.getGroupEntries().entrySet()) { + ACLUserGroupEntry aclEntry = entry.getValue(); + aclDTO.addGroupEntry(aclEntry.getPrincipalId(), aclEntry.getPermission()); + } + return aclDTO; + } + + return null; + } + + @Override + public ACL convertFrom(ACLDTO aclDTO, ACL acl) { + return acl; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/ActivityDozerConverter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/ActivityDozerConverter.java new file mode 100644 index 0000000000..b935bdf519 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/ActivityDozerConverter.java @@ -0,0 +1,99 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.converters; + +import com.docdoku.core.workflow.Activity; +import com.docdoku.core.workflow.ParallelActivity; +import com.docdoku.core.workflow.SequentialActivity; +import com.docdoku.core.workflow.Task; +import com.docdoku.server.rest.dto.ActivityDTO; +import com.docdoku.server.rest.dto.TaskDTO; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.DozerConverter; +import org.dozer.Mapper; + +import java.util.ArrayList; +import java.util.List; + +public class ActivityDozerConverter extends DozerConverter { + + private Mapper mapper; + + public ActivityDozerConverter() { + super(Activity.class, ActivityDTO.class); + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @Override + public ActivityDTO convertTo(Activity activity, ActivityDTO activityDTO) { + List tasksDTO = new ArrayList<>(); + + for (int i = 0; i < activity.getTasks().size(); i++) { + tasksDTO.add(mapper.map(activity.getTasks().get(i), TaskDTO.class)); + } + + ActivityDTO.Type type; + Integer tasksToComplete = null; + Integer relaunchStep = null; + + if (activity.getRelaunchActivity() != null) { + relaunchStep = activity.getRelaunchActivity().getStep(); + } + + if (activity instanceof SequentialActivity) { + type = ActivityDTO.Type.SEQUENTIAL; + } else if (activity instanceof ParallelActivity) { + type = ActivityDTO.Type.PARALLEL; + tasksToComplete = ((ParallelActivity) activity).getTasksToComplete(); + } else { + throw new IllegalArgumentException("Activity type not supported"); + } + + return new ActivityDTO(activity.getStep(), tasksDTO, activity.getLifeCycleState(), type, tasksToComplete, activity.isComplete(), activity.isStopped(), activity.isInProgress(), activity.isToDo(), relaunchStep); + } + + @Override + public Activity convertFrom(ActivityDTO activityDTO, Activity pActivity) { + List tasks = new ArrayList<>(); + for (int i = 0; i < activityDTO.getTasks().size(); i++) { + tasks.add(mapper.map(activityDTO.getTasks().get(i), Task.class)); + } + + Activity activity; + + switch (activityDTO.getType()) { + case SEQUENTIAL: + activity = new SequentialActivity(); + break; + case PARALLEL: + activity = new ParallelActivity(); + ((ParallelActivity) activity).setTasksToComplete(activityDTO.getTasksToComplete()); + break; + default: + throw new IllegalArgumentException("ActivityDTO type not supported"); + } + + activity.setStep(activityDTO.getStep()); + activity.setTasks(tasks); + activity.setLifeCycleState(activityDTO.getLifeCycleState()); + return activity; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/ActivityModelDozerConverter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/ActivityModelDozerConverter.java new file mode 100644 index 0000000000..2d9bc9cb16 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/ActivityModelDozerConverter.java @@ -0,0 +1,105 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.converters; + +import com.docdoku.core.workflow.ActivityModel; +import com.docdoku.core.workflow.ParallelActivityModel; +import com.docdoku.core.workflow.SequentialActivityModel; +import com.docdoku.core.workflow.TaskModel; +import com.docdoku.server.rest.dto.ActivityModelDTO; +import com.docdoku.server.rest.dto.TaskModelDTO; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.DozerConverter; +import org.dozer.Mapper; + +import java.util.ArrayList; +import java.util.List; + + +public class ActivityModelDozerConverter extends DozerConverter { + + private Mapper mapper; + + public ActivityModelDozerConverter() { + super(ActivityModel.class, ActivityModelDTO.class); + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + @Override + public ActivityModelDTO convertTo(ActivityModel activityModel, ActivityModelDTO activityModelDTO) { + + List taskModelsDTO = new ArrayList<>(); + for (int i = 0; i < activityModel.getTaskModels().size(); i++) { + taskModelsDTO.add(mapper.map(activityModel.getTaskModels().get(i), TaskModelDTO.class)); + } + + ActivityModelDTO.Type type; + Integer tasksToComplete = null; + Integer relaunchStep = null; + + if (activityModel.getRelaunchActivity() != null) { + relaunchStep = activityModel.getRelaunchActivity().getStep(); + } + + if (activityModel instanceof SequentialActivityModel) { + type = ActivityModelDTO.Type.SEQUENTIAL; + } else if (activityModel instanceof ParallelActivityModel) { + type = ActivityModelDTO.Type.PARALLEL; + tasksToComplete = ((ParallelActivityModel) activityModel).getTasksToComplete(); + } else { + throw new IllegalArgumentException("ActivityModel type not supported"); + } + + return new ActivityModelDTO(activityModel.getStep(), taskModelsDTO, activityModel.getLifeCycleState(), type, tasksToComplete, relaunchStep); + } + + @Override + public ActivityModel convertFrom(ActivityModelDTO activityModelDTO, ActivityModel pActivityModel) { + + List taskModels = new ArrayList<>(); + for (int i = 0; i < activityModelDTO.getTaskModels().size(); i++) { + taskModels.add(mapper.map(activityModelDTO.getTaskModels().get(i), TaskModel.class)); + } + + ActivityModel activityModel; + + switch (activityModelDTO.getType()) { + case SEQUENTIAL: { + activityModel = new SequentialActivityModel(); + activityModel.setTaskModels(taskModels); + break; + } + case PARALLEL: { + activityModel = new ParallelActivityModel(); + activityModel.setTaskModels(taskModels); + ((ParallelActivityModel) activityModel).setTasksToComplete(activityModelDTO.getTasksToComplete()); + break; + } + default: { + throw new IllegalArgumentException("ActivityModelDTO type not supported"); + } + } + + activityModel.setStep(activityModelDTO.getStep()); + activityModel.setLifeCycleState(activityModelDTO.getLifeCycleState()); + return activityModel; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/BinaryResourceToStringDozerConverter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/BinaryResourceToStringDozerConverter.java new file mode 100644 index 0000000000..b8e7fa9eea --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/BinaryResourceToStringDozerConverter.java @@ -0,0 +1,51 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.converters; + +import com.docdoku.core.common.BinaryResource; +import org.dozer.DozerConverter; + +/** + * @author Florent Garin + */ +public class BinaryResourceToStringDozerConverter extends DozerConverter { + + public BinaryResourceToStringDozerConverter() { + super(BinaryResource.class, String.class); + } + + @Override + public String convertTo(BinaryResource source, String destination) { + return (source != null) ? source.getFullName() : null; + } + + @Override + public BinaryResource convertFrom(String source, BinaryResource destination) { + if (source != null) { + BinaryResource bin = new BinaryResource(); + bin.setFullName(source); + return bin; + } else { + return null; + } + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/CustomConverterProvider.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/CustomConverterProvider.java new file mode 100644 index 0000000000..9fe6548b7b --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/CustomConverterProvider.java @@ -0,0 +1,45 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.converters; + +import javax.ws.rs.ext.ParamConverter; +import javax.ws.rs.ext.ParamConverterProvider; +import javax.ws.rs.ext.Provider; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Date; + +/** + * Created by Florent Garin on 12/03/15. + */ +@Provider +public class CustomConverterProvider implements ParamConverterProvider { + + private final DateAdapter dateAdapter = new DateAdapter(); + + @Override + public ParamConverter getConverter(Class clazz, Type type, Annotation[] annotations) { + if (clazz.getName().equals(Date.class.getName())) { + return (ParamConverter) dateAdapter; + } + return null; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/DateAdapter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/DateAdapter.java new file mode 100644 index 0000000000..8b1302d6fe --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/DateAdapter.java @@ -0,0 +1,75 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.converters; + +/** + * Created by Charles Fallourd on 01/06/15. + */ + +import javax.ws.rs.ext.ParamConverter; +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; +import java.util.logging.Level; +import java.util.logging.Logger; + + +public class DateAdapter extends XmlAdapter implements ParamConverter { + + + private static final Logger LOGGER = Logger.getLogger(DateAdapter.class.getName()); + + private final static String PATTERN = "yyyy-MM-dd'T'HH:mm:ss"; + private final static SimpleDateFormat DF = new SimpleDateFormat(PATTERN); + + static { + DF.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + public String marshal(Date date) { + if (date == null) { + return null; + } + return DF.format(date); + } + + public Date unmarshal(String dateString) { + Date d = null; + try { + d = DF.parse(dateString); + } catch (ParseException e) { + LOGGER.log(Level.SEVERE, "Error unmarshalling date", e); + } + return d; + } + + @Override + public Date fromString(String s) { + return unmarshal(s); + } + + @Override + public String toString(Date date) { + return marshal(date); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/InstanceAttributeDozerConverter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/InstanceAttributeDozerConverter.java new file mode 100644 index 0000000000..a72e3185cf --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/converters/InstanceAttributeDozerConverter.java @@ -0,0 +1,118 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.converters; + +import com.docdoku.core.meta.*; +import com.docdoku.server.rest.dto.InstanceAttributeDTO; +import com.docdoku.server.rest.dto.NameValuePairDTO; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.DozerConverter; +import org.dozer.Mapper; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +/** + * @author Florent Garin + */ +public class InstanceAttributeDozerConverter extends DozerConverter { + + public InstanceAttributeDozerConverter() { + super(InstanceAttribute.class, InstanceAttributeDTO.class); + } + + + @Override + public InstanceAttributeDTO convertTo(InstanceAttribute source, InstanceAttributeDTO bdestination) { + Mapper mapper = DozerBeanMapperSingletonWrapper.getInstance(); + InstanceAttributeDTO.Type type; + String value = ""; + List itemsDTO = null; + if (source instanceof InstanceBooleanAttribute) { + type = InstanceAttributeDTO.Type.BOOLEAN; + value = source.getValue() + ""; + } else if (source instanceof InstanceTextAttribute) { + type = InstanceAttributeDTO.Type.TEXT; + value = source.getValue() + ""; + } else if (source instanceof InstanceNumberAttribute) { + type = InstanceAttributeDTO.Type.NUMBER; + value = source.getValue() + ""; + } else if (source instanceof InstanceDateAttribute) { + type = InstanceAttributeDTO.Type.DATE; + Date date = ((InstanceDateAttribute) source).getDateValue(); + if (date != null) { + DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + df.setTimeZone(TimeZone.getTimeZone("UTC")); + value = df.format(date); + } + } else if (source instanceof InstanceURLAttribute) { + type = InstanceAttributeDTO.Type.URL; + value = source.getValue() + ""; + } else if (source instanceof InstanceListOfValuesAttribute) { + type = InstanceAttributeDTO.Type.LOV; + value = ((InstanceListOfValuesAttribute) source).getIndexValue() + ""; + + List items = null; + items = ((InstanceListOfValuesAttribute) source).getItems(); + itemsDTO = new ArrayList<>(); + for (NameValuePair item : items) { + itemsDTO.add(mapper.map(item, NameValuePairDTO.class)); + } + } else if (source instanceof InstanceLongTextAttribute) { + type = InstanceAttributeDTO.Type.LONG_TEXT; + value = source.getValue() + ""; + } + else { + throw new IllegalArgumentException("Instance attribute not supported"); + } + InstanceAttributeDTO dto = new InstanceAttributeDTO(source.getName(), type, value, source.isMandatory(), source.isLocked()); + if (itemsDTO != null) { + dto.setItems(itemsDTO); + } + return dto; + } + + @Override + public InstanceAttribute convertFrom(InstanceAttributeDTO source, InstanceAttribute destination) { + //TODO: complete method, should not be called, not taking into account LOV Attributes. + switch (source.getType()) { + case BOOLEAN: + return new InstanceBooleanAttribute(source.getName(), Boolean.parseBoolean(source.getValue()), source.isMandatory()); + case DATE: + return new InstanceDateAttribute(source.getName(), new Date(Long.parseLong(source.getValue())), source.isMandatory()); + case NUMBER: + return new InstanceNumberAttribute(source.getName(), Float.parseFloat(source.getValue()), source.isMandatory()); + case TEXT: + return new InstanceTextAttribute(source.getName(), source.getValue(), source.isMandatory()); + case URL: + return new InstanceURLAttribute(source.getName(), source.getValue(), source.isMandatory()); + case LONG_TEXT: + return new InstanceLongTextAttribute(source.getName(), source.getValue(), source.isMandatory()); + + } + throw new IllegalArgumentException("Instance attribute not supported"); + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ACLDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ACLDTO.java new file mode 100644 index 0000000000..ead9ab29dd --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ACLDTO.java @@ -0,0 +1,75 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import com.docdoku.core.security.ACL; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +@XmlRootElement +public class ACLDTO implements Serializable { + + @XmlElement(nillable = true) + protected Map userEntries = new HashMap(); + + @XmlElement(nillable = true) + protected Map groupEntries = new HashMap(); + + public ACLDTO() { + } + + public void addUserEntry(String login, ACL.Permission perm) { + userEntries.put(login, perm); + } + + public void addGroupEntry(String groupId, ACL.Permission perm) { + groupEntries.put(groupId, perm); + } + + public void removeUserEntry(String login) { + userEntries.remove(login); + } + + public void removeGroupEntry(String groupId) { + groupEntries.remove(groupId); + } + + public Map getGroupEntries() { + return groupEntries; + } + + public void setGroupEntries(Map groupEntries) { + this.groupEntries = groupEntries; + } + + public Map getUserEntries() { + return userEntries; + } + + public void setUserEntries(Map userEntries) { + this.userEntries = userEntries; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/AccountDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/AccountDTO.java new file mode 100644 index 0000000000..6fa3960b81 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/AccountDTO.java @@ -0,0 +1,95 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class AccountDTO implements Serializable { + + private String login; + private String name; + private String email; + private String language; + private String timeZone; + private boolean admin; + private String newPassword; + + public AccountDTO() { + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getTimeZone() { + return timeZone; + } + + public void setTimeZone(String timeZone) { + this.timeZone = timeZone; + } + + public boolean isAdmin() { + return admin; + } + + public void setAdmin(boolean admin) { + this.admin = admin; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ActivityDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ActivityDTO.java new file mode 100644 index 0000000000..1aea5d0597 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ActivityDTO.java @@ -0,0 +1,143 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@XmlRootElement +public class ActivityDTO implements Serializable { + + private int step; + private Integer relaunchStep; + private List tasks; + private String lifeCycleState; + private Type type; + private Integer tasksToComplete; + private boolean complete; + private boolean stopped; + private boolean inProgress; + private boolean toDo; + + public ActivityDTO(int step, List tasks, String lifeCycleState, Type type, Integer tasksToComplete, boolean complete, boolean stopped, boolean inProgress, boolean toDo, Integer relaunchStep) { + this.step = step; + this.relaunchStep = relaunchStep; + this.tasks = tasks; + this.lifeCycleState = lifeCycleState; + this.type = type; + this.tasksToComplete = tasksToComplete; + this.complete = complete; + this.stopped = stopped; + this.inProgress = inProgress; + this.toDo = toDo; + } + + public ActivityDTO() { + tasks = new ArrayList<>(); + } + + public Integer getTasksToComplete() { + return tasksToComplete; + } + + public void setTasksToComplete(Integer tasksToComplete) { + this.tasksToComplete = tasksToComplete; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public List getTasks() { + return tasks; + } + + public void setTasks(List tasks) { + this.tasks = tasks; + } + + public boolean isStopped() { + return stopped; + } + + public void setStopped(boolean stopped) { + this.stopped = stopped; + } + + public boolean isComplete() { + return complete; + } + + public void setComplete(boolean complete) { + this.complete = complete; + } + + public boolean isInProgress() { + return inProgress; + } + + public void setInProgress(boolean inProgress) { + this.inProgress = inProgress; + } + + public boolean isToDo() { + return toDo; + } + + public void setToDo(boolean toDo) { + this.toDo = toDo; + } + + public String getLifeCycleState() { + return lifeCycleState; + } + + public void setLifeCycleState(String lifeCycleState) { + this.lifeCycleState = lifeCycleState; + } + + public Integer getStep() { + return step; + } + + public void setStep(Integer step) { + this.step = step; + } + + public Integer getRelaunchStep() { + return relaunchStep; + } + + public void setRelaunchStep(Integer relaunchStep) { + this.relaunchStep = relaunchStep; + } + + public enum Type { + SEQUENTIAL, + PARALLEL + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ActivityModelDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ActivityModelDTO.java new file mode 100644 index 0000000000..fdf55410a4 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ActivityModelDTO.java @@ -0,0 +1,110 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@XmlRootElement +public class ActivityModelDTO implements Serializable { + + private int step; + private Integer relaunchStep; + + @XmlElement(nillable = false, required = true) + private List taskModels; + + private String lifeCycleState; + private Type type; + private Integer tasksToComplete; + + public ActivityModelDTO() { + this.taskModels = new ArrayList(); + } + + public ActivityModelDTO(int step, List taskModels, String lifeCycleState, Type type, Integer tasksToComplete, Integer relaunchStep) { + this.step = step; + this.relaunchStep = relaunchStep; + this.taskModels = taskModels; + this.lifeCycleState = lifeCycleState; + this.type = type; + this.tasksToComplete = tasksToComplete; + } + + public int getStep() { + return step; + } + + public void setStep(int step) { + this.step = step; + } + + public void addTaskModel(TaskModelDTO m) { + this.taskModels.add(m); + } + + public void removeTaskModel(TaskModelDTO m) { + this.taskModels.remove(m); + } + + public List getTaskModels() { + return this.taskModels; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public Integer getTasksToComplete() { + return tasksToComplete; + } + + public void setTasksToComplete(Integer tasksToComplete) { + this.tasksToComplete = tasksToComplete; + } + + public String getLifeCycleState() { + return lifeCycleState; + } + + public void setLifeCycleState(String lifeCycleState) { + this.lifeCycleState = lifeCycleState; + } + + public Integer getRelaunchStep() { + return relaunchStep; + } + + public void setRelaunchStep(Integer relaunchStep) { + this.relaunchStep = relaunchStep; + } + + public enum Type { + SEQUENTIAL, PARALLEL + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/CADInstanceDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/CADInstanceDTO.java new file mode 100644 index 0000000000..5a312c47aa --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/CADInstanceDTO.java @@ -0,0 +1,116 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import com.docdoku.core.product.CADInstance; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class CADInstanceDTO implements Serializable { + + private Double rx; + private Double ry; + private Double rz; + private Double tx; + private Double ty; + private Double tz; + private double[] matrix; + private CADInstance.RotationType rotationType; + + + public CADInstanceDTO() { + } + + public CADInstanceDTO(Double rx, Double ry, Double rz, Double tx, Double ty, Double tz) { + this.rx = rx; + this.ry = ry; + this.rz = rz; + this.tx = tx; + this.ty = ty; + this.tz = tz; + } + + public double[] getMatrix() { + return matrix; + } + + public void setMatrix(double[] matrix) { + this.matrix = matrix; + } + + public CADInstance.RotationType getRotationType() { + return rotationType; + } + + public void setRotationType(CADInstance.RotationType rotationType) { + this.rotationType = rotationType; + } + + public Double getRx() { + return rx; + } + + public void setRx(Double rx) { + this.rx = rx; + } + + public Double getRy() { + return ry; + } + + public void setRy(Double ry) { + this.ry = ry; + } + + public Double getRz() { + return rz; + } + + public void setRz(Double rz) { + this.rz = rz; + } + + public Double getTx() { + return tx; + } + + public void setTx(Double tx) { + this.tx = tx; + } + + public Double getTy() { + return ty; + } + + public void setTy(Double ty) { + this.ty = ty; + } + + public Double getTz() { + return tz; + } + + public void setTz(Double tz) { + this.tz = tz; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ComponentDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ComponentDTO.java new file mode 100644 index 0000000000..4605c2ab70 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ComponentDTO.java @@ -0,0 +1,299 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * @author Julien Maffre + */ +@XmlRootElement +public class ComponentDTO implements Serializable { + + private String author; + private String authorLogin; + private String number; + private String name = ""; + private String version; + private int iteration; + private String description; + private boolean standardPart; + private boolean assembly; + private boolean substitute; + private String partUsageLinkId; + private String partUsageLinkReferenceDescription; + private List components; + private double amount; + private String unit; + private List attributes; + private UserDTO checkOutUser; + private Date checkOutDate; + private boolean released; + private boolean obsolete; + private boolean optional; + @XmlElement(nillable = true) + private int lastIterationNumber; + @XmlElement(nillable = true) + private boolean accessDeny; + @XmlElement(nillable = true) + private List substituteIds; + private List notifications; + private boolean hasPathData; + private boolean isVirtual; + private String path; + + + public ComponentDTO() { + + } + + public ComponentDTO(String number) { + this.number = number; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public boolean isAssembly() { + return assembly; + } + + public void setAssembly(boolean assembly) { + this.assembly = assembly; + } + + public boolean isStandardPart() { + return standardPart; + } + + public void setStandardPart(boolean standardPart) { + this.standardPart = standardPart; + } + + public int getIteration() { + return iteration; + } + + public void setIteration(int iteration) { + this.iteration = iteration; + } + + public List getComponents() { + return components; + } + + public void setComponents(List components) { + this.components = components; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getPartUsageLinkId() { + return partUsageLinkId; + } + + public void setPartUsageLinkId(String partUsageLinkId) { + this.partUsageLinkId = partUsageLinkId; + } + + public List getAttributes() { + return attributes; + } + + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getAuthorLogin() { + return authorLogin; + } + + public void setAuthorLogin(String authorLogin) { + this.authorLogin = authorLogin; + } + + public double getAmount() { + return amount; + } + + public void setAmount(double amount) { + this.amount = amount; + } + + public UserDTO getCheckOutUser() { + return checkOutUser; + } + + public void setCheckOutUser(UserDTO checkOutUser) { + this.checkOutUser = checkOutUser; + } + + public Date getCheckOutDate() { + return (checkOutDate != null) ? (Date) checkOutDate.clone() : null; + } + + public void setCheckOutDate(Date checkOutDate) { + this.checkOutDate = (checkOutDate != null) ? (Date) checkOutDate.clone() : null; + } + + public int getLastIterationNumber() { + return lastIterationNumber; + } + + public void setLastIterationNumber(int lastIterationNumber) { + this.lastIterationNumber = lastIterationNumber; + } + + public boolean isAccessDeny() { + return accessDeny; + } + + public void setAccessDeny(boolean accessDeny) { + this.accessDeny = accessDeny; + } + + public boolean isReleased() { + return released; + } + + public void setReleased(boolean released) { + this.released = released; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + public boolean isSubstitute() { + return substitute; + } + + public void setSubstitute(boolean substitute) { + this.substitute = substitute; + } + + public List getNotifications() { + return notifications; + } + + public void setNotifications(List notifications) { + this.notifications = notifications; + } + + public boolean isObsolete() { + return obsolete; + } + + public void setObsolete(boolean obsolete) { + this.obsolete = obsolete; + } + + public String getPartUsageLinkReferenceDescription() { + return partUsageLinkReferenceDescription; + } + + public void setPartUsageLinkReferenceDescription(String partUsageLinkReferenceDescription) { + this.partUsageLinkReferenceDescription = partUsageLinkReferenceDescription; + } + + public boolean isOptional() { + return optional; + } + + public void setOptional(boolean optional) { + this.optional = optional; + } + + public boolean isHasPathData() { + return hasPathData; + } + + public void setHasPathData(boolean hasPathData) { + this.hasPathData = hasPathData; + } + + public boolean isVirtual() { + return isVirtual; + } + + public void setVirtual(boolean isVirtual) { + this.isVirtual = isVirtual; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public List getSubstituteIds() { + return substituteIds; + } + + public void setSubstituteIds(List substituteIds) { + this.substituteIds = substituteIds; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ConfigurationItemDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ConfigurationItemDTO.java new file mode 100644 index 0000000000..5f973cac13 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ConfigurationItemDTO.java @@ -0,0 +1,128 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +/** + * @author Florent Garin + */ +@XmlRootElement +public class ConfigurationItemDTO implements Serializable { + + private String id; + private String workspaceId; + private String description; + private String designItemNumber; + private String designItemName; + private String designItemLatestVersion; + private UserDTO author; + private boolean hasModificationNotification; + private List pathToPathLinks; + + + public ConfigurationItemDTO() { + } + + public ConfigurationItemDTO(UserDTO author, String id, String workspaceId, String description, String designItemNumber, + String designItemName, String designItemLatestVersion) { + this.id = id; + this.author = author; + this.workspaceId = workspaceId; + this.description = description; + this.designItemNumber = designItemNumber; + this.designItemName = designItemName; + this.designItemLatestVersion = designItemLatestVersion; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getDesignItemNumber() { + return designItemNumber; + } + + public void setDesignItemNumber(String designItemNumber) { + this.designItemNumber = designItemNumber; + } + + public String getDesignItemName() { + return designItemName; + } + + public void setDesignItemName(String designItemName) { + this.designItemName = designItemName; + } + + public String getDesignItemLatestVersion() { + return designItemLatestVersion; + } + + public void setDesignItemLatestVersion(String designItemLatestVersion) { + this.designItemLatestVersion = designItemLatestVersion; + } + + public List getPathToPathLinks() { + return pathToPathLinks; + } + + public void setPathToPathLinks(List pathToPathLinks) { + this.pathToPathLinks = pathToPathLinks; + } + + public UserDTO getAuthor() { + return author; + } + + public void setAuthor(UserDTO author) { + this.author = author; + } + + public boolean isHasModificationNotification() { + return hasModificationNotification; + } + + public void setHasModificationNotification(boolean hasModificationNotification) { + this.hasModificationNotification = hasModificationNotification; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ConversionDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ConversionDTO.java new file mode 100644 index 0000000000..8142a41afd --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ConversionDTO.java @@ -0,0 +1,76 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; + +@XmlRootElement +public class ConversionDTO implements Serializable { + + private Date endDate; + private Date startDate; + private boolean succeed; + private boolean pending; + + public ConversionDTO() { + } + + public ConversionDTO(Date endDate, Date startDate, boolean succeed, boolean pending) { + this.endDate = endDate; + this.startDate = startDate; + this.succeed = succeed; + this.pending = pending; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public boolean isSucceed() { + return succeed; + } + + public void setSucceed(boolean succeed) { + this.succeed = succeed; + } + + public boolean isPending() { + return pending; + } + + public void setPending(boolean pending) { + this.pending = pending; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/CountDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/CountDTO.java new file mode 100644 index 0000000000..796da7ac91 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/CountDTO.java @@ -0,0 +1,45 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class CountDTO implements Serializable { + + private int count; + + public CountDTO(int count) { + this.count = count; + } + + public CountDTO() { + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DiskUsageSpaceDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DiskUsageSpaceDTO.java new file mode 100644 index 0000000000..756a6c3477 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DiskUsageSpaceDTO.java @@ -0,0 +1,51 @@ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +/** + * @author Morgan Guimard + */ +@XmlRootElement +public class DiskUsageSpaceDTO implements Serializable { + + private long documents; + private long parts; + private long documentTemplates; + private long partTemplates; + + public DiskUsageSpaceDTO() { + } + + public long getDocuments() { + return documents; + } + + public void setDocuments(long documents) { + this.documents = documents; + } + + public long getParts() { + return parts; + } + + public void setParts(long parts) { + this.parts = parts; + } + + public long getDocumentTemplates() { + return documentTemplates; + } + + public void setDocumentTemplates(long documentTemplates) { + this.documentTemplates = documentTemplates; + } + + public long getPartTemplates() { + return partTemplates; + } + + public void setPartTemplates(long partTemplates) { + this.partTemplates = partTemplates; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentCreationDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentCreationDTO.java new file mode 100644 index 0000000000..6b2076d569 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentCreationDTO.java @@ -0,0 +1,183 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +/** + * @author Yassine Belouad + */ +@XmlRootElement +public class DocumentCreationDTO implements Serializable, Comparable { + + private String workspaceId; + private String reference; + private String version; + private String type; + private String title; + private String description; + private String workflowModelId; + private String templateId; + private String path; + private RoleMappingDTO[] roleMapping; + private ACLDTO acl; + + public DocumentCreationDTO() { + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setWorkspaceID(String workspaceID) { + this.workspaceId = workspaceID; + } + + public String getReference() { + return reference; + } + + public void setReference(String reference) { + this.reference = reference; + } + + public String getTemplateId() { + return templateId; + } + + public void setTemplateId(String templateId) { + this.templateId = templateId; + } + + public void setDocumentMsTemplate(String templateId) { + this.templateId = templateId; + } + + public String getWorkflowModelId() { + return workflowModelId; + } + + public void setWorkflowModelId(String workflowModelId) { + this.workflowModelId = workflowModelId; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + + public RoleMappingDTO[] getRoleMapping() { + return roleMapping; + } + + public void setRoleMapping(RoleMappingDTO[] roleMapping) { + this.roleMapping = roleMapping; + } + + public ACLDTO getAcl() { + return acl; + } + + public void setAcl(ACLDTO acl) { + this.acl = acl; + } + + @Override + public String toString() { + return workspaceId + "-" + reference + "-" + version; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof DocumentCreationDTO)) { + return false; + } + DocumentCreationDTO docM = (DocumentCreationDTO) pObj; + return docM.reference.equals(reference) && docM.workspaceId.equals(workspaceId) && docM.version.equals(version); + + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + reference.hashCode(); + hash = 31 * hash + version.hashCode(); + return hash; + } + + public int compareTo(DocumentCreationDTO pDocM) { + int wksComp = workspaceId.compareTo(pDocM.workspaceId); + if (wksComp != 0) { + return wksComp; + } + int refComp = reference.compareTo(pDocM.reference); + if (refComp != 0) { + return refComp; + } else { + return version.compareTo(pDocM.version); + } + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentIterationDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentIterationDTO.java new file mode 100644 index 0000000000..7ec0970742 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentIterationDTO.java @@ -0,0 +1,180 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * @author Florent Garin + */ +@XmlRootElement +public class DocumentIterationDTO implements Serializable { + + private String workspaceId; + private String id; + private String documentMasterId; + private String version; + private int iteration; + private Date creationDate; + private Date modificationDate; + private Date checkInDate; + private String title; + private UserDTO author; + @XmlElement(nillable = true) + private String revisionNote; + private List attachedFiles; + private List instanceAttributes; + private List linkedDocuments; + + public DocumentIterationDTO() { + } + + public DocumentIterationDTO(String pWorkspaceId, String pDocumentMasterId, String pVersion, int pIteration) { + workspaceId = pWorkspaceId; + documentMasterId = pDocumentMasterId; + version = pVersion; + iteration = pIteration; + } + + public UserDTO getAuthor() { + return author; + } + + public void setAuthor(UserDTO author) { + this.author = author; + } + + public String getId() { + return documentMasterId + "-" + version + "-" + iteration; + } + + public void setId(String id) { + this.id = id; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public Date getModificationDate() { + return modificationDate; + } + + public void setModificationDate(Date modificationDate) { + this.modificationDate = modificationDate; + } + + public Date getCheckInDate() { + return checkInDate; + } + + public void setCheckInDate(Date checkInDate) { + this.checkInDate = checkInDate; + } + + public String getRevisionNote() { + return revisionNote; + } + + public void setRevisionNote(String pRevisionNote) { + revisionNote = pRevisionNote; + } + + public List getAttachedFiles() { + return attachedFiles; + } + + public void setAttachedFiles(List attachedFiles) { + this.attachedFiles = attachedFiles; + } + + public List getLinkedDocuments() { + return linkedDocuments; + } + + public void setLinkedDocuments(List linkedDocuments) { + this.linkedDocuments = linkedDocuments; + } + + public List getInstanceAttributes() { + return instanceAttributes; + } + + public void setInstanceAttributes(List instanceAttributes) { + this.instanceAttributes = instanceAttributes; + } + + @Override + public String toString() { + return workspaceId + "-" + documentMasterId + "-" + version + "-" + iteration; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String pWorkspaceId) { + workspaceId = pWorkspaceId; + } + + public String getDocumentMasterId() { + return documentMasterId; + } + + public void setDocumentMasterId(String pDocumentMasterId) { + documentMasterId = pDocumentMasterId; + } + + public int getIteration() { + return iteration; + } + + public void setIteration(int pIteration) { + iteration = pIteration; + } + + public DocumentRevisionDTO getDocumentRevision() { + return new DocumentRevisionDTO(workspaceId, id + "-" + version, version); + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentIterationLinkDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentIterationLinkDTO.java new file mode 100644 index 0000000000..38e606f109 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentIterationLinkDTO.java @@ -0,0 +1,85 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class DocumentIterationLinkDTO implements Serializable { + + private String workspaceId; + private String documentMasterId; + private String version; + private int iteration; + private String title; + private String commentLink; + + public DocumentIterationLinkDTO() { + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getDocumentMasterId() { + return documentMasterId; + } + + public void setDocumentMasterId(String documentMasterId) { + this.documentMasterId = documentMasterId; + } + + public int getIteration() { + return iteration; + } + + public void setIteration(int iteration) { + this.iteration = iteration; + } + + public String getCommentLink() { + return commentLink; + } + + public void setCommentLink(String commentLink) { + this.commentLink = commentLink; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentIterationListDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentIterationListDTO.java new file mode 100644 index 0000000000..d24c6dea74 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentIterationListDTO.java @@ -0,0 +1,44 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +/** + * @author Morgan Guimard + */ +@XmlRootElement +public class DocumentIterationListDTO implements Serializable { + + private List documents; + + public DocumentIterationListDTO() { + } + + public List getDocuments() { + return documents; + } + + public void setDocuments(List documents) { + this.documents = documents; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentMasterTemplateDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentMasterTemplateDTO.java new file mode 100644 index 0000000000..0b3aafb285 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentMasterTemplateDTO.java @@ -0,0 +1,164 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * @author Florent Garin + */ +@XmlRootElement +public class DocumentMasterTemplateDTO implements Serializable { + + private String workspaceId; + private String id; + private String documentType; + private UserDTO author; + private Date creationDate; + private Date modificationDate; + private boolean idGenerated; + private String mask; + private String workflowModelId; + + private List attachedFiles; + private List attributeTemplates; + private boolean attributesLocked; + private ACLDTO acl; + + public DocumentMasterTemplateDTO() { + + } + + public DocumentMasterTemplateDTO(String workspaceId, String id, String documentType) { + this.workspaceId = workspaceId; + this.id = id; + this.documentType = documentType; + } + + public UserDTO getAuthor() { + return author; + } + + public void setAuthor(UserDTO author) { + this.author = author; + } + + + public String getDocumentType() { + return documentType; + } + + public void setDocumentType(String documentType) { + this.documentType = documentType; + } + + public String getMask() { + return mask; + } + + public void setMask(String mask) { + this.mask = mask; + } + + + public String getWorkflowModelId() { + return workflowModelId; + } + + public void setWorkflowModelId(String workflowModelId) { + this.workflowModelId = workflowModelId; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public List getAttachedFiles() { + return attachedFiles; + } + + public void setAttachedFiles(List attachedFiles) { + this.attachedFiles = attachedFiles; + } + + public Date getModificationDate() { + return modificationDate; + } + + public void setModificationDate(Date modificationDate) { + this.modificationDate = modificationDate; + } + + public boolean isIdGenerated() { + return idGenerated; + } + + public void setIdGenerated(boolean idGenerated) { + this.idGenerated = idGenerated; + } + + public List getAttributeTemplates() { + return attributeTemplates; + } + + public void setAttributeTemplates(List attributeTemplates) { + this.attributeTemplates = attributeTemplates; + } + + public boolean isAttributesLocked() { + return attributesLocked; + } + + public void setAttributesLocked(boolean attributesLocked) { + this.attributesLocked = attributesLocked; + } + + public ACLDTO getAcl() { + return acl; + } + + public void setAcl(ACLDTO acl) { + this.acl = acl; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentRevisionDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentRevisionDTO.java new file mode 100644 index 0000000000..53edad9c4c --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentRevisionDTO.java @@ -0,0 +1,392 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import com.docdoku.core.document.DocumentRevision; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * @author Florent Garin + */ +@XmlRootElement +public class DocumentRevisionDTO implements Serializable, Comparable { + + private String workspaceId; + private String id; + private String documentMasterId; + private String version; + private String type; + private UserDTO author; + private Date creationDate; + @XmlElement(nillable = true) + private String commentLink; + private String title; + + @XmlElement(nillable = true) + private UserDTO checkOutUser; + + @XmlElement(nillable = true) + private Date checkOutDate; + + private String[] tags; + private String description; + private boolean iterationSubscription; + private boolean stateSubscription; + private List documentIterations; + private WorkflowDTO workflow; + private Integer workflowId; + private String path; + private String routePath; + private String lifeCycleState; + private boolean publicShared; + private boolean attributesLocked; + + @XmlElement(nillable = true) + private DocumentRevision.RevisionStatus status; + private Date obsoleteDate; + @XmlElement(nillable = true) + private UserDTO obsoleteAuthor; + private Date releaseDate; + @XmlElement(nillable = true) + private UserDTO releaseAuthor; + + @XmlElement(nillable = true) + private ACLDTO acl; + + public DocumentRevisionDTO() { + } + + public DocumentRevisionDTO(String workspaceId, String id, String version) { + this.workspaceId = workspaceId; + this.id = id; + this.documentMasterId = id; + this.version = version; + } + + public DocumentRevisionDTO(String workspaceId, String id, String title, String version) { + this.workspaceId = workspaceId; + this.id = id; + this.documentMasterId = id; + this.title = title; + this.version = version; + } + + public Integer getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(Integer workflowId) { + this.workflowId = workflowId; + } + + public UserDTO getAuthor() { + return author; + } + + public void setAuthor(UserDTO author) { + this.author = author; + } + + public UserDTO getCheckOutUser() { + return checkOutUser; + } + + public void setCheckOutUser(UserDTO checkOutUser) { + this.checkOutUser = checkOutUser; + } + + public Date getCheckOutDate() { + return (checkOutDate != null) ? (Date) checkOutDate.clone() : null; + } + + public void setCheckOutDate(Date checkOutDate) { + this.checkOutDate = (checkOutDate != null) ? (Date) checkOutDate.clone() : null; + } + + public Date getCreationDate() { + return (creationDate != null) ? (Date) creationDate.clone() : null; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = (creationDate != null) ? (Date) creationDate.clone() : null; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public WorkflowDTO getWorkflow() { + return workflow; + } + + public void setWorkflow(WorkflowDTO workflow) { + this.workflow = workflow; + } + + public String getLifeCycleState() { + if (lifeCycleState != null) { + return lifeCycleState; + } + if (workflow != null) { + return workflow.getLifeCycleState(); + } + return null; + } + + public void setLifeCycleState(String lifeCycleState) { + this.lifeCycleState = lifeCycleState; + } + + public String[] getTags() { + return tags; + } + + public void setTags(String[] tags) { + this.tags = tags; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + + public List getDocumentIterations() { + return documentIterations; + } + + public void setDocumentIterations(List documentIterations) { + this.documentIterations = documentIterations; + } + + public String getRoutePath() { + return routePath; + } + + public void setRoutePath(String routePath) { + this.routePath = routePath; + } + + @XmlTransient + public DocumentIterationDTO getLastIteration() { + if (documentIterations != null) { + int index = documentIterations.size() - 1; + if (index < 0) { + return null; + } else { + return documentIterations.get(index); + } + } else { + return null; + } + } + + public String getId() { + return id + "-" + version; + } + + public void setId(String id) { + this.id = id; + } + + public String getDocumentMasterId() { + return documentMasterId; + } + + public void setDocumentMasterId(String documentMasterId) { + this.documentMasterId = documentMasterId; + } + + public String getCommentLink() { + return commentLink; + } + + public void setCommentLink(String commentLink) { + this.commentLink = commentLink; + } + + public boolean isIterationSubscription() { + return iterationSubscription; + } + + public void setIterationSubscription(boolean iterationSubscription) { + this.iterationSubscription = iterationSubscription; + } + + public boolean isStateSubscription() { + return stateSubscription; + } + + public void setStateSubscription(boolean stateSubscription) { + this.stateSubscription = stateSubscription; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public boolean isPublicShared() { + return publicShared; + } + + public void setPublicShared(boolean publicShared) { + this.publicShared = publicShared; + } + + public ACLDTO getAcl() { + return acl; + } + + public void setAcl(ACLDTO acl) { + this.acl = acl; + } + + public boolean isAttributesLocked() { + return attributesLocked; + } + + public void setAttributesLocked(boolean attributesLocked) { + this.attributesLocked = attributesLocked; + } + + public DocumentRevision.RevisionStatus getStatus() { + return status; + } + + public void setStatus(DocumentRevision.RevisionStatus status) { + this.status = status; + } + + public Date getObsoleteDate() { + return obsoleteDate; + } + + public void setObsoleteDate(Date obsoleteDate) { + this.obsoleteDate = obsoleteDate; + } + + public UserDTO getObsoleteAuthor() { + return obsoleteAuthor; + } + + public void setObsoleteAuthor(UserDTO obsoleteAuthor) { + this.obsoleteAuthor = obsoleteAuthor; + } + + public Date getReleaseDate() { + return releaseDate; + } + + public void setReleaseDate(Date releasedDate) { + this.releaseDate = releasedDate; + } + + public UserDTO getReleaseAuthor() { + return releaseAuthor; + } + + public void setReleaseAuthor(UserDTO releasedAuthor) { + this.releaseAuthor = releasedAuthor; + } + + @Override + public String toString() { + return workspaceId + "-" + id + "-" + version; + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof DocumentRevisionDTO)) { + return false; + } + DocumentRevisionDTO docR = (DocumentRevisionDTO) pObj; + return docR.id.equals(id) && + docR.workspaceId.equals(workspaceId) && + docR.version.equals(version); + + } + + @Override + public int hashCode() { + int hash = 1; + hash = 31 * hash + workspaceId.hashCode(); + hash = 31 * hash + id.hashCode(); + hash = 31 * hash + version.hashCode(); + return hash; + } + + public int compareTo(DocumentRevisionDTO pDocR) { + int wksComp = workspaceId.compareTo(pDocR.workspaceId); + if (wksComp != 0) { + return wksComp; + } + int idComp = id.compareTo(pDocR.id); + if (idComp != 0) { + return idComp; + } else { + return version.compareTo(pDocR.version); + } + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentTemplateCreationDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentTemplateCreationDTO.java new file mode 100644 index 0000000000..db400a786d --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/DocumentTemplateCreationDTO.java @@ -0,0 +1,124 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +/** + * @author Florent Garin + */ +@XmlRootElement +public class DocumentTemplateCreationDTO implements Serializable { + + private String workspaceId; + private String reference; + private String documentType; + private boolean idGenerated; + private String mask; + private String workflowModelId; + + private List attachedFiles; + private List attributeTemplates; + private boolean attributesLocked; + + public DocumentTemplateCreationDTO() { + + } + + public DocumentTemplateCreationDTO(String workspaceId, String documentType) { + this.workspaceId = workspaceId; + this.documentType = documentType; + } + + public String getDocumentType() { + return documentType; + } + + public void setDocumentType(String documentType) { + this.documentType = documentType; + } + + public String getMask() { + return mask; + } + + public void setMask(String mask) { + this.mask = mask; + } + + public String getWorkflowModelId() { + return workflowModelId; + } + + public void setWorkflowModelId(String workflowModelId) { + this.workflowModelId = workflowModelId; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public List getAttachedFiles() { + return attachedFiles; + } + + public void setAttachedFiles(List attachedFiles) { + this.attachedFiles = attachedFiles; + } + + public boolean isIdGenerated() { + return idGenerated; + } + + public void setIdGenerated(boolean idGenerated) { + this.idGenerated = idGenerated; + } + + public List getAttributeTemplates() { + return attributeTemplates; + } + + public void setAttributeTemplates(List attributeTemplates) { + this.attributeTemplates = attributeTemplates; + } + + public String getReference() { + return reference; + } + + public void setReference(String reference) { + this.reference = reference; + } + + public boolean isAttributesLocked() { + return attributesLocked; + } + + public void setAttributesLocked(boolean attributesLocked) { + this.attributesLocked = attributesLocked; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/FileDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/FileDTO.java new file mode 100644 index 0000000000..c556ac07a5 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/FileDTO.java @@ -0,0 +1,64 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class FileDTO implements Serializable { + private boolean created; + private String fullName; + private String shortName; + + public FileDTO(boolean created, String fullName, String shortName) { + this.created = created; + this.fullName = fullName; + this.shortName = shortName; + } + + public FileDTO() { + } + + public boolean isCreated() { + return created; + } + + public void setCreated(boolean created) { + this.created = created; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getShortName() { + return shortName; + } + + public void setShortName(String shortName) { + this.shortName = shortName; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/FolderDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/FolderDTO.java new file mode 100644 index 0000000000..bc708e32b3 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/FolderDTO.java @@ -0,0 +1,107 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +/** + * @author Yassine Belouad + */ + +@XmlRootElement +public class FolderDTO implements Serializable { + + private String path; + private String id; + private String name; + private boolean home; + + + public FolderDTO() { + + } + + public FolderDTO(String parentFolder, String name) { + this.name = name.trim(); + path = parentFolder + "/" + this.name; + } + + public static String replaceSlashWithColon(String slashedCompletePath) { + return slashedCompletePath.replaceAll("/", ":"); + } + + public static String replaceColonWithSlash(String colonedCompletePath) { + return colonedCompletePath.replaceAll(":", "/"); + } + + public static String extractName(String slashedCompletePath) { + stripTrailingSlash(slashedCompletePath); + int lastSlash = slashedCompletePath.lastIndexOf('/'); + return slashedCompletePath.substring(lastSlash, slashedCompletePath.length()); + } + + public static String extractParentFolder(String slashedCompletePath) { + stripTrailingSlash(slashedCompletePath); + int lastSlash = slashedCompletePath.lastIndexOf('/'); + return slashedCompletePath.substring(0, lastSlash); + } + + private static String stripTrailingSlash(String completePath) { + if (completePath.charAt(completePath.length() - 1) == '/') { + return completePath.substring(0, completePath.length() - 1); + } else { + return completePath; + } + } + + public boolean isHome() { + return home; + } + + public void setHome(boolean home) { + this.home = home; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name.trim(); + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/GCMAccountDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/GCMAccountDTO.java new file mode 100644 index 0000000000..73f573f691 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/GCMAccountDTO.java @@ -0,0 +1,50 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class GCMAccountDTO implements Serializable { + + private String login; + private String gcmId; + + public GCMAccountDTO() { + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getGcmId() { + return gcmId; + } + + public void setGcmId(String gcmId) { + this.gcmId = gcmId; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ImportDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ImportDTO.java new file mode 100644 index 0000000000..45f6b21c21 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ImportDTO.java @@ -0,0 +1,117 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@XmlRootElement +public class ImportDTO implements Serializable { + + private String id; + private String fileName; + private Date endDate; + private Date startDate; + private boolean succeed; + private boolean pending; + private List errors; + private List warnings; + + public ImportDTO() { + } + + public ImportDTO(String id, String fileName, Date endDate, Date startDate, boolean succeed, boolean pending, List errors, List warnings) { + this.id = id; + this.fileName = fileName; + this.endDate = endDate; + this.startDate = startDate; + this.succeed = succeed; + this.pending = pending; + this.errors = errors; + this.warnings = warnings; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public boolean isSucceed() { + return succeed; + } + + public void setSucceed(boolean succeed) { + this.succeed = succeed; + } + + public boolean isPending() { + return pending; + } + + public void setPending(boolean pending) { + this.pending = pending; + } + + public List getErrors() { + return errors; + } + + public void setErrors(List errors) { + this.errors = errors; + } + + public List getWarnings() { + return warnings; + } + + public void setWarnings(List warnings) { + this.warnings = warnings; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/InstanceAttributeDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/InstanceAttributeDTO.java new file mode 100644 index 0000000000..faf9ad7a49 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/InstanceAttributeDTO.java @@ -0,0 +1,116 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +/** + * @author Yassine Belouad + */ +@XmlRootElement +public class InstanceAttributeDTO implements Serializable { + + private String name; + private boolean mandatory; + private boolean locked; + + private Type type; + private String value; + private String lovName; + private List items; + + public InstanceAttributeDTO() { + + } + + public InstanceAttributeDTO(String pName, Type pType, String pValue, Boolean pMandatory, Boolean pLocked) { + this.name = pName; + this.type = pType; + this.value = pValue; + this.mandatory = pMandatory; + this.locked = pLocked; + } + + public InstanceAttributeDTO(String pName, String pType, String pValue, Boolean pMandatory, Boolean pLocked) { + this(pName, InstanceAttributeDTO.Type.valueOf(pType), pValue, pMandatory, pLocked); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public InstanceAttributeDTO.Type getType() { + return type; + } + + public void setType(InstanceAttributeDTO.Type type) { + this.type = type; + } + + public boolean isMandatory() { + return mandatory; + } + + public void setMandatory(boolean mandatory) { + this.mandatory = mandatory; + } + + public boolean isLocked() { + return locked; + } + + public void setLocked(boolean locked) { + this.locked = locked; + } + + public String getLovName() { + return lovName; + } + + public void setLovName(String lovName) { + this.lovName = lovName; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public enum Type { + TEXT, NUMBER, DATE, BOOLEAN, URL, LOV, LONG_TEXT + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/InstanceAttributeDescriptorDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/InstanceAttributeDescriptorDTO.java new file mode 100644 index 0000000000..89482fc182 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/InstanceAttributeDescriptorDTO.java @@ -0,0 +1,63 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +/** + * @author Morgan Guimard + */ +@XmlRootElement +public class InstanceAttributeDescriptorDTO implements Serializable { + + private String name; + private String type; + private List lovItems; + + public InstanceAttributeDescriptorDTO() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List getLovItems() { + return lovItems; + } + + public void setLovItems(List lovItems) { + this.lovItems = lovItems; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/InstanceAttributeTemplateDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/InstanceAttributeTemplateDTO.java new file mode 100644 index 0000000000..65120aab67 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/InstanceAttributeTemplateDTO.java @@ -0,0 +1,114 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class InstanceAttributeTemplateDTO implements Serializable { + + private String name; + + private boolean mandatory; + + private AttributeType attributeType; + + private String lovName; + + private boolean locked; + + public InstanceAttributeTemplateDTO() { + } + + public InstanceAttributeTemplateDTO(String pName, AttributeType pAttributeType, boolean pMandatory, boolean locked) { + name = pName; + attributeType = pAttributeType; + mandatory = pMandatory; + this.locked = locked; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isMandatory() { + return mandatory; + } + + public void setMandatory(boolean mandatory) { + this.mandatory = mandatory; + } + + public InstanceAttributeTemplateDTO.AttributeType getAttributeType() { + return attributeType; + } + + public void setAttributeType(InstanceAttributeTemplateDTO.AttributeType attributeType) { + this.attributeType = attributeType; + } + + public String getLovName() { + return lovName; + } + + public void setLovName(String lovName) { + this.lovName = lovName; + } + + public boolean isLocked() { + return locked; + } + + public void setLocked(boolean locked) { + this.locked = locked; + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(Object pObj) { + if (this == pObj) { + return true; + } + if (!(pObj instanceof InstanceAttributeTemplateDTO)) { + return false; + } + InstanceAttributeTemplateDTO attr = (InstanceAttributeTemplateDTO) pObj; + return attr.name.equals(name); + } + + @Override + public String toString() { + return name + "-" + attributeType; + } + + public enum AttributeType { + TEXT, NUMBER, DATE, BOOLEAN, URL, LOV, LONG_TEXT + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/IterationNoteDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/IterationNoteDTO.java new file mode 100644 index 0000000000..35b957cb9f --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/IterationNoteDTO.java @@ -0,0 +1,44 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class IterationNoteDTO implements Serializable { + private String iterationNote; + + public IterationNoteDTO() { + } + + public IterationNoteDTO(String iterationNote) { + this.iterationNote = iterationNote; + } + + public String getIterationNote() { + return iterationNote; + } + + public void setIterationNote(String iterationNote) { + this.iterationNote = iterationNote; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LayerDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LayerDTO.java new file mode 100644 index 0000000000..4b4d7beb41 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LayerDTO.java @@ -0,0 +1,71 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class LayerDTO implements Serializable { + + private int id; + private String name; + private String color; + + public LayerDTO() { + } + + public LayerDTO(String pName) { + this.name = pName; + } + + public LayerDTO(int pId, String pName, String color) { + this.id = pId; + this.name = pName; + this.color = color; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPartLinkDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPartLinkDTO.java new file mode 100644 index 0000000000..eefba55b99 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPartLinkDTO.java @@ -0,0 +1,77 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import com.docdoku.core.product.PartLink; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class LightPartLinkDTO implements Serializable { + + private String number; + private String name; + private String referenceDescription; + private String fullId; + + public LightPartLinkDTO() { + } + + public LightPartLinkDTO(PartLink partLink) { + number = partLink.getComponent().getNumber(); + name = partLink.getComponent().getName(); + referenceDescription = partLink.getReferenceDescription(); + fullId = partLink.getFullId(); + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getReferenceDescription() { + return referenceDescription; + } + + public void setReferenceDescription(String referenceDescription) { + this.referenceDescription = referenceDescription; + } + + public String getFullId() { + return fullId; + } + + public void setFullId(String fullId) { + this.fullId = fullId; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPartLinkListDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPartLinkListDTO.java new file mode 100644 index 0000000000..4eb5ef8b3a --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPartLinkListDTO.java @@ -0,0 +1,47 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@XmlRootElement +public class LightPartLinkListDTO implements Serializable { + + private List partLinks = new ArrayList<>(); + + public LightPartLinkListDTO() { + } + + public LightPartLinkListDTO(List partLinks) { + this.partLinks = partLinks; + } + + public List getPartLinks() { + return partLinks; + } + + public void setPartLinks(List partLinks) { + this.partLinks = partLinks; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPartMasterDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPartMasterDTO.java new file mode 100644 index 0000000000..775c3c0902 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPartMasterDTO.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class LightPartMasterDTO implements Serializable { + + private String partNumber; + private String partName; + + public LightPartMasterDTO() { + } + + public LightPartMasterDTO(String partNumber, String partName) { + this.partNumber = partNumber; + this.partName = partName; + } + + public String getPartNumber() { + return partNumber; + } + + public void setPartNumber(String partNumber) { + this.partNumber = partNumber; + } + + public String getPartName() { + return partName; + } + + public void setPartName(String partName) { + this.partName = partName; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPartRevisionDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPartRevisionDTO.java new file mode 100644 index 0000000000..16caba2225 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPartRevisionDTO.java @@ -0,0 +1,44 @@ +package com.docdoku.server.rest.dto; + +/** + * Created by laurentlevan on 29/06/16. + */ +public class LightPartRevisionDTO { + + private String workspaceId; + private String partNumber; + private String version; + + public LightPartRevisionDTO() { + } + + public LightPartRevisionDTO(String workspaceId, String partNumber, String version){ + this.workspaceId = workspaceId; + this.partNumber = partNumber; + this.version = version; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getPartNumber() { + return partNumber; + } + + public void setPartNumber(String partNumber) { + this.partNumber = partNumber; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPathToPathLinkDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPathToPathLinkDTO.java new file mode 100644 index 0000000000..867993da18 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LightPathToPathLinkDTO.java @@ -0,0 +1,77 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class LightPathToPathLinkDTO implements Serializable { + + private Integer id; + private String type; + private String sourcePath; + private String targetPath; + private String description; + + public LightPathToPathLinkDTO() { + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getSourcePath() { + return sourcePath; + } + + public void setSourcePath(String sourcePath) { + this.sourcePath = sourcePath; + } + + public String getTargetPath() { + return targetPath; + } + + public void setTargetPath(String targetPath) { + this.targetPath = targetPath; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ListOfValuesDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ListOfValuesDTO.java new file mode 100644 index 0000000000..85eb7b17e2 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ListOfValuesDTO.java @@ -0,0 +1,90 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +/** + * @author lebeaujulien on 03/03/15. + */ +@XmlRootElement +public class ListOfValuesDTO implements Serializable { + + private String workspaceId; + private String name; + private WorkspaceDTO workspace; + private List values; + private String id; + + private boolean isDeletable = true; + + public ListOfValuesDTO() { + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getId() { + return name; + } + + public void setId(String id) { + this.id = id; + } + + public WorkspaceDTO getWorkspace() { + return workspace; + } + + public void setWorkspace(WorkspaceDTO workspace) { + this.workspace = workspace; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + + public boolean isDeletable() { + return isDeletable; + } + + public void setDeletable(boolean isDeletable) { + this.isDeletable = isDeletable; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LoginRequestDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LoginRequestDTO.java new file mode 100644 index 0000000000..ce6fa171f5 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/LoginRequestDTO.java @@ -0,0 +1,50 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class LoginRequestDTO implements Serializable { + + private String login; + private String password; + + public LoginRequestDTO() { + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/MarkerDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/MarkerDTO.java new file mode 100644 index 0000000000..73f8f8bc2a --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/MarkerDTO.java @@ -0,0 +1,107 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class MarkerDTO implements Serializable { + + private int id; + + private String title; + private String description; + + private double x; + private double y; + private double z; + + public MarkerDTO() { + } + + public MarkerDTO(String pTitle, String pDescription, double pX, double pY, double pZ) { + this.title = pTitle; + this.description = pDescription; + this.x = pX; + this.y = pY; + this.z = pZ; + } + + public MarkerDTO(int pId, String pTitle, String pDescription, double pX, double pY, double pZ) { + this.id = pId; + this.title = pTitle; + this.description = pDescription; + this.x = pX; + this.y = pY; + this.z = pZ; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + public double getZ() { + return z; + } + + public void setZ(double z) { + this.z = z; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ModificationNotificationDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ModificationNotificationDTO.java new file mode 100644 index 0000000000..577d6dd3b0 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/ModificationNotificationDTO.java @@ -0,0 +1,167 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; + +@XmlRootElement +public class ModificationNotificationDTO implements Serializable { + + private int id; + + private String impactedPartNumber; + private String impactedPartVersion; + + private String modifiedPartNumber; + private String modifiedPartName; + private String modifiedPartVersion; + private int modifiedPartIteration; + + private Date checkInDate; + private String iterationNote; + private UserDTO author; + + @XmlElement(nillable = true) + private boolean acknowledged; + + private String ackComment; + private UserDTO ackAuthor; + private Date ackDate; + + public ModificationNotificationDTO() { + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getImpactedPartNumber() { + return impactedPartNumber; + } + + public void setImpactedPartNumber(String impactedPartNumber) { + this.impactedPartNumber = impactedPartNumber; + } + + public String getImpactedPartVersion() { + return impactedPartVersion; + } + + public void setImpactedPartVersion(String impactedPartVersion) { + this.impactedPartVersion = impactedPartVersion; + } + + public String getModifiedPartNumber() { + return modifiedPartNumber; + } + + public void setModifiedPartNumber(String modifiedPartNumber) { + this.modifiedPartNumber = modifiedPartNumber; + } + + public String getModifiedPartVersion() { + return modifiedPartVersion; + } + + public void setModifiedPartVersion(String modifiedPartVersion) { + this.modifiedPartVersion = modifiedPartVersion; + } + + public int getModifiedPartIteration() { + return modifiedPartIteration; + } + + public void setModifiedPartIteration(int modifiedPartIteration) { + this.modifiedPartIteration = modifiedPartIteration; + } + + public String getModifiedPartName() { + return modifiedPartName; + } + + public void setModifiedPartName(String modifiedPartName) { + this.modifiedPartName = modifiedPartName; + } + + public Date getCheckInDate() { + return checkInDate; + } + + public void setCheckInDate(Date checkInDate) { + this.checkInDate = checkInDate; + } + + public String getIterationNote() { + return iterationNote; + } + + public void setIterationNote(String iterationNote) { + this.iterationNote = iterationNote; + } + + public UserDTO getAuthor() { + return author; + } + + public void setAuthor(UserDTO author) { + this.author = author; + } + + public boolean isAcknowledged() { + return acknowledged; + } + + public void setAcknowledged(boolean acknowledged) { + this.acknowledged = acknowledged; + } + + public String getAckComment() { + return ackComment; + } + + public void setAckComment(String ackComment) { + this.ackComment = ackComment; + } + + public UserDTO getAckAuthor() { + return ackAuthor; + } + + public void setAckAuthor(UserDTO ackAuthor) { + this.ackAuthor = ackAuthor; + } + + public Date getAckDate() { + return ackDate; + } + + public void setAckDate(Date ackDate) { + this.ackDate = ackDate; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/NameValuePairDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/NameValuePairDTO.java new file mode 100644 index 0000000000..47a1686e6d --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/NameValuePairDTO.java @@ -0,0 +1,52 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +/** + * @author lebeaujulien on 03/03/15. + */ +@XmlRootElement +public class NameValuePairDTO implements Serializable { + + private String name; + private String value; + + public NameValuePairDTO() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartCreationDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartCreationDTO.java new file mode 100644 index 0000000000..c1da33b8eb --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartCreationDTO.java @@ -0,0 +1,178 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@XmlRootElement +public class PartCreationDTO implements Serializable { + + private String partKey; + private String number; + private String version; + private String name; + private UserDTO author; + private Date creationDate; + private String description; + private List partIterations; + private UserDTO checkOutUser; + private Date checkOutDate; + private String workflowModelId; + private boolean standardPart; + private String workspaceId; + private String templateId; + private RoleMappingDTO[] roleMapping; + private ACLDTO acl; + + public PartCreationDTO() { + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public UserDTO getAuthor() { + return author; + } + + public void setAuthor(UserDTO author) { + this.author = author; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isStandardPart() { + return standardPart; + } + + public void setStandardPart(boolean standardPart) { + this.standardPart = standardPart; + } + + public List getPartIterations() { + return partIterations; + } + + public void setPartIterations(List partIterations) { + this.partIterations = partIterations; + } + + public UserDTO getCheckOutUser() { + return checkOutUser; + } + + public void setCheckOutUser(UserDTO checkOutUser) { + this.checkOutUser = checkOutUser; + } + + public Date getCheckOutDate() { + return checkOutDate; + } + + public void setCheckOutDate(Date checkOutDate) { + this.checkOutDate = checkOutDate; + } + + public String getWorkflowModelId() { + return workflowModelId; + } + + public void setWorkflowModelId(String workflowModelId) { + this.workflowModelId = workflowModelId; + } + + public String getPartKey() { + return partKey; + } + + public void setPartKey(String partKey) { + this.partKey = partKey; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getTemplateId() { + return templateId; + } + + public void setTemplateId(String templateId) { + this.templateId = templateId; + } + + public RoleMappingDTO[] getRoleMapping() { + return roleMapping; + } + + public void setRoleMapping(RoleMappingDTO[] roleMapping) { + this.roleMapping = roleMapping; + } + + public ACLDTO getAcl() { + return acl; + } + + public void setAcl(ACLDTO acl) { + this.acl = acl; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartIterationDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartIterationDTO.java new file mode 100644 index 0000000000..7b02ad127b --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartIterationDTO.java @@ -0,0 +1,195 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@XmlRootElement +public class PartIterationDTO implements Serializable { + + private String workspaceId; + private int iteration; + private String nativeCADFile; + private String geometryFileURI; + private String iterationNote; + private UserDTO author; + + private Date creationDate; + private Date modificationDate; + private Date checkInDate; + private List instanceAttributes; + private List instanceAttributeTemplates; + private List components; + private List linkedDocuments; + private String number; + private String name; + private String version; + private List attachedFiles; + + public PartIterationDTO() { + } + + public PartIterationDTO(String pWorkspaceId, String pName, String pNumber, String pVersion, int pIteration) { + workspaceId = pWorkspaceId; + number = pNumber; + name = pName; + version = pVersion; + iteration = pIteration; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public int getIteration() { + return iteration; + } + + public void setIteration(int iteration) { + this.iteration = iteration; + } + + public String getNativeCADFile() { + return nativeCADFile; + } + + public void setNativeCADFile(String nativeCADFile) { + this.nativeCADFile = nativeCADFile; + } + + public String getIterationNote() { + return iterationNote; + } + + public void setIterationNote(String iterationNote) { + this.iterationNote = iterationNote; + } + + public UserDTO getAuthor() { + return author; + } + + public void setAuthor(UserDTO author) { + this.author = author; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public Date getModificationDate() { + return modificationDate; + } + + public void setModificationDate(Date modificationDate) { + this.modificationDate = modificationDate; + } + + public Date getCheckInDate() { + return checkInDate; + } + + public void setCheckInDate(Date checkInDate) { + this.checkInDate = checkInDate; + } + + public List getInstanceAttributes() { + return instanceAttributes; + } + + public void setInstanceAttributes(List instanceAttributes) { + this.instanceAttributes = instanceAttributes; + } + + public List getComponents() { + return components; + } + + public void setComponents(List components) { + this.components = components; + } + + public List getLinkedDocuments() { + return linkedDocuments; + } + + public void setLinkedDocuments(List linkedDocuments) { + this.linkedDocuments = linkedDocuments; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getInstanceAttributeTemplates() { + return instanceAttributeTemplates; + } + + public void setInstanceAttributeTemplates(List instanceAttributeTemplates) { + this.instanceAttributeTemplates = instanceAttributeTemplates; + } + + public List getAttachedFiles() { + return attachedFiles; + } + + public void setAttachedFiles(List attachedFiles) { + this.attachedFiles = attachedFiles; + } + + public String getGeometryFileURI() { + return geometryFileURI; + } + + public void setGeometryFileURI(String geometryFileURI) { + this.geometryFileURI = geometryFileURI; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartIterationListDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartIterationListDTO.java new file mode 100644 index 0000000000..85dcc6b865 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartIterationListDTO.java @@ -0,0 +1,46 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +/** + * @author Morgan Guimard + */ +@XmlRootElement +public class PartIterationListDTO implements Serializable { + + private List parts; + + public PartIterationListDTO() { + } + + public List getParts() { + return parts; + } + + public void setParts(List parts) { + this.parts = parts; + } +} + + diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartMasterTemplateDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartMasterTemplateDTO.java new file mode 100644 index 0000000000..de47e473ba --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartMasterTemplateDTO.java @@ -0,0 +1,175 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@XmlRootElement +public class PartMasterTemplateDTO implements Serializable { + + private String workspaceId; + private String id; + private String reference; + private String partType; + private UserDTO author; + private Date creationDate; + private Date modificationDate; + private boolean idGenerated; + private String mask; + private String attachedFile; + private List attributeTemplates; + private List attributeInstanceTemplates; + private boolean attributesLocked; + private String workflowModelId; + private ACLDTO acl; + + public PartMasterTemplateDTO() { + } + + public PartMasterTemplateDTO(String workspaceId, String id, String partType) { + this.workspaceId = workspaceId; + this.id = id; + this.partType = partType; + } + + public UserDTO getAuthor() { + return author; + } + + public void setAuthor(UserDTO author) { + this.author = author; + } + + public String getMask() { + return mask; + } + + public void setMask(String mask) { + this.mask = mask; + } + + public String getWorkflowModelId() { + return workflowModelId; + } + + public void setWorkflowModelId(String workflowModelId) { + this.workflowModelId = workflowModelId; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public Date getModificationDate() { + return modificationDate; + } + + public void setModificationDate(Date modificationDate) { + this.modificationDate = modificationDate; + } + + public String getAttachedFile() { + return attachedFile; + } + + public void setAttachedFile(String attachedFile) { + this.attachedFile = attachedFile; + } + + public boolean isIdGenerated() { + return idGenerated; + } + + public void setIdGenerated(boolean idGenerated) { + this.idGenerated = idGenerated; + } + + public List getAttributeInstanceTemplates() { + return attributeInstanceTemplates; + } + + public void setAttributeInstanceTemplates(List attributeInstanceTemplates) { + this.attributeInstanceTemplates = attributeInstanceTemplates; + } + + public List getAttributeTemplates() { + return attributeTemplates; + } + + public void setAttributeTemplates(List attributeTemplates) { + this.attributeTemplates = attributeTemplates; + } + + public String getPartType() { + return partType; + } + + public void setPartType(String partType) { + this.partType = partType; + } + + public String getReference() { + return reference; + } + + public void setReference(String reference) { + this.reference = reference; + } + + public boolean isAttributesLocked() { + return attributesLocked; + } + + public void setAttributesLocked(boolean attributesLocked) { + this.attributesLocked = attributesLocked; + } + + public ACLDTO getAcl() { + return acl; + } + + public void setAcl(ACLDTO acl) { + this.acl = acl; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartRevisionDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartRevisionDTO.java new file mode 100644 index 0000000000..42faff9e8a --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartRevisionDTO.java @@ -0,0 +1,333 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import com.docdoku.core.product.PartRevision; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@XmlRootElement +public class PartRevisionDTO implements Serializable { + + @XmlElement(nillable = true) + int lastIterationNumber; + private String partKey; + private String number; + private String version; + private String type; + private String name; + private UserDTO author; + private Date creationDate; + private Date modificationDate; + private Date checkInDate; + private String description; + private List partIterations; + @XmlElement(nillable = true) + private UserDTO checkOutUser; + @XmlElement(nillable = true) + private Date checkOutDate; + @XmlElement(nillable = true) + private WorkflowDTO workflow; + private String lifeCycleState; + private boolean standardPart; + private String workspaceId; + private boolean publicShared; + @XmlElement(nillable = true) + private ACLDTO acl; + private boolean attributesLocked; + @XmlElement(nillable = true) + private PartRevision.RevisionStatus status; + private String[] tags; + private List notifications; + private Date obsoleteDate; + @XmlElement(nillable = true) + private UserDTO obsoleteAuthor; + private Date releaseDate; + @XmlElement(nillable = true) + private UserDTO releaseAuthor; + + public PartRevisionDTO() { + } + + public PartRevisionDTO(String workspaceId, String number, String name, String version) { + this.number = number; + this.name = name; + this.version = version; + this.workspaceId = workspaceId; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public UserDTO getAuthor() { + return author; + } + + public void setAuthor(UserDTO author) { + this.author = author; + } + + public Date getCreationDate() { + return (creationDate != null) ? (Date) creationDate.clone() : null; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = (creationDate != null) ? (Date) creationDate.clone() : null; + } + + public Date getModificationDate() { + return (modificationDate != null) ? (Date) modificationDate.clone() : null; + } + + public void setModificationDate(Date modificationDate) { + this.modificationDate = (modificationDate != null) ? (Date) modificationDate.clone() : null; + } + + public Date getCheckInDate() { + return (checkInDate != null) ? (Date) checkInDate.clone() : null; + } + + public void setCheckInDate(Date checkInDate) { + this.checkInDate = (checkInDate != null) ? (Date) checkInDate.clone() : null; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isStandardPart() { + return standardPart; + } + + public void setStandardPart(boolean standardPart) { + this.standardPart = standardPart; + } + + public List getPartIterations() { + return partIterations; + } + + public void setPartIterations(List partIterations) { + this.partIterations = partIterations; + } + + public List getNotifications() { + return notifications; + } + + public void setNotifications(List notifications) { + this.notifications = notifications; + } + + public UserDTO getCheckOutUser() { + return checkOutUser; + } + + public void setCheckOutUser(UserDTO checkOutUser) { + this.checkOutUser = checkOutUser; + } + + public Date getCheckOutDate() { + return (checkOutDate != null) ? (Date) checkOutDate.clone() : null; + } + + public void setCheckOutDate(Date checkOutDate) { + this.checkOutDate = (checkOutDate != null) ? (Date) checkOutDate.clone() : null; + } + + public WorkflowDTO getWorkflow() { + return workflow; + } + + public void setWorkflow(WorkflowDTO workflow) { + this.workflow = workflow; + } + + public String getPartKey() { + return partKey; + } + + public void setPartKey(String partKey) { + this.partKey = partKey; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getLifeCycleState() { + return lifeCycleState; + } + + public void setLifeCycleState(String lifeCycleState) { + this.lifeCycleState = lifeCycleState; + } + + public boolean isPublicShared() { + return publicShared; + } + + public void setPublicShared(boolean publicShared) { + this.publicShared = publicShared; + } + + public ACLDTO getAcl() { + return acl; + } + + public void setAcl(ACLDTO acl) { + this.acl = acl; + } + + public boolean isAttributesLocked() { + return attributesLocked; + } + + public void setAttributesLocked(boolean attributesLocked) { + this.attributesLocked = attributesLocked; + } + + public PartRevision.RevisionStatus getStatus() { + return status; + } + + public void setStatus(PartRevision.RevisionStatus status) { + this.status = status; + } + + public int getLastIterationNumber() { + return lastIterationNumber; + } + + public void setLastIterationNumber(int lastIterationNumber) { + this.lastIterationNumber = lastIterationNumber; + } + + public String[] getTags() { + return tags; + } + + public void setTags(String[] tags) { + this.tags = tags; + } + + public Date getObsoleteDate() { + return obsoleteDate; + } + + public void setObsoleteDate(Date obsoleteDate) { + this.obsoleteDate = obsoleteDate; + } + + public UserDTO getObsoleteAuthor() { + return obsoleteAuthor; + } + + public void setObsoleteAuthor(UserDTO obsoleteAuthor) { + this.obsoleteAuthor = obsoleteAuthor; + } + + public Date getReleaseDate() { + return releaseDate; + } + + public void setReleaseDate(Date releasedDate) { + this.releaseDate = releasedDate; + } + + public UserDTO getReleaseAuthor() { + return releaseAuthor; + } + + public void setReleaseAuthor(UserDTO releasedAuthor) { + this.releaseAuthor = releasedAuthor; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PartRevisionDTO partRevisionDTO = (PartRevisionDTO) o; + + if (!number.equals(partRevisionDTO.number)) { + return false; + } + if (!version.equals(partRevisionDTO.version)) { + return false; + } + return workspaceId.equals(partRevisionDTO.workspaceId); + + } + + @Override + public int hashCode() { + int result = number.hashCode(); + result = 31 * result + version.hashCode(); + result = 31 * result + workspaceId.hashCode(); + return result; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartSubstituteLinkDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartSubstituteLinkDTO.java new file mode 100644 index 0000000000..3de1b09041 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartSubstituteLinkDTO.java @@ -0,0 +1,101 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +@XmlRootElement +public class PartSubstituteLinkDTO implements Serializable { + + private int id; + private String fullId; + private double amount; + private String unit; + private String referenceDescription; + private String comment; + private ComponentDTO substitute; + private List cadInstances; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public double getAmount() { + return amount; + } + + public void setAmount(double amount) { + this.amount = amount; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + public String getReferenceDescription() { + return referenceDescription; + } + + public void setReferenceDescription(String referenceDescription) { + this.referenceDescription = referenceDescription; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public ComponentDTO getSubstitute() { + return substitute; + } + + public void setSubstitute(ComponentDTO substitute) { + this.substitute = substitute; + } + + public List getCadInstances() { + return cadInstances; + } + + public void setCadInstances(List cadInstances) { + this.cadInstances = cadInstances; + } + + public String getFullId() { + return fullId; + } + + public void setFullId(String fullId) { + this.fullId = fullId; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartTemplateCreationDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartTemplateCreationDTO.java new file mode 100644 index 0000000000..a8e9392099 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartTemplateCreationDTO.java @@ -0,0 +1,129 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +@XmlRootElement +public class PartTemplateCreationDTO implements Serializable { + + private String workspaceId; + private String reference; + private String partType; + private boolean idGenerated; + private String mask; + private String attachedFiles; + private List attributeTemplates; + private List attributeInstanceTemplates; + private boolean attributesLocked; + private String workflowModelId; + + public PartTemplateCreationDTO() { + } + + public PartTemplateCreationDTO(String workspaceId, String partType) { + this.workspaceId = workspaceId; + this.partType = partType; + } + + public String getPartType() { + return partType; + } + + public void setPartType(String partType) { + this.partType = partType; + } + + public String getMask() { + return mask; + } + + public void setMask(String mask) { + this.mask = mask; + } + + public String getWorkflowModelId() { + return workflowModelId; + } + + public void setWorkflowModelId(String workflowModelId) { + this.workflowModelId = workflowModelId; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getAttachedFiles() { + return attachedFiles; + } + + public void setAttachedFiles(String attachedFiles) { + this.attachedFiles = attachedFiles; + } + + public boolean isIdGenerated() { + return idGenerated; + } + + public void setIdGenerated(boolean idGenerated) { + this.idGenerated = idGenerated; + } + + public List getAttributeTemplates() { + return attributeTemplates; + } + + public void setAttributeTemplates(List attributeTemplates) { + this.attributeTemplates = attributeTemplates; + } + + public List getAttributeInstanceTemplates() { + return attributeInstanceTemplates; + } + + public void setAttributeInstanceTemplates(List attributeInstanceTemplates) { + this.attributeInstanceTemplates = attributeInstanceTemplates; + } + + public boolean isAttributesLocked() { + return attributesLocked; + } + + public void setAttributesLocked(boolean attributesLocked) { + this.attributesLocked = attributesLocked; + } + + public String getReference() { + return reference; + } + + public void setReference(String reference) { + this.reference = reference; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartUsageLinkDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartUsageLinkDTO.java new file mode 100644 index 0000000000..c3e88f1996 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PartUsageLinkDTO.java @@ -0,0 +1,120 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +@XmlRootElement +public class PartUsageLinkDTO implements Serializable { + + private int id; + private String fullId; + private double amount; + private String comment; + private ComponentDTO component; + private String referenceDescription; + private String unit; + private boolean optional; + private List cadInstances; + private List substitutes; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public double getAmount() { + return amount; + } + + public void setAmount(double amount) { + this.amount = amount; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public ComponentDTO getComponent() { + return component; + } + + public void setComponent(ComponentDTO component) { + this.component = component; + } + + public String getReferenceDescription() { + return referenceDescription; + } + + public void setReferenceDescription(String referenceDescription) { + this.referenceDescription = referenceDescription; + } + + public boolean isOptional() { + return optional; + } + + public void setOptional(boolean optional) { + this.optional = optional; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + public List getCadInstances() { + return cadInstances; + } + + public void setCadInstances(List cadInstances) { + this.cadInstances = cadInstances; + } + + public List getSubstitutes() { + return substitutes; + } + + public void setSubstitutes(List substitutes) { + this.substitutes = substitutes; + } + + public String getFullId() { + return fullId; + } + + public void setFullId(String fullId) { + this.fullId = fullId; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PasswordRecoverDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PasswordRecoverDTO.java new file mode 100644 index 0000000000..d63335c1c5 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PasswordRecoverDTO.java @@ -0,0 +1,50 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class PasswordRecoverDTO implements Serializable { + + private String uuid; + private String newPassword; + + public PasswordRecoverDTO() { + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PasswordRecoveryRequestDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PasswordRecoveryRequestDTO.java new file mode 100644 index 0000000000..a4ddf4a83c --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PasswordRecoveryRequestDTO.java @@ -0,0 +1,41 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class PasswordRecoveryRequestDTO implements Serializable { + + private String login; + + public PasswordRecoveryRequestDTO() { + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathDTO.java new file mode 100644 index 0000000000..346b64493d --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathDTO.java @@ -0,0 +1,45 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class PathDTO implements Serializable { + + private String path; + + public PathDTO() { + } + + public PathDTO(String path) { + this.path = path; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathDataIterationCreationDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathDataIterationCreationDTO.java new file mode 100644 index 0000000000..2cf581e86e --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathDataIterationCreationDTO.java @@ -0,0 +1,105 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; +import java.util.Set; + +/** + * @author Chadid Asmae + */ +@XmlRootElement +public class PathDataIterationCreationDTO implements Serializable { + + private int id; + private String path; + private int iteration; + private String iterationNote; + private List attachedFiles; + private Set linkedDocuments; + private List instanceAttributes; + + public PathDataIterationCreationDTO() { + } + + public PathDataIterationCreationDTO(String path) { + this.path = path; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public int getIteration() { + return iteration; + } + + public void setIteration(int iteration) { + this.iteration = iteration; + } + + public String getIterationNote() { + return iterationNote; + } + + public void setIterationNote(String iterationNote) { + this.iterationNote = iterationNote; + } + + public List getAttachedFiles() { + return attachedFiles; + } + + public void setAttachedFiles(List attachedFiles) { + this.attachedFiles = attachedFiles; + } + + public Set getLinkedDocuments() { + return linkedDocuments; + } + + public void setLinkedDocuments(Set linkedDocuments) { + this.linkedDocuments = linkedDocuments; + } + + public List getInstanceAttributes() { + return instanceAttributes; + } + + public void setInstanceAttributes(List instanceAttributes) { + this.instanceAttributes = instanceAttributes; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathDataIterationDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathDataIterationDTO.java new file mode 100644 index 0000000000..79a049e565 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathDataIterationDTO.java @@ -0,0 +1,141 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; +import java.util.Set; + +/** + * @author Chadid Asmae + */ +@XmlRootElement +public class PathDataIterationDTO implements Serializable { + private String serialNumber; + private int pathDataMasterId; + private int iteration; + private String iterationNote; + private LightPartLinkListDTO partLinksList; + private String path; + private List attachedFiles; + private Set linkedDocuments; + private List instanceAttributes; + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public int getIteration() { + return iteration; + } + + public void setIteration(int iteration) { + this.iteration = iteration; + } + + public String getIterationNote() { + return iterationNote; + } + + public void setIterationNote(String iterationNote) { + this.iterationNote = iterationNote; + } + + public LightPartLinkListDTO getPartLinksList() { + return partLinksList; + } + + public void setPartLinksList(LightPartLinkListDTO partLinksList) { + this.partLinksList = partLinksList; + } + + public List getAttachedFiles() { + return attachedFiles; + } + + public void setAttachedFiles(List attachedFiles) { + this.attachedFiles = attachedFiles; + } + + public Set getLinkedDocuments() { + return linkedDocuments; + } + + public void setLinkedDocuments(Set linkedDocuments) { + this.linkedDocuments = linkedDocuments; + } + + public List getInstanceAttributes() { + return instanceAttributes; + } + + public void setInstanceAttributes(List instanceAttributes) { + this.instanceAttributes = instanceAttributes; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public int getPathDataMasterId() { + return pathDataMasterId; + } + + public void setPathDataMasterId(int pathDataMasterId) { + this.pathDataMasterId = pathDataMasterId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PathDataIterationDTO dto = (PathDataIterationDTO) o; + + if (iteration != dto.iteration) { + return false; + } + if (pathDataMasterId != dto.pathDataMasterId) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = pathDataMasterId; + result = 31 * result + iteration; + return result; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathDataMasterDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathDataMasterDTO.java new file mode 100644 index 0000000000..d4496720fd --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathDataMasterDTO.java @@ -0,0 +1,129 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@XmlRootElement +public class PathDataMasterDTO implements Serializable { + + private Integer id; + private String path; + private String serialNumber; + private LightPartLinkListDTO partLinksList; + private List pathDataIterations = new ArrayList(); + private List partAttributes; + private List partAttributeTemplates; + + public PathDataMasterDTO() { + } + + public PathDataMasterDTO(String path) { + this.path = path; + } + + public PathDataMasterDTO(Integer id) { + this.id = id; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public LightPartLinkListDTO getPartLinksList() { + return partLinksList; + } + + public void setPartLinksList(LightPartLinkListDTO partLinksList) { + this.partLinksList = partLinksList; + } + + public List getPathDataIterations() { + return pathDataIterations; + } + + public void setPathDataIterations(List pathDataIterations) { + this.pathDataIterations = pathDataIterations; + } + + public List getPartAttributes() { + return partAttributes; + } + + public void setPartAttributes(List partAttributes) { + this.partAttributes = partAttributes; + } + + public List getPartAttributeTemplates() { + return partAttributeTemplates; + } + + public void setPartAttributeTemplates(List partAttributeTemplates) { + this.partAttributeTemplates = partAttributeTemplates; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PathDataMasterDTO that = (PathDataMasterDTO) o; + + if (id != null ? !id.equals(that.id) : that.id != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathListDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathListDTO.java new file mode 100644 index 0000000000..3459da490c --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathListDTO.java @@ -0,0 +1,55 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class PathListDTO implements Serializable { + private String configSpec; + private String[] paths; + + public PathListDTO() { + } + + public PathListDTO(String configSpec, String[] paths) { + this.configSpec = configSpec; + this.paths = paths; + } + + public String getConfigSpec() { + return configSpec; + } + + public void setConfigSpec(String configSpec) { + this.configSpec = configSpec; + } + + public String[] getPaths() { + return paths; + } + + public void setPaths(String[] paths) { + this.paths = paths; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathToPathLinkDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathToPathLinkDTO.java new file mode 100644 index 0000000000..a1abcb4410 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/PathToPathLinkDTO.java @@ -0,0 +1,78 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +@XmlRootElement +public class PathToPathLinkDTO implements Serializable { + + private Integer id; + private String type; + private List sourceComponents; + private List targetComponents; + private String description; + + public PathToPathLinkDTO() { + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List getSourceComponents() { + return sourceComponents; + } + + public void setSourceComponents(List sourceComponents) { + this.sourceComponents = sourceComponents; + } + + public List getTargetComponents() { + return targetComponents; + } + + public void setTargetComponents(List targetComponents) { + this.targetComponents = targetComponents; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/QueryContextDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/QueryContextDTO.java new file mode 100644 index 0000000000..d223c1796d --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/QueryContextDTO.java @@ -0,0 +1,62 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +/** + * @author lebeaujulien on 23/04/15. + */ +@XmlRootElement +public class QueryContextDTO implements Serializable { + + private String workspaceId; + private String serialNumber; + private String configurationItemId; + + public QueryContextDTO() { + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getConfigurationItemId() { + return configurationItemId; + } + + public void setConfigurationItemId(String configurationItemId) { + this.configurationItemId = configurationItemId; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/QueryDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/QueryDTO.java new file mode 100644 index 0000000000..33fb63133f --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/QueryDTO.java @@ -0,0 +1,115 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * @author morgan on 09/04/15. + */ + +@XmlRootElement +public class QueryDTO implements Serializable { + + private int id; + private String name; + private Date creationDate; + + @XmlElement(nillable = false) + private QueryRuleDTO queryRule; + + private List selects; + private List orderByList; + private List groupedByList; + + private List contexts; + + public QueryDTO() { + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public QueryRuleDTO getQueryRule() { + return queryRule; + } + + public void setQueryRule(QueryRuleDTO queryRule) { + this.queryRule = queryRule; + } + + public List getSelects() { + return selects; + } + + public void setSelects(List selects) { + this.selects = selects; + } + + public List getOrderByList() { + return orderByList; + } + + public void setOrderByList(List orderByList) { + this.orderByList = orderByList; + } + + public List getGroupedByList() { + return groupedByList; + } + + public void setGroupedByList(List groupedByList) { + this.groupedByList = groupedByList; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public List getContexts() { + return contexts; + } + + public void setContexts(List contexts) { + this.contexts = contexts; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/QueryRuleDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/QueryRuleDTO.java new file mode 100644 index 0000000000..26602d83bd --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/QueryRuleDTO.java @@ -0,0 +1,108 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +/** + * @author morgan on 09/04/15. + */ + +@XmlRootElement +public class QueryRuleDTO implements Serializable { + + private String condition; + private String id; + private String field; + private String type; + private String operator; + private List values; + private List rules; + + public QueryRuleDTO() { + } + + public String getCondition() { + return condition; + } + + public void setCondition(String condition) { + this.condition = condition; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getField() { + return field; + } + + public void setField(String field) { + this.field = field; + } + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules; + } + + public List getSubQueryRules() { + return getRules(); + } + + public void setSubQueryRules(List rules) { + setRules(rules); + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/RoleDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/RoleDTO.java new file mode 100644 index 0000000000..b381466c61 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/RoleDTO.java @@ -0,0 +1,84 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +/** + * @author Yassine Belouad + */ +@XmlRootElement +public class RoleDTO implements Serializable { + + private String id; + private String name; + private String workspaceId; + private List defaultAssignedUsers; + private List defaultAssignedGroups; + + public RoleDTO() { + } + + public RoleDTO(String name) { + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public List getDefaultAssignedUsers() { + return defaultAssignedUsers; + } + + public void setDefaultAssignedUsers(List defaultAssignedUsers) { + this.defaultAssignedUsers = defaultAssignedUsers; + } + + public List getDefaultAssignedGroups() { + return defaultAssignedGroups; + } + + public void setDefaultAssignedGroups(List defaultAssignedGroups) { + this.defaultAssignedGroups = defaultAssignedGroups; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/RoleMappingDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/RoleMappingDTO.java new file mode 100644 index 0000000000..a0f0ae9920 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/RoleMappingDTO.java @@ -0,0 +1,68 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Morgan Guimard + */ + +@XmlRootElement +public class RoleMappingDTO implements Serializable { + + private String roleName; + private List userLogins = new ArrayList<>(); + private List groupIds = new ArrayList<>(); + + public RoleMappingDTO() { + } + + public RoleMappingDTO(String roleName) { + this.roleName = roleName; + } + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public List getUserLogins() { + return userLogins; + } + + public void setUserLogins(List userLogins) { + this.userLogins = userLogins; + } + + public List getGroupIds() { + return groupIds; + } + + public void setGroupIds(List groupIds) { + this.groupIds = groupIds; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/SharedDocumentDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/SharedDocumentDTO.java new file mode 100644 index 0000000000..1d0e3e4753 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/SharedDocumentDTO.java @@ -0,0 +1,109 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; + +/** + * @author Morgan Guimard + */ +@XmlRootElement +public class SharedDocumentDTO implements Serializable { + + private String uuid; + private String workspaceId; + private String password; + private Date expireDate; + private String userLogin; + + private String documentMasterId; + private String documentMasterVersion; + + public SharedDocumentDTO() { + + } + + public SharedDocumentDTO(String uuid, String workspaceId, String password, Date expireDate, String userLogin) { + this.uuid = uuid; + this.workspaceId = workspaceId; + this.password = password; + this.expireDate = expireDate; + this.userLogin = userLogin; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Date getExpireDate() { + return expireDate; + } + + public void setExpireDate(Date expireDate) { + this.expireDate = expireDate; + } + + public String getUserLogin() { + return userLogin; + } + + public void setUserLogin(String userLogin) { + this.userLogin = userLogin; + } + + public String getDocumentMasterId() { + return documentMasterId; + } + + public void setDocumentMasterId(String documentMasterId) { + this.documentMasterId = documentMasterId; + } + + public String getDocumentMasterVersion() { + return documentMasterVersion; + } + + public void setDocumentMasterVersion(String documentMasterVersion) { + this.documentMasterVersion = documentMasterVersion; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/SharedPartDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/SharedPartDTO.java new file mode 100644 index 0000000000..bec88de526 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/SharedPartDTO.java @@ -0,0 +1,109 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; + +/** + * @author Morgan Guimard + */ +@XmlRootElement +public class SharedPartDTO implements Serializable { + + private String uuid; + private String workspaceId; + private String password; + private Date expireDate; + private String userLogin; + + private String partMasterNumber; + private String partMasterVersion; + + public SharedPartDTO() { + + } + + public SharedPartDTO(String uuid, String workspaceId, String password, Date expireDate, String userLogin) { + this.uuid = uuid; + this.workspaceId = workspaceId; + this.password = password; + this.expireDate = expireDate; + this.userLogin = userLogin; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Date getExpireDate() { + return expireDate; + } + + public void setExpireDate(Date expireDate) { + this.expireDate = expireDate; + } + + public String getUserLogin() { + return userLogin; + } + + public void setUserLogin(String userLogin) { + this.userLogin = userLogin; + } + + public String getPartMasterNumber() { + return partMasterNumber; + } + + public void setPartMasterNumber(String partMasterNumber) { + this.partMasterNumber = partMasterNumber; + } + + public String getPartMasterVersion() { + return partMasterVersion; + } + + public void setPartMasterVersion(String partMasterVersion) { + this.partMasterVersion = partMasterVersion; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/StatsOverviewDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/StatsOverviewDTO.java new file mode 100644 index 0000000000..2174ed66b4 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/StatsOverviewDTO.java @@ -0,0 +1,71 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +/** + * @author Morgan Guimard + */ +@XmlRootElement +public class StatsOverviewDTO implements Serializable { + + private int documents; + private int parts; + private int products; + private int users; + + public StatsOverviewDTO() { + } + + public int getDocuments() { + return documents; + } + + public void setDocuments(int documents) { + this.documents = documents; + } + + public int getParts() { + return parts; + } + + public void setParts(int parts) { + this.parts = parts; + } + + public int getUsers() { + return users; + } + + public void setUsers(int users) { + this.users = users; + } + + public int getProducts() { + return products; + } + + public void setProducts(int products) { + this.products = products; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TagDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TagDTO.java new file mode 100644 index 0000000000..adabac15f3 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TagDTO.java @@ -0,0 +1,79 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +/** + * @author Yassine Belouad + */ + +@XmlRootElement +public class TagDTO implements Serializable { + + @XmlElement(nillable = true) + private String id; + @XmlElement(nillable = true) + private String label; + @XmlElement(nillable = true) + private String workspaceId; + + public TagDTO() { + } + + public TagDTO(String label) { + this.label = label; + } + + public TagDTO(String label, String workspaceId) { + this.id = label; + this.label = label; + this.workspaceId = workspaceId; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getId() { + id = this.label; + return id; + } + + public void setId(String id) { + this.id = id; + } + + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TagListDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TagListDTO.java new file mode 100644 index 0000000000..1bee53fc90 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TagListDTO.java @@ -0,0 +1,44 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +/** + * @author Morgan Guimard + */ +@XmlRootElement +public class TagListDTO implements Serializable { + + private List tags; + + public TagListDTO() { + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TaskDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TaskDTO.java new file mode 100644 index 0000000000..5377ac36b8 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TaskDTO.java @@ -0,0 +1,197 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@XmlRootElement +public class TaskDTO implements Serializable { + + private String closureComment; + private String title; + private String instructions; + private int targetIteration; + private Date closureDate; + private String signature; + + private List assignedUsers = new ArrayList<>(); + private List assignedGroups = new ArrayList<>(); + + private UserDTO worker; + private Status status; + + private String workspaceId; + + private int workflowId; + private int activityStep; + private int num; + + private String holderType; + private String holderReference; + private String holderVersion; + + public TaskDTO() { + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getClosureComment() { + return closureComment; + } + + public void setClosureComment(String closureComment) { + this.closureComment = closureComment; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getInstructions() { + return instructions; + } + + public void setInstructions(String instructions) { + this.instructions = instructions; + } + + public int getTargetIteration() { + return targetIteration; + } + + public void setTargetIteration(int targetIteration) { + this.targetIteration = targetIteration; + } + + public Date getClosureDate() { + return (closureDate != null) ? (Date) closureDate.clone() : null; + } + + public void setClosureDate(Date date) { + this.closureDate = (date != null) ? (Date) date.clone() : null; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public UserDTO getWorker() { + return worker; + } + + public void setWorker(UserDTO worker) { + this.worker = worker; + } + + public String getSignature() { + return signature; + } + + public void setSignature(String signature) { + this.signature = signature; + } + + public int getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(int workflowId) { + this.workflowId = workflowId; + } + + public int getActivityStep() { + return activityStep; + } + + public void setActivityStep(int activityStep) { + this.activityStep = activityStep; + } + + public int getNum() { + return num; + } + + public void setNum(int num) { + this.num = num; + } + + public String getHolderType() { + return holderType; + } + + public void setHolderType(String holderType) { + this.holderType = holderType; + } + + public String getHolderReference() { + return holderReference; + } + + public void setHolderReference(String holderReference) { + this.holderReference = holderReference; + } + + public String getHolderVersion() { + return holderVersion; + } + + public void setHolderVersion(String holderVersion) { + this.holderVersion = holderVersion; + } + + public List getAssignedUsers() { + return assignedUsers; + } + + public void setAssignedUsers(List assignedUsers) { + this.assignedUsers = assignedUsers; + } + + public List getAssignedGroups() { + return assignedGroups; + } + + public void setAssignedGroups(List assignedGroups) { + this.assignedGroups = assignedGroups; + } + + public enum Status { + NOT_STARTED, IN_PROGRESS, APPROVED, REJECTED, NOT_TO_BE_DONE + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TaskModelDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TaskModelDTO.java new file mode 100644 index 0000000000..a3b0b47f9c --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TaskModelDTO.java @@ -0,0 +1,88 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +/** + * @author Emmanuel Nhan + */ +@XmlRootElement +public class TaskModelDTO implements Serializable { + + private int num; + private String title; + private String instructions; + private RoleDTO role; + private int duration; + + public TaskModelDTO() { + } + + public TaskModelDTO(int num, String title, String instructions, RoleDTO role, int duration) { + this.num = num; + this.title = title; + this.instructions = instructions; + this.role = role; + this.duration = duration; + } + + public int getNum() { + return num; + } + + public void setNum(int num) { + this.num = num; + } + + public int getDuration() { + return duration; + } + + public void setDuration(int duration) { + this.duration = duration; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getInstructions() { + return instructions; + } + + public void setInstructions(String instructions) { + this.instructions = instructions; + } + + public RoleDTO getRole() { + return role; + } + + public void setRole(RoleDTO role) { + this.role = role; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TaskProcessDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TaskProcessDTO.java new file mode 100644 index 0000000000..a512320db2 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TaskProcessDTO.java @@ -0,0 +1,70 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class TaskProcessDTO implements Serializable { + + private Action action; + private String comment; + private String signature; + + public TaskProcessDTO(Action action, String comment, String signature) { + this.action = action; + this.comment = comment; + this.signature = signature; + } + + public TaskProcessDTO() { + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public String getSignature() { + return signature; + } + + public void setSignature(String signature) { + this.signature = signature; + } + + public Action getAction() { + return action; + } + + public void setAction(Action action) { + this.action = action; + } + + public enum Action { + APPROVE, + REJECT + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TemplateGeneratedIdDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TemplateGeneratedIdDTO.java new file mode 100644 index 0000000000..edb6806c78 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TemplateGeneratedIdDTO.java @@ -0,0 +1,44 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class TemplateGeneratedIdDTO implements Serializable { + private String id; + + public TemplateGeneratedIdDTO() { + } + + public TemplateGeneratedIdDTO(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TransformationDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TransformationDTO.java new file mode 100644 index 0000000000..26bb97b608 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/TransformationDTO.java @@ -0,0 +1,135 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +/** + * @author Florent Garin + */ + +@XmlRootElement +public class TransformationDTO implements Serializable, Cloneable { + + /** + * Translation on x axis. + */ + private double tx; + + /** + * Translation on y axis. + */ + private double ty; + + /** + * Translation on z axis. + */ + private double tz; + + /** + * Radian rotation on x axis. + */ + private double rx; + + /** + * Radian rotation on y axis. + */ + private double ry; + + /** + * Radian rotation on z axis. + */ + private double rz; + + + public TransformationDTO() { + + } + + public TransformationDTO(double tx, double ty, double tz, double rx, double ry, double rz) { + this.tx = tx; + this.ty = ty; + this.tz = tz; + this.rx = rx; + this.ry = ry; + this.rz = rz; + } + + public double getTx() { + return tx; + } + + public void setTx(double tx) { + this.tx = tx; + } + + public double getTy() { + return ty; + } + + public void setTy(double ty) { + this.ty = ty; + } + + public double getTz() { + return tz; + } + + public void setTz(double tz) { + this.tz = tz; + } + + public double getRx() { + return rx; + } + + public void setRx(double rx) { + this.rx = rx; + } + + public double getRy() { + return ry; + } + + public void setRy(double ry) { + this.ry = ry; + } + + public double getRz() { + return rz; + } + + public void setRz(double rz) { + this.rz = rz; + } + + @Override + public TransformationDTO clone() { + TransformationDTO clone = null; + try { + clone = (TransformationDTO) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + return clone; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/UserDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/UserDTO.java new file mode 100644 index 0000000000..09b9ba8b97 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/UserDTO.java @@ -0,0 +1,98 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +/** + * @author Florent Garin + */ +@XmlRootElement +public class UserDTO implements Serializable { + + private String workspaceId; + private String login; + private String name; + private String email; + private String language; + + private WorkspaceMembership membership; + + public UserDTO() { + + } + + public UserDTO(String workspaceId, String login, String name) { + this.workspaceId = workspaceId; + this.login = login; + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public WorkspaceMembership getMembership() { + return membership; + } + + public void setMembership(WorkspaceMembership membership) { + this.membership = membership; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/UserGroupDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/UserGroupDTO.java new file mode 100644 index 0000000000..55b042c5e8 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/UserGroupDTO.java @@ -0,0 +1,46 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +/** + * @author Charles Fallourd on 23/03/16. + */ +public class UserGroupDTO { + + private String id; + private String workspaceId; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkflowDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkflowDTO.java new file mode 100644 index 0000000000..51545217fd --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkflowDTO.java @@ -0,0 +1,123 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@XmlRootElement +public class WorkflowDTO implements Serializable, Comparable { + + private int id; + private String finalLifeCycleState; + private List activities; + private Date abortedDate; + + public WorkflowDTO() { + activities = new ArrayList<>(); + } + + public List getActivities() { + return activities; + } + + public void setActivities(List activities) { + this.activities = activities; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getFinalLifeCycleState() { + return finalLifeCycleState; + } + + public void setFinalLifeCycleState(String finalLifeCycleState) { + this.finalLifeCycleState = finalLifeCycleState; + } + + public String getLifeCycleState() { + ActivityDTO current = getCurrentActivity(); + return (current == null) ? finalLifeCycleState : current.getLifeCycleState(); + } + + public Date getAbortedDate() { + return (abortedDate != null) ? (Date) abortedDate.clone() : null; + } + + public void setAbortedDate(Date abortedDate) { + this.abortedDate = (abortedDate != null) ? (Date) abortedDate.clone() : null; + } + + public ActivityDTO getCurrentActivity() { + if (getCurrentStep() < activities.size()) { + return activities.get(getCurrentStep()); + } else { + return null; + } + } + + public int getCurrentStep() { + int i = 0; + for (ActivityDTO activity : activities) { + if (activity.isComplete()) { + i++; + } else { + break; + } + } + return i; + } + + @Override + public int compareTo(WorkflowDTO o) { + return o.getAbortedDate().compareTo(this.getAbortedDate()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + WorkflowDTO that = (WorkflowDTO) o; + return id == that.id; + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + (finalLifeCycleState != null ? finalLifeCycleState.hashCode() : 0); + result = 31 * result + (activities != null ? activities.hashCode() : 0); + result = 31 * result + (abortedDate != null ? abortedDate.hashCode() : 0); + return result; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkflowModelDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkflowModelDTO.java new file mode 100644 index 0000000000..9abcb3fb05 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkflowModelDTO.java @@ -0,0 +1,130 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@XmlRootElement +public class WorkflowModelDTO implements Serializable { + + private String id; + private String reference; + private String finalLifeCycleState; + private UserDTO author; + private Date creationDate; + private ACLDTO acl; + + @XmlElement(nillable = false, required = true) + private List activityModels; + + public WorkflowModelDTO() { + activityModels = new ArrayList(); + } + + public WorkflowModelDTO(String id) { + this.id = id; + } + + public WorkflowModelDTO(String id, String reference, String finalLifeCycleState, UserDTO author, Date creationDate, List activityModels) { + this.id = id; + this.reference = reference; + this.finalLifeCycleState = finalLifeCycleState; + this.author = author; + this.creationDate = creationDate; + this.activityModels = activityModels; + + } + + public WorkflowModelDTO(String id, UserDTO pAuthor) { + this.id = id; + this.reference = id; + this.author = pAuthor; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public void addActivity(ActivityModelDTO activity) { + this.activityModels.add(activity); + } + + public void removeActivity(ActivityModelDTO activity) { + this.activityModels.remove(activity); + } + + public List getActivityModels() { + return this.activityModels; + } + + public void setActivityModels(List activityModels) { + this.activityModels = activityModels; + } + + public String getFinalLifeCycleState() { + return finalLifeCycleState; + } + + public void setFinalLifeCycleState(String finalLifeCycleState) { + this.finalLifeCycleState = finalLifeCycleState; + } + + public UserDTO getAuthor() { + return author; + } + + public void setAuthor(UserDTO author) { + this.author = author; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public String getReference() { + return reference; + } + + public void setReference(String reference) { + this.reference = reference; + } + + public ACLDTO getAcl() { + return acl; + } + + public void setAcl(ACLDTO acl) { + this.acl = acl; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceDTO.java new file mode 100644 index 0000000000..584507e355 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceDTO.java @@ -0,0 +1,59 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class WorkspaceDTO implements Serializable { + + private String id; + private String description; + private boolean folderLocked; + + public WorkspaceDTO() { + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public boolean isFolderLocked() { + return folderLocked; + } + + public void setFolderLocked(boolean folderLocked) { + this.folderLocked = folderLocked; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceDetailsDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceDetailsDTO.java new file mode 100644 index 0000000000..1e22cd09ac --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceDetailsDTO.java @@ -0,0 +1,65 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class WorkspaceDetailsDTO implements Serializable { + + private String id; + private String admin; + private String description; + + public WorkspaceDetailsDTO() { + } + + public WorkspaceDetailsDTO(String id, String admin, String description) { + this.id = id; + this.admin = admin; + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAdmin() { + return admin; + } + + public void setAdmin(String admin) { + this.admin = admin; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceListDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceListDTO.java new file mode 100644 index 0000000000..269babcdca --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceListDTO.java @@ -0,0 +1,59 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@XmlRootElement +public class WorkspaceListDTO implements Serializable { + private List administratedWorkspaces = new ArrayList<>(); + private List allWorkspaces = new ArrayList<>(); + + public WorkspaceListDTO() { + } + + public void addAdministratedWorkspaces(WorkspaceDTO workspace) { + this.administratedWorkspaces.add(workspace); + } + + public List getAdministratedWorkspaces() { + return administratedWorkspaces; + } + + public void setAdministratedWorkspaces(List administratedWorkspaces) { + this.administratedWorkspaces = administratedWorkspaces; + } + + public List getAllWorkspaces() { + return allWorkspaces; + } + + public void setAllWorkspaces(List allWorkspaces) { + this.allWorkspaces = allWorkspaces; + } + + public void addAllWorkspaces(WorkspaceDTO workspace) { + this.allWorkspaces.add(workspace); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceMembership.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceMembership.java new file mode 100644 index 0000000000..627de873da --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceMembership.java @@ -0,0 +1,29 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +/** + * @author Florent Garin + */ +public enum WorkspaceMembership { + READ_ONLY, + FULL_ACCESS +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceUserGroupMemberShipDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceUserGroupMemberShipDTO.java new file mode 100644 index 0000000000..6edb3f1238 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceUserGroupMemberShipDTO.java @@ -0,0 +1,66 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class WorkspaceUserGroupMemberShipDTO implements Serializable { + + private String workspaceId; + private String memberId; + private boolean readOnly; + + public WorkspaceUserGroupMemberShipDTO() { + } + + public WorkspaceUserGroupMemberShipDTO(String workspaceId, String memberId, boolean readOnly) { + this.workspaceId = workspaceId; + this.memberId = memberId; + this.readOnly = readOnly; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getMemberId() { + return memberId; + } + + public void setMemberId(String memberId) { + this.memberId = memberId; + } + + public boolean isReadOnly() { + return readOnly; + } + + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceUserMemberShipDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceUserMemberShipDTO.java new file mode 100644 index 0000000000..34886bf270 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceUserMemberShipDTO.java @@ -0,0 +1,66 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class WorkspaceUserMemberShipDTO implements Serializable { + + private String workspaceId; + private UserDTO member; + private boolean readOnly; + + public WorkspaceUserMemberShipDTO() { + } + + public WorkspaceUserMemberShipDTO(String workspaceId, UserDTO member, boolean readOnly) { + this.workspaceId = workspaceId; + this.member = member; + this.readOnly = readOnly; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public UserDTO getMember() { + return member; + } + + public void setMember(UserDTO member) { + this.member = member; + } + + public boolean isReadOnly() { + return readOnly; + } + + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceWorkflowCreationDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceWorkflowCreationDTO.java new file mode 100644 index 0000000000..eecb257d2d --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceWorkflowCreationDTO.java @@ -0,0 +1,62 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +/** + * @author Morgan Guimard + */ +@XmlRootElement +public class WorkspaceWorkflowCreationDTO implements Serializable { + + private String id; + private String workflowModelId; + private RoleMappingDTO[] roleMapping; + + public WorkspaceWorkflowCreationDTO() { + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public RoleMappingDTO[] getRoleMapping() { + return roleMapping; + } + + public void setRoleMapping(RoleMappingDTO[] roleMapping) { + this.roleMapping = roleMapping; + } + + public String getWorkflowModelId() { + return workflowModelId; + } + + public void setWorkflowModelId(String workflowModelId) { + this.workflowModelId = workflowModelId; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceWorkflowDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceWorkflowDTO.java new file mode 100644 index 0000000000..e1f63241ff --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/WorkspaceWorkflowDTO.java @@ -0,0 +1,63 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +/** + * @author Morgan Guimard + */ +@XmlRootElement +public class WorkspaceWorkflowDTO implements Serializable { + + private String workspaceId; + private String id; + + private WorkflowDTO workflow; + + public WorkspaceWorkflowDTO() { + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public WorkflowDTO getWorkflow() { + return workflow; + } + + public void setWorkflow(WorkflowDTO workflow) { + this.workflow = workflow; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/BaselinedDocumentDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/BaselinedDocumentDTO.java new file mode 100644 index 0000000000..ce83acd39d --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/BaselinedDocumentDTO.java @@ -0,0 +1,73 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.baseline; + +import com.docdoku.core.document.DocumentIteration; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class BaselinedDocumentDTO implements Serializable { + + private String documentId; + private String version; + private int iteration; + + public BaselinedDocumentDTO() { + } + + public BaselinedDocumentDTO(DocumentIteration partIteration) { + this.documentId = partIteration.getDocumentMasterId(); + this.version = partIteration.getVersion(); + this.iteration = partIteration.getIteration(); + } + + public BaselinedDocumentDTO(String number, String version, int iteration) { + this.documentId = number; + this.version = version; + this.iteration = iteration; + } + + public String getDocumentId() { + return documentId; + } + + public void setDocumentId(String documentId) { + this.documentId = documentId; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public int getIteration() { + return iteration; + } + + public void setIteration(int iteration) { + this.iteration = iteration; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/BaselinedPartDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/BaselinedPartDTO.java new file mode 100644 index 0000000000..237fa87244 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/BaselinedPartDTO.java @@ -0,0 +1,122 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.baseline; + +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartRevision; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@XmlRootElement +public class BaselinedPartDTO implements Serializable { + + private String number; + private String name; + private String version; + private int iteration; + private List availableIterations; + + public BaselinedPartDTO() { + } + + public BaselinedPartDTO(PartIteration partIteration) { + this.number = partIteration.getPartNumber(); + this.version = partIteration.getVersion(); + this.name = partIteration.getPartRevision().getPartMaster().getName(); + this.iteration = partIteration.getIteration(); + + this.availableIterations = new ArrayList<>(); + for (PartRevision partRevision : partIteration.getPartRevision().getPartMaster().getPartRevisions()) { + BaselinedPartOptionDTO option = new BaselinedPartOptionDTO(partRevision.getVersion(), + partRevision.getLastIteration().getIteration(), + partRevision.isReleased()); + this.availableIterations.add(option); + } + } + + public BaselinedPartDTO(List availableParts) { + + PartIteration max = Collections.max(availableParts); + + this.number = max.getPartNumber(); + this.version = max.getVersion(); + this.name = max.getPartRevision().getPartMaster().getName(); + this.iteration = max.getIteration(); + + this.availableIterations = new ArrayList<>(); + for (PartIteration partIteration : availableParts) { + this.availableIterations.add(new BaselinedPartOptionDTO(partIteration.getVersion(), partIteration.getIteration(), partIteration.getPartRevision().isReleased())); + } + + } + + public BaselinedPartDTO(String number, String version, int iteration) { + this.number = number; + this.version = version; + this.iteration = iteration; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public int getIteration() { + return iteration; + } + + public void setIteration(int iteration) { + this.iteration = iteration; + } + + + public List getAvailableIterations() { + return availableIterations; + } + + public void setAvailableIterations(List availableIterations) { + this.availableIterations = availableIterations; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/BaselinedPartOptionDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/BaselinedPartOptionDTO.java new file mode 100644 index 0000000000..56982d44b1 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/BaselinedPartOptionDTO.java @@ -0,0 +1,64 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.baseline; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class BaselinedPartOptionDTO implements Serializable { + private String version; + private int lastIteration; + private boolean released; + + public BaselinedPartOptionDTO() { + } + + public BaselinedPartOptionDTO(String version, int lastIteration, boolean released) { + this.version = version; + this.lastIteration = lastIteration; + this.released = released; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public int getLastIteration() { + return lastIteration; + } + + public void setLastIteration(int lastIteration) { + this.lastIteration = lastIteration; + } + + public boolean isReleased() { + return released; + } + + public void setReleased(boolean released) { + this.released = released; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/DocumentBaselineDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/DocumentBaselineDTO.java new file mode 100644 index 0000000000..25a6dff04f --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/DocumentBaselineDTO.java @@ -0,0 +1,90 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.baseline; + +import com.docdoku.server.rest.dto.FolderDTO; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@XmlRootElement +public class DocumentBaselineDTO implements Serializable { + + private int id; + private String name; + private String description; + private Date creationDate; + private String workspaceId; + private List baselinedFolders; + + public DocumentBaselineDTO() { + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public List getBaselinedFolders() { + return baselinedFolders; + } + + public void setBaselinedFolders(List baselinedFolders) { + this.baselinedFolders = baselinedFolders; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/PathChoiceDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/PathChoiceDTO.java new file mode 100644 index 0000000000..7558f75294 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/PathChoiceDTO.java @@ -0,0 +1,67 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.baseline; + +import com.docdoku.core.configuration.PathChoice; +import com.docdoku.core.configuration.ResolvedPartLink; +import com.docdoku.server.rest.dto.PartUsageLinkDTO; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@XmlRootElement +public class PathChoiceDTO implements Serializable { + + private List resolvedPath = new ArrayList<>(); + private PartUsageLinkDTO partUsageLink; + + public PathChoiceDTO() { + } + + public PathChoiceDTO(PathChoice choice) { + for (ResolvedPartLink resolvedPartLink : choice.getResolvedPath()) { + resolvedPath.add(new ResolvedPartLinkDTO(resolvedPartLink)); + } + + Mapper mapper = DozerBeanMapperSingletonWrapper.getInstance(); + partUsageLink = mapper.map(choice.getPartUsageLink(), PartUsageLinkDTO.class); + } + + public List getResolvedPath() { + return resolvedPath; + } + + public void setResolvedPath(List resolvedPath) { + this.resolvedPath = resolvedPath; + } + + public PartUsageLinkDTO getPartUsageLink() { + return partUsageLink; + } + + public void setPartUsageLink(PartUsageLinkDTO partUsageLink) { + this.partUsageLink = partUsageLink; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/ProductBaselineDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/ProductBaselineDTO.java new file mode 100644 index 0000000000..c3a519c515 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/ProductBaselineDTO.java @@ -0,0 +1,175 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.baseline; + +import com.docdoku.core.configuration.ProductBaseline; +import com.docdoku.server.rest.dto.LightPartLinkListDTO; +import com.docdoku.server.rest.dto.PathToPathLinkDTO; +import com.docdoku.server.rest.dto.UserDTO; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@XmlRootElement +public class ProductBaselineDTO implements Serializable { + + private int id; + private String name; + private String description; + private Date creationDate; + private String configurationItemId; + private String configurationItemLatestRevision; + private ProductBaseline.BaselineType type; + private List baselinedParts; + private List substituteLinks; + private List optionalUsageLinks; + + private List substitutesParts; + private List optionalsParts; + private UserDTO author; + private boolean hasObsoletePartRevisions; + private List pathToPathLinks; + + public ProductBaselineDTO() { + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public List getBaselinedParts() { + return baselinedParts; + } + + public void setBaselinedParts(List baselinedParts) { + this.baselinedParts = baselinedParts; + } + + public String getConfigurationItemId() { + return configurationItemId; + } + + public void setConfigurationItemId(String configurationItemId) { + this.configurationItemId = configurationItemId; + } + + public List getSubstituteLinks() { + return substituteLinks; + } + + public void setSubstituteLinks(List substituteLinks) { + this.substituteLinks = substituteLinks; + } + + public List getOptionalUsageLinks() { + return optionalUsageLinks; + } + + public void setOptionalUsageLinks(List optionalUsageLinks) { + this.optionalUsageLinks = optionalUsageLinks; + } + + public String getConfigurationItemLatestRevision() { + return configurationItemLatestRevision; + } + + public void setConfigurationItemLatestRevision(String configurationItemLatestRevision) { + this.configurationItemLatestRevision = configurationItemLatestRevision; + } + + public List getSubstitutesParts() { + return substitutesParts; + } + + public void setSubstitutesParts(List substitutesParts) { + this.substitutesParts = substitutesParts; + } + + public List getOptionalsParts() { + return optionalsParts; + } + + public void setOptionalsParts(List optionalsParts) { + this.optionalsParts = optionalsParts; + } + + public UserDTO getAuthor() { + return author; + } + + public void setAuthor(UserDTO author) { + this.author = author; + } + + public boolean isHasObsoletePartRevisions() { + return hasObsoletePartRevisions; + } + + public void setHasObsoletePartRevisions(boolean hasObsoletePartRevisions) { + this.hasObsoletePartRevisions = hasObsoletePartRevisions; + } + + public List getPathToPathLinks() { + return pathToPathLinks; + } + + public void setPathToPathLinks(List pathToPathLinks) { + this.pathToPathLinks = pathToPathLinks; + } + + public ProductBaseline.BaselineType getType() { + return type; + } + + public void setType(ProductBaseline.BaselineType type) { + this.type = type; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/ProductConfigurationDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/ProductConfigurationDTO.java new file mode 100644 index 0000000000..7f8f7d4831 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/ProductConfigurationDTO.java @@ -0,0 +1,141 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.baseline; + +import com.docdoku.server.rest.dto.ACLDTO; +import com.docdoku.server.rest.dto.LightPartLinkListDTO; +import com.docdoku.server.rest.dto.UserDTO; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@XmlRootElement +public class ProductConfigurationDTO implements Serializable { + + @XmlElement(nillable = true) + private int id; + private String configurationItemId; + private String name; + private String description; + private List substituteLinks; + private List optionalUsageLinks; + private Date creationDate; + private UserDTO author; + + private List substitutesParts; + private List optionalsParts; + + private ACLDTO acl; + + public ProductConfigurationDTO() { + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getConfigurationItemId() { + return configurationItemId; + } + + public void setConfigurationItemId(String configurationItemId) { + this.configurationItemId = configurationItemId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getSubstituteLinks() { + return substituteLinks; + } + + public void setSubstituteLinks(List substituteLinks) { + this.substituteLinks = substituteLinks; + } + + public List getOptionalUsageLinks() { + return optionalUsageLinks; + } + + public void setOptionalUsageLinks(List optionalUsageLinks) { + this.optionalUsageLinks = optionalUsageLinks; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public ACLDTO getAcl() { + return acl; + } + + public void setAcl(ACLDTO acl) { + this.acl = acl; + } + + public UserDTO getAuthor() { + return author; + } + + public void setAuthor(UserDTO author) { + this.author = author; + } + + public List getSubstitutesParts() { + return substitutesParts; + } + + public void setSubstitutesParts(List substitutesParts) { + this.substitutesParts = substitutesParts; + } + + public List getOptionalsParts() { + return optionalsParts; + } + + public void setOptionalsParts(List optionalsParts) { + this.optionalsParts = optionalsParts; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/ResolvedPartLinkDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/ResolvedPartLinkDTO.java new file mode 100644 index 0000000000..26a9552e0c --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/baseline/ResolvedPartLinkDTO.java @@ -0,0 +1,61 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.baseline; + +import com.docdoku.core.configuration.ResolvedPartLink; +import com.docdoku.core.product.PartIteration; +import com.docdoku.server.rest.dto.LightPartLinkDTO; +import com.docdoku.server.rest.dto.PartIterationDTO; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class ResolvedPartLinkDTO implements Serializable { + + private PartIterationDTO partIteration; + private LightPartLinkDTO partLink; + + public ResolvedPartLinkDTO() { + } + + public ResolvedPartLinkDTO(ResolvedPartLink resolvedPartLink) { + PartIteration resolvedIteration = resolvedPartLink.getPartIteration(); + this.partIteration = new PartIterationDTO(resolvedIteration.getWorkspaceId(), resolvedIteration.getName(), resolvedIteration.getNumber(), resolvedIteration.getVersion(), resolvedIteration.getIteration()); + this.partLink = new LightPartLinkDTO(resolvedPartLink.getPartLink()); + } + + public PartIterationDTO getPartIteration() { + return partIteration; + } + + public void setPartIteration(PartIterationDTO partIteration) { + this.partIteration = partIteration; + } + + public LightPartLinkDTO getPartLink() { + return partLink; + } + + public void setPartLink(LightPartLinkDTO partLink) { + this.partLink = partLink; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeIssueDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeIssueDTO.java new file mode 100644 index 0000000000..8fccde41c6 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeIssueDTO.java @@ -0,0 +1,41 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.change; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class ChangeIssueDTO extends ChangeItemDTO implements Serializable { + private String initiator; + + public ChangeIssueDTO() { + + } + + public String getInitiator() { + return initiator; + } + + public void setInitiator(String initiator) { + this.initiator = initiator; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeIssueListDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeIssueListDTO.java new file mode 100644 index 0000000000..2d569d0e05 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeIssueListDTO.java @@ -0,0 +1,45 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto.change; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +/** + * @author Morgan Guimard + */ + +@XmlRootElement +public class ChangeIssueListDTO implements Serializable { + + private List issues; + + public ChangeIssueListDTO() { + } + + public List getIssues() { + return issues; + } + + public void setIssues(List issues) { + this.issues = issues; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeItemDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeItemDTO.java new file mode 100644 index 0000000000..6f8ba59f13 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeItemDTO.java @@ -0,0 +1,185 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.change; + +import com.docdoku.core.change.ChangeItem; +import com.docdoku.server.rest.dto.ACLDTO; +import com.docdoku.server.rest.dto.DocumentIterationDTO; +import com.docdoku.server.rest.dto.PartIterationDTO; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@XmlRootElement +public class ChangeItemDTO implements Serializable { + private int id; + private String name; + private String workspaceId; + private String author; + private String authorName; + private String assignee; + private String assigneeName; + private Date creationDate; + private String description; + private ChangeItem.Priority priority; + private ChangeItem.Category category; + private List affectedDocuments; + private List affectedParts; + private String[] tags; + @XmlElement(nillable = true) + private ACLDTO acl; + private boolean writable; + + public ChangeItemDTO() { + + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public String getAssignee() { + return assignee; + } + + public void setAssignee(String assignee) { + this.assignee = assignee; + } + + public String getAssigneeName() { + return assigneeName; + } + + public void setAssigneeName(String assigneeName) { + this.assigneeName = assigneeName; + } + + public Date getCreationDate() { + return (creationDate != null) ? (Date) creationDate.clone() : null; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = (creationDate != null) ? (Date) creationDate.clone() : null; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public ChangeItem.Priority getPriority() { + return priority; + } + + public void setPriority(ChangeItem.Priority priority) { + this.priority = priority; + } + + public ChangeItem.Category getCategory() { + return category; + } + + public void setCategory(ChangeItem.Category category) { + this.category = category; + } + + public List getAffectedDocuments() { + return affectedDocuments; + } + + public void setAffectedDocuments(List affectedDocuments) { + this.affectedDocuments = affectedDocuments; + } + + public List getAffectedParts() { + return affectedParts; + } + + public void setAffectedParts(List affectedParts) { + this.affectedParts = affectedParts; + } + + public String[] getTags() { + return tags; + } + + public void setTags(String[] tags) { + this.tags = tags; + } + + public ACLDTO getAcl() { + return acl; + } + + public void setAcl(ACLDTO acl) { + this.acl = acl; + } + + public boolean isWritable() { + return writable; + } + + public void setWritable(boolean writable) { + this.writable = writable; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeOrderDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeOrderDTO.java new file mode 100644 index 0000000000..8bffe4ff71 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeOrderDTO.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.change; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +@XmlRootElement +public class ChangeOrderDTO extends ChangeItemDTO implements Serializable { + private List addressedChangeRequests; + @XmlElement(nillable = true) + private int milestoneId; + + public ChangeOrderDTO() { + + } + + public List getAddressedChangeRequests() { + return addressedChangeRequests; + } + + public void setAddressedChangeRequests(List addressedChangeRequests) { + this.addressedChangeRequests = addressedChangeRequests; + } + + public int getMilestoneId() { + return milestoneId; + } + + public void setMilestoneId(int milestoneId) { + this.milestoneId = milestoneId; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeRequestDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeRequestDTO.java new file mode 100644 index 0000000000..cf3ecbb87b --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeRequestDTO.java @@ -0,0 +1,53 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.change; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +@XmlRootElement +public class ChangeRequestDTO extends ChangeItemDTO implements Serializable { + private List addressedChangeIssues; + @XmlElement(nillable = true) + private int milestoneId; + + public ChangeRequestDTO() { + + } + + public List getAddressedChangeIssues() { + return addressedChangeIssues; + } + + public void setAddressedChangeIssues(List addressedChangeRequests) { + this.addressedChangeIssues = addressedChangeRequests; + } + + public int getMilestoneId() { + return milestoneId; + } + + public void setMilestoneId(int milestoneId) { + this.milestoneId = milestoneId; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeRequestListDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeRequestListDTO.java new file mode 100644 index 0000000000..5ca209182e --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/ChangeRequestListDTO.java @@ -0,0 +1,44 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.dto.change; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +/** + * @author Morgan Guimard + */ +@XmlRootElement +public class ChangeRequestListDTO implements Serializable { + + private List requests; + + public ChangeRequestListDTO() { + } + + public List getRequests() { + return requests; + } + + public void setRequests(List requests) { + this.requests = requests; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/MilestoneDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/MilestoneDTO.java new file mode 100644 index 0000000000..515daac249 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/MilestoneDTO.java @@ -0,0 +1,117 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.change; + +import com.docdoku.server.rest.dto.ACLDTO; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Date; + +@XmlRootElement +public class MilestoneDTO implements Serializable { + + private int id; + private String title; + private Date dueDate; + private String description; + private String workspaceId; + private int numberOfRequests; + private int numberOfOrders; + private ACLDTO acl; + private boolean writable; + + public MilestoneDTO() { + + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Date getDueDate() { + return dueDate; + } + + public void setDueDate(Date dueDate) { + this.dueDate = dueDate; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getWorkspaceId() { + return workspaceId; + } + + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + + public int getNumberOfRequests() { + return numberOfRequests; + } + + public void setNumberOfRequests(int numberOfRequests) { + this.numberOfRequests = numberOfRequests; + } + + public int getNumberOfOrders() { + return numberOfOrders; + } + + public void setNumberOfOrders(int numberOfOrders) { + this.numberOfOrders = numberOfOrders; + } + + public ACLDTO getAcl() { + return acl; + } + + public void setAcl(ACLDTO acl) { + this.acl = acl; + } + + public boolean isWritable() { + return writable; + } + + public void setWritable(boolean writable) { + this.writable = writable; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/package-info.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/package-info.java new file mode 100644 index 0000000000..1616bb25c3 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/change/package-info.java @@ -0,0 +1,30 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + + +/** + * @author kelto on 01/06/15. + */ +@XmlJavaTypeAdapter(value = DateAdapter.class, type = Date.class) package com.docdoku.server.rest.dto.change; + +import com.docdoku.server.rest.converters.DateAdapter; + +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.Date; diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/package-info.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/package-info.java new file mode 100644 index 0000000000..9338a475f5 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/package-info.java @@ -0,0 +1,29 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +/** + * @author kelto on 01/06/15. + */ +@XmlJavaTypeAdapter(value = DateAdapter.class, type = Date.class) package com.docdoku.server.rest.dto; + +import com.docdoku.server.rest.converters.DateAdapter; + +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.Date; diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/product/ProductInstanceCreationDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/product/ProductInstanceCreationDTO.java new file mode 100644 index 0000000000..2b3ea2cadd --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/product/ProductInstanceCreationDTO.java @@ -0,0 +1,103 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.product; + +import com.docdoku.server.rest.dto.ACLDTO; +import com.docdoku.server.rest.dto.DocumentRevisionDTO; +import com.docdoku.server.rest.dto.InstanceAttributeDTO; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@XmlRootElement +public class ProductInstanceCreationDTO implements Serializable { + + private String serialNumber; + private String configurationItemId; + private int baselineId; + private ACLDTO acl; + private List instanceAttributes = new ArrayList<>(); + private Set linkedDocuments = new HashSet<>(); + private List attachedFiles; + + public ProductInstanceCreationDTO() { + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public String getConfigurationItemId() { + return configurationItemId; + } + + public void setConfigurationItemId(String configurationItemId) { + this.configurationItemId = configurationItemId; + } + + public int getBaselineId() { + return baselineId; + } + + public void setBaselineId(int baselineId) { + this.baselineId = baselineId; + } + + public ACLDTO getAcl() { + return acl; + } + + public void setAcl(ACLDTO acl) { + this.acl = acl; + } + + public List getInstanceAttributes() { + return instanceAttributes; + } + + public void setInstanceAttributes(List instanceAttributes) { + this.instanceAttributes = instanceAttributes; + } + + public Set getLinkedDocuments() { + return linkedDocuments; + } + + public void setLinkedDocuments(Set linkedDocuments) { + this.linkedDocuments = linkedDocuments; + } + + public List getAttachedFiles() { + return attachedFiles; + } + + public void setAttachedFiles(List attachedFiles) { + this.attachedFiles = attachedFiles; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/product/ProductInstanceIterationDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/product/ProductInstanceIterationDTO.java new file mode 100644 index 0000000000..a91af9968e --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/product/ProductInstanceIterationDTO.java @@ -0,0 +1,226 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.product; + +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.rest.dto.baseline.BaselinedPartDTO; +import com.docdoku.server.rest.dto.baseline.ProductBaselineDTO; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.*; + +@XmlRootElement +public class ProductInstanceIterationDTO implements Serializable { + private String serialNumber; + private int iteration; + private String iterationNote; + private String configurationItemId; + private String updateAuthor; + private String updateAuthorName; + private Date modificationDate; + private List baselinedParts; + private List substituteLinks; + private List optionalUsageLinks; + private UserDTO author; + private Date creationDate; + private List substitutesParts; + private List optionalsParts; + private List pathToPathLinks; + + private ProductBaselineDTO basedOn; + private List pathDataMasterList; + private List pathDataPaths; + private List instanceAttributes = new ArrayList<>(); + private Set linkedDocuments = new HashSet<>(); + private List attachedFiles; + + public ProductInstanceIterationDTO() { + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public int getIteration() { + return iteration; + } + + public void setIteration(int iteration) { + this.iteration = iteration; + } + + public String getIterationNote() { + return iterationNote; + } + + public void setIterationNote(String iterationNote) { + this.iterationNote = iterationNote; + } + + public String getConfigurationItemId() { + return configurationItemId; + } + + public void setConfigurationItemId(String configurationItemId) { + this.configurationItemId = configurationItemId; + } + + public String getUpdateAuthor() { + return updateAuthor; + } + + public void setUpdateAuthor(String updateAuthor) { + this.updateAuthor = updateAuthor; + } + + public String getUpdateAuthorName() { + return updateAuthorName; + } + + public void setUpdateAuthorName(String updateAuthorName) { + this.updateAuthorName = updateAuthorName; + } + + public Date getModificationDate() { + return modificationDate; + } + + public void setModificationDate(Date modificationDate) { + this.modificationDate = modificationDate; + } + + public List getBaselinedParts() { + return baselinedParts; + } + + public void setBaselinedParts(List baselinedParts) { + this.baselinedParts = baselinedParts; + } + + public List getOptionalUsageLinks() { + return optionalUsageLinks; + } + + public void setOptionalUsageLinks(List optionalUsageLinks) { + this.optionalUsageLinks = optionalUsageLinks; + } + + public List getInstanceAttributes() { + return instanceAttributes; + } + + public void setInstanceAttributes(List instanceAttributes) { + this.instanceAttributes = instanceAttributes; + } + + public Set getLinkedDocuments() { + return linkedDocuments; + } + + public void setLinkedDocuments(Set linkedDocuments) { + this.linkedDocuments = linkedDocuments; + } + + public List getAttachedFiles() { + return attachedFiles; + } + + public void setAttachedFiles(List attachedFiles) { + this.attachedFiles = attachedFiles; + } + + public ProductBaselineDTO getBasedOn() { + return basedOn; + } + + public void setBasedOn(ProductBaselineDTO basedOn) { + this.basedOn = basedOn; + } + + public List getPathDataMasterList() { + return pathDataMasterList; + } + + public void setPathDataMasterList(List pathDataMasterList) { + this.pathDataMasterList = pathDataMasterList; + } + + public List getSubstituteLinks() { + return substituteLinks; + } + + public void setSubstituteLinks(List substituteLinks) { + this.substituteLinks = substituteLinks; + } + + public List getSubstitutesParts() { + return substitutesParts; + } + + public void setSubstitutesParts(List substitutesParts) { + this.substitutesParts = substitutesParts; + } + + public List getOptionalsParts() { + return optionalsParts; + } + + public void setOptionalsParts(List optionalsParts) { + this.optionalsParts = optionalsParts; + } + + public UserDTO getAuthor() { + return author; + } + + public void setAuthor(UserDTO author) { + this.author = author; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public List getPathDataPaths() { + return pathDataPaths; + } + + public void setPathDataPaths(List pathDataPaths) { + this.pathDataPaths = pathDataPaths; + } + + public List getPathToPathLinks() { + return this.pathToPathLinks; + } + + public void setPathToPathLinks(List pathToPathLinks) { + this.pathToPathLinks = pathToPathLinks; + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/product/ProductInstanceMasterDTO.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/product/ProductInstanceMasterDTO.java new file mode 100644 index 0000000000..812f142a53 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/product/ProductInstanceMasterDTO.java @@ -0,0 +1,81 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.dto.product; + +import com.docdoku.server.rest.dto.ACLDTO; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.List; + +@XmlRootElement +public class ProductInstanceMasterDTO implements Serializable { + + private String identifier; + private String serialNumber; + private String configurationItemId; + private List productInstanceIterations; + private ACLDTO acl; + + public ProductInstanceMasterDTO() { + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public String getConfigurationItemId() { + return configurationItemId; + } + + public void setConfigurationItemId(String configurationItemId) { + this.configurationItemId = configurationItemId; + } + + public List getProductInstanceIterations() { + return productInstanceIterations; + } + + public void setProductInstanceIterations(List productInstanceIterations) { + this.productInstanceIterations = productInstanceIterations; + } + + public ACLDTO getAcl() { + return acl; + } + + public void setAcl(ACLDTO acl) { + this.acl = acl; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/product/package-info.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/product/package-info.java new file mode 100644 index 0000000000..9f0c163bba --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/dto/product/package-info.java @@ -0,0 +1,29 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +/** + * @author kelto on 01/06/15. + */ +@XmlJavaTypeAdapter(value = DateAdapter.class, type = Date.class) package com.docdoku.server.rest.dto.product; + +import com.docdoku.server.rest.converters.DateAdapter; + +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.Date; diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/ExpiredLinkException.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/ExpiredLinkException.java new file mode 100644 index 0000000000..5089f5e121 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/ExpiredLinkException.java @@ -0,0 +1,35 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions; + +/** + * @author Taylor LABEJOF + */ +public class ExpiredLinkException extends RestApiException { + + public ExpiredLinkException() { + super(); + } + + @Override + public String getMessage() { + return "This resource can not be found. Maybe the link has expired."; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/FileConversionException.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/FileConversionException.java new file mode 100644 index 0000000000..e60d981ff6 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/FileConversionException.java @@ -0,0 +1,37 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions; + +/** + * @author Taylor LABEJOF + */ +public class FileConversionException extends RestApiException { + private final Throwable cause; + + public FileConversionException(Throwable cause) { + super(); + this.cause = cause; + } + + @Override + public Throwable getCause() { + return cause; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/InterruptedStreamException.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/InterruptedStreamException.java new file mode 100644 index 0000000000..304d807995 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/InterruptedStreamException.java @@ -0,0 +1,30 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions; + +/** + * @author Taylor LABEJOF + */ +public class InterruptedStreamException extends RestApiException { + + public InterruptedStreamException() { + super(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/NotModifiedException.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/NotModifiedException.java new file mode 100644 index 0000000000..28e267d688 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/NotModifiedException.java @@ -0,0 +1,47 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions; + +import java.util.Calendar; +import java.util.Date; + +/** + * @author Taylor LABEJOF + */ +public class NotModifiedException extends RestApiException { + private static final int CACHE_SECOND = 60 * 60 * 24; + + private final String eTag; + + public NotModifiedException(String eTag) { + super(); + this.eTag = eTag; + } + + public String getETag() { + return eTag; + } + + public Date getExpireDate() { + Calendar expirationDate = Calendar.getInstance(); + expirationDate.add(Calendar.SECOND, CACHE_SECOND); + return expirationDate.getTime(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/PreconditionFailedException.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/PreconditionFailedException.java new file mode 100644 index 0000000000..02313cf063 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/PreconditionFailedException.java @@ -0,0 +1,38 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions; + +/** + * @author Taylor LABEJOF + */ +public class PreconditionFailedException extends RestApiException { + private final String resource; + + + public PreconditionFailedException(String resource) { + super(); + this.resource = resource; + } + + @Override + public String getMessage() { + return "Http preconditions have failed for " + resource; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/RequestedRangeNotSatisfiableException.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/RequestedRangeNotSatisfiableException.java new file mode 100644 index 0000000000..cfa2f2e35d --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/RequestedRangeNotSatisfiableException.java @@ -0,0 +1,43 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.exceptions; + +/** + * @author Julien Maffre + */ +public class RequestedRangeNotSatisfiableException extends RestApiException { + private final String resource; + private final long contentRange; + + public RequestedRangeNotSatisfiableException(String resource, long contentRange) { + this.resource = resource; + this.contentRange = contentRange; + } + + @Override + public String getMessage() { + return "Http requested range is not satisfiable for " + resource; + } + + public long getContentRange() { + return contentRange; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/RestApiException.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/RestApiException.java new file mode 100644 index 0000000000..debdf594c2 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/RestApiException.java @@ -0,0 +1,28 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.exceptions; + +public class RestApiException extends Exception { + + public RestApiException() { + super(); + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/UnmatchingUuidException.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/UnmatchingUuidException.java new file mode 100644 index 0000000000..956d3bae7c --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/UnmatchingUuidException.java @@ -0,0 +1,30 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions; + +/** + * @author Taylor LABEJOF + */ +public class UnmatchingUuidException extends RestApiException { + + public UnmatchingUuidException() { + super(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/AccessLocalExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/AccessLocalExceptionMapper.java new file mode 100644 index 0000000000..ad2271687b --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/AccessLocalExceptionMapper.java @@ -0,0 +1,22 @@ +package com.docdoku.server.rest.exceptions.mapper; + +import javax.ejb.AccessLocalException; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +@Provider +public class AccessLocalExceptionMapper implements ExceptionMapper { + + private static final Logger LOGGER = Logger.getLogger(AccessLocalExceptionMapper.class.getName()); + + @Override + public Response toResponse(AccessLocalException e) { + LOGGER.log(Level.SEVERE, "Access denied : " + e.getMessage(), e); + return Response.status(Response.Status.UNAUTHORIZED) + .header("Reason-Phrase", "Access denied") + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/AccessRightExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/AccessRightExceptionMapper.java new file mode 100644 index 0000000000..7268ea20ad --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/AccessRightExceptionMapper.java @@ -0,0 +1,51 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.core.exceptions.AccessRightException; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class AccessRightExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(AccessRightExceptionMapper.class.getName()); + + public AccessRightExceptionMapper() { + } + + @Override + public Response toResponse(AccessRightException e) { + LOGGER.log(Level.WARNING, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.FORBIDDEN) + .header("Reason-Phrase", e.getMessage()) + .entity(e.toString()) + .type(MediaType.TEXT_PLAIN) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/AlreadyExistsExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/AlreadyExistsExceptionMapper.java new file mode 100644 index 0000000000..0da140affd --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/AlreadyExistsExceptionMapper.java @@ -0,0 +1,51 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.core.exceptions.EntityAlreadyExistsException; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class AlreadyExistsExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(AlreadyExistsExceptionMapper.class.getName()); + + public AlreadyExistsExceptionMapper() { + } + + @Override + public Response toResponse(EntityAlreadyExistsException e) { + LOGGER.log(Level.WARNING, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.CONFLICT) + .header("Reason-Phrase", e.getMessage()) + .entity(e.toString()) + .type(MediaType.TEXT_PLAIN) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/ApplicationExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/ApplicationExceptionMapper.java new file mode 100644 index 0000000000..c67af7600b --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/ApplicationExceptionMapper.java @@ -0,0 +1,51 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.core.exceptions.ApplicationException; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class ApplicationExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(ApplicationExceptionMapper.class.getName()); + + public ApplicationExceptionMapper() { + } + + @Override + public Response toResponse(ApplicationException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.BAD_REQUEST) + .header("Reason-Phrase", e.getMessage()) + .entity(e.toString()) + .type(MediaType.TEXT_PLAIN) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/CreationExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/CreationExceptionMapper.java new file mode 100644 index 0000000000..fafc555f76 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/CreationExceptionMapper.java @@ -0,0 +1,51 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.core.exceptions.CreationException; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class CreationExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(CreationExceptionMapper.class.getName()); + + public CreationExceptionMapper() { + } + + @Override + public Response toResponse(CreationException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.BAD_REQUEST) + .header("Reason-Phrase", e.getMessage()) + .entity(e.toString()) + .type(MediaType.TEXT_PLAIN) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/ESServerExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/ESServerExceptionMapper.java new file mode 100644 index 0000000000..1537106997 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/ESServerExceptionMapper.java @@ -0,0 +1,51 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.core.exceptions.ESServerException; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class ESServerExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(ESServerExceptionMapper.class.getName()); + + public ESServerExceptionMapper() { + } + + @Override + public Response toResponse(ESServerException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .header("Reason-Phrase", e.getMessage()) + .entity(e.toString()) + .type(MediaType.TEXT_PLAIN) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/EntityConstraintExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/EntityConstraintExceptionMapper.java new file mode 100644 index 0000000000..4b9024e427 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/EntityConstraintExceptionMapper.java @@ -0,0 +1,51 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.core.exceptions.EntityConstraintException; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class EntityConstraintExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(EntityConstraintExceptionMapper.class.getName()); + + public EntityConstraintExceptionMapper() { + } + + @Override + public Response toResponse(EntityConstraintException e) { + LOGGER.log(Level.WARNING, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.BAD_REQUEST) + .header("Reason-Phrase", e.getMessage()) + .entity(e.toString()) + .type(MediaType.TEXT_PLAIN) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/ExpiredLinkExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/ExpiredLinkExceptionMapper.java new file mode 100644 index 0000000000..f8bdeb124f --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/ExpiredLinkExceptionMapper.java @@ -0,0 +1,51 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.server.rest.exceptions.ExpiredLinkException; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class ExpiredLinkExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(ExpiredLinkExceptionMapper.class.getName()); + + public ExpiredLinkExceptionMapper() { + } + + @Override + public Response toResponse(ExpiredLinkException e) { + LOGGER.log(Level.WARNING, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.NOT_FOUND) + .header("Reason-Phrase", e.getMessage()) + .entity(e.getMessage()) + .type(MediaType.TEXT_PLAIN) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/NotAllowedExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/NotAllowedExceptionMapper.java new file mode 100644 index 0000000000..97d14181a9 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/NotAllowedExceptionMapper.java @@ -0,0 +1,51 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.core.exceptions.NotAllowedException; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class NotAllowedExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(NotAllowedExceptionMapper.class.getName()); + + public NotAllowedExceptionMapper() { + } + + @Override + public Response toResponse(NotAllowedException e) { + LOGGER.log(Level.WARNING, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.BAD_REQUEST) + .header("Reason-Phrase", e.getMessage()) + .entity(e.toString()) + .type(MediaType.TEXT_PLAIN) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/NotFoundExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/NotFoundExceptionMapper.java new file mode 100644 index 0000000000..74b4d032ba --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/NotFoundExceptionMapper.java @@ -0,0 +1,51 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.core.exceptions.EntityNotFoundException; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class NotFoundExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(NotFoundExceptionMapper.class.getName()); + + public NotFoundExceptionMapper() { + } + + @Override + public Response toResponse(EntityNotFoundException e) { + LOGGER.log(Level.WARNING, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.NOT_FOUND) + .header("Reason-Phrase", e.getMessage()) + .entity(e.toString()) + .type(MediaType.TEXT_PLAIN) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/NotModifiedExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/NotModifiedExceptionMapper.java new file mode 100644 index 0000000000..ec56a4940d --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/NotModifiedExceptionMapper.java @@ -0,0 +1,48 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.server.rest.exceptions.NotModifiedException; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class NotModifiedExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(NotModifiedExceptionMapper.class.getName()); + + public NotModifiedExceptionMapper() { + } + + @Override + public Response toResponse(NotModifiedException e) { + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.NOT_MODIFIED) + .expires(e.getExpireDate()) + .tag(e.getETag()) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/PreconditionFailedExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/PreconditionFailedExceptionMapper.java new file mode 100644 index 0000000000..ee082626e4 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/PreconditionFailedExceptionMapper.java @@ -0,0 +1,47 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.server.rest.exceptions.PreconditionFailedException; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class PreconditionFailedExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(PreconditionFailedExceptionMapper.class.getName()); + + public PreconditionFailedExceptionMapper() { + } + + @Override + public Response toResponse(PreconditionFailedException e) { + LOGGER.log(Level.WARNING, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.PRECONDITION_FAILED) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/RequestedRangeNotSatisfiableExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/RequestedRangeNotSatisfiableExceptionMapper.java new file mode 100644 index 0000000000..8e3c8c39c2 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/RequestedRangeNotSatisfiableExceptionMapper.java @@ -0,0 +1,48 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.server.rest.exceptions.RequestedRangeNotSatisfiableException; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class RequestedRangeNotSatisfiableExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(RequestedRangeNotSatisfiableExceptionMapper.class.getName()); + + public RequestedRangeNotSatisfiableExceptionMapper() { + } + + @Override + public Response toResponse(RequestedRangeNotSatisfiableException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.REQUESTED_RANGE_NOT_SATISFIABLE) + .header("Content-Range", "bytes */" + e.getContentRange()) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/RestApiExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/RestApiExceptionMapper.java new file mode 100644 index 0000000000..258628731f --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/RestApiExceptionMapper.java @@ -0,0 +1,47 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.server.rest.exceptions.RestApiException; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class RestApiExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(RestApiExceptionMapper.class.getName()); + + public RestApiExceptionMapper() { + } + + @Override + public Response toResponse(RestApiException e) { + LOGGER.log(Level.SEVERE, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.BAD_REQUEST) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/RuntimeExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/RuntimeExceptionMapper.java new file mode 100644 index 0000000000..806f377d3c --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/RuntimeExceptionMapper.java @@ -0,0 +1,77 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Morgan Guimard + */ +@Provider +public class RuntimeExceptionMapper implements ExceptionMapper { + + private static final Logger LOGGER = Logger.getLogger(RuntimeExceptionMapper.class.getName()); + + private static final String MESSAGE_PREFIX = "Unhandled system error"; + + public RuntimeExceptionMapper() { + } + + @Override + public Response toResponse(RuntimeException e) { + + LOGGER.log(Level.SEVERE, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + + Throwable cause = e; + while (cause.getCause() != null) { + cause = cause.getCause(); + } + + StackTraceElement[] stackTrace = cause.getStackTrace(); + StackTraceElement firstTraceElement = stackTrace[0]; + String fileName = firstTraceElement.getFileName(); + String methodName = firstTraceElement.getMethodName(); + int lineNumber = firstTraceElement.getLineNumber(); + String className = firstTraceElement.getClassName(); + + String fullMessage = MESSAGE_PREFIX + + " : " + + className + "." + methodName + + " threw " + + cause.toString() + + " in " + + fileName + + " at line " + + lineNumber; + + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .header("Reason-Phrase", fullMessage) + .entity(fullMessage) + .type(MediaType.TEXT_PLAIN) + .build(); + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/UnmatchingUuidExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/UnmatchingUuidExceptionMapper.java new file mode 100644 index 0000000000..698ea32be6 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/UnmatchingUuidExceptionMapper.java @@ -0,0 +1,46 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.server.rest.exceptions.UnmatchingUuidException; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class UnmatchingUuidExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(UnmatchingUuidExceptionMapper.class.getName()); + + public UnmatchingUuidExceptionMapper() { + } + + @Override + public Response toResponse(UnmatchingUuidException e) { + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.METHOD_NOT_ALLOWED) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/UserNotActiveExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/UserNotActiveExceptionMapper.java new file mode 100644 index 0000000000..b40df567f1 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/UserNotActiveExceptionMapper.java @@ -0,0 +1,51 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.core.exceptions.UserNotActiveException; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class UserNotActiveExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(UserNotActiveExceptionMapper.class.getName()); + + public UserNotActiveExceptionMapper() { + } + + @Override + public Response toResponse(UserNotActiveException e) { + LOGGER.log(Level.WARNING, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.FORBIDDEN) + .header("Reason-Phrase", e.getMessage()) + .entity(e.toString()) + .type(MediaType.TEXT_PLAIN) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/UserNotFoundExceptionMapper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/UserNotFoundExceptionMapper.java new file mode 100644 index 0000000000..fb1f99bd49 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/exceptions/mapper/UserNotFoundExceptionMapper.java @@ -0,0 +1,51 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.exceptions.mapper; + +import com.docdoku.core.exceptions.UserNotFoundException; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +@Provider +public class UserNotFoundExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = Logger.getLogger(UserNotFoundExceptionMapper.class.getName()); + + public UserNotFoundExceptionMapper() { + } + + @Override + public Response toResponse(UserNotFoundException e) { + LOGGER.log(Level.WARNING, e.getMessage()); + LOGGER.log(Level.FINE, null, e); + return Response.status(Response.Status.FORBIDDEN) + .header("Reason-Phrase", e.getMessage()) + .entity(e.toString()) + .type(MediaType.TEXT_PLAIN) + .build(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/DocumentBinaryResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/DocumentBinaryResource.java new file mode 100644 index 0000000000..d9835993fa --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/DocumentBinaryResource.java @@ -0,0 +1,287 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.file; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentIterationKey; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.*; +import com.docdoku.core.sharing.SharedDocument; +import com.docdoku.core.sharing.SharedEntity; +import com.docdoku.server.filters.GuestProxy; +import com.docdoku.server.helpers.Streams; +import com.docdoku.server.rest.exceptions.*; +import com.docdoku.server.rest.file.util.BinaryResourceDownloadMeta; +import com.docdoku.server.rest.file.util.BinaryResourceDownloadResponseBuilder; +import com.docdoku.server.rest.file.util.BinaryResourceUpload; +import com.docdoku.server.rest.interceptors.Compress; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.Part; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.text.Normalizer; +import java.util.Collection; +import java.util.Date; +import java.util.Locale; +import java.util.logging.Logger; + +@RequestScoped +@Api(hidden = true, value = "document-binary", description = "Operations about document files") +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) +@RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) +public class DocumentBinaryResource { + + private static final Logger LOGGER = Logger.getLogger(DocumentBinaryResource.class.getName()); + @Inject + private IDataManagerLocal dataManager; + @Inject + private IDocumentManagerLocal documentService; + @Inject + private IContextManagerLocal contextManager; + @Inject + private IDocumentResourceGetterManagerLocal documentResourceGetterService; + @Inject + private IDocumentPostUploaderManagerLocal documentPostUploaderService; + @Inject + private IShareManagerLocal shareService; + @Inject + private GuestProxy guestProxy; + + public DocumentBinaryResource() { + } + + @POST + @ApiOperation(value = "Upload document file", response = Response.class) + @Path("/{iteration}") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + public Response uploadDocumentFiles(@Context HttpServletRequest request, + @PathParam("workspaceId") final String workspaceId, + @PathParam("documentId") final String documentId, + @PathParam("version") final String version, + @PathParam("iteration") final int iteration) + throws EntityNotFoundException, EntityAlreadyExistsException, UserNotActiveException, AccessRightException, NotAllowedException, CreationException { + try { + String fileName = null; + DocumentIterationKey docPK = new DocumentIterationKey(workspaceId, documentId, version, iteration); + Collection formParts = request.getParts(); + + for (Part formPart : formParts) { + fileName = uploadAFile(formPart, docPK); + } + + if (formParts.size() == 1) { + return BinaryResourceUpload.tryToRespondCreated(request.getRequestURI() + URLEncoder.encode(fileName, "UTF-8")); + } + return Response.ok().build(); + + } catch (IOException | ServletException | StorageException e) { + return BinaryResourceUpload.uploadError(e); + } + } + + private String uploadAFile(Part formPart, DocumentIterationKey docPK) + throws EntityNotFoundException, EntityAlreadyExistsException, AccessRightException, NotAllowedException, CreationException, UserNotActiveException, StorageException, IOException { + + String fileName = Normalizer.normalize(formPart.getSubmittedFileName(), Normalizer.Form.NFC); + // Init the binary resource with a null length + BinaryResource binaryResource = documentService.saveFileInDocument(docPK, fileName, 0); + OutputStream outputStream = dataManager.getBinaryResourceOutputStream(binaryResource); + long length = BinaryResourceUpload.uploadBinary(outputStream, formPart); + documentService.saveFileInDocument(docPK, fileName, length); + documentPostUploaderService.process(binaryResource); + return fileName; + } + + // TODO split method using query params for uuid and virtualSubResource + // TODO -> will simplify swagger json generation + @GET + @ApiOperation(value = "Download document file", response = Response.class) + @Path("/{iteration}/{fileName}{uuid:(/uuid/[^/]+?)?}{virtualSubResource : (/[^/]+?)?}") + @Compress + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Response downloadDocumentFile(@Context Request request, + @HeaderParam("Range") String range, + @HeaderParam("Referer") String referer, + @PathParam("workspaceId") final String workspaceId, + @PathParam("documentId") final String documentId, + @PathParam("version") final String version, + @PathParam("iteration") final int iteration, + @PathParam("fileName") final String fileName, + @PathParam("virtualSubResource") final String virtualSubResource, + @QueryParam("type") String type, + @QueryParam("output") String output, + @PathParam("uuid") final String pUuid) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException, NotModifiedException, PreconditionFailedException, RequestedRangeNotSatisfiableException, UnmatchingUuidException, ExpiredLinkException { + + String fullName; + if (pUuid != null && !pUuid.isEmpty()) { + String uuid = pUuid.split("/")[2]; + SharedEntity sharedEntity = shareService.findSharedEntityForGivenUUID(uuid); + + // Check uuid & access right + checkUuidValidity(sharedEntity, workspaceId, documentId, version, iteration, referer); + + DocumentRevision documentRevision = ((SharedDocument) sharedEntity).getDocumentRevision(); + fullName = sharedEntity.getWorkspace().getId() + + "/documents/" + + documentRevision.getId() + "/" + + documentRevision.getVersion() + "/" + + iteration + "/" + fileName; + + } else { + // Check access right + DocumentIterationKey docIK = new DocumentIterationKey(workspaceId, documentId, version, iteration); + if (!canAccess(docIK)) { + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + + fullName = workspaceId + "/documents/" + documentId + "/" + version + "/" + iteration + "/" + fileName; + } + return downloadDocumentFile(request, range, fullName, virtualSubResource, type, output); + } + + + private Response downloadDocumentFile(Request request, String range, String fullName, String virtualSubResource, String type, String output) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException, NotModifiedException, PreconditionFailedException, RequestedRangeNotSatisfiableException { + BinaryResource binaryResource = getBinaryResource(fullName); + BinaryResourceDownloadMeta binaryResourceDownloadMeta = new BinaryResourceDownloadMeta(binaryResource, output, type); + binaryResourceDownloadMeta.setSubResourceVirtualPath(virtualSubResource); + + // Check cache precondition + Response.ResponseBuilder rb = request.evaluatePreconditions(binaryResourceDownloadMeta.getLastModified(), binaryResourceDownloadMeta.getETag()); + if (rb != null) { + return rb.build(); + } + + InputStream binaryContentInputStream = null; + try { + if (virtualSubResource != null && !virtualSubResource.isEmpty()) { + binaryContentInputStream = dataManager.getBinarySubResourceInputStream(binaryResource, fullName + "/" + virtualSubResource); + } else if (output != null && !output.isEmpty()) { + binaryContentInputStream = getConvertedBinaryResource(binaryResource, output); + } else { + binaryContentInputStream = dataManager.getBinaryResourceInputStream(binaryResource); + } + return BinaryResourceDownloadResponseBuilder.prepareResponse(binaryContentInputStream, binaryResourceDownloadMeta, range); + } catch (StorageException | FileConversionException e) { + Streams.close(binaryContentInputStream); + return BinaryResourceDownloadResponseBuilder.downloadError(e, fullName); + } + } + + /** + * Try to convert a binary resource to a specific format + * + * @param binaryResource The binary resource + * @param outputFormat The wanted output + * @return The binary resource stream in the wanted output + * @throws com.docdoku.server.rest.exceptions.FileConversionException + */ + private InputStream getConvertedBinaryResource(BinaryResource binaryResource, String outputFormat) throws FileConversionException { + try { + if (contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)) { + return documentResourceGetterService.getDocumentConvertedResource(outputFormat, binaryResource); + } else { + return guestProxy.getDocumentConvertedResource(outputFormat, binaryResource); + } + } catch (Exception e) { + throw new FileConversionException(e); + } + } + + private boolean canAccess(DocumentIterationKey docIKey) throws AccessRightException, NotAllowedException, WorkspaceNotFoundException, UserNotFoundException, DocumentRevisionNotFoundException, UserNotActiveException { + DocumentRevision publicDocumentRevision = guestProxy.getPublicDocumentRevision(docIKey.getDocumentRevision()); + if(publicDocumentRevision!=null){ + return true; + } + return documentService.canAccess(docIKey); + } + + private BinaryResource getBinaryResource(String fullName) + throws NotAllowedException, AccessRightException, UserNotActiveException, EntityNotFoundException { + BinaryResource binaryResource = guestProxy.getPublicBinaryResourceForDocument(fullName); + if(binaryResource != null){ + return binaryResource; + } + else{ + return documentService.getBinaryResource(fullName); + } + } + + private void checkUuidValidity(SharedEntity sharedEntity, String workspaceId, String documentId, String version, int iteration, String referer) + throws UnmatchingUuidException, ExpiredLinkException, NotAllowedException { + if (!(sharedEntity instanceof SharedDocument)) { + throw new UnmatchingUuidException(); + } + + checkUuidReferer(sharedEntity, referer); + checkUuidExpiredDate(sharedEntity); + + String shareEntityWorkspaceId = sharedEntity.getWorkspace().getId(); + DocumentRevision documentRevision = ((SharedDocument) sharedEntity).getDocumentRevision(); + DocumentIteration lastCheckedInIteration = documentRevision.getLastCheckedInIteration(); + if (!shareEntityWorkspaceId.equals(workspaceId) || + !documentRevision.getDocumentMasterId().equals(documentId) || + !documentRevision.getVersion().equals(version) || + (null != lastCheckedInIteration && lastCheckedInIteration.getIteration() < iteration)) { + throw new UnmatchingUuidException(); + } + } + + private void checkUuidExpiredDate(SharedEntity sharedEntity) throws ExpiredLinkException { + // Check shared entity expired + if (sharedEntity.getExpireDate() != null && sharedEntity.getExpireDate().getTime() < new Date().getTime()) { + shareService.deleteSharedEntityIfExpired(sharedEntity); + throw new ExpiredLinkException(); + } + } + + private void checkUuidReferer(SharedEntity sharedEntity, String referer) throws NotAllowedException { + if (referer == null || referer.isEmpty()) { + throw new NotAllowedException(Locale.getDefault(), "NotAllowedException18"); + } + + String refererPath[] = referer.split("/"); + String refererUUID = refererPath[refererPath.length - 1]; + if (sharedEntity.getPassword() != null && !sharedEntity.getUuid().equals(refererUUID)) { + throw new NotAllowedException(Locale.getDefault(), "NotAllowedException18"); + } + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/DocumentTemplateBinaryResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/DocumentTemplateBinaryResource.java new file mode 100644 index 0000000000..0fa304258f --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/DocumentTemplateBinaryResource.java @@ -0,0 +1,169 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.file; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.document.DocumentMasterTemplateKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.core.services.IDocumentResourceGetterManagerLocal; +import com.docdoku.server.helpers.Streams; +import com.docdoku.server.rest.exceptions.FileConversionException; +import com.docdoku.server.rest.exceptions.NotModifiedException; +import com.docdoku.server.rest.exceptions.PreconditionFailedException; +import com.docdoku.server.rest.exceptions.RequestedRangeNotSatisfiableException; +import com.docdoku.server.rest.file.util.BinaryResourceDownloadMeta; +import com.docdoku.server.rest.file.util.BinaryResourceDownloadResponseBuilder; +import com.docdoku.server.rest.file.util.BinaryResourceUpload; +import com.docdoku.server.rest.interceptors.Compress; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.Part; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.text.Normalizer; +import java.util.Collection; +import java.util.logging.Logger; + +@RequestScoped +@Api(hidden = true, value = "document-template-binary", description = "Operations about document templates files") +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID}) +@RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) +public class DocumentTemplateBinaryResource { + + private static final Logger LOGGER = Logger.getLogger(DocumentTemplateBinaryResource.class.getName()); + @Inject + private IDataManagerLocal dataManager; + @Inject + private IDocumentManagerLocal documentService; + @Inject + private IDocumentResourceGetterManagerLocal documentResourceGetterService; + + + public DocumentTemplateBinaryResource() { + } + + @POST + @ApiOperation(value = "Upload document template file", response = Response.class) + @Consumes(MediaType.MULTIPART_FORM_DATA) + public Response uploadDocumentTemplateFiles(@Context HttpServletRequest request, + @PathParam("workspaceId") final String workspaceId, + @PathParam("templateId") final String templateId) + throws EntityNotFoundException, EntityAlreadyExistsException, UserNotActiveException, AccessRightException, NotAllowedException, CreationException { + + try { + BinaryResource binaryResource; + String fileName = null; + long length; + DocumentMasterTemplateKey templatePK = new DocumentMasterTemplateKey(workspaceId, templateId); + Collection formParts = request.getParts(); + + for (Part formPart : formParts) { + fileName = Normalizer.normalize(formPart.getSubmittedFileName(), Normalizer.Form.NFC); + // Init the binary resource with a null length + binaryResource = documentService.saveFileInTemplate(templatePK, fileName, 0); + OutputStream outputStream = dataManager.getBinaryResourceOutputStream(binaryResource); + length = BinaryResourceUpload.uploadBinary(outputStream, formPart); + documentService.saveFileInTemplate(templatePK, fileName, length); + } + + if (formParts.size() == 1) { + return BinaryResourceUpload.tryToRespondCreated(request.getRequestURI() + URLEncoder.encode(fileName, "UTF-8")); + } + return Response.ok().build(); + + } catch (IOException | ServletException | StorageException e) { + return BinaryResourceUpload.uploadError(e); + } + } + + + @GET + @ApiOperation(value = "Download document template file", response = Response.class) + @Path("/{fileName}") + @Compress + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Response downloadDocumentTemplateFile(@Context Request request, + @HeaderParam("Range") String range, + @PathParam("workspaceId") final String workspaceId, + @PathParam("templateId") final String templateId, + @PathParam("fileName") final String fileName, + @QueryParam("type") String type, + @QueryParam("output") String output) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException, PreconditionFailedException, NotModifiedException, RequestedRangeNotSatisfiableException { + + + String fullName = workspaceId + "/document-templates/" + templateId + "/" + fileName; + BinaryResource binaryResource = documentService.getTemplateBinaryResource(fullName); + BinaryResourceDownloadMeta binaryResourceDownloadMeta = new BinaryResourceDownloadMeta(binaryResource); + + // Check cache precondition + Response.ResponseBuilder rb = request.evaluatePreconditions(binaryResourceDownloadMeta.getLastModified(), binaryResourceDownloadMeta.getETag()); + if (rb != null) { + return rb.build(); + } + + InputStream binaryContentInputStream = null; + try { + if (output != null && !output.isEmpty()) { + binaryContentInputStream = getConvertedBinaryResource(binaryResource, output); + } else { + binaryContentInputStream = dataManager.getBinaryResourceInputStream(binaryResource); + } + return BinaryResourceDownloadResponseBuilder.prepareResponse(binaryContentInputStream, binaryResourceDownloadMeta, range); + } catch (StorageException | FileConversionException e) { + Streams.close(binaryContentInputStream); + return BinaryResourceDownloadResponseBuilder.downloadError(e, fullName); + } + } + + /** + * Try to convert a binary resource to a specific format + * + * @param binaryResource The binary resource + * @param output The wanted output + * @return The binary resource stream in the wanted output + * @throws com.docdoku.server.rest.exceptions.FileConversionException + */ + private InputStream getConvertedBinaryResource(BinaryResource binaryResource, String output) throws FileConversionException { + try { + return documentResourceGetterService.getDocumentConvertedResource(output, binaryResource); + } catch (Exception e) { + throw new FileConversionException(e); + } + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/PartBinaryResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/PartBinaryResource.java new file mode 100644 index 0000000000..ff33393b90 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/PartBinaryResource.java @@ -0,0 +1,405 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.file; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartIterationKey; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.*; +import com.docdoku.core.sharing.SharedEntity; +import com.docdoku.core.sharing.SharedPart; +import com.docdoku.server.filters.GuestProxy; +import com.docdoku.server.helpers.Streams; +import com.docdoku.server.rest.exceptions.*; +import com.docdoku.server.rest.file.util.BinaryResourceDownloadMeta; +import com.docdoku.server.rest.file.util.BinaryResourceDownloadResponseBuilder; +import com.docdoku.server.rest.file.util.BinaryResourceUpload; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.Part; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.text.Normalizer; +import java.util.Collection; +import java.util.Date; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +@RequestScoped +@Api(hidden = true, value = "part-binary", description = "Operations about part files") +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID, UserGroupMapping.GUEST_PROXY_ROLE_ID}) +public class PartBinaryResource { + + private static final Logger LOGGER = Logger.getLogger(PartBinaryResource.class.getName()); + private static final String NATIVE_CAD_SUBTYPE = "nativecad"; + private static final String ATTACHED_FILES_SUBTYPE = "attachedfiles"; + private static final String UTF8_ENCODING = "UTF-8"; + @Inject + private IDataManagerLocal dataManager; + @Inject + private IProductManagerLocal productService; + @Inject + private IContextManagerLocal contextManager; + @Inject + private IConverterManagerLocal converterService; + @Inject + private IShareManagerLocal shareService; + @Inject + private GuestProxy guestProxy; + @Inject + private IDocumentResourceGetterManagerLocal documentResourceGetterService; + + + public PartBinaryResource() { + } + + @POST + @ApiOperation(value = "Upload CAD file", response = Response.class) + @Path("/{iteration}/" + NATIVE_CAD_SUBTYPE) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + public Response uploadNativeCADFile(@Context HttpServletRequest request, + @PathParam("workspaceId") final String workspaceId, + @PathParam("partNumber") final String partNumber, + @PathParam("version") final String version, + @PathParam("iteration") final int iteration) + throws EntityNotFoundException, EntityAlreadyExistsException, UserNotActiveException, AccessRightException, NotAllowedException, CreationException { + try { + + PartIterationKey partPK = new PartIterationKey(workspaceId, partNumber, version, iteration); + Collection parts = request.getParts(); + + if (parts.isEmpty() || parts.size() > 1) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + + Part part = parts.iterator().next(); + String fileName = part.getSubmittedFileName(); + BinaryResource binaryResource = productService.saveNativeCADInPartIteration(partPK, fileName, 0); + OutputStream outputStream = dataManager.getBinaryResourceOutputStream(binaryResource); + long length = BinaryResourceUpload.uploadBinary(outputStream, part); + productService.saveNativeCADInPartIteration(partPK, fileName, length); + tryToConvertCADFileToOBJ(partPK, binaryResource); + + return BinaryResourceUpload.tryToRespondCreated(request.getRequestURI() + URLEncoder.encode(fileName, UTF8_ENCODING)); + + } catch (IOException | ServletException | StorageException e) { + return BinaryResourceUpload.uploadError(e); + } + } + + @POST + @ApiOperation(value = "Upload attached file", response = Response.class) + @Path("/{iteration}/" + ATTACHED_FILES_SUBTYPE) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + public Response uploadAttachedFiles(@Context HttpServletRequest request, + @PathParam("workspaceId") final String workspaceId, + @PathParam("partNumber") final String partNumber, + @PathParam("version") final String version, + @PathParam("iteration") final int iteration) + throws EntityNotFoundException, EntityAlreadyExistsException, UserNotActiveException, AccessRightException, NotAllowedException, CreationException { + try { + + PartIterationKey partPK = new PartIterationKey(workspaceId, partNumber, version, iteration); + Collection formParts = request.getParts(); + + String fileName = null; + + for (Part formPart : formParts) { + fileName = Normalizer.normalize(formPart.getSubmittedFileName(), Normalizer.Form.NFC); + BinaryResource binaryResource = productService.saveFileInPartIteration(partPK, fileName, ATTACHED_FILES_SUBTYPE, 0); + OutputStream outputStream = dataManager.getBinaryResourceOutputStream(binaryResource); + long length = BinaryResourceUpload.uploadBinary(outputStream, formPart); + productService.saveFileInPartIteration(partPK, fileName, ATTACHED_FILES_SUBTYPE, length); + } + + if (formParts.size() == 1) { + return BinaryResourceUpload.tryToRespondCreated(request.getRequestURI() + URLEncoder.encode(fileName, UTF8_ENCODING)); + } + + return Response.ok().build(); + + } catch (IOException | ServletException | StorageException e) { + return BinaryResourceUpload.uploadError(e); + } + } + + // Split on several methods because of Path conflict when we use regex + @GET + @ApiOperation(value = "Download direct part file", response = Response.class) + @Path("/{iteration}/{fileName}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Response downloadDirectPartFile(@Context Request request, + @HeaderParam("Range") String range, + @PathParam("workspaceId") final String workspaceId, + @PathParam("partNumber") final String partNumber, + @PathParam("version") final String version, + @PathParam("iteration") final int iteration, + @PathParam("fileName") final String fileName, + @QueryParam("type") String type, + @QueryParam("output") String output) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException, PreconditionFailedException, NotModifiedException, RequestedRangeNotSatisfiableException, UnmatchingUuidException, ExpiredLinkException { + return downloadPartFile(request, range, null, workspaceId, partNumber, version, iteration, null, fileName, type, output, null); + } + + @GET + @ApiOperation(value = "Upload part file from uuid", response = Response.class) + @Path("/{iteration}/{fileName}/uuid/{uuid}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Response downloadPartFileWithUuid(@Context Request request, + @HeaderParam("Range") String range, + @HeaderParam("Referer") String referer, + @PathParam("workspaceId") final String workspaceId, + @PathParam("partNumber") final String partNumber, + @PathParam("version") final String version, + @PathParam("iteration") final int iteration, + @PathParam("fileName") final String fileName, + @QueryParam("type") String type, + @QueryParam("output") String output, + @PathParam("uuid") final String uuid) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException, PreconditionFailedException, NotModifiedException, RequestedRangeNotSatisfiableException, UnmatchingUuidException, ExpiredLinkException { + return downloadPartFile(request, range, referer, workspaceId, partNumber, version, iteration, null, fileName, type, output, uuid); + } + + @GET + @ApiOperation(value = "Download part file with subtype", response = Response.class) + @Path("/{iteration}/{subType}/{fileName}/") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Response downloadPartFileWithSubtype(@Context Request request, + @HeaderParam("Range") String range, + @PathParam("workspaceId") final String workspaceId, + @PathParam("partNumber") final String partNumber, + @PathParam("version") final String version, + @PathParam("iteration") final int iteration, + @PathParam("subType") final String subType, + @PathParam("fileName") final String fileName, + @QueryParam("type") String type, + @QueryParam("output") String output) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException, PreconditionFailedException, NotModifiedException, RequestedRangeNotSatisfiableException, UnmatchingUuidException, ExpiredLinkException { + return downloadPartFile(request, range, null, workspaceId, partNumber, version, iteration, subType, fileName, type, output, null); + } + + + @GET + @ApiOperation(value = "Download part file", response = Response.class) + @Path("/{iteration}/{subType}/{fileName}/uuid/{uuid}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Response downloadPartFile(@Context Request request, + @HeaderParam("Range") String range, + @HeaderParam("Referer") String referer, + @PathParam("workspaceId") final String workspaceId, + @PathParam("partNumber") final String partNumber, + @PathParam("version") final String version, + @PathParam("iteration") final int iteration, + @PathParam("subType") final String subType, + @PathParam("fileName") final String fileName, + @QueryParam("type") String type, + @QueryParam("output") String output, + @PathParam("uuid") final String pUuid) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException, PreconditionFailedException, NotModifiedException, RequestedRangeNotSatisfiableException, UnmatchingUuidException, ExpiredLinkException { + + String fullName; + if (pUuid != null && !pUuid.isEmpty()) { + + SharedEntity sharedEntity = shareService.findSharedEntityForGivenUUID(pUuid); + + // Check uuid & access right + checkUuidValidity(sharedEntity, workspaceId, partNumber, version, iteration, referer); + + PartRevision partRevision = ((SharedPart) sharedEntity).getPartRevision(); + fullName = sharedEntity.getWorkspace().getId() + + "/parts/" + + partRevision.getPartNumber() + "/" + + partRevision.getVersion() + "/" + + iteration + "/"; + } else { + // Check access right + PartIterationKey partIK = new PartIterationKey(workspaceId, partNumber, version, iteration); + if (!canAccess(partIK)) { + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + + fullName = workspaceId + "/parts/" + partNumber + "/" + version + "/" + iteration + "/"; + } + + String decodedFileName = fileName; + + try { + decodedFileName = URLDecoder.decode(fileName, UTF8_ENCODING); + } catch (UnsupportedEncodingException e) { + LOGGER.log(Level.SEVERE, "Cannot decode filename"); + LOGGER.log(Level.FINER, null, e); + } + + fullName += (subType != null && !subType.isEmpty()) ? subType + "/" + decodedFileName : decodedFileName; + + return downloadPartFile(request, range, fullName, subType, type, output, pUuid); + } + + + private Response downloadPartFile(Request request, String range, String fullName, String subType, String type, String output, String uuid) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException, PreconditionFailedException, NotModifiedException, RequestedRangeNotSatisfiableException { + + BinaryResource binaryResource; + + if(uuid != null && !uuid.isEmpty()){ + binaryResource = guestProxy.getBinaryResourceForSharedPart(fullName); + }else { + binaryResource = getBinaryResource(fullName); + } + + BinaryResourceDownloadMeta binaryResourceDownloadMeta = new BinaryResourceDownloadMeta(binaryResource, output, type); + + // Check cache precondition + Response.ResponseBuilder rb = request.evaluatePreconditions(binaryResourceDownloadMeta.getLastModified(), binaryResourceDownloadMeta.getETag()); + if (rb != null) { + return rb.build(); + } + + //attachedfiles is not a valid subtype + if (subType != null && !subType.isEmpty() && !subType.equals("attachedfiles")) { + binaryResourceDownloadMeta.setSubResourceVirtualPath(subType); + } + + InputStream binaryContentInputStream = null; + try { + if ("attachedfiles".equals(subType) && output != null && !output.isEmpty()) { + binaryResourceDownloadMeta.setSubResourceVirtualPath(null); + binaryContentInputStream = getConvertedBinaryResource(binaryResource, output, uuid); + } else { + binaryContentInputStream = dataManager.getBinaryResourceInputStream(binaryResource); + } + return BinaryResourceDownloadResponseBuilder.prepareResponse(binaryContentInputStream, binaryResourceDownloadMeta, range); + } catch (StorageException | FileConversionException e) { + Streams.close(binaryContentInputStream); + return BinaryResourceDownloadResponseBuilder.downloadError(e, fullName); + } + } + + /** + * Try to convert a binary resource to a specific format + * + * @param binaryResource The binary resource + * @param outputFormat The wanted output + * @return The binary resource stream in the wanted output + * @throws com.docdoku.server.rest.exceptions.FileConversionException + */ + private InputStream getConvertedBinaryResource(BinaryResource binaryResource, String outputFormat, String uuid) throws FileConversionException { + try { + if(uuid != null && !uuid.isEmpty()){ + return guestProxy.getPartConvertedResource(outputFormat, binaryResource); + } + if (contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)) { + return documentResourceGetterService.getPartConvertedResource(outputFormat, binaryResource); + } else { + return guestProxy.getPartConvertedResource(outputFormat, binaryResource); + } + } catch (Exception e) { + throw new FileConversionException(e); + } + } + + private void tryToConvertCADFileToOBJ(PartIterationKey partPK, BinaryResource binaryResource) { + try { + //TODO: Should be put in a DocumentPostUploader plugin + converterService.convertCADFileToOBJ(partPK, binaryResource); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "A CAD file conversion can not be done", e); + } + } + + private boolean canAccess(PartIterationKey partIKey) throws UserNotActiveException, EntityNotFoundException { + if(guestProxy.canAccess(partIKey)){ + return true; + } + return productService.canAccess(partIKey); + } + + private BinaryResource getBinaryResource(String fullName) + throws NotAllowedException, AccessRightException, UserNotActiveException, EntityNotFoundException { + BinaryResource publicBinaryResourceForPart = guestProxy.getPublicBinaryResourceForPart(fullName); + if (publicBinaryResourceForPart != null) { + return publicBinaryResourceForPart; + } + return productService.getBinaryResource(fullName); + } + + private void checkUuidValidity(SharedEntity sharedEntity, String workspaceId, String partNumber, String version, int iteration, String referer) + throws UnmatchingUuidException, ExpiredLinkException, NotAllowedException { + if (!(sharedEntity instanceof SharedPart)) { + throw new UnmatchingUuidException(); + } + + checkUuidReferer(sharedEntity, referer); + checkUuidExpiredDate(sharedEntity); + + String shareEntityWorkspaceId = sharedEntity.getWorkspace().getId(); + PartRevision partRevision = ((SharedPart) sharedEntity).getPartRevision(); + PartIteration lastCheckedInIteration = partRevision.getLastCheckedInIteration(); + if (!shareEntityWorkspaceId.equals(workspaceId) || + !partRevision.getPartMasterNumber().equals(partNumber) || + !partRevision.getVersion().equals(version) || + (null != lastCheckedInIteration && lastCheckedInIteration.getIteration() < iteration)) { + throw new UnmatchingUuidException(); + } + } + + private void checkUuidExpiredDate(SharedEntity sharedEntity) throws ExpiredLinkException { + // Check shared entity expired + if (sharedEntity.getExpireDate() != null && sharedEntity.getExpireDate().getTime() < new Date().getTime()) { + shareService.deleteSharedEntityIfExpired(sharedEntity); + throw new ExpiredLinkException(); + } + } + + private void checkUuidReferer(SharedEntity sharedEntity, String referer) throws NotAllowedException { + if (referer == null || referer.isEmpty()) { + throw new NotAllowedException(Locale.getDefault(), "NotAllowedException18"); + } + + String refererPath[] = referer.split("/"); + String refererUUID = refererPath[refererPath.length - 1]; + if (sharedEntity.getPassword() != null && !sharedEntity.getUuid().equals(refererUUID)) { + throw new NotAllowedException(Locale.getDefault(), "NotAllowedException18"); + } + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/PartTemplateBinaryResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/PartTemplateBinaryResource.java new file mode 100644 index 0000000000..936a823c16 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/PartTemplateBinaryResource.java @@ -0,0 +1,141 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.file; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.product.PartMasterTemplateKey; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.server.helpers.Streams; +import com.docdoku.server.rest.exceptions.NotModifiedException; +import com.docdoku.server.rest.exceptions.PreconditionFailedException; +import com.docdoku.server.rest.exceptions.RequestedRangeNotSatisfiableException; +import com.docdoku.server.rest.file.util.BinaryResourceDownloadMeta; +import com.docdoku.server.rest.file.util.BinaryResourceDownloadResponseBuilder; +import com.docdoku.server.rest.file.util.BinaryResourceUpload; +import com.docdoku.server.rest.interceptors.Compress; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.Part; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.text.Normalizer; +import java.util.Collection; + +@RequestScoped +@Api(hidden = true, value = "part-template-binary", description = "Operations about part templates files") +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID}) +@RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) +public class PartTemplateBinaryResource { + + @Inject + private IDataManagerLocal dataManager; + + @Inject + private IProductManagerLocal productService; + + public PartTemplateBinaryResource() { + } + + @POST + @ApiOperation(value = "Upload part template files", response = Response.class) + @Consumes(MediaType.MULTIPART_FORM_DATA) + public Response uploadPartTemplateFiles(@Context HttpServletRequest request, + @PathParam("workspaceId") final String workspaceId, + @PathParam("templateId") final String templateId) + throws EntityNotFoundException, EntityAlreadyExistsException, UserNotActiveException, AccessRightException, NotAllowedException, CreationException { + + try { + BinaryResource binaryResource; + String fileName = null; + long length; + PartMasterTemplateKey templatePK = new PartMasterTemplateKey(workspaceId, templateId); + Collection formParts = request.getParts(); + + for (Part formPart : formParts) { + fileName = Normalizer.normalize(formPart.getSubmittedFileName(), Normalizer.Form.NFC); + // Init the binary resource with a null length + binaryResource = productService.saveFileInTemplate(templatePK, fileName, 0); + OutputStream outputStream = dataManager.getBinaryResourceOutputStream(binaryResource); + length = BinaryResourceUpload.uploadBinary(outputStream, formPart); + productService.saveFileInTemplate(templatePK, fileName, length); + } + + if (formParts.size() == 1) { + return BinaryResourceUpload.tryToRespondCreated(request.getRequestURI() + URLEncoder.encode(fileName, "UTF-8")); + } + return Response.ok().build(); + + } catch (IOException | ServletException | StorageException e) { + return BinaryResourceUpload.uploadError(e); + } + } + + @GET + @ApiOperation(value = "Download part template file", response = Response.class) + @Path("/{fileName}") + @Compress + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Response downloadPartTemplateFile(@Context Request request, + @HeaderParam("Range") String range, + @PathParam("workspaceId") final String workspaceId, + @PathParam("templateId") final String templateId, + @PathParam("fileName") final String fileName) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, NotAllowedException, PreconditionFailedException, NotModifiedException, RequestedRangeNotSatisfiableException { + + + String fullName = workspaceId + "/part-templates/" + templateId + "/" + fileName; + BinaryResource binaryResource = productService.getTemplateBinaryResource(fullName); + BinaryResourceDownloadMeta binaryResourceDownloadMeta = new BinaryResourceDownloadMeta(binaryResource); + + // Check cache precondition + Response.ResponseBuilder rb = request.evaluatePreconditions(binaryResourceDownloadMeta.getLastModified(), binaryResourceDownloadMeta.getETag()); + if (rb != null) { + return rb.build(); + } + + InputStream binaryContentInputStream = null; + try { + binaryContentInputStream = dataManager.getBinaryResourceInputStream(binaryResource); + return BinaryResourceDownloadResponseBuilder.prepareResponse(binaryContentInputStream, binaryResourceDownloadMeta, range); + } catch (StorageException e) { + Streams.close(binaryContentInputStream); + return BinaryResourceDownloadResponseBuilder.downloadError(e, fullName); + } + + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/ProductInstanceBinaryResource.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/ProductInstanceBinaryResource.java new file mode 100644 index 0000000000..320c2dbb97 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/ProductInstanceBinaryResource.java @@ -0,0 +1,348 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.file; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.configuration.ProductInstanceIterationKey; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.exceptions.NotAllowedException; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.IContextManagerLocal; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.services.IProductInstanceManagerLocal; +import com.docdoku.server.filters.GuestProxy; +import com.docdoku.server.helpers.Streams; +import com.docdoku.server.rest.exceptions.*; +import com.docdoku.server.rest.file.util.BinaryResourceDownloadMeta; +import com.docdoku.server.rest.file.util.BinaryResourceDownloadResponseBuilder; +import com.docdoku.server.rest.file.util.BinaryResourceUpload; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.Part; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.text.Normalizer; +import java.util.Collection; + +/* + * + * @author Asmae CHADID on 30/03/15. + */ + +@RequestScoped +@Api(hidden = true, value = "product-instance-binary", description = "Operations about product instances files") +@DeclareRoles({UserGroupMapping.REGULAR_USER_ROLE_ID}) +@RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) +public class ProductInstanceBinaryResource { + + @Inject + private IDataManagerLocal dataManager; + + @Inject + private IContextManagerLocal contextManager; + + @Inject + private IProductInstanceManagerLocal productInstanceManagerLocal; + + @Inject + private GuestProxy guestProxy; + + @POST + @ApiOperation(value = "Upload product instance files", response = Response.class) + @Path("iterations/{iteration}") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + public Response uploadFilesToProductInstanceIteration( + @Context HttpServletRequest request, + @PathParam("workspaceId") String workspaceId, @PathParam("ciId") String configurationItemId, @PathParam("serialNumber") String serialNumber, @PathParam("iteration") int iteration) + throws EntityNotFoundException, UserNotActiveException, NotAllowedException, AccessRightException, EntityAlreadyExistsException, CreationException { + + + try { + String fileName = null; + ProductInstanceIterationKey iterationKey = new ProductInstanceIterationKey(serialNumber, workspaceId, configurationItemId, iteration); + Collection formParts = request.getParts(); + + for (Part formPart : formParts) { + fileName = uploadAFile(workspaceId, formPart, iterationKey); + } + + if (formParts.size() == 1) { + return BinaryResourceUpload.tryToRespondCreated(request.getRequestURI() + URLEncoder.encode(fileName, "UTF-8")); + } + return Response.ok().build(); + + } catch (IOException | ServletException | StorageException e) { + return BinaryResourceUpload.uploadError(e); + } + + } + + + @GET + @ApiOperation(value = "Download product instance file", response = Response.class) + @Path("iterations/{iteration}/{fileName}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Response downloadFileFromProductInstance(@Context Request request, + @HeaderParam("Range") String range, + @PathParam("workspaceId") final String workspaceId, + @PathParam("serialNumber") final String serialNumber, + @PathParam("ciId") final String configurationItemId, + @PathParam("iteration") final int iteration, + @PathParam("fileName") final String fileName, + @QueryParam("type") String type, + @QueryParam("output") String output) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, com.docdoku.core.exceptions.NotAllowedException, PreconditionFailedException, NotModifiedException, RequestedRangeNotSatisfiableException, UnmatchingUuidException, ExpiredLinkException { + + + String fullName = workspaceId + "/product-instances/" + serialNumber + "/iterations/" + iteration + "/" + fileName; + BinaryResource binaryResource = getBinaryResource(fullName); + BinaryResourceDownloadMeta binaryResourceDownloadMeta = new BinaryResourceDownloadMeta(binaryResource, output, type); + + // Check cache precondition + Response.ResponseBuilder rb = request.evaluatePreconditions(binaryResourceDownloadMeta.getLastModified(), binaryResourceDownloadMeta.getETag()); + if (rb != null) { + return rb.build(); + } + InputStream binaryContentInputStream = null; + try { + binaryContentInputStream = dataManager.getBinaryResourceInputStream(binaryResource); + return BinaryResourceDownloadResponseBuilder.prepareResponse(binaryContentInputStream, binaryResourceDownloadMeta, range); + } catch (StorageException e) { + Streams.close(binaryContentInputStream); + return BinaryResourceDownloadResponseBuilder.downloadError(e, fullName); + } + + } + + + @POST + @ApiOperation(value = "Upload path data file", response = Response.class) + @Path("pathdata/{pathDataId}") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + public Response uploadFilesToPathData( + @Context HttpServletRequest request, + @PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String configurationItemId, + @PathParam("serialNumber") String serialNumber, + @PathParam("pathDataId") int pathDataId, + @PathParam("iteration") int iteration) + throws EntityNotFoundException, UserNotActiveException, NotAllowedException, AccessRightException, EntityAlreadyExistsException, CreationException { + + // TODO: determine if this WS is really used... + + try { + String fileName = null; + Collection formParts = request.getParts(); + + for (Part formPart : formParts) { + fileName = uploadAFileToPathData(workspaceId, formPart, configurationItemId, serialNumber, pathDataId, iteration); + } + + if (formParts.size() == 1) { + return BinaryResourceUpload.tryToRespondCreated(request.getRequestURI() + URLEncoder.encode(fileName, "UTF-8")); + } + return Response.ok().build(); + + } catch (IOException | ServletException | StorageException e) { + return BinaryResourceUpload.uploadError(e); + } + + } + + @POST + @ApiOperation(value = "Upload path data iteration file", response = Response.class) + @Path("pathdata/{path}/iterations/{iteration}") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @RolesAllowed({UserGroupMapping.REGULAR_USER_ROLE_ID}) + public Response uploadFilesToPathDataIteration( + @Context HttpServletRequest request, + @PathParam("workspaceId") String workspaceId, + @PathParam("ciId") String configurationItemId, + @PathParam("serialNumber") String serialNumber, + @PathParam("path") int path, + @PathParam("iteration") int iteration) + throws EntityNotFoundException, UserNotActiveException, NotAllowedException, AccessRightException, EntityAlreadyExistsException, CreationException { + + + try { + String fileName = null; + Collection formParts = request.getParts(); + + for (Part formPart : formParts) { + fileName = uploadAFileToPathDataIteration(workspaceId, formPart, configurationItemId, serialNumber, path, iteration); + } + + if (formParts.size() == 1) { + return BinaryResourceUpload.tryToRespondCreated(request.getRequestURI() + URLEncoder.encode(fileName, "UTF-8")); + } + return Response.ok().build(); + + } catch (IOException | ServletException | StorageException e) { + return BinaryResourceUpload.uploadError(e); + } + + } + + @GET + @ApiOperation(value = "Download path data file", response = Response.class) + @Path("pathdata/{pathDataId}/{fileName}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Response downloadFileFromPathData(@Context Request request, + @HeaderParam("Range") String range, + @PathParam("workspaceId") final String workspaceId, + @PathParam("serialNumber") final String serialNumber, + @PathParam("ciId") final String configurationItemId, + @PathParam("pathDataId") final int pathDataId, + @PathParam("fileName") final String fileName, + @QueryParam("type") String type, + @QueryParam("output") String output) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, com.docdoku.core.exceptions.NotAllowedException, PreconditionFailedException, NotModifiedException, RequestedRangeNotSatisfiableException, UnmatchingUuidException, ExpiredLinkException { + + + String fullName = workspaceId + "/product-instances/" + serialNumber + "/pathdata/" + pathDataId + "/" + fileName; + BinaryResource binaryResource = getPathDataBinaryResource(fullName); + BinaryResourceDownloadMeta binaryResourceDownloadMeta = new BinaryResourceDownloadMeta(binaryResource, output, type); + + // Check cache precondition + Response.ResponseBuilder rb = request.evaluatePreconditions(binaryResourceDownloadMeta.getLastModified(), binaryResourceDownloadMeta.getETag()); + if (rb != null) { + return rb.build(); + } + + InputStream binaryContentInputStream = null; + try { + binaryContentInputStream = dataManager.getBinaryResourceInputStream(binaryResource); + return BinaryResourceDownloadResponseBuilder.prepareResponse(binaryContentInputStream, binaryResourceDownloadMeta, range); + } catch (StorageException e) { + Streams.close(binaryContentInputStream); + return BinaryResourceDownloadResponseBuilder.downloadError(e, fullName); + } + } + + @GET + @ApiOperation(value = "Download path data iteration file", response = Response.class) + @Path("pathdata/{pathDataId}/iterations/{iteration}/{fileName}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Response downloadFileFromPathDataIteration(@Context Request request, + @HeaderParam("Range") String range, + @PathParam("workspaceId") final String workspaceId, + @PathParam("serialNumber") final String serialNumber, + @PathParam("pathDataId") String pathDataId, + @PathParam("iteration") final int iteration, + @PathParam("fileName") final String fileName, + @QueryParam("type") String type, + @QueryParam("output") String output) + throws EntityNotFoundException, UserNotActiveException, AccessRightException, com.docdoku.core.exceptions.NotAllowedException, PreconditionFailedException, NotModifiedException, RequestedRangeNotSatisfiableException, UnmatchingUuidException, ExpiredLinkException { + + + String fullName = workspaceId + "/product-instances/" + serialNumber + "/pathdata/" + pathDataId + "/iterations/" + iteration + '/' + fileName; + BinaryResource binaryResource = getPathDataBinaryResource(fullName); + BinaryResourceDownloadMeta binaryResourceDownloadMeta = new BinaryResourceDownloadMeta(binaryResource, output, type); + + // Check cache precondition + Response.ResponseBuilder rb = request.evaluatePreconditions(binaryResourceDownloadMeta.getLastModified(), binaryResourceDownloadMeta.getETag()); + if (rb != null) { + return rb.build(); + } + + InputStream binaryContentInputStream = null; + + try { + binaryContentInputStream = dataManager.getBinaryResourceInputStream(binaryResource); + return BinaryResourceDownloadResponseBuilder.prepareResponse(binaryContentInputStream, binaryResourceDownloadMeta, range); + } catch (StorageException e) { + Streams.close(binaryContentInputStream); + return BinaryResourceDownloadResponseBuilder.downloadError(e, fullName); + } + } + + private BinaryResource getBinaryResource(String fullName) + throws NotAllowedException, AccessRightException, UserNotActiveException, EntityNotFoundException { + + if (contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)) { + return productInstanceManagerLocal.getBinaryResource(fullName); + } else { + return guestProxy.getBinaryResourceForProducInstance(fullName); + } + } + + private BinaryResource getPathDataBinaryResource(String fullName) + throws NotAllowedException, AccessRightException, UserNotActiveException, EntityNotFoundException { + if (contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)) { + return productInstanceManagerLocal.getPathDataBinaryResource(fullName); + } else { + return guestProxy.getBinaryResourceForPathData(fullName); + } + } + + + private String uploadAFile(String workspaceId, Part formPart, ProductInstanceIterationKey pdtIterationKey) + throws EntityNotFoundException, EntityAlreadyExistsException, AccessRightException, NotAllowedException, CreationException, UserNotActiveException, StorageException, IOException { + + String fileName = Normalizer.normalize(formPart.getSubmittedFileName(), Normalizer.Form.NFC); + // Init the binary resource with a null length + BinaryResource binaryResource = productInstanceManagerLocal.saveFileInProductInstance(workspaceId, pdtIterationKey, fileName, 0); + OutputStream outputStream = dataManager.getBinaryResourceOutputStream(binaryResource); + long length = BinaryResourceUpload.uploadBinary(outputStream, formPart); + productInstanceManagerLocal.saveFileInProductInstance(workspaceId, pdtIterationKey, fileName, (int) length); + return fileName; + } + + private String uploadAFileToPathData(String workspaceId, Part formPart, String configurationItemId, String serialNumber, int pathDataId, int iteration) + throws EntityNotFoundException, EntityAlreadyExistsException, AccessRightException, NotAllowedException, CreationException, UserNotActiveException, StorageException, IOException { + + String fileName = Normalizer.normalize(formPart.getSubmittedFileName(), Normalizer.Form.NFC); + // Init the binary resource with a null length + BinaryResource binaryResource = productInstanceManagerLocal.saveFileInPathData(workspaceId, configurationItemId, serialNumber, pathDataId, iteration, fileName, 0); + OutputStream outputStream = dataManager.getBinaryResourceOutputStream(binaryResource); + long length = BinaryResourceUpload.uploadBinary(outputStream, formPart); + productInstanceManagerLocal.saveFileInPathData(workspaceId, configurationItemId, serialNumber, pathDataId, iteration, fileName, (int) length); + return fileName; + } + + private String uploadAFileToPathDataIteration(String workspaceId, Part formPart, String configurationItemId, String serialNumber, int pathDataId, int iteration) + throws EntityNotFoundException, EntityAlreadyExistsException, AccessRightException, NotAllowedException, CreationException, UserNotActiveException, StorageException, IOException { + + String fileName = Normalizer.normalize(formPart.getSubmittedFileName(), Normalizer.Form.NFC); + // Init the binary resource with a null length + BinaryResource binaryResource = productInstanceManagerLocal.saveFileInPathDataIteration(workspaceId, configurationItemId, serialNumber, pathDataId, iteration, fileName, 0); + OutputStream outputStream = dataManager.getBinaryResourceOutputStream(binaryResource); + long length = BinaryResourceUpload.uploadBinary(outputStream, formPart); + productInstanceManagerLocal.saveFileInPathDataIteration(workspaceId, configurationItemId, serialNumber, pathDataId, iteration, fileName, (int) length); + return fileName; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/util/BinaryResourceBinaryStreamingOutput.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/util/BinaryResourceBinaryStreamingOutput.java new file mode 100644 index 0000000000..c6292ef30b --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/util/BinaryResourceBinaryStreamingOutput.java @@ -0,0 +1,91 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.file.util; + +import com.docdoku.core.util.FileIO; +import com.docdoku.server.helpers.Streams; +import com.docdoku.server.rest.exceptions.InterruptedStreamException; + +import javax.ws.rs.core.StreamingOutput; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class BinaryResourceBinaryStreamingOutput implements StreamingOutput { + private static final Logger LOGGER = Logger.getLogger(BinaryResourceBinaryStreamingOutput.class.getName()); + private final InputStream binaryContentInputStream; + private final Range fullRange; + + public BinaryResourceBinaryStreamingOutput(InputStream binaryContentInputStream, long start, long end, long length) { + this.binaryContentInputStream = binaryContentInputStream; + this.fullRange = new Range(start, end, length); + } + + @Override + public void write(OutputStream outputStream) throws IOException { + try { + if (binaryContentInputStream == null) { + LOGGER.log(Level.SEVERE, "The file input stream is null"); + } else { + copy(binaryContentInputStream, outputStream, fullRange.start, fullRange.length); + } + } catch (InterruptedStreamException e) { + LOGGER.log(Level.WARNING, "Downloading file interrupted"); + LOGGER.log(Level.FINE, "Streaming file interruption", e); + } + } + + private void copy(final InputStream input, OutputStream output, long start, long length) throws InterruptedStreamException { + // Slice the input stream considering offset and length + try (InputStream in=input) { + in.skip(start); + byte[] data = new byte[1024*8]; + long remaining = length; + int nr; + while (remaining > 0) { + nr = in.read(data); + if (nr < 0) { + break; + } + remaining -= nr; + output.write(data, 0, nr); + } + } catch (IOException e) { + // may be caused by a client side cancel + LOGGER.log(Level.FINE, "A downloading stream was interrupted.", e); + throw new InterruptedStreamException(); + } + } + + private static class Range { + long start; + long length; + long total; + + public Range(long start, long end, long total) { + this.start = start; + this.length = end - start + 1; + this.total = total; + } + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/util/BinaryResourceDownloadMeta.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/util/BinaryResourceDownloadMeta.java new file mode 100644 index 0000000000..b943d835f2 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/util/BinaryResourceDownloadMeta.java @@ -0,0 +1,189 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.file.util; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.server.rest.util.FileDownloadTools; + +import javax.activation.FileTypeMap; +import javax.activation.MimetypesFileTypeMap; +import javax.ws.rs.core.EntityTag; +import java.util.Date; +import java.util.logging.Logger; + +/** + * + */ +public class BinaryResourceDownloadMeta { + private static final Logger LOGGER = Logger.getLogger(BinaryResourceDownloadMeta.class.getName()); + private static final String CHARSET = "UTF-8"; + private static MimetypesFileTypeMap fileTypeMap = null; + private String fullName; + private String outputFormat; + private String downloadType; + private long length; + private Date lastModified; + + private String subResourceVirtualPath; + + public BinaryResourceDownloadMeta(BinaryResource binaryResource, String outputFormat, String downloadType) { + this.fullName = binaryResource.getName(); + this.outputFormat = outputFormat; + this.downloadType = downloadType; + this.length = binaryResource.getContentLength(); + this.lastModified = binaryResource.getLastModified(); + if (fileTypeMap == null) { + BinaryResourceDownloadMeta.initFileTypeMap(); + } + } + + public BinaryResourceDownloadMeta(BinaryResource binaryResource) { + this(binaryResource, null, null); + } + + private static void initFileTypeMap() { + fileTypeMap = new MimetypesFileTypeMap(); + + // Additional MIME types + fileTypeMap.addMimeTypes("application/atom+xml atom"); + fileTypeMap.addMimeTypes("application/msword doc dot"); + fileTypeMap.addMimeTypes("application/mspowerpoint ppt pot"); + fileTypeMap.addMimeTypes("application/msexcel xls"); + fileTypeMap.addMimeTypes("application/pdf pdf"); + fileTypeMap.addMimeTypes("application/rdf+xml rdf rss"); + fileTypeMap.addMimeTypes("application/x-vnd.openxmlformat docx docm dotx dotm"); + fileTypeMap.addMimeTypes("application/x-vnd.openxmlformat xlsx xlsm"); + fileTypeMap.addMimeTypes("application/x-vnd.openxmlformat pptx pptm potx"); + fileTypeMap.addMimeTypes("application/x-javascript js"); + fileTypeMap.addMimeTypes("application/x-rar-compressed rar"); + fileTypeMap.addMimeTypes("application/x-textedit bat cmd"); + fileTypeMap.addMimeTypes("application/zip zip"); + fileTypeMap.addMimeTypes("audio/mpeg mp3"); + fileTypeMap.addMimeTypes("image/bmp bmp"); + fileTypeMap.addMimeTypes("image/gif gif"); + fileTypeMap.addMimeTypes("image/jpeg jpg jpeg jpe"); + fileTypeMap.addMimeTypes("image/png png"); + fileTypeMap.addMimeTypes("text/css css"); + fileTypeMap.addMimeTypes("text/csv csv"); + fileTypeMap.addMimeTypes("text/html htm html"); + fileTypeMap.addMimeTypes("text/xml xml"); + fileTypeMap.addMimeTypes("video/quicktime qt mov moov"); + fileTypeMap.addMimeTypes("video/mpeg mpeg mpg mpe mpv vbs mpegv"); + fileTypeMap.addMimeTypes("video/msvideo avi"); + fileTypeMap.addMimeTypes("video/mp4 mp4"); + fileTypeMap.addMimeTypes("video/ogg ogg"); + + FileTypeMap.setDefaultFileTypeMap(fileTypeMap); + } + + /** + * Get the full name of the file + * + * @return Full name of the file + */ + public String getFullName() { + return fullName; + } + + public boolean isConverted() { + return outputFormat != null && !outputFormat.isEmpty(); + } + + /** + * Get the file size + * + * @return File size + */ + public long getLength() { + return length; + } + + /** + * Get the last modification date of the file + * + * @return Last modification date + */ + public Date getLastModified() { + return (lastModified != null) ? (Date) lastModified.clone() : null; + } + + /** + * Get the last modification date of the file + * + * @return Last modification date + */ + public long getLastModifiedTime() { + return (lastModified != null) ? lastModified.getTime() : 0; + } + + /** + * Get file entity tag + * + * @return Unique Entity Tag for the file + */ + public EntityTag getETag() { + //Todo add iteration and version + //Todo remove special char from full Name + return new EntityTag(fullName + "_" + length + "_" + lastModified.getTime()); + } + + public void setSubResourceVirtualPath(String subResourceVirtualPath) { + if (subResourceVirtualPath != null && !subResourceVirtualPath.isEmpty()) { + this.subResourceVirtualPath = subResourceVirtualPath; + } + } + + /** + * Get the Content type for this file + * + * @return Http Response content type + */ + public String getContentType() { + String contentType; + if (subResourceVirtualPath != null) { + contentType = FileTypeMap.getDefaultFileTypeMap().getContentType(subResourceVirtualPath); + } else { + if (outputFormat != null) { + contentType = FileTypeMap.getDefaultFileTypeMap().getContentType(fullName + "." + outputFormat); + } else { + contentType = FileTypeMap.getDefaultFileTypeMap().getContentType(fullName); + } + } + + if (contentType != null && contentType.startsWith("text")) { + contentType += ";charset=" + CHARSET; + } + + return (contentType != null) ? contentType : "application/octet-stream"; + } + + /** + * Get the Content disposition for this file + * + * @return Http Response content disposition + */ + // Todo check if we can have unencoding contentDisposition + // Todo check accept request + public String getContentDisposition() { + String fileName = FileDownloadTools.getFileName(fullName, outputFormat); + return FileDownloadTools.getContentDisposition(downloadType, fileName); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/util/BinaryResourceDownloadResponseBuilder.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/util/BinaryResourceDownloadResponseBuilder.java new file mode 100644 index 0000000000..a86cd0c986 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/util/BinaryResourceDownloadResponseBuilder.java @@ -0,0 +1,150 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.file.util; + +import com.docdoku.server.rest.exceptions.RequestedRangeNotSatisfiableException; + +import javax.ws.rs.core.CacheControl; +import javax.ws.rs.core.EntityTag; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.InputStream; +import java.util.Calendar; +import java.util.Date; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ + +public class BinaryResourceDownloadResponseBuilder { + private static final Logger LOGGER = Logger.getLogger(BinaryResourceDownloadResponseBuilder.class.getName()); + private static final int CACHE_SECOND = 60 * 60 * 24; + + + private BinaryResourceDownloadResponseBuilder() { + super(); + } + + + /** + * Set the header of the downloading response. + * + * @param binaryContentInputStream The stream of the binary content to download. + * @param binaryResourceDownloadMeta The header parameters for the binary content download. + * @param range The string of the queried range. Null if no range are specified + * @return A response builder with the header & the content. + * @throws RequestedRangeNotSatisfiableException If the range is not satisfiable. + */ + public static Response prepareResponse(InputStream binaryContentInputStream, BinaryResourceDownloadMeta binaryResourceDownloadMeta, String range) + throws RequestedRangeNotSatisfiableException { + + Response.ResponseBuilder responseBuilder; + + if (range == null || range.isEmpty()) { + long length = binaryResourceDownloadMeta.getLength(); + responseBuilder = Response.ok() + .header("Content-Disposition", binaryResourceDownloadMeta.getContentDisposition()) + .header("Content-Type", binaryResourceDownloadMeta.getContentType()) + .entity(new BinaryResourceBinaryStreamingOutput(binaryContentInputStream, 0, length - 1, length)); + + // Converting files modify its length so we don't specify the length on converted content + if (!binaryResourceDownloadMeta.isConverted()) { + responseBuilder.header("Content-Length", length); + } + } else { + responseBuilder = prepareStreamingDownloadResponse(binaryResourceDownloadMeta, binaryContentInputStream, range); + } + + responseBuilder = applyCachePolicyToResponse(responseBuilder, binaryResourceDownloadMeta.getETag(), binaryResourceDownloadMeta.getLastModified()); + return responseBuilder.build(); + } + + private static Response.ResponseBuilder prepareStreamingDownloadResponse(BinaryResourceDownloadMeta binaryResourceDownloadMeta, InputStream binaryContentInputStream, String range) throws RequestedRangeNotSatisfiableException { + long length = binaryResourceDownloadMeta.getLength(); + + // Range header should match format "bytes=n-n,n-n,n-n...". If not, then return 416. + if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) { + throw new RequestedRangeNotSatisfiableException("", length); + } + + String[] ranges = range.split("=")[1].split("-"); + final long from = Integer.parseInt(ranges[0]); + long to; + if (ranges.length > 1) { + to = Integer.parseInt(ranges[1]); + to = (to > length - 1) ? length - 1 : to; + } else { + to = length - 1; + } + + final String responseRange = String.format("bytes %d-%d/%d", from, to, length); + + return Response.status(Response.Status.PARTIAL_CONTENT) + .header("Content-Disposition", binaryResourceDownloadMeta.getContentDisposition()) + .header("Accept-Ranges", "bytes") + .header("Content-Length", length - from) + .header("Content-Range", responseRange) + .header("Content-Type", binaryResourceDownloadMeta.getContentType()) + .entity(new BinaryResourceBinaryStreamingOutput(binaryContentInputStream, from, length - 1, length)); + } + + /** + * Log error & return a 500 error. + * + * @param e The catched exception which cause the error. + * @param fullName The full name of the wanted file. + * @return A 500 error. + */ + public static Response downloadError(Exception e, String fullName) { + String message = "Error while downloading the file : " + fullName; + LOGGER.log(Level.SEVERE, message, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .header("Reason-Phrase", message) + .entity(message) + .type(MediaType.TEXT_PLAIN) + .build(); + } + + /** + * Apply cache policy to a response. + * + * @param response The response builder. + * @param eTag The ETag of the resource. + * @param lastModified The last modified date of the resource. + * @return The response builder with the cache policy. + */ + private static Response.ResponseBuilder applyCachePolicyToResponse(Response.ResponseBuilder response, EntityTag eTag, Date lastModified) { + CacheControl cc = new CacheControl(); + cc.setMaxAge(CACHE_SECOND); + cc.setNoTransform(false); + cc.setPrivate(false); + + Calendar expirationDate = Calendar.getInstance(); + expirationDate.add(Calendar.SECOND, CACHE_SECOND); + + return response.cacheControl(cc) + .expires(expirationDate.getTime()) + .lastModified(lastModified) + .tag(eTag); + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/util/BinaryResourceUpload.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/util/BinaryResourceUpload.java new file mode 100644 index 0000000000..330e63b228 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/file/util/BinaryResourceUpload.java @@ -0,0 +1,86 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.file.util; + +import com.google.common.io.ByteStreams; + +import javax.servlet.http.Part; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +public class BinaryResourceUpload { + private static final Logger LOGGER = Logger.getLogger(BinaryResourceUpload.class.getName()); + + private BinaryResourceUpload() { + super(); + } + + + /** + * Upload a form file in a specific output + * + * @param outputStream BinaryResource output stream (in server vault repository) + * @param formPart The form part list + * @return The length of the file uploaded + */ + public static long uploadBinary(OutputStream outputStream, Part formPart) + throws IOException { + long length; + try (InputStream in = formPart.getInputStream(); OutputStream out = outputStream) { + length = ByteStreams.copy(in, out); + } + return length; + } + + /** + * Log error & return a 500 error. + * + * @param e The exception which cause the error. + * @return A 500 error. + */ + public static Response uploadError(Exception e) { + String message = "Error while uploading the file(s)."; + LOGGER.log(Level.SEVERE, message, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .header("Reason-Phrase", message) + .entity(message) + .type(MediaType.TEXT_PLAIN) + .build(); + } + + public static Response tryToRespondCreated(String uri) { + try { + return Response.created(new URI(uri)).build(); + } catch (URISyntaxException e) { + LOGGER.log(Level.WARNING, null, e); + return Response.ok().build(); + } + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/interceptors/Compress.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/interceptors/Compress.java new file mode 100644 index 0000000000..49eb66085a --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/interceptors/Compress.java @@ -0,0 +1,30 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.interceptors; + +import javax.ws.rs.NameBinding; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@NameBinding +@Retention(RetentionPolicy.RUNTIME) +public @interface Compress { +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/interceptors/GZIPWriterInterceptor.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/interceptors/GZIPWriterInterceptor.java new file mode 100644 index 0000000000..195edcffed --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/interceptors/GZIPWriterInterceptor.java @@ -0,0 +1,61 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.interceptors; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.Provider; +import javax.ws.rs.ext.WriterInterceptor; +import javax.ws.rs.ext.WriterInterceptorContext; +import java.io.IOException; +import java.util.zip.GZIPOutputStream; + +@Provider +@Compress +public class GZIPWriterInterceptor implements WriterInterceptor { + private static final int DEFAULT_BUFFER_SIZE = 4096; + + + @Override + public void aroundWriteTo(WriterInterceptorContext context) throws IOException { + MultivaluedMap headers = context.getHeaders(); + Object rangeHeader = headers.getFirst("Content-Range"); + + GZIPOutputStream gzipOutputStream = null; + + + if (rangeHeader == null) { + headers.add("Content-Encoding", "gzip"); + headers.remove("Content-Length"); + gzipOutputStream = new GZIPOutputStream(context.getOutputStream(), DEFAULT_BUFFER_SIZE); + context.setOutputStream(gzipOutputStream); + } + + try { + context.proceed(); + } finally { + if (gzipOutputStream != null) { + gzipOutputStream.finish(); + } + } + } + + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/ConfigSpecHelper.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/ConfigSpecHelper.java new file mode 100644 index 0000000000..492f983580 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/ConfigSpecHelper.java @@ -0,0 +1,61 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.util; + +import com.docdoku.core.configuration.DocumentConfigSpec; +import com.docdoku.core.exceptions.BaselineNotFoundException; +import com.docdoku.core.exceptions.UserNotActiveException; +import com.docdoku.core.exceptions.UserNotFoundException; +import com.docdoku.core.exceptions.WorkspaceNotFoundException; +import com.docdoku.core.services.IDocumentConfigSpecManagerLocal; + +/** + * Created by morgan on 15/04/16. + */ +public class ConfigSpecHelper { + public static final String BASELINE_LATEST = "latest"; + public static final String BASELINE_UNDEFINED = "undefined"; + + /** + * Get a configuration specification according a string params + * + * @param workspaceId The current workspace + * @param configSpecType The string discribing the configSpec + * @return A configuration specification + * @throws com.docdoku.core.exceptions.UserNotFoundException If the user login-workspace doesn't exist + * @throws com.docdoku.core.exceptions.UserNotActiveException If the user is disabled + * @throws com.docdoku.core.exceptions.WorkspaceNotFoundException If the workspace doesn't exist + * @throws com.docdoku.core.exceptions.BaselineNotFoundException If the baseline doesn't exist + */ + public static DocumentConfigSpec getConfigSpec(String workspaceId, String configSpecType, IDocumentConfigSpecManagerLocal documentConfigSpecService) throws UserNotActiveException, WorkspaceNotFoundException, UserNotFoundException, BaselineNotFoundException { + DocumentConfigSpec cs; + switch (configSpecType) { + case BASELINE_LATEST: + case BASELINE_UNDEFINED: + cs = documentConfigSpecService.getLatestConfigSpec(workspaceId); + break; + default: + cs = documentConfigSpecService.getConfigSpecForBaseline(Integer.parseInt(configSpecType)); + break; + } + return cs; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/FileDownloadTools.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/FileDownloadTools.java new file mode 100644 index 0000000000..0e7f0c7566 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/FileDownloadTools.java @@ -0,0 +1,69 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.util; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Elisabel Généreux + */ +public class FileDownloadTools { + + private static final Logger LOGGER = Logger.getLogger(FileDownloadTools.class.getName()); + private static final String CHARSET = "UTF-8"; + + /** + * Get the output name of file + * + * @return Output name of file + */ + public static String getFileName(String fullName, String outputFormat) { + String fileName = fullName; + + try { + fileName = URLEncoder.encode(fileName, CHARSET).replace("+", " "); + } catch (UnsupportedEncodingException e) { + LOGGER.log(Level.WARNING, null, e); + } + + if (outputFormat != null) { + fileName += "." + outputFormat; + } + + return fileName; + } + + /** + * Get the Content disposition for this file + * + * @return Http Response content disposition + */ + // Todo check if we can have unencoding contentDisposition + // Todo check accept request + public static String getContentDisposition(String downloadType, String fileName) { + String dispositionType = ("viewer".equals(downloadType)) ? "inline" : "attachement"; + return dispositionType + "; filename=\"" + fileName + "\" ; filename*=\"" + fileName + "\""; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/FileExportEntity.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/FileExportEntity.java new file mode 100644 index 0000000000..e48bcf7c69 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/FileExportEntity.java @@ -0,0 +1,91 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.util; + +import com.docdoku.core.configuration.PSFilter; +import com.docdoku.core.product.ConfigurationItemKey; + +/** + * @author morgan on 29/04/15. + */ +public class FileExportEntity { + + private ConfigurationItemKey configurationItemKey; + private PSFilter psFilter; + + private String serialNumber; + private Integer baselineId; + + private boolean exportNativeCADFile; + private boolean exportDocumentLinks; + + + public FileExportEntity() { + } + + public PSFilter getPsFilter() { + return psFilter; + } + + public void setPsFilter(PSFilter psFilter) { + this.psFilter = psFilter; + } + + public ConfigurationItemKey getConfigurationItemKey() { + return configurationItemKey; + } + + public void setConfigurationItemKey(ConfigurationItemKey configurationItemKey) { + this.configurationItemKey = configurationItemKey; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public Integer getBaselineId() { + return baselineId; + } + + public void setBaselineId(Integer baselineId) { + this.baselineId = baselineId; + } + + public boolean isExportNativeCADFile() { + return exportNativeCADFile; + } + + public void setExportNativeCADFile(boolean exportNativeCADFile) { + this.exportNativeCADFile = exportNativeCADFile; + } + + public boolean isExportDocumentLinks() { + return exportDocumentLinks; + } + + public void setExportDocumentLinks(boolean exportDocumentLinks) { + this.exportDocumentLinks = exportDocumentLinks; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/InstanceAttributeFactory.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/InstanceAttributeFactory.java new file mode 100644 index 0000000000..212016d610 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/InstanceAttributeFactory.java @@ -0,0 +1,127 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.util; + +import com.docdoku.core.meta.*; +import com.docdoku.server.rest.dto.InstanceAttributeDTO; +import com.docdoku.server.rest.dto.InstanceAttributeTemplateDTO; +import com.docdoku.server.rest.dto.NameValuePairDTO; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Asmae CHADID on 25/03/15. + *

+ * Should be replaced with DozerMapper. + * @see com.docdoku.server.rest.converters.InstanceAttributeDozerConverter + */ +public class InstanceAttributeFactory { + + private Mapper mapper; + + public InstanceAttributeFactory() { + mapper = DozerBeanMapperSingletonWrapper.getInstance(); + } + + public List createInstanceAttributes(List dtos) { + if (dtos == null) { + return new ArrayList<>(); + } + List data = new ArrayList<>(); + for (InstanceAttributeDTO dto : dtos) { + data.add(createInstanceAttribute(dto)); + } + + return data; + } + + public InstanceAttribute createInstanceAttribute(InstanceAttributeDTO dto) { + InstanceAttribute attr; + switch (dto.getType()) { + case BOOLEAN: + attr = new InstanceBooleanAttribute(); + break; + case TEXT: + attr = new InstanceTextAttribute(); + break; + case NUMBER: + attr = new InstanceNumberAttribute(); + break; + case DATE: + attr = new InstanceDateAttribute(); + break; + case URL: + attr = new InstanceURLAttribute(); + break; + case LOV: + attr = new InstanceListOfValuesAttribute(); + List itemsDTO = dto.getItems(); + List items = new ArrayList<>(); + if (itemsDTO != null) { + for (NameValuePairDTO itemDTO : itemsDTO) { + items.add(mapper.map(itemDTO, NameValuePair.class)); + } + } + ((InstanceListOfValuesAttribute) attr).setItems(items); + break; + case LONG_TEXT: + attr = new InstanceLongTextAttribute(); + break; + default: + throw new IllegalArgumentException("Instance attribute not supported"); + } + + attr.setName(dto.getName()); + attr.setValue(dto.getValue()); + attr.setLocked(dto.isLocked()); + attr.setMandatory(dto.isMandatory()); + return attr; + } + + public InstanceAttributeTemplate createInstanceAttributeTemplateObject(InstanceAttributeTemplateDTO dto) { + InstanceAttributeTemplate data; + if (dto.getLovName() == null || dto.getLovName().isEmpty()) { + DefaultAttributeTemplate defaultIA = new DefaultAttributeTemplate(); + defaultIA.setAttributeType(InstanceAttributeTemplate.AttributeType.valueOf(dto.getAttributeType().name())); + data = defaultIA; + } else { + ListOfValuesAttributeTemplate lovA = new ListOfValuesAttributeTemplate(); + data = lovA; + } + + data.setName(dto.getName()); + data.setMandatory(dto.isMandatory()); + data.setLocked(dto.isLocked()); + return data; + } + + public List createInstanceAttributeTemplateFromDTO(List DTOs) { + List data = new ArrayList<>(); + for (InstanceAttributeTemplateDTO instanceAttributeTemplateDTO : DTOs) { + data.add(createInstanceAttributeTemplateObject(instanceAttributeTemplateDTO)); + } + return data; + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/InstanceBodyWriterTools.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/InstanceBodyWriterTools.java new file mode 100644 index 0000000000..d6238f7c1e --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/InstanceBodyWriterTools.java @@ -0,0 +1,249 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.util; + +import com.docdoku.core.configuration.PSFilter; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.product.*; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.core.util.Tools; +import com.docdoku.server.rest.collections.InstanceCollection; +import com.docdoku.server.rest.collections.VirtualInstanceCollection; +import com.docdoku.server.rest.dto.InstanceAttributeDTO; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; + +import javax.json.stream.JsonGenerator; +import javax.vecmath.Matrix3d; +import javax.vecmath.Matrix4d; +import javax.vecmath.Vector3d; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Taylor LABEJOF + */ +public class InstanceBodyWriterTools { + + private static final Logger LOGGER = Logger.getLogger(InstanceBodyWriterTools.class.getName()); + private static Mapper mapper = DozerBeanMapperSingletonWrapper.getInstance(); + + public static void generateInstanceStreamWithGlobalMatrix(IProductManagerLocal productService, List currentPath, Matrix4d matrix, InstanceCollection instanceCollection, List instanceIds, JsonGenerator jg) { + + try { + + if (currentPath == null) { + PartLink rootPartUsageLink = productService.getRootPartUsageLink(instanceCollection.getCiKey()); + currentPath = new ArrayList<>(); + currentPath.add(rootPartUsageLink); + } + + Component component = productService.filterProductStructure(instanceCollection.getCiKey(), + instanceCollection.getFilter(), currentPath, 1); + + PartLink partLink = component.getPartLink(); + PartIteration partI = component.getRetainedIteration(); + + // Filter ACL on part + if (!productService.canAccess(partI.getPartRevision().getKey())) { + return; + } + + for (CADInstance instance : partLink.getCadInstances()) { + + List copyInstanceIds = new ArrayList<>(instanceIds); + copyInstanceIds.add(instance.getId()); + Vector3d instanceTranslation = new Vector3d(instance.getTx(), instance.getTy(), instance.getTz()); + Matrix4d combinedMatrix; + switch (instance.getRotationType()) { + case ANGLE: + Vector3d instanceRotation = new Vector3d(instance.getRx(), instance.getRy(), instance.getRz()); + combinedMatrix = combineTransformation(matrix, instanceTranslation, instanceRotation); + break; + case MATRIX: + Matrix4d rotationMatrix = new Matrix4d(new Matrix3d(instance.getRotationMatrix().getValues()), instanceTranslation, 1); + combinedMatrix = combineTransformation(matrix, rotationMatrix); + break; + default: + LOGGER.log(Level.SEVERE, "Unknown rotation Type, matrix not calculated"); + combinedMatrix = matrix; + } + + + if (!partI.isAssembly() && !partI.getGeometries().isEmpty() && instanceCollection.isFiltered(currentPath)) { + writeLeaf(currentPath, copyInstanceIds, partI, combinedMatrix, jg); + } else { + for (Component subComponent : component.getComponents()) { + generateInstanceStreamWithGlobalMatrix(productService, subComponent.getPath(), combinedMatrix, instanceCollection, copyInstanceIds, jg); + } + } + } + + } catch (PartMasterNotFoundException | PartRevisionNotFoundException | PartUsageLinkNotFoundException | UserNotFoundException | WorkspaceNotFoundException | ConfigurationItemNotFoundException e) { + LOGGER.log(Level.SEVERE, null, e); + } catch (AccessRightException | EntityConstraintException | NotAllowedException | UserNotActiveException e) { + LOGGER.log(Level.FINEST, null, e); + } + + } + + public static void generateInstanceStreamWithGlobalMatrix(IProductManagerLocal productService, List currentPath, Matrix4d matrix, VirtualInstanceCollection virtualInstanceCollection, List instanceIds, JsonGenerator jg) { + try { + + PartLink partLink = currentPath.get(currentPath.size() - 1); + PSFilter filter = virtualInstanceCollection.getFilter(); + List filteredPartIterations = filter.filter(partLink.getComponent()); + + if (!filteredPartIterations.isEmpty()) { + + PartIteration partI = filteredPartIterations.iterator().next(); + + // Filter ACL on part + if (!productService.canAccess(partI.getPartRevision().getKey())) { + return; + } + + for (CADInstance instance : partLink.getCadInstances()) { + + List copyInstanceIds = new ArrayList<>(instanceIds); + copyInstanceIds.add(instance.getId()); + + Vector3d instanceTranslation = new Vector3d(instance.getTx(), instance.getTy(), instance.getTz()); + Vector3d instanceRotation = new Vector3d(instance.getRx(), instance.getRy(), instance.getRz()); + Matrix4d combinedMatrix = combineTransformation(matrix, instanceTranslation, instanceRotation); + + if (!partI.isAssembly() && !partI.getGeometries().isEmpty()) { + writeLeaf(currentPath, copyInstanceIds, partI, combinedMatrix, jg); + } else { + for (PartLink subLink : partI.getComponents()) { + List subPath = new ArrayList<>(currentPath); + subPath.add(subLink); + generateInstanceStreamWithGlobalMatrix(productService, subPath, combinedMatrix, virtualInstanceCollection, copyInstanceIds, jg); + } + } + } + } + + } catch (UserNotFoundException | UserNotActiveException | WorkspaceNotFoundException | PartRevisionNotFoundException e) { + LOGGER.log(Level.SEVERE, null, e); + } + + } + + private static Matrix4d combineTransformation(Matrix4d matrix, Vector3d translation, Vector3d rotation) { + Matrix4d gM = new Matrix4d(matrix); + Matrix4d m = new Matrix4d(); + + m.setIdentity(); + m.setTranslation(translation); + gM.mul(m); + + m.setIdentity(); + m.rotZ(rotation.z); + gM.mul(m); + + m.setIdentity(); + m.rotY(rotation.y); + gM.mul(m); + + m.setIdentity(); + m.rotX(rotation.x); + gM.mul(m); + + return gM; + } + + private static Matrix4d combineTransformation(Matrix4d matrix, Matrix4d transformation) { + Matrix4d gM = new Matrix4d(matrix); + gM.mul(transformation); + + return gM; + } + + private static void writeLeaf(List currentPath, List copyInstanceIds, PartIteration partI, Matrix4d combinedMatrix, JsonGenerator jg) { + String partIterationId = partI.toString(); + List attributes = new ArrayList<>(); + for (InstanceAttribute attr : partI.getInstanceAttributes()) { + attributes.add(mapper.map(attr, InstanceAttributeDTO.class)); + } + + jg.writeStartObject(); + jg.write("id", Tools.getPathInstanceAsString(currentPath, copyInstanceIds)); + jg.write("partIterationId", partIterationId); + jg.write("path", Tools.getPathAsString(currentPath)); + + writeMatrix(combinedMatrix, jg); + writeGeometries(partI.getSortedGeometries(), jg); + writeAttributes(attributes, jg); + + jg.writeEnd(); + jg.flush(); + } + + private static void writeMatrix(Matrix4d matrix, JsonGenerator jg) { + jg.writeStartArray("matrix"); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + jg.write(matrix.getElement(i, j)); + } + } + jg.writeEnd(); + } + + private static void writeGeometries(List files, JsonGenerator jg) { + jg.write("qualities", files.size()); + + if (!files.isEmpty()) { + Geometry geometry = files.get(0); + jg.write("xMin", geometry.getxMin()); + jg.write("yMin", geometry.getyMin()); + jg.write("zMin", geometry.getzMin()); + jg.write("xMax", geometry.getxMax()); + jg.write("yMax", geometry.getyMax()); + jg.write("zMax", geometry.getzMax()); + } + + jg.writeStartArray("files"); + + for (Geometry g : files) { + jg.writeStartObject(); + jg.write("fullName", "api/files/" + g.getFullName()); + jg.writeEnd(); + } + jg.writeEnd(); + } + + private static void writeAttributes(List attributes, JsonGenerator jg) { + jg.writeStartArray("attributes"); + for (InstanceAttributeDTO a : attributes) { + jg.writeStartObject(); + jg.write("name", a.getName()); + jg.write("type", a.getType().toString()); + jg.write("value", a.getValue()); + jg.writeEnd(); + } + jg.writeEnd(); + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/SearchQueryParser.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/SearchQueryParser.java new file mode 100644 index 0000000000..38a127154c --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/util/SearchQueryParser.java @@ -0,0 +1,308 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.util; + +import com.docdoku.core.query.DocumentSearchQuery; +import com.docdoku.core.query.PartSearchQuery; +import com.docdoku.core.query.SearchQuery; + +import javax.ws.rs.core.MultivaluedMap; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class SearchQueryParser { + + private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + private static final Logger LOGGER = Logger.getLogger(SearchQueryParser.class.getName()); + private static final String ATTRIBUTES_DELIMITER = ";"; + private static final String ATTRIBUTES_SPLITTER = ":"; + private static final String FILTERS_DELIMITER = "="; + private static final String QUERY_DELIMITER = "&"; + + private SearchQueryParser() { + super(); + } + + public static DocumentSearchQuery parseDocumentStringQuery(String workspaceId, MultivaluedMap query) { + String fullText = null; + String pDocMId = null; + String pTitle = null; + String pVersion = null; + String pAuthor = null; + String pType = null; + Date pCreationDateFrom = null; + Date pCreationDateTo = null; + Date pModificationDateFrom = null; + Date pModificationDateTo = null; + List pAttributes = new ArrayList<>(); + String[] pTags = null; + String pContent = null; + + + for (String filter : query.keySet()) { + List values = query.get(filter); + if (values.size() == 1) { + String value = null; + try { + value = URLDecoder.decode(values.get(0), "UTF-8"); + } catch (UnsupportedEncodingException e) { + LOGGER.log(Level.FINEST, null, e); + } + switch (filter) { + case "q": + fullText = value; + break; + case "id": + pDocMId = value; + break; + case "title": + pTitle = value; + break; + case "version": + pVersion = value; + break; + case "author": + pAuthor = value; + break; + case "type": + pType = value; + break; + case "createdFrom": + try { + pCreationDateFrom = SIMPLE_DATE_FORMAT.parse(value); + } catch (ParseException e) { + LOGGER.log(Level.FINEST, null, e); + } + break; + case "createdTo": + try { + pCreationDateTo = SIMPLE_DATE_FORMAT.parse(value); + } catch (ParseException e) { + LOGGER.log(Level.FINEST, null, e); + } + break; + case "modifiedFrom": + try { + pModificationDateFrom = SIMPLE_DATE_FORMAT.parse(value); + } catch (ParseException e) { + LOGGER.log(Level.FINEST, null, e); + } + break; + case "modifiedTo": + try { + pModificationDateTo = SIMPLE_DATE_FORMAT.parse(value); + } catch (ParseException e) { + LOGGER.log(Level.FINEST, null, e); + } + break; + case "tags": + pTags = value.split(","); + break; + case "content": + pContent = value; + break; + case "attributes": + pAttributes = parseAttributeStringQuery(value); + break; + default: + break; + + } + } + + } + + DocumentSearchQuery.AbstractAttributeQuery[] pAttributesArray = pAttributes.toArray(new DocumentSearchQuery.AbstractAttributeQuery[pAttributes.size()]); + + return new DocumentSearchQuery(workspaceId, fullText, pDocMId, pTitle, pVersion, pAuthor, pType, + pCreationDateFrom, pCreationDateTo, pModificationDateFrom, pModificationDateTo, + pAttributesArray, pTags, pContent); + + } + + public static PartSearchQuery parsePartStringQuery(String workspaceId, MultivaluedMap query) { + String fullText = null; + String pNumber = null; + String pName = null; + String pVersion = null; + String pAuthor = null; + String pType = null; + Date pCreationDateFrom = null; + Date pCreationDateTo = null; + Date pModificationDateFrom = null; + Date pModificationDateTo = null; + List pAttributes = new ArrayList<>(); + String[] pTags = null; + Boolean standardPart = null; + String content = null; + + for (String filter : query.keySet()) { + List values = query.get(filter); + if (values.size() == 1) { + String value = null; + try { + value = URLDecoder.decode(values.get(0), "UTF-8"); + } catch (UnsupportedEncodingException e) { + LOGGER.log(Level.FINEST, null, e); + } + switch (filter) { + case "q": + fullText = value; + break; + case "number": + pNumber = value; + break; + case "name": + pName = value; + break; + case "version": + pVersion = value; + break; + case "author": + pAuthor = value; + break; + case "type": + pType = value; + break; + case "createdFrom": + try { + pCreationDateFrom = SIMPLE_DATE_FORMAT.parse(value); + } catch (ParseException e) { + LOGGER.log(Level.FINEST, null, e); + } + break; + case "createdTo": + try { + pCreationDateTo = SIMPLE_DATE_FORMAT.parse(value); + } catch (ParseException e) { + LOGGER.log(Level.FINEST, null, e); + } + break; + case "modifiedFrom": + try { + pModificationDateFrom = SIMPLE_DATE_FORMAT.parse(value); + } catch (ParseException e) { + LOGGER.log(Level.FINEST, null, e); + } + break; + case "modifiedTo": + try { + pModificationDateTo = SIMPLE_DATE_FORMAT.parse(value); + } catch (ParseException e) { + LOGGER.log(Level.FINEST, null, e); + } + break; + case "tags": + pTags = value.split(","); + break; + case "standardPart": + standardPart = Boolean.valueOf(value); + break; + case "content": + content = value; + break; + case "attributes": + pAttributes = parseAttributeStringQuery(value); + break; + } + } + } + + PartSearchQuery.AbstractAttributeQuery[] pAttributesArray = pAttributes.toArray(new PartSearchQuery.AbstractAttributeQuery[pAttributes.size()]); + + return new PartSearchQuery(workspaceId, fullText, pNumber, pName, pVersion, pAuthor, pType, + pCreationDateFrom, pCreationDateTo, pModificationDateFrom, pModificationDateTo, + pAttributesArray, pTags, standardPart, content); + + } + + private static List parseAttributeStringQuery(String attributeQuery) { + List pAttributes = new ArrayList<>(); + String[] attributesString = attributeQuery.split(ATTRIBUTES_DELIMITER); + + for (String attributeString : attributesString) { + + int firstColon = attributeString.indexOf(ATTRIBUTES_SPLITTER); + String attributeType = attributeString.substring(0, firstColon); + attributeString = attributeString.substring(firstColon + 1); + + int secondColon = attributeString.indexOf(ATTRIBUTES_SPLITTER); + String attributeName = attributeString.substring(0, secondColon); + String attributeValue = attributeString.substring(secondColon + 1); + + switch (attributeType) { + case "BOOLEAN": + SearchQuery.BooleanAttributeQuery baq = new SearchQuery.BooleanAttributeQuery(attributeName, Boolean.valueOf(attributeValue)); + pAttributes.add(baq); + break; + case "DATE": + SearchQuery.DateAttributeQuery daq = new SearchQuery.DateAttributeQuery(); + daq.setName(attributeName); + try { + //Date attributes are always UTC, should not use the default timezone + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + df.setTimeZone(TimeZone.getTimeZone("UTC")); + daq.setDate(df.parse(attributeValue)); + pAttributes.add(daq); + } catch (ParseException e) { + LOGGER.log(Level.FINEST, null, e); + } + break; + case "TEXT": + SearchQuery.TextAttributeQuery taq = new SearchQuery.TextAttributeQuery(attributeName, attributeValue); + pAttributes.add(taq); + break; + case "NUMBER": + try { + SearchQuery.NumberAttributeQuery naq = new SearchQuery.NumberAttributeQuery(attributeName, NumberFormat.getInstance().parse(attributeValue).floatValue()); + pAttributes.add(naq); + } catch (ParseException e) { + LOGGER.log(Level.INFO, null, e); + } + break; + case "URL": + SearchQuery.URLAttributeQuery uaq = new SearchQuery.URLAttributeQuery(attributeName, attributeValue); + pAttributes.add(uaq); + break; + + case "LOV": + SearchQuery.LovAttributeQuery laq = new SearchQuery.LovAttributeQuery(attributeName, attributeValue); + pAttributes.add(laq); + break; + + default: + break; + } + + } + return pAttributes; + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/writer/FileExportMessageBodyWriter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/writer/FileExportMessageBodyWriter.java new file mode 100644 index 0000000000..9a49f0176b --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/writer/FileExportMessageBodyWriter.java @@ -0,0 +1,163 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.writer; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.configuration.ProductInstanceIteration; +import com.docdoku.core.configuration.ProductInstanceMaster; +import com.docdoku.core.configuration.ProductInstanceMasterKey; +import com.docdoku.core.document.DocumentLink; +import com.docdoku.core.exceptions.*; +import com.docdoku.core.product.ConfigurationItemKey; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.services.IProductInstanceManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.server.rest.util.FileExportEntity; + +import javax.inject.Inject; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +@Provider +public class FileExportMessageBodyWriter implements MessageBodyWriter { + + private static final Logger LOGGER = Logger.getLogger(FileExportMessageBodyWriter.class.getName()); + @Inject + private IDataManagerLocal dataManager; + @Inject + private IProductManagerLocal productService; + @Inject + private IProductInstanceManagerLocal productInstanceService; + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return type.equals(FileExportEntity.class); + } + + @Override + public long getSize(FileExportEntity fileExportEntity, Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { + return -1; + } + + @Override + public void writeTo(FileExportEntity fileExportEntity, Class aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap multivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException { + + ZipOutputStream zs = new ZipOutputStream(outputStream); + + try { + + Map> binariesInTree = productService.getBinariesInTree(fileExportEntity.getBaselineId(), fileExportEntity.getConfigurationItemKey().getWorkspace(), fileExportEntity.getConfigurationItemKey(), fileExportEntity.getPsFilter(), fileExportEntity.isExportNativeCADFile(), fileExportEntity.isExportDocumentLinks()); + Set>> entries = binariesInTree.entrySet(); + List baselinedSourcesName = new ArrayList<>(); + + if (fileExportEntity.isExportDocumentLinks() && fileExportEntity.getBaselineId() != null) { + List baselinedSources = productService.getBinaryResourceFromBaseline(fileExportEntity.getBaselineId()); + + for (BinaryResource binaryResource : baselinedSources) { + String[] parts = binaryResource.getFullName().split("/"); + String folderName = parts[2] + "-" + parts[3] + "-" + parts[4]; + baselinedSourcesName.add(folderName); + addToZipFile(binaryResource, "links/" + folderName, zs); + } + } + + for (Map.Entry> entry : entries) { + String partNumberFolderName = entry.getKey(); + String folderName; + Set files = entry.getValue(); + + for (BinaryResource binaryResource : files) { + try { + String fileType = binaryResource.getFileType(); + folderName = partNumberFolderName + (fileType == null ? "" : "/" + fileType); + addToZipFile(binaryResource, folderName, zs); + + } catch (StorageException e) { + LOGGER.log(Level.FINEST, null, e); + } + } + } + + if (fileExportEntity.getSerialNumber() != null) { + addProductInstanceDataToZip(zs, fileExportEntity.getConfigurationItemKey(), fileExportEntity.getSerialNumber(), baselinedSourcesName); + } + + } catch (UserNotFoundException | UserNotActiveException | WorkspaceNotFoundException | ConfigurationItemNotFoundException | + NotAllowedException | EntityConstraintException | PartMasterNotFoundException | ProductInstanceMasterNotFoundException | + StorageException e) { + LOGGER.log(Level.FINEST, null, e); + } + + zs.close(); + + } + + private void addProductInstanceDataToZip(ZipOutputStream zs, ConfigurationItemKey configurationItemKey, String serialNumber, List baselinedSourcesName) throws UserNotFoundException, WorkspaceNotFoundException, UserNotActiveException, ProductInstanceMasterNotFoundException, IOException, StorageException { + ProductInstanceMaster productInstanceMaster = productInstanceService.getProductInstanceMaster(new ProductInstanceMasterKey(serialNumber, configurationItemKey)); + ProductInstanceIteration lastIteration = productInstanceMaster.getLastIteration(); + + for (BinaryResource attachedFile : lastIteration.getAttachedFiles()) { + addToZipFile(attachedFile, "attachedfiles", zs); + } + + for (DocumentLink docLink : lastIteration.getLinkedDocuments()) { + for (BinaryResource linkedFile : docLink.getTargetDocument().getLastIteration().getAttachedFiles()) { + String folderName = docLink.getTargetDocument().getLastIteration().toString(); + + if (!baselinedSourcesName.contains(folderName)) { + addToZipFile(linkedFile, "links/" + folderName, zs); + } + } + } + } + + public void addToZipFile(BinaryResource binaryResource, String folderName, ZipOutputStream zos) throws IOException, StorageException { + + try (InputStream binaryResourceInputStream = dataManager.getBinaryResourceInputStream(binaryResource)) { + + ZipEntry zipEntry = new ZipEntry(folderName + "/" + binaryResource.getName()); + zos.putNextEntry(zipEntry); + + byte[] bytes = new byte[1024]; + int length; + while ((length = binaryResourceInputStream.read(bytes)) >= 0) { + zos.write(bytes, 0, length); + } + zos.closeEntry(); + } + } + +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/writer/InstanceCollectionMessageBodyWriter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/writer/InstanceCollectionMessageBodyWriter.java new file mode 100644 index 0000000000..470e5b47f3 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/writer/InstanceCollectionMessageBodyWriter.java @@ -0,0 +1,74 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.writer; + +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.server.rest.collections.InstanceCollection; +import com.docdoku.server.rest.util.InstanceBodyWriterTools; + +import javax.inject.Inject; +import javax.json.Json; +import javax.json.stream.JsonGenerator; +import javax.vecmath.Matrix4d; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.ArrayList; + +/** + * @author Florent Garin + */ +@Provider +@Produces(MediaType.APPLICATION_JSON) +public class InstanceCollectionMessageBodyWriter implements MessageBodyWriter { + + @Inject + private IProductManagerLocal productService; + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return type.equals(InstanceCollection.class); + } + + @Override + public long getSize(InstanceCollection t, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return -1; + } + + @Override + public void writeTo(InstanceCollection instanceCollection, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws UnsupportedEncodingException { + String charSet = "UTF-8"; + JsonGenerator jg = Json.createGenerator(new OutputStreamWriter(entityStream, charSet)); + jg.writeStartArray(); + + Matrix4d gM = new Matrix4d(); + gM.setIdentity(); + InstanceBodyWriterTools.generateInstanceStreamWithGlobalMatrix(productService, null, gM, instanceCollection, new ArrayList<>(), jg); + jg.writeEnd(); + jg.flush(); + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/writer/QueryResultMessageBodyWriter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/writer/QueryResultMessageBodyWriter.java new file mode 100644 index 0000000000..970e0ce4de --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/writer/QueryResultMessageBodyWriter.java @@ -0,0 +1,400 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.writer; + +import com.docdoku.core.common.User; +import com.docdoku.core.configuration.*; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.document.DocumentLink; +import com.docdoku.core.document.DocumentRevision; +import com.docdoku.core.exceptions.ProductInstanceMasterNotFoundException; +import com.docdoku.core.exceptions.UserNotActiveException; +import com.docdoku.core.exceptions.UserNotFoundException; +import com.docdoku.core.exceptions.WorkspaceNotFoundException; +import com.docdoku.core.meta.InstanceAttribute; +import com.docdoku.core.meta.InstanceAttributeDescriptor; +import com.docdoku.core.meta.InstanceDateAttribute; +import com.docdoku.core.meta.InstanceListOfValuesAttribute; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartLinkList; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.query.QueryContext; +import com.docdoku.core.query.QueryField; +import com.docdoku.core.query.QueryResultRow; +import com.docdoku.core.services.IProductInstanceManagerLocal; +import com.docdoku.core.util.Tools; +import com.docdoku.server.export.ExcelGenerator; +import com.docdoku.server.rest.collections.QueryResult; + +import javax.inject.Inject; +import javax.json.Json; +import javax.json.JsonValue; +import javax.json.stream.JsonGenerator; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + + +@Provider +@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM}) +public class QueryResultMessageBodyWriter implements MessageBodyWriter { + + private static final Logger LOGGER = Logger.getLogger(QueryResultMessageBodyWriter.class.getName()); + private static SimpleDateFormat FORMAT = QueryResultMessageBodyWriter.getFormat(); + private ExcelGenerator excelGenerator = new ExcelGenerator(); + @Inject + private IProductInstanceManagerLocal productInstanceService; + + private static SimpleDateFormat getFormat() { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format; + } + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return type.equals(QueryResult.class); + } + + @Override + public long getSize(QueryResult queryResult, Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { + return -1; + } + + @Override + public void writeTo(QueryResult queryResult, Class aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream outputStream) throws IOException, WebApplicationException { + + if (queryResult.getExportType().equals(QueryResult.ExportType.JSON)) { + httpHeaders.putSingle("Content-Type", "application/json"); + httpHeaders.putSingle("Content-Disposition", "inline"); + generateJSONResponse(outputStream, queryResult); + } else if (queryResult.getExportType().equals(QueryResult.ExportType.XLS)) { + httpHeaders.putSingle("Content-Type", "application/octet-stream"); + httpHeaders.putSingle("Content-Disposition", "attachment; filename=\"TSR.csv\""); + excelGenerator.generateXLSResponse(queryResult, new Locale(queryResult.getQuery().getAuthor().getLanguage()), ""); + } else { + throw new IllegalArgumentException(); + } + + } + + private void generateJSONResponse(OutputStream outputStream, QueryResult queryResult) throws UnsupportedEncodingException { + + String charSet = "UTF-8"; + JsonGenerator jg = Json.createGenerator(new OutputStreamWriter(outputStream, charSet)); + jg.writeStartArray(); + + List selects = queryResult.getQuery().getSelects(); + List partIterationSelectedAttributes = getPartIterationSelectedAttributes(selects); + List pathDataSelectedAttributes = getPathDataSelectedAttributes(selects); + + for (QueryResultRow row : queryResult.getRows()) { + + QueryContext queryContext = row.getContext(); + + PartRevision part = row.getPartRevision(); + PartIteration lastCheckedInIteration = part.getLastCheckedInIteration(); + + jg.writeStartObject(); + + jg.write(QueryField.PART_REVISION_PART_KEY, part.getPartNumber() + '-' + part.getVersion()); + + // PartMaster data + + if (selects.contains(QueryField.PART_MASTER_NUMBER)) { + jg.write(QueryField.PART_MASTER_NUMBER, part.getPartNumber()); + } + + if (selects.contains(QueryField.PART_MASTER_NAME)) { + String sName = part.getPartName(); + jg.write(QueryField.PART_MASTER_NAME, sName != null ? sName : ""); + } + + if (selects.contains(QueryField.PART_MASTER_TYPE)) { + String sType = part.getType(); + jg.write(QueryField.PART_MASTER_TYPE, sType != null ? sType : ""); + } + + // PartRevision data + + if (selects.contains(QueryField.PART_REVISION_MODIFICATION_DATE)) { + PartIteration pi = part.getLastIteration(); + if (pi != null) { + writeDate(jg, QueryField.PART_REVISION_MODIFICATION_DATE, pi.getModificationDate()); + } + } + + if (selects.contains(QueryField.PART_REVISION_CREATION_DATE)) { + writeDate(jg, QueryField.PART_REVISION_CREATION_DATE, part.getCreationDate()); + } + + if (selects.contains(QueryField.PART_REVISION_CHECKOUT_DATE)) { + writeDate(jg, QueryField.PART_REVISION_CHECKOUT_DATE, part.getCheckOutDate()); + } + + if (selects.contains(QueryField.PART_REVISION_CHECKIN_DATE)) { + writeDate(jg, QueryField.PART_REVISION_CHECKIN_DATE, lastCheckedInIteration != null ? lastCheckedInIteration.getCheckInDate() : null); + } + + if (selects.contains(QueryField.PART_REVISION_VERSION)) { + String version = part.getVersion(); + jg.write(QueryField.PART_REVISION_VERSION, version); + } + + if (selects.contains(QueryField.PART_REVISION_LIFECYCLE_STATE)) { + String lifeCycleState = part.getLifeCycleState(); + jg.write(QueryField.PART_REVISION_LIFECYCLE_STATE, lifeCycleState != null ? lifeCycleState : ""); + } + + if (selects.contains(QueryField.PART_REVISION_STATUS)) { + PartRevision.RevisionStatus status = part.getStatus(); + jg.write(QueryField.PART_REVISION_STATUS, status.toString()); + } + + if (selects.contains(QueryField.AUTHOR_LOGIN)) { + User user = part.getAuthor(); + jg.write(QueryField.AUTHOR_LOGIN, user.getLogin()); + } + + if (selects.contains(QueryField.AUTHOR_NAME)) { + User user = part.getAuthor(); + jg.write(QueryField.AUTHOR_NAME, user.getName()); + } + + if (selects.contains(QueryField.CTX_DEPTH)) { + jg.write(QueryField.CTX_DEPTH, row.getDepth()); + } + + if (selects.contains(QueryField.PART_ITERATION_LINKED_DOCUMENTS)) { + + StringBuilder sb = new StringBuilder(); + + if (null != queryContext && null != queryContext.getSerialNumber()) { + try { + ProductInstanceMaster productInstanceMaster = productInstanceService.getProductInstanceMaster(new ProductInstanceMasterKey(queryContext.getSerialNumber(), queryContext.getWorkspaceId(), queryContext.getConfigurationItemId())); + ProductInstanceIteration lastIteration = productInstanceMaster.getLastIteration(); + PartCollection partCollection = lastIteration.getPartCollection(); + BaselinedPart baselinedPart = partCollection.getBaselinedPart(new BaselinedPartKey(partCollection.getId(), queryContext.getWorkspaceId(), part.getPartNumber())); + PartIteration targetPart = baselinedPart.getTargetPart(); + Set linkedDocuments = targetPart.getLinkedDocuments(); + DocumentCollection documentCollection = lastIteration.getDocumentCollection(); + + for (DocumentLink documentLink : linkedDocuments) { + DocumentRevision targetDocument = documentLink.getTargetDocument(); + BaselinedDocument baselinedDocument = documentCollection.getBaselinedDocument(new BaselinedDocumentKey(documentCollection.getId(), queryContext.getWorkspaceId(), targetDocument.getDocumentMasterId(), targetDocument.getVersion())); + if (null != baselinedDocument) { + DocumentIteration targetDocumentIteration = baselinedDocument.getTargetDocument(); + sb.append(targetDocumentIteration.toString() + ","); + } + } + + } catch (UserNotFoundException | UserNotActiveException | WorkspaceNotFoundException | ProductInstanceMasterNotFoundException e) { + LOGGER.log(Level.FINEST, null, e); + } + } else { + if (lastCheckedInIteration != null) { + Set linkedDocuments = lastCheckedInIteration.getLinkedDocuments(); + + for (DocumentLink documentLink : linkedDocuments) { + DocumentRevision targetDocument = documentLink.getTargetDocument(); + DocumentIteration targetDocumentLastCheckedInIteration = targetDocument.getLastCheckedInIteration(); + if (targetDocumentLastCheckedInIteration != null) { + sb.append(targetDocumentLastCheckedInIteration.toString() + ","); + } + } + } + } + + jg.write(QueryField.PART_ITERATION_LINKED_DOCUMENTS, sb.toString()); + + } + + for (String attributeSelect : partIterationSelectedAttributes) { + + String attributeSelectType = attributeSelect.substring(0, attributeSelect.indexOf(".")).substring(QueryField.PART_REVISION_ATTRIBUTES_PREFIX.length()); + + String attributeSelectName = attributeSelect.substring(attributeSelect.indexOf(".") + 1); + + String attributeValue = ""; + + PartIteration pi = part.getLastIteration(); + + if (pi != null) { + List attributes = pi.getInstanceAttributes(); + + if (attributes != null) { + jg.writeStartArray(attributeSelect); + + for (InstanceAttribute attribute : attributes) { + InstanceAttributeDescriptor attributeDescriptor = new InstanceAttributeDescriptor(attribute); + + if (attributeDescriptor.getName().equals(attributeSelectName) + && attributeDescriptor.getStringType().equals(attributeSelectType)) { + + attributeValue = attribute.getValue() + ""; + + if (attribute instanceof InstanceDateAttribute) { + attributeValue = getFormattedDate(((InstanceDateAttribute) attribute).getDateValue()); + } else if (attribute instanceof InstanceListOfValuesAttribute) { + attributeValue = ((InstanceListOfValuesAttribute) attribute).getSelectedName(); + } + + jg.write(attributeValue); + } + } + + jg.writeEnd(); + + } else { + jg.write(attributeSelect, attributeValue); + } + + } else { + // TODO: maybe this line is useless and should be removed + jg.write(attributeSelect, attributeValue); + } + } + + for (String attributeSelect : pathDataSelectedAttributes) { + + String attributeSelectType = attributeSelect.substring(0, attributeSelect.indexOf(".")).substring(QueryField.PATH_DATA_ATTRIBUTES_PREFIX.length()); + + String attributeSelectName = attributeSelect.substring(attributeSelect.indexOf(".") + 1); + + String attributeValue = ""; + + PathDataIteration pdi = row.getPathDataIteration(); + + if (pdi != null) { + List attributes = pdi.getInstanceAttributes(); + + if (attributes != null) { + jg.writeStartArray(attributeSelect); + + for (InstanceAttribute attribute : attributes) { + InstanceAttributeDescriptor attributeDescriptor = new InstanceAttributeDescriptor(attribute); + + if (attributeDescriptor.getName().equals(attributeSelectName) + && attributeDescriptor.getStringType().equals(attributeSelectType)) { + + attributeValue = attribute.getValue() + ""; + + if (attribute instanceof InstanceDateAttribute) { + attributeValue = getFormattedDate(((InstanceDateAttribute) attribute).getDateValue()); + } else if (attribute instanceof InstanceListOfValuesAttribute) { + attributeValue = ((InstanceListOfValuesAttribute) attribute).getSelectedName(); + } + + jg.write(attributeValue); + } + } + + jg.writeEnd(); + + } else { + jg.write(attributeSelect, attributeValue); + } + } + } + + if (selects.contains(QueryField.CTX_PRODUCT_ID)) { + String configurationItemId = queryContext != null ? queryContext.getConfigurationItemId() : ""; + jg.write(QueryField.CTX_PRODUCT_ID, configurationItemId); + } + if (selects.contains(QueryField.CTX_SERIAL_NUMBER)) { + String serialNumber = queryContext != null ? queryContext.getSerialNumber() : ""; + jg.write(QueryField.CTX_SERIAL_NUMBER, serialNumber != null ? serialNumber : ""); + } + if (selects.contains(QueryField.CTX_AMOUNT)) { + String amount = row.getAmount() + ""; + jg.write(QueryField.CTX_AMOUNT, amount); + } + + if (selects.contains(QueryField.CTX_P2P_SOURCE)) { + Map> sources = row.getSources(); + String partLinksAsString = Tools.getPartLinksAsHumanString(sources); + jg.write(QueryField.CTX_P2P_SOURCE, partLinksAsString); + } + + if (selects.contains(QueryField.CTX_P2P_TARGET)) { + Map> targets = row.getTargets(); + String partLinksAsString = Tools.getPartLinksAsHumanString(targets); + jg.write(QueryField.CTX_P2P_TARGET, partLinksAsString); + } + + if (selects.contains(QueryField.PART_MASTER_IS_STANDARD)) { + boolean isStandard = row.getPartRevision().getPartMaster().isStandardPart(); + jg.write(QueryField.PART_MASTER_IS_STANDARD, isStandard); + } + + jg.writeEnd(); + } + + jg.writeEnd(); + jg.flush(); + } + + + private void writeDate(JsonGenerator jg, String key, Date date) { + if (date != null) { + String formattedDate = getFormattedDate(date); + jg.write(key, formattedDate); + } else { + jg.write(key, JsonValue.NULL); + } + } + + private String getFormattedDate(Date date) { + return date != null ? FORMAT.format(date) : ""; + } + + public List getPartIterationSelectedAttributes(List selects) { + List attributesSelect = new ArrayList<>(); + for (String select : selects) { + if (select.contains(QueryField.PART_REVISION_ATTRIBUTES_PREFIX)) { + attributesSelect.add(select); + } + } + return attributesSelect; + } + + public List getPathDataSelectedAttributes(List selects) { + List attributesSelect = new ArrayList<>(); + for (String select : selects) { + if (select.contains(QueryField.PATH_DATA_ATTRIBUTES_PREFIX)) { + attributesSelect.add(select); + } + } + return attributesSelect; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/writer/VirtualInstanceCollectionMessageBodyWriter.java b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/writer/VirtualInstanceCollectionMessageBodyWriter.java new file mode 100644 index 0000000000..3be669e316 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/java/com/docdoku/server/rest/writer/VirtualInstanceCollectionMessageBodyWriter.java @@ -0,0 +1,147 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.rest.writer; + +import com.docdoku.core.product.CADInstance; +import com.docdoku.core.product.PartLink; +import com.docdoku.core.product.PartMaster; +import com.docdoku.core.product.PartSubstituteLink; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.server.rest.collections.VirtualInstanceCollection; +import com.docdoku.server.rest.util.InstanceBodyWriterTools; + +import javax.inject.Inject; +import javax.json.Json; +import javax.json.stream.JsonGenerator; +import javax.vecmath.Matrix4d; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Morgan Guimard + */ +@Provider +@Produces(MediaType.APPLICATION_JSON) +public class VirtualInstanceCollectionMessageBodyWriter implements MessageBodyWriter { + + @Inject + private IProductManagerLocal productService; + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return type.equals(VirtualInstanceCollection.class); + } + + @Override + public long getSize(VirtualInstanceCollection virtualInstanceCollection, Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { + return -1; + } + + @Override + public void writeTo(VirtualInstanceCollection virtualInstanceCollection, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws UnsupportedEncodingException { + String charSet = "UTF-8"; + JsonGenerator jg = Json.createGenerator(new OutputStreamWriter(entityStream, charSet)); + jg.writeStartArray(); + + Matrix4d gM = new Matrix4d(); + gM.setIdentity(); + + PartLink virtualRootPartLink = getVirtualRootPartLink(virtualInstanceCollection); + List path = new ArrayList<>(); + path.add(virtualRootPartLink); + InstanceBodyWriterTools.generateInstanceStreamWithGlobalMatrix(productService, path, gM, virtualInstanceCollection, new ArrayList<>(), jg); + jg.writeEnd(); + jg.flush(); + } + + + private PartLink getVirtualRootPartLink(VirtualInstanceCollection virtualInstanceCollection) { + return new PartLink() { + @Override + public int getId() { + return 1; + } + + @Override + public Character getCode() { + return '-'; + } + + @Override + public String getFullId() { + return "-1"; + } + + @Override + public double getAmount() { + return 1; + } + + @Override + public String getUnit() { + return null; + } + + @Override + public String getComment() { + return virtualInstanceCollection.getRootPart().getDescription(); + } + + @Override + public boolean isOptional() { + return false; + } + + @Override + public PartMaster getComponent() { + return virtualInstanceCollection.getRootPart().getPartMaster(); + } + + @Override + public List getSubstitutes() { + return null; + } + + @Override + public String getReferenceDescription() { + return virtualInstanceCollection.getRootPart().getDescription(); + } + + @Override + public List getCadInstances() { + CADInstance virtualInstance = new CADInstance(0, 0, 0, 0, 0, 0); + List virtualCadInstances = new ArrayList<>(); + virtualCadInstances.add(virtualInstance); + return virtualCadInstances; + } + }; + + } +} diff --git a/docdoku-server/docdoku-server-rest/src/main/resources/META-INF/LICENSE.txt b/docdoku-server/docdoku-server-rest/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 0000000000..2def0e8831 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/resources/META-INF/beans.xml b/docdoku-server/docdoku-server-rest/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..5d71326a5c --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/resources/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/resources/META-INF/mime.types b/docdoku-server/docdoku-server-rest/src/main/resources/META-INF/mime.types new file mode 100644 index 0000000000..6dfeb97b8a --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/resources/META-INF/mime.types @@ -0,0 +1,5 @@ +application/msword doc dot +application/pdf pdf +application/mspowerpoint ppt pps +application/excel xls xlc xlw xlt xla xll xlb xlm +text/html html htm \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/resources/com/docdoku/api/securityDefinitions.json b/docdoku-server/docdoku-server-rest/src/main/resources/com/docdoku/api/securityDefinitions.json new file mode 100644 index 0000000000..324797dba1 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/resources/com/docdoku/api/securityDefinitions.json @@ -0,0 +1,5 @@ +{ + "basicAuth": { + "type": "basic" + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/resources/com/docdoku/server/i18n/LocalStrings.properties b/docdoku-server/docdoku-server-rest/src/main/resources/com/docdoku/server/i18n/LocalStrings.properties new file mode 100644 index 0000000000..fff44ff0fb --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/resources/com/docdoku/server/i18n/LocalStrings.properties @@ -0,0 +1,22 @@ +pm.number=Part number +pm.name=Part name +pm.type=Part type +pm.standardPart=Standard +pr.partKey=Part key +pr.version=Version +pr.modificationDate=Modification date +pr.checkInDate=Checkin date +pr.checkOutDate=Checkout date +pr.creationDate=Creation date +pr.lifeCycleState=Lifecycle state +pr.status=Status +pr.linkedDocuments=Linked documents +attr-=Attribute +author.login=Author login +author.name=Author name +ctx.serialNumber=Serial number +ctx.productId=Product id +ctx.depth=Depth +ctx.amount=Amount +ctx.p2p.source=Source typed links +ctx.p2p.target=Target typed links \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/resources/com/docdoku/server/i18n/LocalStrings_en.properties b/docdoku-server/docdoku-server-rest/src/main/resources/com/docdoku/server/i18n/LocalStrings_en.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docdoku-server/docdoku-server-rest/src/main/resources/com/docdoku/server/i18n/LocalStrings_fr.properties b/docdoku-server/docdoku-server-rest/src/main/resources/com/docdoku/server/i18n/LocalStrings_fr.properties new file mode 100644 index 0000000000..e229ab8fc8 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/resources/com/docdoku/server/i18n/LocalStrings_fr.properties @@ -0,0 +1,22 @@ +pm.number=Num\u00e9ro d'article +pm.name=Nom d'article +pm.type=Type +pm.standardPart=Standard +pr.partKey=Identifiant d'article +pr.version=Version +pr.modificationDate=Date de modification +pr.checkInDate=Date de lib\u00e9ration +pr.checkOutDate=Date de r\u00e9servation +pr.creationDate=Date de cr\u00e9ation +pr.lifeCycleState=Etat du cycle de vie +pr.status=Statut +pr.linkedDocuments=Documents li\u00e9s +attr-=Attribut +author.login=Identifiant de l'auteur +author.name=Nom de l'auteur +ctx.serialNumber=Num\u00e9ro de s\u00e9rie +ctx.productId=Identifiant du produit +ctx.depth=Profondeur +ctx.amount=Montant +ctx.p2p.source=Liens sources +ctx.p2p.target=Liens destinations \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/main/resources/dozerBeanMapping.xml b/docdoku-server/docdoku-server-rest/src/main/resources/dozerBeanMapping.xml new file mode 100644 index 0000000000..f6662f22de --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/resources/dozerBeanMapping.xml @@ -0,0 +1,71 @@ + + + + + + + com.docdoku.core.common.BinaryResource + java.lang.String + + + com.docdoku.core.meta.InstanceAttribute + com.docdoku.server.rest.dto.InstanceAttributeDTO + + + com.docdoku.core.workflow.ActivityModel + com.docdoku.server.rest.dto.ActivityModelDTO + + + com.docdoku.core.workflow.Activity + com.docdoku.server.rest.dto.ActivityDTO + + + com.docdoku.core.security.ACL + com.docdoku.server.rest.dto.ACLDTO + + + + + + com.docdoku.core.document.DocumentLink + com.docdoku.server.rest.dto.DocumentRevisionDTO + + targetDocumentMasterId + documentMasterId + + + targetDocumentVersion + version + + + targetDocumentWorkspaceId + workspaceId + + + comment + commentLink + + + documentTitle + title + + + + + com.docdoku.core.meta.ListOfValues + com.docdoku.server.rest.dto.ListOfValuesDTO + + name + id + + + name + name + + + + + diff --git a/docdoku-server/docdoku-server-rest/src/main/webapp/WEB-INF/sun-web.xml b/docdoku-server/docdoku-server-rest/src/main/webapp/WEB-INF/sun-web.xml new file mode 100644 index 0000000000..3677f86b00 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/webapp/WEB-INF/sun-web.xml @@ -0,0 +1,24 @@ + + + + + users + users + + + admin + admin + + + guest-proxy + guest-proxy + + + + + Keep a copy of the generated servlet class' java code. + + + + diff --git a/docdoku-server/docdoku-server-rest/src/main/webapp/WEB-INF/web.xml b/docdoku-server/docdoku-server-rest/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..e611f9b8f7 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,112 @@ + + + + + + + + SessionFilter + com.docdoku.server.filters.SessionFilter + true + + + SessionFilter + /api/* + + + + + + + + BasicFilter + com.docdoku.server.filters.BasicFilter + true + + + BasicFilter + /api/* + + + + + PublicFilter + com.docdoku.server.filters.PublicFilter + true + + publicPaths + /api,/api/accounts/create,/api/viewer,/api/shared/**,/api/languages,/api/timezones,/api/auth/**,/api/files/*/documents/**,/api/files/*/parts/** + + + + PublicFilter + /api/* + + + + + RequestFilter + com.docdoku.server.filters.RequestFilter + true + + + RequestFilter + /api/* + + + + + + javax.ws.rs.core.Application + 1 + true + + 1048576 + + + + javax.ws.rs.core.Application + /api/* + + + WebSocket channels + + Main Channel + /ws + + + + users + + + + BASIC + docdokuRealm + + + All the regular users + users + + + + com.docdoku.server.http.WebSessionListener + + + + + *.html + UTF-8 + + + + diff --git a/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/mainchannel/collaborative/CollaborativeRoomTest.java b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/mainchannel/collaborative/CollaborativeRoomTest.java new file mode 100644 index 0000000000..defb2e1f47 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/mainchannel/collaborative/CollaborativeRoomTest.java @@ -0,0 +1,249 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.mainchannel.collaborative; + + +import com.docdoku.server.mainchannel.module.CollaborativeMessage; +import com.docdoku.server.mainchannel.util.ChannelMessagesType; +import org.junit.After; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.websocket.Session; +import java.io.StringReader; +import java.security.Principal; + +/** + * @author Asmae CHADID + */ +@RunWith(MockitoJUnitRunner.class) +public class CollaborativeRoomTest { + private static Session master = Mockito.mock(Session.class); + private static Session slave1 = Mockito.mock(Session.class); + private static Session slave2 = Mockito.mock(Session.class); + + private static Principal principalMaster = Mockito.mock(Principal.class); + private static Principal principal1 = Mockito.mock(Principal.class); + private static Principal principal2 = Mockito.mock(Principal.class); + + + + @BeforeClass + public static void init() { + Mockito.when(master.getUserPrincipal()).thenReturn(principalMaster); + Mockito.when(slave1.getUserPrincipal()).thenReturn(principal1); + Mockito.when(slave2.getUserPrincipal()).thenReturn(principal2); + + Mockito.when(master.getUserPrincipal().getName()).thenReturn("master1"); + Mockito.when(slave1.getUserPrincipal().getName()).thenReturn("slave1"); + Mockito.when(slave2.getUserPrincipal().getName()).thenReturn("slave2"); + } + + @Test + public void shouldReturnEmptyMasterName() { + //Given + CollaborativeRoom nullCollaborativeRoom = Mockito.spy(new CollaborativeRoom(null)); + //then + Assert.assertTrue(nullCollaborativeRoom.getMasterName().isEmpty()); + } + + @Test + public void shouldReturnNotNullMasterName() { + //Given + CollaborativeRoom nullCollaborativeRoom = Mockito.spy(new CollaborativeRoom(master)); + //then + Assert.assertTrue(!nullCollaborativeRoom.getMasterName().isEmpty()); + } + + @Test + public void shouldReturnNotNullCollaborativeRoom() { + //Given + CollaborativeRoom collaborativeRoom = Mockito.spy(new CollaborativeRoom(master)); + //Then + Assert.assertTrue(CollaborativeRoom.getByKeyName(collaborativeRoom.getKey()) != null); } + + @Test + public void shouldReturnFourRooms() { + //Given + CollaborativeRoom collaborativeRoom1 = Mockito.spy(new CollaborativeRoom(master)); + CollaborativeRoom collaborativeRoom2 = Mockito.spy(new CollaborativeRoom(master)); + CollaborativeRoom collaborativeRoom3 = Mockito.spy(new CollaborativeRoom(master)); + CollaborativeRoom collaborativeRoom4 = Mockito.spy(new CollaborativeRoom(master)); + + //Then + Assert.assertTrue(CollaborativeRoom.getAllCollaborativeRooms().size() == 4); + } + + @Test + public void shouldDeleteRooms(){ + //Given + CollaborativeRoom collaborativeRoom1 = Mockito.spy(new CollaborativeRoom(master)); + CollaborativeRoom collaborativeRoom2 = Mockito.spy(new CollaborativeRoom(master)); + CollaborativeRoom collaborativeRoom3 = Mockito.spy(new CollaborativeRoom(master)); + CollaborativeRoom collaborativeRoom4 = Mockito.spy(new CollaborativeRoom(master)); + //When + collaborativeRoom1.delete(); + collaborativeRoom2.delete(); + //Then + Assert.assertTrue(CollaborativeRoom.getAllCollaborativeRooms().size() == 2); + } + + @Test + public void shouldReturnMaster() { + //Given + CollaborativeRoom collaborativeRoom1 = Mockito.spy(new CollaborativeRoom(master)); + //Then + Assert.assertTrue("master1".equals(collaborativeRoom1.getLastMaster())); + } + + @Test + public void shouldAddSlave() { + //Given + CollaborativeRoom collaborativeRoom1 = Mockito.spy(new CollaborativeRoom(master)); + //When + collaborativeRoom1.addSlave(slave1); + //Then + Assert.assertTrue(collaborativeRoom1.getSlaves().size() == 1); + + } + + @Test + public void shouldRemoveSlave(){ + //Given + CollaborativeRoom collaborativeRoom1 = Mockito.spy(new CollaborativeRoom(master)); + //When + collaborativeRoom1.addSlave(slave1); + collaborativeRoom1.addSlave(slave2); + collaborativeRoom1.removeSlave(slave1); + //Then + Assert.assertTrue(collaborativeRoom1.getSlaves().get(0).equals(slave2)); + } + + @Test + public void shouldAddPendingUser(){ + //Given + CollaborativeRoom collaborativeRoom1 = Mockito.spy(new CollaborativeRoom(master)); + //When + collaborativeRoom1.addPendingUser("user1"); + collaborativeRoom1.addPendingUser("user2"); + collaborativeRoom1.addPendingUser("user2"); + //Then + Assert.assertTrue(collaborativeRoom1.getPendingUsers().size() == 3); + } + + @Test + public void shouldRemovePendingUser(){ + //Given + CollaborativeRoom collaborativeRoom1 = Mockito.spy(new CollaborativeRoom(master)); + //When + collaborativeRoom1.addPendingUser("user1"); + collaborativeRoom1.addPendingUser("user2"); + collaborativeRoom1.addPendingUser("user2"); + collaborativeRoom1.addPendingUser("user2"); + + collaborativeRoom1.removePendingUser("user2"); + //Then + Assert.assertTrue(collaborativeRoom1.getPendingUsers().size() == 3); + } + + @Test + public void shouldReturnSlave1Slave2AndNull(){ + //Given + CollaborativeRoom collaborativeRoom1 = Mockito.spy(new CollaborativeRoom(master)); + //When + collaborativeRoom1.addSlave(slave1); + collaborativeRoom1.addSlave(slave2); + //Then + Assert.assertTrue(collaborativeRoom1.findUserSession("slave1").equals(slave1)); + Assert.assertTrue(collaborativeRoom1.findUserSession("slave2").equals(slave2)); + Assert.assertTrue(collaborativeRoom1.findUserSession("slave3") == null); + } + + @Test + public void shouldSaveCommands(){ + //Given + String msg = "{ \n" + + " \"messageBroadcast\":{ \n" + + " \"cameraInfos\":{ \n" + + " \"target\":{ \n" + + " \"x\":0,\n" + + " \"y\":0,\n" + + " \"z\":0\n" + + " },\n" + + " \"camPos\":{ \n" + + " \"x\":2283.8555345202267,\n" + + " \"y\":1742.2368392950543,\n" + + " \"z\":306.5925754554133\n" + + " },\n" + + " \"camOrientation\":{ \n" + + " \"x\":-0.16153026619659236,\n" + + " \"y\":0.9837903505522302,\n" + + " \"z\":0.07787502335635015\n" + + " },\n" + + " \"layers\":\"create layer\",\n" + + " \"colourEditedObjects\":true,\n" + + " \"clipping\":\"1\",\n" + + " \"explode\":\"3\",\n" + + " \"smartPath\":[ \n" + + " \"9447-9445-9441\",\n" + + " \"9447-9445-9443\",\n" + + " \"9447-9445-9444\",\n" + + " \"9446\"\n" + + " ]\n" + + " }\n" + + " }\n" + + "}"; + + + + JsonObject jsObj = Json.createReader(new StringReader(msg)).readObject(); + JsonObject messageBroadcast = jsObj.containsKey("messageBroadcast")?jsObj.getJsonObject("messageBroadcast"):null; + + CollaborativeMessage collaborativeMessage = Mockito.spy(new CollaborativeMessage(ChannelMessagesType.COLLABORATIVE_COMMANDS,"key-12545695-7859-458",messageBroadcast,"slave1")); + CollaborativeRoom room = Mockito.spy(new CollaborativeRoom(master)); + //When + room.addSlave(slave1); + room.addSlave(slave2); + + JsonObject commands = collaborativeMessage.getMessageBroadcast(); + room.saveCommand(commands); + Assert.assertTrue(room.getCommands().entrySet().size() == 1); + } + + + + + + @After + public void clearData() { + for (CollaborativeRoom room : CollaborativeRoom.getAllCollaborativeRooms()) { + room.delete(); + } + } + + +} diff --git a/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/mainchannel/util/RoomTest.java b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/mainchannel/util/RoomTest.java new file mode 100644 index 0000000000..eaa474f934 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/mainchannel/util/RoomTest.java @@ -0,0 +1,199 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.mainchannel.util; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import javax.websocket.Session; +import java.security.Principal; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * @author Asmae CHADID + */ +@RunWith(MockitoJUnitRunner.class) + public class RoomTest { + + private static Room room = Mockito.mock(Room.class); + private static ConcurrentMap DB = Mockito.mock(ConcurrentHashMap.class); + private static Session userSession1 = Mockito.mock(Session.class); + private static Principal principal1 = Mockito.mock(Principal.class); + private static Principal principal2 = Mockito.mock(Principal.class); + private static Principal principal3 = Mockito.mock(Principal.class); + private static Session userSession2 = Mockito.mock(Session.class); + private static Room secondRoom = Mockito.spy(new Room("PLMRoom")); + private static Room thirdRoom = Mockito.spy(new Room("ChatRoom")); + private static Session userSession3 = Mockito.mock(Session.class); + + @BeforeClass + public static void setUp() { + + Mockito.when(room.addUserSession(userSession1)).thenCallRealMethod(); + Mockito.when(room.addUserSession(userSession2)).thenCallRealMethod(); + Mockito.when(room.key()).thenReturn("plm"); + + Mockito.when(userSession1.getUserPrincipal()).thenReturn(principal1); + Mockito.when(principal1.getName()).thenReturn("user1"); + Mockito.when(room.getUser1Login()).thenCallRealMethod(); + + Mockito.when(userSession2.getUserPrincipal()).thenReturn(principal2); + Mockito.when(principal2.getName()).thenReturn("user2"); + Mockito.when(room.getUser2Login()).thenCallRealMethod(); + + Mockito.when(principal3.getName()).thenReturn("user3"); + Mockito.when(userSession3.getUserPrincipal()).thenReturn(principal3); + + Mockito.when(room.getUserSession(Matchers.anyString())).thenCallRealMethod(); + Mockito.when(room.getOtherUserSession(Matchers.any(Session.class))).thenCallRealMethod(); + Mockito.when(DB.get(Matchers.anyString())).thenReturn(room); + Mockito.when(DB.put(Matchers.anyString(), Matchers.any(Room.class))).thenReturn(room); + Mockito.when(DB.get("plm").getSessionForUserLogin(Matchers.anyString())).thenCallRealMethod(); + Mockito.when(room.getOccupancy()).thenCallRealMethod(); + Mockito.doCallRealMethod().when(room).put(); + + Mockito.when(room.hasUser(Matchers.anyString())).thenCallRealMethod(); + + room.addUserSession(userSession1); + room.addUserSession(userSession2); + + secondRoom.put(); + thirdRoom.put(); + + Mockito.when(room.key()).thenReturn("plm"); + + } + + @Test + public void testGetByKeyName() { + Assert.assertTrue("plm".equals(DB.get(" ").key())); + } + + @Test + public void testGetRoom() { + Assert.assertTrue(DB.get("null").equals(room)); + } + + @Test + public void testAddUserSession() { + Assert.assertTrue(room.addUserSession(userSession1)); + Assert.assertTrue(room.addUserSession(userSession2)); + + } + + @Test + public void testGetUser1Login() { + Assert.assertTrue("user1".equals(DB.get("plm").getUser1Login())); + + } + + @Test + public void testGetUser2Login() { + Assert.assertTrue("user2".equals(DB.get("plm").getUser2Login())); + + } + + @Test + public void testGetSessionForUserLogin() { + Assert.assertTrue(room.getSessionForUserLogin("user1") == userSession1); + Assert.assertTrue(room.getSessionForUserLogin("user4") == null); + } + + @Test + public void testGetOccupency() { + Assert.assertTrue(DB.get("plm").getOccupancy() == 2); + } + + @Test + public void testGetOtherUserSession() { + //When + Session userSession3 = Mockito.mock(Session.class); + //Then + Assert.assertTrue(DB.get("plm").getOtherUserSession(userSession1) == userSession2); + Assert.assertTrue(DB.get("plm").getOtherUserSession(userSession2) == userSession1); + Assert.assertTrue(DB.get("plm").getOtherUserSession(userSession3) == null); + + } + + @Test + public void testHasUser() { + Assert.assertTrue(DB.get("plm").hasUser("user1")); + } + + @Test + public void testGetUserSession() { + Assert.assertTrue(DB.get("plm").getUserSession("user1").equals(userSession1)); + Assert.assertTrue(DB.get("plm").getUserSession("user2").equals(userSession2)); + } + + + @Test + public void testAddUserSessionSecondRoom() { + //When + secondRoom.addUserSession(userSession1); + secondRoom.addUserSession(userSession2); + + //Then + Mockito.verify(secondRoom, Mockito.times(1)).addUserSession(userSession1); + Mockito.verify(secondRoom, Mockito.times(1)).addUserSession(userSession2); + Mockito.verify(secondRoom, Mockito.times(1)).put(); + } + + @Test + public void testRemoveUser() { + //Given + thirdRoom.addUserSession(userSession1); + thirdRoom.addUserSession(userSession2); + + secondRoom.addUserSession(userSession1); + secondRoom.addUserSession(userSession2); + //When + secondRoom.removeUser("user1"); + thirdRoom.removeUser("user2"); + //Then + Assert.assertTrue(!secondRoom.hasUser("user1")); + Assert.assertTrue(room.hasUser("user1")); + + Assert.assertTrue(secondRoom.hasUser("user2")); + Assert.assertTrue(!thirdRoom.hasUser("user2")); + Assert.assertTrue(room.hasUser("user2")); + } + + @Test + public void testRemoveUserFromAllRooms() { + //Given + thirdRoom.addUserSession(userSession3); + thirdRoom.addUserSession(userSession2); + + secondRoom.addUserSession(userSession1); + secondRoom.addUserSession(userSession2); + //When + Room.removeUserFromAllRoom("user1"); + //Then + Assert.assertTrue(!thirdRoom.hasUser("user1")); + + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/PartResourceTest.java b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/PartResourceTest.java new file mode 100644 index 0000000000..ddff7f098c --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/PartResourceTest.java @@ -0,0 +1,168 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.product.PartMaster; +import com.docdoku.core.product.PartMasterKey; +import com.docdoku.core.product.PartUsageLink; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.services.IMailerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.core.services.IUserManagerLocal; +import com.docdoku.server.rest.dto.*; +import com.docdoku.server.util.ResourceUtil; +import org.dozer.DozerBeanMapperSingletonWrapper; +import org.dozer.Mapper; +import org.junit.Before; +import org.junit.Test; +import org.mockito.*; + +import javax.persistence.EntityManager; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import static org.junit.Assert.*; +import static org.mockito.MockitoAnnotations.initMocks; + +public class PartResourceTest { + + @InjectMocks + PartResource partResource = new PartResource(); + @Mock + private IProductManagerLocal productService; + @Mock + private IDataManagerLocal dataManager; + @Mock + private EntityManager em; + @Mock + private IMailerLocal mailer; + @Mock + private IUserManagerLocal userManager; + @Spy + Workspace workspace = new Workspace(); + @Spy + private User user = new User(workspace, new Account("login", "user", "@docdoku.com", "en", new Date(),null)); + @Spy + private PartMaster partMaster = new PartMaster(workspace, "partNumber", user); + @Spy + private PartMaster subPartMaster = new PartMaster(workspace, "SubPartNumber", user); + @Spy + Mapper mapper = DozerBeanMapperSingletonWrapper.getInstance(); + + @Before + public void setup() throws Exception { + initMocks(this); + + } + + @Test + public void createComponents() { + //Given + PartIterationDTO data = new PartIterationDTO(ResourceUtil.WORKSPACE_ID, "partName", "partNumber", "A", 1); + List partUsageLinkDTOs = new ArrayList(); + PartUsageLinkDTO partUsageLinkDTO = new PartUsageLinkDTO(); + partUsageLinkDTO.setAmount(2); + partUsageLinkDTO.setUnit(""); + partUsageLinkDTO.setOptional(true); + partUsageLinkDTO.setComment("comment part usage link"); + partUsageLinkDTO.setReferenceDescription("description part usage link"); + ComponentDTO componentDTO = new ComponentDTO("component01"); + componentDTO.setStandardPart(false); + partUsageLinkDTO.setComponent(componentDTO); + List substituteDTOs = new ArrayList<>(); + PartSubstituteLinkDTO substituteLinkDTO = new PartSubstituteLinkDTO(); + substituteLinkDTO.setAmount(3); + substituteLinkDTO.setUnit("Kg"); + ComponentDTO subComponentDTO = new ComponentDTO("subComponent01"); + substituteLinkDTO.setSubstitute(subComponentDTO); + List cadInstanceDTOs = new ArrayList(); + List subCadInstanceDTOs = new ArrayList(); + cadInstanceDTOs.add(new CADInstanceDTO((Double) 12.0, (Double) 12.0, (Double) 12.0, (Double) 62.0, (Double) 24.0, (Double) 95.0)); + cadInstanceDTOs.add(new CADInstanceDTO((Double) 22.0, (Double) 12.0, (Double) 72.0, (Double) 52.0, (Double) 14.0, (Double) 45.0)); + subCadInstanceDTOs.add(new CADInstanceDTO((Double) 10.0, (Double) 11.0, (Double) 12.0, (Double) 13.0, (Double) 14.0, (Double) 15.0)); + subCadInstanceDTOs.add(new CADInstanceDTO((Double) 110.0, (Double) 10.0, (Double) 10.0, (Double) 52.0, (Double) 14.0, (Double) 45.0)); + subCadInstanceDTOs.add(new CADInstanceDTO((Double) 120.0, (Double) 10.0, (Double) 10.0, (Double) 52.0, (Double) 14.0, (Double) 45.0)); + + substituteLinkDTO.setCadInstances(subCadInstanceDTOs); + substituteDTOs.add(substituteLinkDTO); + partUsageLinkDTO.setSubstitutes(substituteDTOs); + partUsageLinkDTO.setCadInstances(cadInstanceDTOs); + partUsageLinkDTOs.add(partUsageLinkDTO); + data.setComponents(partUsageLinkDTOs); + List newComponents = new ArrayList<>(); + + //when + try { + Mockito.when(productService.partMasterExists(Matchers.any(PartMasterKey.class))).thenReturn(false); + Mockito.when(userManager.checkWorkspaceWriteAccess(ResourceUtil.WORKSPACE_ID)).thenReturn(user); + Mockito.when(partResource.findOrCreatePartMaster(ResourceUtil.WORKSPACE_ID, componentDTO)).thenReturn(partMaster); + Mockito.when(partResource.findOrCreatePartMaster(ResourceUtil.WORKSPACE_ID, subComponentDTO)).thenReturn(subPartMaster); + + newComponents = partResource.createComponents(ResourceUtil.WORKSPACE_ID, partUsageLinkDTOs); + + } catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + fail("Part Creation failed"); + } + + //Then + assertNotNull(newComponents); + assertTrue(newComponents.size() == 1); + assertTrue("description part usage link".equals(newComponents.get(0).getReferenceDescription())); + assertTrue("partNumber".equals(newComponents.get(0).getComponent().getNumber())); + assertTrue(newComponents.get(0).getAmount() == 2); + assertTrue(newComponents.get(0).getUnit().isEmpty()); + //check that the component is optional + assertTrue(newComponents.get(0).isOptional()); + //check the amount of CADInstances + assertTrue(newComponents.get(0).getCadInstances().size() == 2); + + // check if the cad instances mapping of the part usage link is correct + assertTrue(newComponents.get(0).getCadInstances().get(0).getRx() == (Double) 12.0); + assertTrue(newComponents.get(0).getCadInstances().get(0).getRy() == (Double) 12.0); + assertTrue(newComponents.get(0).getCadInstances().get(0).getRz() == (Double) 12.0); + assertTrue(newComponents.get(0).getCadInstances().get(0).getTx() == (Double) 62.0); + assertTrue(newComponents.get(0).getCadInstances().get(0).getTy() == (Double) 24.0); + assertTrue(newComponents.get(0).getCadInstances().get(0).getTz() == (Double) 95.0); + + assertTrue(newComponents.get(0).getCadInstances().get(1).getRx() == (Double) 22.0); + assertTrue(newComponents.get(0).getCadInstances().get(1).getRy() == (Double) 12.0); + assertTrue(newComponents.get(0).getCadInstances().get(1).getRz() == (Double) 72.0); + assertTrue(newComponents.get(0).getCadInstances().get(1).getTx() == (Double) 52.0); + assertTrue(newComponents.get(0).getCadInstances().get(1).getTy() == (Double) 14.0); + assertTrue(newComponents.get(0).getCadInstances().get(1).getTz() == (Double) 45.0); + // check if the cad instances mapping of the substitute part usage link is correct + assertTrue(newComponents.get(0).getSubstitutes().get(0).getCadInstances().size() == 3); + assertTrue(newComponents.get(0).getSubstitutes().get(0).getCadInstances().get(0).getRx() == 10); + assertTrue(newComponents.get(0).getSubstitutes().get(0).getCadInstances().get(0).getRy() == 11); + assertTrue(newComponents.get(0).getSubstitutes().get(0).getCadInstances().get(0).getRz() == 12); + assertTrue(newComponents.get(0).getSubstitutes().get(0).getCadInstances().get(0).getTx() == 13.0); + assertTrue(newComponents.get(0).getSubstitutes().get(0).getCadInstances().get(0).getTy() == 14.0); + assertTrue(newComponents.get(0).getSubstitutes().get(0).getCadInstances().get(0).getTz() == 15.0); + + + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/file/DocumentBinaryResourceTest.java b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/file/DocumentBinaryResourceTest.java new file mode 100644 index 0000000000..ae86e7f980 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/file/DocumentBinaryResourceTest.java @@ -0,0 +1,387 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.file; + + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.document.*; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.*; +import com.docdoku.core.sharing.SharedDocument; +import com.docdoku.core.util.Tools; +import com.docdoku.server.filters.GuestProxy; +import com.docdoku.server.util.PartImpl; +import com.docdoku.server.util.ResourceUtil; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.*; + +import javax.ejb.SessionContext; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.Part; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.MockitoAnnotations.initMocks; + +public class DocumentBinaryResourceTest { + + @InjectMocks + DocumentBinaryResource documentBinaryResource = new DocumentBinaryResource(); + + @Mock + private IDataManagerLocal dataManager; + @Mock + private IContextManagerLocal contextManager; + @Mock + private IDocumentManagerLocal documentService; + @Mock + private IDocumentResourceGetterManagerLocal documentResourceGetterService; + @Mock + private IDocumentPostUploaderManagerLocal documentPostUploaderService; + @Mock + private IShareManagerLocal shareService; + @Mock + private SessionContext ctx; + @Mock + private GuestProxy guestProxy; + @Spy + BinaryResource binaryResource; + + + @Before + public void setup() throws Exception { + initMocks(this); + } + + /** + * Test the upload of file to a document + * + * @throws Exception + */ + @Test + public void uploadDocumentFiles() throws Exception { + //Given + HttpServletRequestWrapper request = Mockito.mock(HttpServletRequestWrapper.class); + Collection filesParts = new ArrayList(); + + filesParts.add(new PartImpl(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.FILENAME1).getFile()))); + + BinaryResource binaryResource = new BinaryResource(ResourceUtil.FILENAME1, ResourceUtil.DOCUMENT_SIZE, new Date()); + + File uploadedFile1 = File.createTempFile(ResourceUtil.TARGET_FILE_STORAGE + "new_" + ResourceUtil.FILENAME1, ResourceUtil.TEMP_SUFFIX); + + OutputStream outputStream1 = new FileOutputStream(uploadedFile1); + + Mockito.when(request.getParts()).thenReturn(filesParts); + Mockito.when(documentService.saveFileInDocument(Matchers.any(DocumentIterationKey.class), Matchers.anyString(), Matchers.anyInt())).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceOutputStream(Matchers.any(BinaryResource.class))).thenReturn(outputStream1); + Mockito.when(request.getRequestURI()).thenReturn(ResourceUtil.WORKSPACE_ID + "/documents/" + ResourceUtil.DOCUMENT_ID + "/" + ResourceUtil.FILENAME1); + + //When + Response response = documentBinaryResource.uploadDocumentFiles(request, ResourceUtil.WORKSPACE_ID, ResourceUtil.DOCUMENT_ID, ResourceUtil.VERSION, ResourceUtil.ITERATION); + + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 201); + assertEquals(response.getStatusInfo(), Response.Status.CREATED); + + //delete tem file + uploadedFile1.deleteOnExit(); + } + + /** + * Test to upload a file to a document with special characters + * + * @throws Exception + */ + @Test + @Ignore + public void uploadFileWithSpecialCharactersToDocumentTemplates() throws Exception { + + //Given + HttpServletRequestWrapper request = Mockito.mock(HttpServletRequestWrapper.class); + Collection filesParts = new ArrayList(); + filesParts.add(new PartImpl(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE).getFile() + ResourceUtil.FILENAME2))); + + BinaryResource binaryResource = new BinaryResource(Tools.unAccent(ResourceUtil.FILENAME2), ResourceUtil.DOCUMENT_SIZE, new Date()); + + File uploadedFile1 = File.createTempFile(ResourceUtil.TARGET_FILE_STORAGE + "new_" + ResourceUtil.FILENAME2,ResourceUtil.TEMP_SUFFIX); + + + OutputStream outputStream1 = new FileOutputStream(uploadedFile1); + + Mockito.when(request.getParts()).thenReturn(filesParts); + Mockito.when(documentService.saveFileInDocument(Matchers.any(DocumentIterationKey.class), Matchers.anyString(), Matchers.anyInt())).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceOutputStream(Matchers.any(BinaryResource.class))).thenReturn(outputStream1); + Mockito.when(request.getRequestURI()).thenReturn(ResourceUtil.WORKSPACE_ID + "/documents/" + ResourceUtil.DOCUMENT_ID + "/" + ResourceUtil.FILENAME2); + + + //When + Response response = documentBinaryResource.uploadDocumentFiles(request, ResourceUtil.WORKSPACE_ID, ResourceUtil.DOCUMENT_ID, ResourceUtil.VERSION, ResourceUtil.ITERATION); + + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 201); + assertEquals(response.getStatusInfo(), Response.Status.CREATED); + + //delete tem file + uploadedFile1.deleteOnExit(); + } + + /** + * Test to upload several file to a document + * + * @throws Exception + */ + @Test + @Ignore + public void uploadSeveralFilesToDocumentsTemplates() throws Exception { + + //Given + HttpServletRequestWrapper request = Mockito.mock(HttpServletRequestWrapper.class); + Collection filesParts = new ArrayList(); + filesParts.add(new PartImpl(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.FILENAME1).getFile()))); + filesParts.add(new PartImpl(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE).getFile() + ResourceUtil.FILENAME2))); + filesParts.add(new PartImpl(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.FILENAME3).getFile()))); + + BinaryResource binaryResource1 = new BinaryResource(ResourceUtil.FILENAME1, ResourceUtil.DOCUMENT_SIZE, new Date()); + BinaryResource binaryResource2 = new BinaryResource(ResourceUtil.FILENAME2, ResourceUtil.DOCUMENT_SIZE, new Date()); + BinaryResource binaryResource3 = new BinaryResource(ResourceUtil.FILENAME3, ResourceUtil.DOCUMENT_SIZE, new Date()); + + File uploadedFile1 = File.createTempFile(ResourceUtil.TARGET_FILE_STORAGE + "new_" + ResourceUtil.FILENAME1, ResourceUtil.TEMP_SUFFIX); + File uploadedFile2 = File.createTempFile(ResourceUtil.TARGET_FILE_STORAGE + "new_" + ResourceUtil.FILENAME2, ResourceUtil.TEMP_SUFFIX); + File uploadedFile3 = File.createTempFile( ResourceUtil.TARGET_FILE_STORAGE + "new_" + ResourceUtil.FILENAME3,ResourceUtil.TEMP_SUFFIX); + + OutputStream outputStream1 = new FileOutputStream(uploadedFile1); + OutputStream outputStream2 = new FileOutputStream(uploadedFile2); + OutputStream outputStream3 = new FileOutputStream(uploadedFile3); + Mockito.when(request.getParts()).thenReturn(filesParts); + Mockito.when(documentService.saveFileInDocument(Matchers.any(DocumentIterationKey.class), Matchers.anyString(), Matchers.anyInt())).thenReturn(binaryResource1, binaryResource1, binaryResource2, binaryResource2, binaryResource3, binaryResource3); + Mockito.when(dataManager.getBinaryResourceOutputStream(Matchers.any(BinaryResource.class))).thenReturn(outputStream1, outputStream2, outputStream3); + //When + Response response = documentBinaryResource.uploadDocumentFiles(request, ResourceUtil.WORKSPACE_ID, ResourceUtil.DOCUMENT_ID, ResourceUtil.VERSION, ResourceUtil.ITERATION); + + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 200); + assertEquals(response.getStatusInfo(), Response.Status.OK); + + //delete temp files + uploadedFile1.deleteOnExit(); + uploadedFile2.deleteOnExit(); + uploadedFile3.deleteOnExit(); + + } + + /** + * Test to download a document file as a guest and the document is public + * + * @throws Exception + */ + @Test + public void downloadDocumentFileAsGuestDocumentIsPublic() throws Exception { + + //Given + Request request = Mockito.mock(Request.class); + + + String output = null; + String fullName= ResourceUtil.WORKSPACE_ID + "/documents/" + ResourceUtil.DOCUMENT_ID + "/" + ResourceUtil.VERSION + "/" + ResourceUtil.ITERATION + "/" + ResourceUtil.FILENAME1; + + BinaryResource binaryResource = new BinaryResource(ResourceUtil.FILENAME1, ResourceUtil.DOCUMENT_SIZE, new Date()); + Mockito.when(documentService.canAccess(new DocumentIterationKey(ResourceUtil.WORKSPACE_ID, ResourceUtil.DOCUMENT_ID, ResourceUtil.VERSION,ResourceUtil.ITERATION))).thenReturn(false); + Mockito.when(documentService.getBinaryResource(fullName)).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(new FileInputStream(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.FILENAME1).getFile()))); + Mockito.when(guestProxy.getPublicDocumentRevision(Matchers.any(DocumentRevisionKey.class))).thenReturn(new DocumentRevision()); + Mockito.when(guestProxy.getPublicBinaryResourceForDocument(fullName)).thenReturn(binaryResource); + Mockito.when(contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)).thenReturn(false); + Mockito.when(guestProxy.canAccess(Matchers.any(DocumentIterationKey.class))).thenReturn(true); + + //When + Response response = documentBinaryResource.downloadDocumentFile(request, ResourceUtil.RANGE,ResourceUtil.DOC_REFER, ResourceUtil.WORKSPACE_ID,ResourceUtil.DOCUMENT_ID, ResourceUtil.VERSION, ResourceUtil.ITERATION,ResourceUtil.FILENAME1,null,ResourceUtil.FILE_TYPE,output,null); + + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 206); + assertEquals(response.getStatusInfo(), Response.Status.PARTIAL_CONTENT); + } + + /** + * Test to download a document file as a guest but the document is private + * + * @throws Exception + */ + @Test + public void downloadDocumentFileAsGuestDocumentIsPrivate() throws Exception { + //Given + Request request = Mockito.mock(Request.class); + //Workspace workspace, User author, Date expireDate, String password, DocumentRevision documentRevision + Account account = Mockito.spy(new Account("user2" , "user2", "user2@docdoku.com", "en",new Date(),null)); + Workspace workspace = new Workspace(ResourceUtil.WORKSPACE_ID,account, "pDescription", false); + User user = new User(workspace, new Account("user1" , "user1", "user1@docdoku.com", "en", new Date(),null)); + DocumentMaster documentMaster = new DocumentMaster(workspace,ResourceUtil.DOCUMENT_ID,user); + DocumentRevision documentRevision = new DocumentRevision(documentMaster,ResourceUtil.VERSION,user); + List iterations = new ArrayList<>(); + DocumentIteration documentIteration = new DocumentIteration(documentRevision, user); + iterations.add(documentIteration); + documentRevision.setDocumentIterations(iterations); + SharedDocument sharedEntity = new SharedDocument(workspace,user,new Date(2025,12,02),"password",documentRevision); + String output = null; + String fullName= ResourceUtil.WORKSPACE_ID + "/documents/" + ResourceUtil.DOCUMENT_ID + "/" + ResourceUtil.VERSION + "/" + ResourceUtil.ITERATION + "/" + ResourceUtil.FILENAME1; + + BinaryResource binaryResource = new BinaryResource(ResourceUtil.FILENAME1, ResourceUtil.DOCUMENT_SIZE, new Date()); + Mockito.when(documentService.getBinaryResource(fullName)).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(new FileInputStream(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.FILENAME1).getFile()))); + Mockito.when(guestProxy.getPublicBinaryResourceForDocument(fullName)).thenReturn(binaryResource); + Mockito.when(contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)).thenReturn(false); + Mockito.when(shareService.findSharedEntityForGivenUUID(ResourceUtil.SHARED_DOC_ENTITY_UUID.split("/")[2])).thenReturn(sharedEntity); + //When + Response response = documentBinaryResource.downloadDocumentFile(request, ResourceUtil.RANGE,"refers/"+sharedEntity.getUuid(), ResourceUtil.WORKSPACE_ID,ResourceUtil.DOCUMENT_ID, ResourceUtil.VERSION, ResourceUtil.ITERATION,ResourceUtil.FILENAME1,null,ResourceUtil.FILE_TYPE,output,ResourceUtil.SHARED_DOC_ENTITY_UUID); + + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 206); + assertEquals(response.getStatusInfo(), Response.Status.PARTIAL_CONTENT); + + + } + + /** + * Test to download a document file as a regular user who has read access on it + * + * @throws Exception + */ + @Test + public void downloadDocumentFileAsRegularUserWithAccessRights() throws Exception { + //Given + Request request = Mockito.mock(Request.class); + //Workspace workspace, User author, Date expireDate, String password, DocumentRevision documentRevision + Account account = Mockito.spy(new Account("user2" , "user2", "user2@docdoku.com", "en",new Date(),null)); + Workspace workspace = new Workspace(ResourceUtil.WORKSPACE_ID,account, "pDescription", false); + User user = new User(workspace, new Account("user1" , "user1", "user1@docdoku.com", "en", new Date(),null)); + DocumentMaster documentMaster = new DocumentMaster(workspace,ResourceUtil.DOCUMENT_ID,user); + DocumentRevision documentRevision = new DocumentRevision(documentMaster,ResourceUtil.VERSION,user); + List iterations = new ArrayList<>(); + DocumentIteration documentIteration =new DocumentIteration(documentRevision, user); + iterations.add(documentIteration); + documentRevision.setDocumentIterations(iterations); + SharedDocument sharedEntity = new SharedDocument(workspace,user,new Date(2025,12,02),"password",documentRevision); + String output = null; + String fullName= ResourceUtil.WORKSPACE_ID + "/documents/" + ResourceUtil.DOCUMENT_ID + "/" + ResourceUtil.VERSION + "/" + ResourceUtil.ITERATION + "/" + ResourceUtil.FILENAME1; + BinaryResource binaryResource = new BinaryResource(ResourceUtil.FILENAME1, ResourceUtil.DOCUMENT_SIZE, new Date()); + + Mockito.when(documentService.getBinaryResource(fullName)).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(new FileInputStream(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.FILENAME1).getFile()))); + Mockito.when(guestProxy.getPublicBinaryResourceForDocument(fullName)).thenReturn(binaryResource); + Mockito.when(contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)).thenReturn(true); + + Mockito.when(shareService.findSharedEntityForGivenUUID(ResourceUtil.SHARED_DOC_ENTITY_UUID.split("/")[2])).thenReturn(sharedEntity); + //When + Response response = documentBinaryResource.downloadDocumentFile(request, ResourceUtil.RANGE,"refers/"+sharedEntity.getUuid(), ResourceUtil.WORKSPACE_ID,ResourceUtil.DOCUMENT_ID, ResourceUtil.VERSION, ResourceUtil.ITERATION,ResourceUtil.FILENAME1,null,ResourceUtil.FILE_TYPE,output,ResourceUtil.SHARED_DOC_ENTITY_UUID); + + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 206); + assertEquals(response.getStatusInfo(), Response.Status.PARTIAL_CONTENT); + + + } + + /** + * Test to download a document file as a regular user who has no read access on it + * + * @throws Exception + */ + @Test + public void downloadDocumentFileAsUserWithNoAccessRights() throws Exception { + + //Given + Request request = Mockito.mock(Request.class); + String output = null; + String fullName= ResourceUtil.WORKSPACE_ID + "/documents/" + ResourceUtil.DOCUMENT_ID + "/" + ResourceUtil.VERSION + "/" + ResourceUtil.ITERATION + "/" + ResourceUtil.FILENAME1; + + BinaryResource binaryResource = new BinaryResource(ResourceUtil.FILENAME1, ResourceUtil.DOCUMENT_SIZE, new Date()); + Mockito.when(documentService.canAccess(new DocumentIterationKey(ResourceUtil.WORKSPACE_ID, ResourceUtil.DOCUMENT_ID, ResourceUtil.VERSION,ResourceUtil.ITERATION))).thenReturn(false); + Mockito.when(documentService.getBinaryResource(fullName)).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(new FileInputStream(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.FILENAME1).getFile()))); + Mockito.when(guestProxy.getPublicBinaryResourceForDocument(fullName)).thenReturn(binaryResource); + Mockito.when(contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)).thenReturn(true); + Mockito.when(guestProxy.canAccess(Matchers.any(DocumentIterationKey.class))).thenReturn(false); + + //When + Response response= documentBinaryResource.downloadDocumentFile(request, ResourceUtil.RANGE,ResourceUtil.DOC_REFER, ResourceUtil.WORKSPACE_ID,ResourceUtil.DOCUMENT_ID, ResourceUtil.VERSION, ResourceUtil.ITERATION,ResourceUtil.FILENAME1,null,ResourceUtil.FILE_TYPE,output,null); + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 401); + assertEquals(response.getStatusInfo(), Response.Status.UNAUTHORIZED); + + + } + + /** + * Test to download a document SCORM sub-resource + * + * @throws Exception + */ + @Test + public void downloadDocumentScormSubResource() throws Exception { + + //Given + Request request = Mockito.mock(Request.class); + + + String output = "output"; + String fullName= ResourceUtil.WORKSPACE_ID + "/documents/" + ResourceUtil.DOCUMENT_ID + "/" + ResourceUtil.VERSION + "/" + ResourceUtil.ITERATION + "/" + ResourceUtil.FILENAME1; + + BinaryResource binaryResource = new BinaryResource(ResourceUtil.FILENAME1, ResourceUtil.DOCUMENT_SIZE, new Date()); + Mockito.when(documentService.canAccess(new DocumentIterationKey(ResourceUtil.WORKSPACE_ID, ResourceUtil.DOCUMENT_ID, ResourceUtil.VERSION,ResourceUtil.ITERATION))).thenReturn(false); + Mockito.when(documentService.getBinaryResource(fullName)).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(new FileInputStream(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.FILENAME1).getFile()))); + Mockito.when(guestProxy.getPublicBinaryResourceForDocument(fullName)).thenReturn(binaryResource); + Mockito.when(contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)).thenReturn(true); + Mockito.when(documentService.canAccess(Matchers.any(DocumentIterationKey.class))).thenReturn(true); + Mockito.when(documentResourceGetterService.getDocumentConvertedResource(output, binaryResource)).thenReturn(new FileInputStream(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.VIRTUAL_SUB_RESOURCE).getFile()))); + Mockito.when(dataManager.getBinarySubResourceInputStream(binaryResource, fullName+"/"+ResourceUtil.VIRTUAL_SUB_RESOURCE)).thenReturn(new FileInputStream(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.VIRTUAL_SUB_RESOURCE).getFile()))); + //When + Response response = documentBinaryResource.downloadDocumentFile(request, ResourceUtil.RANGE,ResourceUtil.DOC_REFER, ResourceUtil.WORKSPACE_ID,ResourceUtil.DOCUMENT_ID, ResourceUtil.VERSION, ResourceUtil.ITERATION,ResourceUtil.FILENAME1,ResourceUtil.VIRTUAL_SUB_RESOURCE,ResourceUtil.FILE_TYPE,null,null); + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 206); + assertEquals(response.getStatusInfo(), Response.Status.PARTIAL_CONTENT); + + + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/file/DocumentTemplateBinaryResourceTest.java b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/file/DocumentTemplateBinaryResourceTest.java new file mode 100644 index 0000000000..33c52727bf --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/file/DocumentTemplateBinaryResourceTest.java @@ -0,0 +1,114 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.file; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.document.DocumentMasterTemplateKey; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.services.IDocumentManagerLocal; +import com.docdoku.core.services.IDocumentResourceGetterManagerLocal; +import com.docdoku.server.util.PartImpl; +import com.docdoku.server.util.ResourceUtil; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.Part; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +import static org.mockito.MockitoAnnotations.initMocks; + +public class DocumentTemplateBinaryResourceTest { + @InjectMocks + DocumentTemplateBinaryResource documentTemplateBinaryResource = new DocumentTemplateBinaryResource(); + @Mock + private IDataManagerLocal dataManager; + @Mock + private IDocumentManagerLocal documentService; + @Mock + private IDocumentResourceGetterManagerLocal documentResourceGetterService; + @Spy + BinaryResource binaryResource; + @Before + public void setup() throws Exception { + initMocks(this); + } + + @Test + public void downloadDocumentTemplateFile() throws Exception { + Request request = Mockito.mock(Request.class); + + binaryResource = Mockito.spy(new BinaryResource(ResourceUtil.FILENAME1,ResourceUtil.DOCUMENT_SIZE,new Date())); + + + String fullName = ResourceUtil.WORKSPACE_ID + "/document-templates/" + ResourceUtil.DOC_TEMPLATE_ID + "/" + ResourceUtil.FILENAME1; + Mockito.when(documentService.getTemplateBinaryResource(fullName)).thenReturn(binaryResource); + File input = new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE+ResourceUtil.FILENAME1).getFile()); + FileInputStream fileInputStream = new FileInputStream(input); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(fileInputStream); + //When + Response response =documentTemplateBinaryResource.downloadDocumentTemplateFile(request,ResourceUtil.RANGE, ResourceUtil.WORKSPACE_ID, ResourceUtil.DOC_TEMPLATE_ID,ResourceUtil.FILENAME1,ResourceUtil.FILE_TYPE,null); + + //Then + Assert.assertNotNull(response); + Assert.assertEquals(response.getStatus(),206); + Assert.assertEquals(response.getStatusInfo(), Response.Status.PARTIAL_CONTENT); + Assert.assertNotNull(response.getEntity()); + + } + + @Test + public void downloadDocumentTemplateFilementTemplateFiles() throws Exception { + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + binaryResource = Mockito.spy(new BinaryResource(ResourceUtil.FILENAME1,ResourceUtil.DOCUMENT_SIZE,new Date())); + Collection filesParts = new ArrayList(); + filesParts.add(new PartImpl(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.FILENAME1).getFile()))); + File uploadedFile = File.createTempFile(ResourceUtil.TARGET_FILE_STORAGE + "new_" + ResourceUtil.FILENAME1,ResourceUtil.TEMP_SUFFIX); + + OutputStream outputStream = new FileOutputStream(uploadedFile); + Mockito.when(documentService.saveFileInTemplate(Matchers.any(DocumentMasterTemplateKey.class),Matchers.anyString(), Matchers.anyInt())).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceOutputStream(binaryResource)).thenReturn(outputStream); + Mockito.when(request.getRequestURI()).thenReturn(ResourceUtil.WORKSPACE_ID + "/documents/" + ResourceUtil.DOCUMENT_ID + "/" + ResourceUtil.FILENAME1); + + //When + Response response =documentTemplateBinaryResource.uploadDocumentTemplateFiles(request, ResourceUtil.WORKSPACE_ID, ResourceUtil.DOC_TEMPLATE_ID); + + //Then + Assert.assertNotNull(response); + Assert.assertEquals(response.getStatus(),200); + Assert.assertEquals(response.getStatusInfo(), Response.Status.OK); + + //delete temporary file + uploadedFile.deleteOnExit(); + + + } +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/file/PartBinaryResourceTest.java b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/file/PartBinaryResourceTest.java new file mode 100644 index 0000000000..cad0c68593 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/file/PartBinaryResourceTest.java @@ -0,0 +1,357 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.file; + +import com.docdoku.core.common.Account; +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.common.User; +import com.docdoku.core.common.Workspace; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.product.PartIterationKey; +import com.docdoku.core.product.PartMaster; +import com.docdoku.core.product.PartRevision; +import com.docdoku.core.security.UserGroupMapping; +import com.docdoku.core.services.*; +import com.docdoku.core.sharing.SharedPart; +import com.docdoku.server.filters.GuestProxy; +import com.docdoku.server.rest.file.util.BinaryResourceBinaryStreamingOutput; +import com.docdoku.server.util.PartImpl; +import com.docdoku.server.util.ResourceUtil; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; + +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.Part; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import static org.junit.Assert.*; +import static org.mockito.MockitoAnnotations.initMocks; + +public class PartBinaryResourceTest { + + @InjectMocks + private PartBinaryResource partBinaryResource; + @Mock + private IDataManagerLocal dataManager; + @Mock + private IContextManagerLocal contextManager; + @Mock + private IProductManagerLocal productService; + @Mock + private IConverterManagerLocal converterService; + @Mock + private IShareManagerLocal shareService; + @Mock + private GuestProxy guestProxy; + + @Before + public void setup() throws Exception { + initMocks(this); + } + + /** + * Test to upload a file to a part + * + * @throws Exception + */ + @Test + public void uploadFileToPart() throws Exception { + //Given + final File fileToUpload = new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE + ResourceUtil.TEST_PART_FILENAME1).getFile()); + File uploadedFile = File.createTempFile(ResourceUtil.TARGET_PART_STORAGE + ResourceUtil.FILENAME_TARGET_PART,ResourceUtil.TEMP_SUFFIX); + HttpServletRequestWrapper request = Mockito.mock(HttpServletRequestWrapper.class); + Collection parts = new ArrayList(); + parts.add(new PartImpl(fileToUpload)); + Mockito.when(request.getParts()).thenReturn(parts); + BinaryResource binaryResource = new BinaryResource(ResourceUtil.FILENAME1, ResourceUtil.DOCUMENT_SIZE, new Date()); + OutputStream outputStream = new FileOutputStream(uploadedFile); + Mockito.when(request.getRequestURI()).thenReturn(ResourceUtil.WORKSPACE_ID + "/parts/" + ResourceUtil.PART_TEMPLATE_ID + "/"); + Mockito.when(productService.saveFileInPartIteration(Matchers.any(PartIterationKey.class), Matchers.anyString(), Matchers.anyString(), Matchers.anyInt())).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceOutputStream(binaryResource)).thenReturn(outputStream); + + //When + Response response = partBinaryResource.uploadAttachedFiles(request, ResourceUtil.WORKSPACE_ID, ResourceUtil.PART_NUMBER, ResourceUtil.VERSION, ResourceUtil.ITERATION); + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 201); + assertEquals(response.getStatusInfo(), Response.Status.CREATED); + //delete temp file + uploadedFile.deleteOnExit(); + + } + + /** + * Test to upload a native cad to a part + */ + @Test + public void uploadNativeCADToPart() throws Exception { + + //Given + final File fileToUpload = new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE + ResourceUtil.TEST_PART_FILENAME1).getFile()); + File uploadedFile = File.createTempFile(ResourceUtil.TARGET_PART_STORAGE + ResourceUtil.FILENAME_TARGET_PART,ResourceUtil.TEMP_SUFFIX); + HttpServletRequestWrapper request = Mockito.mock(HttpServletRequestWrapper.class); + Collection parts = new ArrayList(); + parts.add(new PartImpl(fileToUpload)); + Mockito.when(request.getParts()).thenReturn(parts); + BinaryResource binaryResource = new BinaryResource(ResourceUtil.FILENAME1, ResourceUtil.DOCUMENT_SIZE, new Date()); + OutputStream outputStream = new FileOutputStream(uploadedFile); + Mockito.when(request.getRequestURI()).thenReturn(ResourceUtil.WORKSPACE_ID + "/parts/" + ResourceUtil.PART_TEMPLATE_ID + "/"); + Mockito.when(productService.saveNativeCADInPartIteration(Matchers.any(PartIterationKey.class), Matchers.anyString(), Matchers.anyInt())).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceOutputStream(binaryResource)).thenReturn(outputStream); + + //When + Response response = partBinaryResource.uploadNativeCADFile(request, ResourceUtil.WORKSPACE_ID, ResourceUtil.PART_NUMBER, ResourceUtil.VERSION, ResourceUtil.ITERATION); + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 201); + assertEquals(response.getStatusInfo(), Response.Status.CREATED); + + //delete temp file + uploadedFile.deleteOnExit(); + + } + + /** + * Test to upload a file to a part with special characters + * + * @throws Exception + */ + @Test + @Ignore + public void uploadFileWithSpecialCharactersToPart() throws Exception { + //Given + File fileToUpload = new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE + ResourceUtil.FILENAME_TO_UPLOAD_PART_SPECIAL_CHARACTER).getFile()); + File uploadedFile = File.createTempFile(ResourceUtil.TARGET_PART_STORAGE + ResourceUtil.FILENAME_TO_UPLOAD_PART_SPECIAL_CHARACTER,ResourceUtil.TEMP_SUFFIX); + HttpServletRequestWrapper request = Mockito.mock(HttpServletRequestWrapper.class); + Collection parts = new ArrayList(); + parts.add(new PartImpl(fileToUpload)); + Mockito.when(request.getParts()).thenReturn(parts); + BinaryResource binaryResource = new BinaryResource(ResourceUtil.FILENAME_TO_UPLOAD_PART_SPECIAL_CHARACTER, ResourceUtil.DOCUMENT_SIZE, new Date()); + + OutputStream outputStream = new FileOutputStream(uploadedFile); + Mockito.when(request.getRequestURI()).thenReturn(ResourceUtil.WORKSPACE_ID + "/parts/" + ResourceUtil.PART_TEMPLATE_ID + "/"); + Mockito.when(productService.saveFileInPartIteration(Matchers.any(PartIterationKey.class), Matchers.anyString(), Matchers.anyString(), Matchers.anyInt())).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceOutputStream(binaryResource)).thenReturn(outputStream); + + //When + Response response = partBinaryResource.uploadAttachedFiles(request, ResourceUtil.WORKSPACE_ID, ResourceUtil.PART_NUMBER, ResourceUtil.VERSION, ResourceUtil.ITERATION); + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 201); + assertEquals(response.getStatusInfo(), Response.Status.CREATED); + assertEquals(response.getLocation().toString(), (ResourceUtil.WORKSPACE_ID + "/parts/" + ResourceUtil.PART_TEMPLATE_ID +"/"+ URLEncoder.encode(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE).getFile() + ResourceUtil.FILENAME_TO_UPLOAD_PART_SPECIAL_CHARACTER, "UTF-8"))); + //delete temp file + uploadedFile.deleteOnExit(); + + } + + /** + * Test to upload several file to a part + * + * @throws Exception + */ + + @Test + @Ignore + public void uploadSeveralFilesToPart() throws Exception { + //Given + File fileToUpload1 = new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE + ResourceUtil.TEST_PART_FILENAME1).getFile()); + File fileToUpload2 = new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE + ResourceUtil.TEST_PART_FILENAME2).getFile()); + File fileToUpload3 = new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE + ResourceUtil.FILENAME_TO_UPLOAD_PART_SPECIAL_CHARACTER).getFile()); + File uploadedFile1 = File.createTempFile(ResourceUtil.TARGET_PART_STORAGE + ResourceUtil.TEST_PART_FILENAME1,ResourceUtil.TEMP_SUFFIX); + File uploadedFile2 = File.createTempFile(ResourceUtil.TARGET_PART_STORAGE + ResourceUtil.TEST_PART_FILENAME2,ResourceUtil.TEMP_SUFFIX); + File uploadedFile3 = File.createTempFile(ResourceUtil.TARGET_PART_STORAGE + ResourceUtil.FILENAME_TO_UPLOAD_PART_SPECIAL_CHARACTER,ResourceUtil.TEMP_SUFFIX); + HttpServletRequestWrapper request = Mockito.mock(HttpServletRequestWrapper.class); + Collection parts = new ArrayList(); + parts.add(new PartImpl(fileToUpload1)); + parts.add(new PartImpl(fileToUpload2)); + parts.add(new PartImpl(fileToUpload3)); + Mockito.when(request.getParts()).thenReturn(parts); + BinaryResource binaryResource1 = new BinaryResource(ResourceUtil.TEST_PART_FILENAME1, ResourceUtil.DOCUMENT_SIZE, new Date()); + BinaryResource binaryResource2 = new BinaryResource(ResourceUtil.TEST_PART_FILENAME2, ResourceUtil.DOCUMENT_SIZE, new Date()); + BinaryResource binaryResource3 = new BinaryResource(ResourceUtil.FILENAME_TO_UPLOAD_PART_SPECIAL_CHARACTER, ResourceUtil.DOCUMENT_SIZE, new Date()); + + OutputStream outputStream1 = new FileOutputStream(uploadedFile1); + OutputStream outputStream2 = new FileOutputStream(uploadedFile2); + OutputStream outputStream3 = new FileOutputStream(uploadedFile3); + Mockito.when(productService.saveFileInPartIteration(Matchers.any(PartIterationKey.class), Matchers.anyString(), Matchers.anyString(), Matchers.anyInt())).thenReturn(binaryResource1, binaryResource1, binaryResource2, binaryResource2, binaryResource3, binaryResource3); + Mockito.when(dataManager.getBinaryResourceOutputStream(Mockito.any(BinaryResource.class))).thenReturn(outputStream1, outputStream2, outputStream3); + + //When + Response response = partBinaryResource.uploadAttachedFiles(request, ResourceUtil.WORKSPACE_ID, ResourceUtil.PART_NUMBER, ResourceUtil.VERSION, ResourceUtil.ITERATION); + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 200); + assertEquals(response.getStatusInfo(), Response.Status.OK); + + //delete temp files + uploadedFile1.deleteOnExit(); + uploadedFile2.deleteOnExit(); + uploadedFile3.deleteOnExit(); + + + } + + + /** + * Test to download a part file as a guest and the part is public + * + * @throws Exception + */ + @Test + public void downloadPartFileAsGuestPartPublic() throws Exception { + + //Given + Request request = Mockito.mock(Request.class); + BinaryResource binaryResource = Mockito.spy(new BinaryResource(ResourceUtil.FILENAME1, ResourceUtil.DOCUMENT_SIZE, new Date())); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(new FileInputStream(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.FILENAME1).getFile()))); + Mockito.when(contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)).thenReturn(false); + Mockito.when(guestProxy.canAccess(Mockito.any(PartIterationKey.class))).thenReturn(true); + Mockito.when(productService.canAccess(Matchers.any(PartIterationKey.class))).thenReturn(false); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(new FileInputStream(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE + ResourceUtil.TEST_PART_FILENAME1).getFile()))); + //When + Mockito.when(guestProxy.getPublicBinaryResourceForPart(Matchers.anyString())).thenReturn(binaryResource); + Response response = partBinaryResource.downloadPartFile(request, ResourceUtil.RANGE, ResourceUtil.DOC_REFER, ResourceUtil.WORKSPACE_ID, ResourceUtil.PART_NUMBER, ResourceUtil.VERSION, ResourceUtil.ITERATION, ResourceUtil.FILE_TYPE, ResourceUtil.TEST_PART_FILENAME1, ResourceUtil.FILE_TYPE, null, null); + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 206); + assertNotNull(response.getEntity()); + assertTrue(response.getEntity() instanceof BinaryResourceBinaryStreamingOutput); + } + + /** + * Test to download a part file as a guest, the part is shared private mode + * + * @throws Exception + */ + @Test + public void downloadPartFilePrivateShare() throws Exception { + + //Given + Request request = Mockito.mock(Request.class); + Account account = Mockito.spy(new Account("user2", "user2", "user2@docdoku.com", "en", new Date(), null)); + Workspace workspace = new Workspace(ResourceUtil.WORKSPACE_ID,account, "pDescription", false); + User user = new User(workspace, new Account("user1" , "user1", "user1@docdoku.com", "en", new Date(), null)); + PartMaster partMaster = new PartMaster(workspace,ResourceUtil.PART_NUMBER,user); + PartRevision partRevision = new PartRevision(partMaster,ResourceUtil.VERSION,user); + List iterations = new ArrayList<>(); + PartIteration partIteration = new PartIteration(partRevision,ResourceUtil.ITERATION,user); + PartIteration partIteration2 = new PartIteration(partRevision,2,user); + iterations.add(partIteration); + partRevision.setPartIterations(iterations); + + SharedPart sharedPart = Mockito.spy(new SharedPart(workspace,user,new Date(2020,12,23),"password",partRevision)); + + BinaryResource binaryResource = Mockito.spy(new BinaryResource(ResourceUtil.FILENAME1, ResourceUtil.DOCUMENT_SIZE, new Date())); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(new FileInputStream(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.FILENAME1).getFile()))); + Mockito.when(contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)).thenReturn(false); + Mockito.when(guestProxy.canAccess(Mockito.any(PartIterationKey.class))).thenReturn(true); + Mockito.when(productService.canAccess(Matchers.any(PartIterationKey.class))).thenReturn(false); + File file = File.createTempFile(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE + ResourceUtil.TEST_PART_FILENAME1).getFile(), ResourceUtil.TEMP_SUFFIX); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(new FileInputStream(file)); + Mockito.when(guestProxy.getBinaryResourceForSharedPart(Matchers.anyString())).thenReturn(binaryResource); + Mockito.when(shareService.findSharedEntityForGivenUUID(ResourceUtil.SHARED_PART_ENTITY_UUID)).thenReturn(sharedPart); + //When + Response response = partBinaryResource.downloadPartFile(request, ResourceUtil.RANGE, "shares/"+sharedPart.getUuid(), ResourceUtil.WORKSPACE_ID, ResourceUtil.PART_NUMBER, ResourceUtil.VERSION, ResourceUtil.ITERATION, ResourceUtil.FILE_TYPE, ResourceUtil.TEST_PART_FILENAME1, ResourceUtil.FILE_TYPE, null, ResourceUtil.SHARED_PART_ENTITY_UUID); + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 206); + assertNotNull(response.getEntity()); + assertTrue(response.getEntity() instanceof BinaryResourceBinaryStreamingOutput); + + //Delete temp file + file.deleteOnExit(); + + } + + /** + * Test to download a part file as a regular user who has read access + * + * @throws Exception + */ + @Test + public void downloadPartFileAsRegularUserReadAccess() throws Exception { + //Given + Request request = Mockito.mock(Request.class); + BinaryResource binaryResource = Mockito.spy(new BinaryResource(ResourceUtil.FILENAME1, ResourceUtil.DOCUMENT_SIZE, new Date())); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(new FileInputStream(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.FILENAME1).getFile()))); + Mockito.when(contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)).thenReturn(true); + Mockito.when(productService.getBinaryResource(Matchers.anyString())).thenReturn(binaryResource); + Mockito.when(productService.canAccess(Matchers.any(PartIterationKey.class))).thenReturn(true); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(new FileInputStream(new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE + ResourceUtil.TEST_PART_FILENAME1).getFile()))); + Mockito.when(guestProxy.getPublicBinaryResourceForPart(Matchers.anyString())).thenReturn(binaryResource); + //When + Response response = partBinaryResource.downloadPartFile(request, ResourceUtil.RANGE, ResourceUtil.DOC_REFER, ResourceUtil.WORKSPACE_ID, ResourceUtil.PART_NUMBER, ResourceUtil.VERSION, ResourceUtil.ITERATION, ResourceUtil.FILE_TYPE, ResourceUtil.TEST_PART_FILENAME1, ResourceUtil.FILE_TYPE, null, null); + //Then + assertNotNull(response); + assertEquals(response.getStatus(), 206); + assertNotNull(response.getEntity()); + assertTrue(response.getEntity() instanceof BinaryResourceBinaryStreamingOutput); + + } + + /** + * Test to download a part file as a regular user who has no access + * + * @throws Exception + */ + @Test + public void downloadPartFileAsRegularUserNoReadAccess() throws Exception { + + //Given + Request request = Mockito.mock(Request.class); + BinaryResource binaryResource = Mockito.spy(new BinaryResource(ResourceUtil.FILENAME1, ResourceUtil.DOCUMENT_SIZE, new Date())); + File file = File.createTempFile(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_FILE_STORAGE + ResourceUtil.FILENAME1).getFile(),ResourceUtil.TEMP_SUFFIX); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(new FileInputStream(file)); + Mockito.when(contextManager.isCallerInRole(UserGroupMapping.REGULAR_USER_ROLE_ID)).thenReturn(true); + Mockito.when(productService.getBinaryResource(Matchers.anyString())).thenReturn(binaryResource); + Mockito.when(productService.canAccess(Matchers.any(PartIterationKey.class))).thenReturn(false); + File file1 = File.createTempFile(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE + ResourceUtil.TEST_PART_FILENAME1).getFile(),ResourceUtil.TEMP_SUFFIX); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(new FileInputStream(file1)); + Mockito.when(guestProxy.getPublicBinaryResourceForPart(Matchers.anyString())).thenReturn(binaryResource); + //When + Response response= partBinaryResource.downloadPartFile(request, ResourceUtil.RANGE, ResourceUtil.DOC_REFER, ResourceUtil.WORKSPACE_ID, ResourceUtil.PART_NUMBER, ResourceUtil.VERSION, ResourceUtil.ITERATION, ResourceUtil.FILE_TYPE, ResourceUtil.TEST_PART_FILENAME1, ResourceUtil.FILE_TYPE, null, null); + + assertNotNull(response); + assertEquals(response.getStatus(), 401); + assertEquals(response.getStatusInfo(), Response.Status.UNAUTHORIZED); + //delete tem files + file.deleteOnExit(); + file1.deleteOnExit(); + + } + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/file/PartTemplateBinaryResourceTest.java b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/file/PartTemplateBinaryResourceTest.java new file mode 100644 index 0000000000..95c4dcae4e --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/rest/file/PartTemplateBinaryResourceTest.java @@ -0,0 +1,266 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.rest.file; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.product.PartMasterTemplateKey; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.services.IProductManagerLocal; +import com.docdoku.server.rest.file.util.BinaryResourceBinaryStreamingOutput; +import com.docdoku.server.util.PartImpl; +import com.docdoku.server.util.ResourceUtil; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.*; + +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.Part; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +import static org.mockito.MockitoAnnotations.initMocks; + +public class PartTemplateBinaryResourceTest { + + @InjectMocks + PartTemplateBinaryResource partTemplateBinaryResource = new PartTemplateBinaryResource(); + @Mock + private IDataManagerLocal dataManager; + @Mock + private IProductManagerLocal productService; + @Spy + BinaryResource binaryResource; + + @Before + public void setup() throws Exception { + initMocks(this); + } + + /** + * test the upload of a simple file in parts templates + * @throws Exception + */ + @Test + public void uploadPartTemplateFiles() throws Exception { + //Given + final File fileToUpload = new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE+ResourceUtil.TEST_PART_FILENAME1).getFile()); + File uploadedFile = File.createTempFile(ResourceUtil.TARGET_PART_STORAGE+ResourceUtil.FILENAME_TARGET_PART,ResourceUtil.TEMP_SUFFIX); + HttpServletRequestWrapper request = Mockito.mock(HttpServletRequestWrapper.class); + Collection parts = new ArrayList(); + parts.add(new PartImpl(fileToUpload)); + Mockito.when(request.getParts()).thenReturn(parts); + binaryResource = new BinaryResource(ResourceUtil.FILENAME1,ResourceUtil.DOCUMENT_SIZE,new Date()); + + OutputStream outputStream= new FileOutputStream(uploadedFile); + Mockito.when(productService.saveFileInTemplate(Matchers.any(PartMasterTemplateKey.class),Matchers.anyString(), Matchers.anyInt())).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceOutputStream(binaryResource)).thenReturn(outputStream); + Mockito.when(request.getRequestURI()).thenReturn(ResourceUtil.WORKSPACE_ID+"/parts-templates/"+ResourceUtil.PART_TEMPLATE_ID+"/"+ResourceUtil.FILENAME_TARGET_PART); + //When + Response response = partTemplateBinaryResource.uploadPartTemplateFiles(request, ResourceUtil.WORKSPACE_ID, ResourceUtil.PART_TEMPLATE_ID); + //Then + Assert.assertNotNull(response); + Assert.assertEquals(response.getStatus(), 201); + Assert.assertEquals(response.getStatusInfo(), Response.Status.CREATED); + //delete temp file + uploadedFile.deleteOnExit(); + + + } + + /** + * test the upload of a file (under name that contains special characters) in parts templates + * @throws Exception + */ + @Test + public void uploadPartTemplateFilesUnderANameContainingSpecialCharacters() throws Exception { + //Given + + final File fileToUpload = new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE+ResourceUtil.TEST_PART_FILENAME1).getFile()); + File uploadedFile = File.createTempFile(ResourceUtil.TARGET_PART_STORAGE+ResourceUtil.FILENAME_TO_UPLOAD_PART_SPECIAL_CHARACTER,ResourceUtil.TEMP_SUFFIX); + HttpServletRequestWrapper request = Mockito.mock(HttpServletRequestWrapper.class); + Collection parts = new ArrayList(); + parts.add(new PartImpl(fileToUpload) ); + Mockito.when(request.getParts()).thenReturn(parts); + binaryResource = new BinaryResource(ResourceUtil.FILENAME1,ResourceUtil.DOCUMENT_SIZE,new Date()); + + OutputStream outputStream= new FileOutputStream(uploadedFile); + Mockito.when(productService.saveFileInTemplate(Matchers.any(PartMasterTemplateKey.class),Matchers.anyString(), Matchers.anyInt())).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceOutputStream(binaryResource)).thenReturn(outputStream); + Mockito.when(request.getRequestURI()).thenReturn(ResourceUtil.WORKSPACE_ID+"/parts-templates/"+ResourceUtil.PART_TEMPLATE_ID+"/"+ResourceUtil.FILENAME_TARGET_PART); + //When + Response response = partTemplateBinaryResource.uploadPartTemplateFiles(request, ResourceUtil.WORKSPACE_ID, ResourceUtil.PART_TEMPLATE_ID); + //Then + Assert.assertNotNull(response); + Assert.assertEquals(response.getStatus(), 201); + Assert.assertEquals(response.getStatusInfo(), Response.Status.CREATED); + + //delete temp file + uploadedFile.deleteOnExit(); + } + /** + * test the upload of a file (that contains special characters) in parts templates + * @throws Exception + */ + @Test + @Ignore + public void uploadPartTemplateFilesNameContainingSpecialCharacters() throws Exception { + //Given + + final File fileToUpload = new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE + ResourceUtil.FILENAME_TO_UPLOAD_PART_SPECIAL_CHARACTER).toURI()); + File uploadedFile =File.createTempFile(ResourceUtil.TARGET_PART_STORAGE+"new_"+ResourceUtil.FILENAME_TO_UPLOAD_PART_SPECIAL_CHARACTER,ResourceUtil.TEMP_SUFFIX); + HttpServletRequestWrapper request = Mockito.mock(HttpServletRequestWrapper.class); + Collection parts = new ArrayList(); + parts.add(new PartImpl(fileToUpload) ); + Mockito.when(request.getParts()).thenReturn(parts); + binaryResource = new BinaryResource(ResourceUtil.FILENAME1,ResourceUtil.DOCUMENT_SIZE,new Date()); + + OutputStream outputStream= new FileOutputStream(uploadedFile); + Mockito.when(productService.saveFileInTemplate(Matchers.any(PartMasterTemplateKey.class),Matchers.anyString(), Matchers.anyInt())).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceOutputStream(binaryResource)).thenReturn(outputStream); + Mockito.when(request.getRequestURI()).thenReturn(ResourceUtil.WORKSPACE_ID+"/parts-templates/"+ResourceUtil.PART_TEMPLATE_ID+"/"+ResourceUtil.FILENAME_TARGET_PART); + + + //When + Response response = partTemplateBinaryResource.uploadPartTemplateFiles(request, ResourceUtil.WORKSPACE_ID, ResourceUtil.PART_TEMPLATE_ID); + //Then + Assert.assertNotNull(response); + Assert.assertEquals(response.getStatus(), 201); + Assert.assertEquals(response.getStatusInfo(), Response.Status.CREATED); + + //delete temp file + uploadedFile.deleteOnExit(); + } + + + /** + * test the upload of several files to parts templates + * @throws Exception + */ + @Test + @Ignore + public void uploadPartTemplateSeveralFiles() throws Exception { + //Given + final File fileToUpload1 = File.createTempFile(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE+ResourceUtil.FILENAME_TO_UPLOAD_PART_SPECIAL_CHARACTER).getFile(),ResourceUtil.TEMP_SUFFIX); + final File fileToUpload2 = new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE+ResourceUtil.TEST_PART_FILENAME1).getFile()); + final File fileToUpload3 = new File(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE+ResourceUtil.TEST_PART_FILENAME2).getFile()); + File uploadedFile1 = File.createTempFile(ResourceUtil.TARGET_PART_STORAGE+"new_"+ResourceUtil.TEST_PART_FILENAME1,ResourceUtil.TEMP_SUFFIX); + File uploadedFile2 = File.createTempFile(ResourceUtil.TARGET_PART_STORAGE+"new_"+ResourceUtil.TEST_PART_FILENAME2,ResourceUtil.TEMP_SUFFIX); + File uploadedFile3 = File.createTempFile(ResourceUtil.TARGET_PART_STORAGE+"new_"+ResourceUtil.FILENAME_TO_UPLOAD_PART_SPECIAL_CHARACTER,ResourceUtil.TEMP_SUFFIX); + HttpServletRequestWrapper request = Mockito.mock(HttpServletRequestWrapper.class); + Collection parts = new ArrayList(); + parts.add(new PartImpl(fileToUpload1) ); + parts.add(new PartImpl(fileToUpload2) ); + parts.add(new PartImpl(fileToUpload3) ); + Mockito.when(request.getParts()).thenReturn(parts); + BinaryResource binaryResource1 = new BinaryResource(ResourceUtil.TEST_PART_FILENAME1,ResourceUtil.DOCUMENT_SIZE,new Date()); + BinaryResource binaryResource2 = new BinaryResource(ResourceUtil.TEST_PART_FILENAME2,ResourceUtil.DOCUMENT_SIZE,new Date()); + BinaryResource binaryResource3 = new BinaryResource(ResourceUtil.FILENAME_TO_UPLOAD_PART_SPECIAL_CHARACTER,ResourceUtil.DOCUMENT_SIZE,new Date()); + + OutputStream outputStream1= new FileOutputStream(uploadedFile1); + OutputStream outputStream2= new FileOutputStream(uploadedFile2); + OutputStream outputStream3= new FileOutputStream(uploadedFile3); + Mockito.when(productService.saveFileInTemplate(Matchers.any(PartMasterTemplateKey.class),Matchers.anyString(), Matchers.anyInt())).thenReturn(binaryResource1,binaryResource1,binaryResource2,binaryResource2,binaryResource3,binaryResource3); + + Mockito.when(dataManager.getBinaryResourceOutputStream(Matchers.any(BinaryResource.class))).thenReturn(outputStream1,outputStream2,outputStream3); + Mockito.when(request.getRequestURI()).thenReturn(ResourceUtil.WORKSPACE_ID+"/parts-templates/"+ResourceUtil.PART_TEMPLATE_ID+"/"+ResourceUtil.FILENAME_TARGET_PART); + + //When + Response response = partTemplateBinaryResource.uploadPartTemplateFiles(request, ResourceUtil.WORKSPACE_ID, ResourceUtil.PART_TEMPLATE_ID); + //Then + Assert.assertNotNull(response); + Assert.assertEquals(response.getStatus(), 200); + Assert.assertEquals(response.getStatusInfo(), Response.Status.OK); + + //delete temp files + uploadedFile1.deleteOnExit(); + uploadedFile2.deleteOnExit(); + uploadedFile3.deleteOnExit(); + + } + + /** + * test the download of file from part templates with non null range + * @throws Exception + */ + @Test + public void downloadPartTemplateFileNonNullRange() throws Exception { + + //Given + Request request = Mockito.mock(Request.class); + binaryResource = new BinaryResource(ResourceUtil.TEST_PART_FILENAME1,ResourceUtil.PART_SIZE,new Date()); + File file = File.createTempFile(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE+ResourceUtil.TEST_PART_FILENAME1).getFile(),ResourceUtil.TEMP_SUFFIX); + FileInputStream fileInputStream = new FileInputStream(file); + Mockito.when(productService.getTemplateBinaryResource(ResourceUtil.WORKSPACE_ID+"/part-templates/" + ResourceUtil.PART_TEMPLATE_ID + "/" + ResourceUtil.TEST_PART_FILENAME1)).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(fileInputStream); + //When + Response response = partTemplateBinaryResource.downloadPartTemplateFile(request,ResourceUtil.RANGE, ResourceUtil.WORKSPACE_ID, ResourceUtil.PART_TEMPLATE_ID,ResourceUtil.TEST_PART_FILENAME1); + //Then + Assert.assertNotNull(response); + Assert.assertEquals(response.getStatusInfo(), Response.Status.PARTIAL_CONTENT); + Assert.assertEquals(response.getStatus(),206); + Assert.assertTrue(response.hasEntity()); + Assert.assertTrue(response.getEntity() instanceof BinaryResourceBinaryStreamingOutput); + + //delete temp file + file.deleteOnExit(); + + } + + /** + * test the download of file from part templates with null range + * @throws Exception + */ + @Test + public void downloadPartTemplateFileNullRange() throws Exception { + + //Given + Request request = Mockito.mock(Request.class); + binaryResource = new BinaryResource(ResourceUtil.TEST_PART_FILENAME1,ResourceUtil.PART_SIZE,new Date()); + File file = File.createTempFile(getClass().getClassLoader().getResource(ResourceUtil.SOURCE_PART_STORAGE+ResourceUtil.TEST_PART_FILENAME1).getFile(),ResourceUtil.TEMP_SUFFIX); + FileInputStream fileInputStream = new FileInputStream(file); + Mockito.when(productService.getTemplateBinaryResource(ResourceUtil.WORKSPACE_ID+"/part-templates/" + ResourceUtil.PART_TEMPLATE_ID + "/" + ResourceUtil.TEST_PART_FILENAME1)).thenReturn(binaryResource); + Mockito.when(dataManager.getBinaryResourceInputStream(binaryResource)).thenReturn(fileInputStream); + //When + Response response = partTemplateBinaryResource.downloadPartTemplateFile(request,null, ResourceUtil.WORKSPACE_ID, ResourceUtil.PART_TEMPLATE_ID,ResourceUtil.TEST_PART_FILENAME1); + //Then + Assert.assertNotNull(response); + Assert.assertEquals(response.getStatusInfo(), Response.Status.OK); + Assert.assertTrue(response.hasEntity()); + Assert.assertTrue(response.getEntity() instanceof BinaryResourceBinaryStreamingOutput); + Assert.assertNotNull(response.getHeaders().getFirst("Content-Disposition")); + Assert.assertNotNull("attachement;filename=\""+ResourceUtil.TEST_PART_FILENAME1+"\"".equals(response.getHeaders().getFirst("Content-Disposition"))); + + //delete temp file + file.deleteOnExit(); + + } + + +} \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/util/PartImpl.java b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/util/PartImpl.java new file mode 100644 index 0000000000..0dbc0b30a1 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/util/PartImpl.java @@ -0,0 +1,86 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.util; + +import javax.servlet.http.Part; +import java.io.*; +import java.util.Collection; + +/** + * @author Asmae Chadid on 12/01/15. + */ +public class PartImpl implements Part { + + private File fileToUpload; + + public PartImpl(File file){ + this.fileToUpload = file; + } + @Override + public InputStream getInputStream() throws IOException { + return new BufferedInputStream(new FileInputStream(this.fileToUpload)); + } + + @Override + public String getContentType() { + return ResourceUtil.FILE_TYPE; + } + + @Override + public String getName() { + return this.fileToUpload.getName(); + } + + @Override + public String getSubmittedFileName() { + return fileToUpload.getPath(); + } + + @Override + public long getSize() { + return fileToUpload.getTotalSpace(); + } + + @Override + public void write(String s) throws IOException { + + } + + @Override + public void delete() throws IOException { + + } + + @Override + public String getHeader(String s) { + return null; + } + + @Override + public Collection getHeaders(String s) { + return null; + } + + @Override + public Collection getHeaderNames() { + return null; + } +} diff --git a/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/util/ResourceUtil.java b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/util/ResourceUtil.java new file mode 100644 index 0000000000..2a80e81fc7 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/test/java/com/docdoku/server/util/ResourceUtil.java @@ -0,0 +1,57 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.util; + +/* + * + * @author Asmae CHADID on 07/01/15. + */ +public class ResourceUtil { + public static final String WORKSPACE_ID = "TestWorkspace"; + public static final String DOCUMENT_ID = "TestDocument"; + public static final String VERSION = "A"; + public static final int ITERATION = 1; + public static final String SOURCE_FILE_STORAGE = "com/docdoku/server/rest/file/toUpload/"; + public static final String TARGET_FILE_STORAGE = System.getProperty("java.io.tmpdir") + "com/docdoku/server/rest/file/uploaded/"; + public static final String FILENAME1 = "TestFile.txt"; + public static final String FILENAME2 = "TestFile_With_éàè.txt"; + public static final String FILENAME3 = "TestFile_3.txt"; + + public static final String DOC_TEMPLATE_ID = "temp_01"; + public static final String FILE_TYPE = "application/pdf"; + public static final String SHARED_DOC_ENTITY_UUID = "documents/share/shareuuid01"; + public static final String SHARED_PART_ENTITY_UUID = "parts/share/123652-85963"; + public static final String RANGE = "bytes=0-366828"; + public static final long DOCUMENT_SIZE = 26; + public static final String PART_TEMPLATE_ID = "part_templ_01"; + public static final String TEST_PART_FILENAME1 = "part_file_test1.txt"; + public static final String TEST_PART_FILENAME2 = "part_file_test2.txt"; + public static final String VIRTUAL_SUB_RESOURCE = "scormFile.zip"; + public static final String DOC_REFER = "refers/073dd114-e13b-46a9-a348-2c61138aba20"; + public static final String PART_NUMBER = "PART01"; + public static final java.lang.String TEMP_SUFFIX = ".tmp"; + + public static final String FILENAME_TO_UPLOAD_PART_SPECIAL_CHARACTER = "part_file_to_upload-èé_spécial&.txt"; + public static final long PART_SIZE = 363666; + public static final String TARGET_PART_STORAGE = System.getProperty("java.io.tmpdir") + "/com/docdoku/server/rest/part/uploaded/"; + public static final String SOURCE_PART_STORAGE = "com/docdoku/server/rest/part/toUpload/"; + public static final String FILENAME_TARGET_PART = "new_part_file.txt"; +} diff --git a/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/file/toUpload/TestFile.txt b/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/file/toUpload/TestFile.txt new file mode 100644 index 0000000000..2ca896fd9a --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/file/toUpload/TestFile.txt @@ -0,0 +1,4 @@ +this is a test file 1 +this is a test file 1 +this is a test file 1 +this is a test file 1 \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/file/toUpload/TestFile_3.txt b/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/file/toUpload/TestFile_3.txt new file mode 100644 index 0000000000..a6330d3a60 --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/file/toUpload/TestFile_3.txt @@ -0,0 +1 @@ +this is a test file 1 \ No newline at end of file diff --git "a/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/file/toUpload/TestFile_With_\303\251\303\240\303\250.txt" "b/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/file/toUpload/TestFile_With_\303\251\303\240\303\250.txt" new file mode 100644 index 0000000000..35b8c6883e --- /dev/null +++ "b/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/file/toUpload/TestFile_With_\303\251\303\240\303\250.txt" @@ -0,0 +1,11 @@ +Uneasy barton seeing remark happen his has. Am possible offering at contempt mr distance stronger an. Attachment excellence announcing or reasonable am on if indulgence. Exeter talked in agreed spirit no he unable do. Betrayed shutters in vicinity it unpacked in. In so impossible appearance considered mr. Mrs him left find are good. + +Ladies others the six desire age. Bred am soon park past read by lain. As excuse eldest no moment. An delight beloved up garrets am cottage private. The far attachment discovered celebrated decisively surrounded for and. Sir new the particular frequently indulgence excellence how. Wishing an if he sixteen visited tedious subject it. Mind mrs yet did quit high even you went. Sex against the two however not nothing prudent colonel greater. Up husband removed parties staying he subject mr. + +Moments its musical age explain. But extremity sex now education concluded earnestly her continual. Oh furniture acuteness suspected continual ye something frankness. Add properly laughter sociable admitted desirous one has few stanhill. Opinion regular in perhaps another enjoyed no engaged he at. It conveying he continual ye suspected as necessary. Separate met packages shy for kindness. + +Six started far placing saw respect females old. Civilly why how end viewing attempt related enquire visitor. Man particular insensible celebrated conviction stimulated principles day. Sure fail or in said west. Right my front it wound cause fully am sorry if. She jointure goodness interest debating did outweigh. Is time from them full my gone in went. Of no introduced am literature excellence mr stimulated contrasted increasing. Age sold some full like rich new. Amounted repeated as believed in confined juvenile. + +To sure calm much most long me mean. Able rent long in do we. Uncommonly no it announcing melancholy an in. Mirth learn it he given. Secure shy favour length all twenty denote. He felicity no an at packages answered opinions juvenile. + +No depending be convinced in unfeeling he. Excellence she unaffected and too sentiments her. Rooms he doors there ye aware in by shall. Education remainder in so cordially. His remainder and own dejection daughters sportsmen. Is easy took he shed to kind. \ No newline at end of file diff --git a/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/file/toUpload/scormFile.zip b/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/file/toUpload/scormFile.zip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/part/toUpload/part_file_test1.txt b/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/part/toUpload/part_file_test1.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/part/toUpload/part_file_test2.txt b/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/part/toUpload/part_file_test2.txt new file mode 100644 index 0000000000..8db7a68cdb --- /dev/null +++ b/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/part/toUpload/part_file_test2.txt @@ -0,0 +1,4 @@ +this is part test file +this is part test file +this is part test file +this is part test file \ No newline at end of file diff --git "a/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/part/toUpload/part_file_to_upload-\303\250\303\251_sp\303\251cial&.txt" "b/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/part/toUpload/part_file_to_upload-\303\250\303\251_sp\303\251cial&.txt" new file mode 100644 index 0000000000..8be3fe4ea1 --- /dev/null +++ "b/docdoku-server/docdoku-server-rest/src/test/resources/com/docdoku/server/rest/part/toUpload/part_file_to_upload-\303\250\303\251_sp\303\251cial&.txt" @@ -0,0 +1,4 @@ +This is a test file +This is a test file +This is a test file +This is a test file \ No newline at end of file diff --git a/docdoku-server/docdoku-server-scorm/pom.xml b/docdoku-server/docdoku-server-scorm/pom.xml new file mode 100644 index 0000000000..12bb0cc576 --- /dev/null +++ b/docdoku-server/docdoku-server-scorm/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-scorm + jar + docdoku-server-scorm SCORM Viewer + + + com.docdoku + docdoku-server-ext + ${project.version} + + + javax + javaee-api + 7.0 + provided + + + com.google.guava + guava + 16.0 + + + com.github.spullara.mustache.java + compiler + 0.8.11 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + ${project.artifactId} + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/postuploaders/ScormPostUploaderImpl.java b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/postuploaders/ScormPostUploaderImpl.java new file mode 100644 index 0000000000..53f1e57bc3 --- /dev/null +++ b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/postuploaders/ScormPostUploaderImpl.java @@ -0,0 +1,89 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.postuploaders; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.StorageException; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.server.InternalService; +import com.docdoku.server.viewers.utils.ScormUtil; +import com.google.common.io.ByteStreams; + +import javax.inject.Inject; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class ScormPostUploaderImpl implements DocumentPostUploader { + + private static final Logger LOGGER = Logger.getLogger(ScormPostUploaderImpl.class.getName()); + + @InternalService + @Inject + private IDataManagerLocal dataManager; + + @Override + public boolean canProcess(final BinaryResource binaryResource) { + try (InputStream binaryContentInputStream = dataManager.getBinaryResourceInputStream(binaryResource)) { + return ScormUtil.isScormArchive(binaryResource.getName(), binaryContentInputStream); + } catch (StorageException | IOException e) { + LOGGER.log(Level.SEVERE, null, e); + return false; + } + } + + @Override + public void process(final BinaryResource archiveBinaryResource){ + unzipScormArchive(archiveBinaryResource); + } + + public void unzipScormArchive(BinaryResource archiveBinaryResource) { + + try (ZipInputStream zipInputStream = new ZipInputStream(dataManager.getBinaryResourceInputStream(archiveBinaryResource), Charset.forName("ISO-8859-1"))){ + + ZipEntry zipEntry; + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + if (!zipEntry.isDirectory()) { + OutputStream outputStream = null; + try { + String entryName = zipEntry.getName(); + String subResourceVirtualPath = ScormUtil.getScormSubResourceVirtualPath(entryName); + outputStream = dataManager.getBinarySubResourceOutputStream(archiveBinaryResource, subResourceVirtualPath); + ByteStreams.copy(zipInputStream, outputStream); + } finally { + if(outputStream!=null) { + outputStream.flush(); + outputStream.close(); + } + } + } + } + } catch (StorageException | IOException e) { + LOGGER.log(Level.SEVERE, null, e); + } + + } + +} diff --git a/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/resourcegetters/ScormResourceGetterImpl.java b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/resourcegetters/ScormResourceGetterImpl.java new file mode 100644 index 0000000000..2e7c9df46c --- /dev/null +++ b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/resourcegetters/ScormResourceGetterImpl.java @@ -0,0 +1,76 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.resourcegetters; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.document.DocumentIteration; +import com.docdoku.core.exceptions.ConvertedResourceException; +import com.docdoku.core.exceptions.StorageException; +import com.docdoku.core.product.PartIteration; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.server.InternalService; +import com.docdoku.server.viewers.utils.ScormUtil; + +import javax.inject.Inject; +import java.io.InputStream; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ScormResourceGetterImpl implements DocumentResourceGetter { + + private static final Logger LOGGER = Logger.getLogger(ScormResourceGetterImpl.class.getName()); + + @InternalService + @Inject + private IDataManagerLocal dataManager; + + + @Override + public boolean canGetConvertedResource(String outputFormat, BinaryResource binaryResource) { + return false; + } + + @Override + public InputStream getConvertedResource(String outputFormat, BinaryResource binaryResource, DocumentIteration docI, Locale locale) throws ConvertedResourceException { + return null; + } + + @Override + public InputStream getConvertedResource(String outputFormat, BinaryResource binaryResource, PartIteration partIteration, Locale locale) throws ConvertedResourceException { + return null; + } + + @Override + public boolean canGetSubResourceVirtualPath(BinaryResource binaryResource) { + try { + return dataManager.exists(binaryResource, ScormUtil.getScormSubResourceVirtualPath(ScormUtil.IMS_MANIFEST)); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + return false; + } + } + + @Override + public String getSubResourceVirtualPath(BinaryResource binaryResource, String subResourceUri) { + return ScormUtil.getScormSubResourceVirtualPath(subResourceUri); + } + +} diff --git a/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/ScormViewerImpl.java b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/ScormViewerImpl.java new file mode 100644 index 0000000000..2b44ed9790 --- /dev/null +++ b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/ScormViewerImpl.java @@ -0,0 +1,74 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.viewers; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.exceptions.StorageException; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.server.InternalService; +import com.docdoku.server.viewers.utils.ScormManifestParser; +import com.docdoku.server.viewers.utils.ScormOrganization; +import com.docdoku.server.viewers.utils.ScormUtil; +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; + +import javax.inject.Inject; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ScormViewerImpl implements DocumentViewer { + + private static final Logger LOGGER = Logger.getLogger(ScormViewerImpl.class.getName()); + + @InternalService + @Inject + private IDataManagerLocal dataManager; + + + @Override + public boolean canRenderViewerTemplate(BinaryResource binaryResource) { + try { + return dataManager.exists(binaryResource, ScormUtil.getScormSubResourceVirtualPath(ScormUtil.IMS_MANIFEST)); + } catch (StorageException e) { + LOGGER.log(Level.INFO, null, e); + return false; + } + } + + @Override + public String renderHtmlForViewer(BinaryResource scormResource, String uuid) throws Exception { + String manifestVirtualPath = ScormUtil.getScormSubResourceVirtualPath(ScormUtil.IMS_MANIFEST); + ScormOrganization scormOrganization = new ScormManifestParser(dataManager.getBinarySubResourceInputStream(scormResource, manifestVirtualPath)).parse(); + MustacheFactory mf = new DefaultMustacheFactory(); + Mustache mustache = mf.compile("com/docdoku/server/viewers/scorm_viewer.mustache"); + Map scopes = new HashMap<>(); + scopes.put("organization", scormOrganization); + scopes.put("uriResource", ViewerUtils.getURI(scormResource, uuid)); + StringWriter templateWriter = new StringWriter(); + mustache.execute(templateWriter, scopes).flush(); + + return ViewerUtils.getViewerTemplate(dataManager, scormResource, uuid, templateWriter.toString(), false); + } + +} diff --git a/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/IScorm.java b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/IScorm.java new file mode 100644 index 0000000000..b5c319ec59 --- /dev/null +++ b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/IScorm.java @@ -0,0 +1,25 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.viewers.utils; + +public interface IScorm { + void addSubActivity(ScormActivity subActivity); + void setTitle(String title); +} diff --git a/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/ScormActivity.java b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/ScormActivity.java new file mode 100644 index 0000000000..3476dfb701 --- /dev/null +++ b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/ScormActivity.java @@ -0,0 +1,86 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.viewers.utils; + +import java.util.ArrayList; +import java.util.List; + +public class ScormActivity implements IScorm { + + private String title; + + private String resourceIdentifier; + + private String resourceHref; + + private List subActivities; + + public ScormActivity() { + subActivities = new ArrayList(); + } + + public String getTitle() { + return title; + } + + @Override + public void setTitle(String title) { + this.title = title; + } + + public String getResourceHref() { + return resourceHref; + } + + public void setResourceHref(String resourceHref) { + this.resourceHref = resourceHref; + } + + public List getSubActivities() { + return subActivities; + } + + public void setSubActivities(List subActivities) { + this.subActivities = subActivities; + } + + @Override + public void addSubActivity(ScormActivity subActivity) { + this.subActivities.add(subActivity); + } + + public String getResourceIdentifier() { + return resourceIdentifier; + } + + public void setResourceIdentifier(String resourceIdentifier) { + this.resourceIdentifier = resourceIdentifier; + } + + @Override + public String toString() { + return "Activity{" + + "title='" + title + '\'' + + ", resourceIdentifier='" + resourceIdentifier + '\'' + + ", resourceHref='" + resourceHref + '\'' + + ", subActivities=" + subActivities + + '}'; + } +} diff --git a/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/ScormManifestParser.java b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/ScormManifestParser.java new file mode 100644 index 0000000000..a8f4c73049 --- /dev/null +++ b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/ScormManifestParser.java @@ -0,0 +1,162 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.viewers.utils; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ScormManifestParser { + + private static final String ORGANIZATIONS = "organizations"; + private static final String ORGANIZATION = "organization"; + private static final String DEFAULT = "default"; + private static final String IDENTIFIER = "identifier"; + private static final String TITLE = "title"; + private static final String ITEM = "item"; + public static final String ISVISIBLE = "isvisible"; + public static final String IDENTIFIERREF = "identifierref"; + public static final String RESOURCES = "resources"; + public static final String RESOURCE = "resource"; + public static final String HREF = "href"; + + private InputStream manifestStream; + private XMLStreamReader reader; + private Map activitiesByIdentifierRef; + + private static final Logger LOGGER = Logger.getLogger(ScormManifestParser.class.getName()); + + public ScormManifestParser(InputStream manifestStream) { + this.manifestStream = manifestStream; + this.activitiesByIdentifierRef = new HashMap<>(); + } + + public ScormOrganization parse() throws FileNotFoundException, XMLStreamException { + + XMLInputFactory factory = XMLInputFactory.newInstance(); + if (factory.isPropertySupported("javax.xml.stream.isValidating")) { + factory.setProperty("javax.xml.stream.isValidating", Boolean.FALSE); + } + + reader = factory.createXMLStreamReader(manifestStream, "UTF-8"); + + ScormOrganization scormOrganization = parseDefaultOrganization(); + + try { + this.manifestStream.close(); + } catch (IOException e) { + LOGGER.log(Level.INFO, null, e); + } + + return scormOrganization; + } + + private ScormOrganization parseDefaultOrganization() throws XMLStreamException { + + boolean inResources = false; + String defaultOrganizationIdentifier = ""; + ScormOrganization defaultOrganization = null; + + while(reader.hasNext()) { + + reader.next(); + + if (reader.isStartElement()) { + + String startElementName = reader.getLocalName(); + + if (startElementName.equals(ORGANIZATIONS)) { + defaultOrganizationIdentifier = reader.getAttributeValue(null, DEFAULT); + } else if (startElementName.equals(ORGANIZATION)) { + String organizationIdentifier = reader.getAttributeValue(null, IDENTIFIER); + if (organizationIdentifier.equals(defaultOrganizationIdentifier)) { + defaultOrganization = new ScormOrganization(); + parseActivities(defaultOrganization); + } + } else if (startElementName.equals(RESOURCES)) { + inResources = true; + } else if (startElementName.equals(RESOURCE) && inResources) { + String resourceIdentifier = reader.getAttributeValue(null, IDENTIFIER); + if (activitiesByIdentifierRef.containsKey(resourceIdentifier)) { + activitiesByIdentifierRef.get(resourceIdentifier).setResourceHref(reader.getAttributeValue(null, HREF)); + } + } + + } else if (reader.isEndElement()) { + String endElementName = reader.getLocalName(); + if (endElementName.equals(RESOURCES)) { + break; + } + } + + } + + return defaultOrganization; + + } + + private void parseActivities(IScorm parent) throws XMLStreamException { + + ScormActivity subActivity; + + while (reader.hasNext()) { + + reader.next(); + + if (reader.isStartElement()) { + + boolean hasPrefix = !reader.getPrefix().isEmpty(); + + String startElement = reader.getLocalName(); + + if (startElement.equals(ITEM)) { + String isVisible = reader.getAttributeValue(null, ISVISIBLE); + if (isVisible == null || Boolean.parseBoolean(isVisible)) { + String identifierRef = reader.getAttributeValue(null, IDENTIFIERREF); + subActivity = new ScormActivity(); + subActivity.setResourceIdentifier(identifierRef); + activitiesByIdentifierRef.put(identifierRef, subActivity); + parent.addSubActivity(subActivity); + parseActivities(subActivity); + } + } else if (startElement.equals(TITLE) && !hasPrefix) { + parent.setTitle(reader.getElementText()); + } + + } else if (reader.isEndElement()) { + String endElementName = reader.getLocalName(); + if (endElementName.equals(ITEM)) { + return; + } else if (endElementName.equals(ORGANIZATIONS)) { + return; + } + } + } + + } + +} diff --git a/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/ScormOrganization.java b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/ScormOrganization.java new file mode 100644 index 0000000000..bcddfae4da --- /dev/null +++ b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/ScormOrganization.java @@ -0,0 +1,64 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.viewers.utils; + +import java.util.ArrayList; +import java.util.List; + +public class ScormOrganization implements IScorm { + + private String title; + + private List activities; + + public ScormOrganization() { + activities = new ArrayList(); + } + + public String getTitle() { + return title; + } + + @Override + public void addSubActivity(ScormActivity subActivity) { + activities.add(subActivity); + } + + @Override + public void setTitle(String title) { + this.title = title; + } + + public List getActivities() { + return activities; + } + + public void setActivities(List activities) { + this.activities = activities; + } + + @Override + public String toString() { + return "Organization{" + + "title='" + title + '\'' + + ", activities=" + activities + + '}'; + } +} diff --git a/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/ScormUtil.java b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/ScormUtil.java new file mode 100644 index 0000000000..0791681c73 --- /dev/null +++ b/docdoku-server/docdoku-server-scorm/src/main/java/com/docdoku/server/viewers/utils/ScormUtil.java @@ -0,0 +1,44 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ + +package com.docdoku.server.viewers.utils; + +import com.docdoku.core.util.FileIO; + +import java.io.InputStream; + +public class ScormUtil { + + public static final String IMS_MANIFEST = "imsmanifest.xml"; + public static final String SCORM_FOLDER = "scorm"; + + public static boolean isScormArchive(String fileName, InputStream inputStream) { + return FileIO.isArchiveFile(fileName) && FileIO.existsInArchive(inputStream, IMS_MANIFEST); + } + + public static String getScormSubResourceVirtualPath(String uriSubResource) { + StringBuilder sb = new StringBuilder().append(SCORM_FOLDER).append("/").append(uriSubResource); + return sb.toString(); + } + + private ScormUtil(){ + } + +} diff --git a/docdoku-server/docdoku-server-scorm/src/main/resources/META-INF/beans.xml b/docdoku-server/docdoku-server-scorm/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..5d71326a5c --- /dev/null +++ b/docdoku-server/docdoku-server-scorm/src/main/resources/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-scorm/src/main/resources/com/docdoku/server/viewers/scorm_viewer.mustache b/docdoku-server/docdoku-server-scorm/src/main/resources/com/docdoku/server/viewers/scorm_viewer.mustache new file mode 100644 index 0000000000..35c5471e6b --- /dev/null +++ b/docdoku-server/docdoku-server-scorm/src/main/resources/com/docdoku/server/viewers/scorm_viewer.mustache @@ -0,0 +1,6 @@ +

{{organization.title}}
+
    +{{#organization.activities}} +
  • {{title}}
  • +{{/organization.activities}} +
\ No newline at end of file diff --git a/docdoku-server/docdoku-server-viewer-image/pom.xml b/docdoku-server/docdoku-server-viewer-image/pom.xml new file mode 100644 index 0000000000..8ec868d3f5 --- /dev/null +++ b/docdoku-server/docdoku-server-viewer-image/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-viewer-image + jar + docdoku-server-viewer-image Image Viewer + + + com.docdoku + docdoku-server-ext + ${project.version} + + + javax + javaee-api + 7.0 + provided + + + com.github.spullara.mustache.java + compiler + 0.8.11 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + ${project.artifactId} + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-viewer-image/src/main/java/com/docdoku/server/viewers/ImageViewerImpl.java b/docdoku-server/docdoku-server-viewer-image/src/main/java/com/docdoku/server/viewers/ImageViewerImpl.java new file mode 100644 index 0000000000..9f4c6b757c --- /dev/null +++ b/docdoku-server/docdoku-server-viewer-image/src/main/java/com/docdoku/server/viewers/ImageViewerImpl.java @@ -0,0 +1,63 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.viewers; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.util.FileIO; +import com.docdoku.server.InternalService; +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; + +import javax.inject.Inject; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +public class ImageViewerImpl implements DocumentViewer { + + private static final Logger LOGGER = Logger.getLogger(ImageViewerImpl.class.getName()); + + @InternalService + @Inject + private IDataManagerLocal dataManager; + + + + @Override + public boolean canRenderViewerTemplate(BinaryResource binaryResource) { + return FileIO.isImageFile(binaryResource.getName()); + } + + @Override + public String renderHtmlForViewer(BinaryResource imageResource, String uuid) throws Exception { + MustacheFactory mf = new DefaultMustacheFactory(); + Mustache mustache = mf.compile("com/docdoku/server/viewers/image_viewer.mustache"); + Map scopes = new HashMap<>(); + scopes.put("uriResource", ViewerUtils.getURI(imageResource,uuid)); + StringWriter templateWriter = new StringWriter(); + mustache.execute(templateWriter, scopes).flush(); + + return ViewerUtils.getViewerTemplate(dataManager, imageResource, uuid, templateWriter.toString(),false); + } + +} diff --git a/docdoku-server/docdoku-server-viewer-image/src/main/resources/META-INF/beans.xml b/docdoku-server/docdoku-server-viewer-image/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..5d71326a5c --- /dev/null +++ b/docdoku-server/docdoku-server-viewer-image/src/main/resources/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-viewer-image/src/main/resources/com/docdoku/server/viewers/image_viewer.mustache b/docdoku-server/docdoku-server-viewer-image/src/main/resources/com/docdoku/server/viewers/image_viewer.mustache new file mode 100644 index 0000000000..9fea8339d1 --- /dev/null +++ b/docdoku-server/docdoku-server-viewer-image/src/main/resources/com/docdoku/server/viewers/image_viewer.mustache @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-viewer-multimedia/pom.xml b/docdoku-server/docdoku-server-viewer-multimedia/pom.xml new file mode 100644 index 0000000000..fdf43a9067 --- /dev/null +++ b/docdoku-server/docdoku-server-viewer-multimedia/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + com.docdoku + docdoku-server + 2.5-SNAPSHOT + + docdoku-server-viewer-multimedia + jar + docdoku-server-viewer-multimedia Video Viewer + + + com.docdoku + docdoku-server-ext + ${project.version} + + + javax + javaee-api + 7.0 + provided + + + com.github.spullara.mustache.java + compiler + 0.8.11 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + ${project.build.sourceEncoding} + + + + ${project.artifactId} + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-viewer-multimedia/src/main/java/com/docdoku/server/viewers/MultimediaViewerImpl.java b/docdoku-server/docdoku-server-viewer-multimedia/src/main/java/com/docdoku/server/viewers/MultimediaViewerImpl.java new file mode 100644 index 0000000000..ede52cec0f --- /dev/null +++ b/docdoku-server/docdoku-server-viewer-multimedia/src/main/java/com/docdoku/server/viewers/MultimediaViewerImpl.java @@ -0,0 +1,58 @@ +/* + * DocDoku, Professional Open Source + * Copyright 2006 - 2015 DocDoku SARL + * + * This file is part of DocDokuPLM. + * + * DocDokuPLM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DocDokuPLM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with DocDokuPLM. If not, see . + */ +package com.docdoku.server.viewers; + +import com.docdoku.core.common.BinaryResource; +import com.docdoku.core.services.IDataManagerLocal; +import com.docdoku.core.util.FileIO; +import com.docdoku.server.InternalService; +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; + +import javax.inject.Inject; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +public class MultimediaViewerImpl implements DocumentViewer { + + @InternalService + @Inject + private IDataManagerLocal dataManager; + + @Override + public boolean canRenderViewerTemplate(BinaryResource binaryResource) { + return FileIO.isAVFile(binaryResource.getName()); + } + + @Override + public String renderHtmlForViewer(BinaryResource multimediaResource, String uuid) throws Exception { + MustacheFactory mf = new DefaultMustacheFactory(); + Mustache mustache = mf.compile("com/docdoku/server/viewers/multimedia_viewer.mustache"); + Map scopes = new HashMap<>(); + scopes.put("uriResource", ViewerUtils.getURI(multimediaResource,uuid)); + StringWriter templateWriter = new StringWriter(); + mustache.execute(templateWriter, scopes).flush(); + + return ViewerUtils.getViewerTemplate(dataManager, multimediaResource, uuid, templateWriter.toString(), false); + } + +} diff --git a/docdoku-server/docdoku-server-viewer-multimedia/src/main/resources/META-INF/beans.xml b/docdoku-server/docdoku-server-viewer-multimedia/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..23bd68ff81 --- /dev/null +++ b/docdoku-server/docdoku-server-viewer-multimedia/src/main/resources/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/docdoku-server/docdoku-server-viewer-multimedia/src/main/resources/com/docdoku/server/viewers/multimedia_viewer.mustache b/docdoku-server/docdoku-server-viewer-multimedia/src/main/resources/com/docdoku/server/viewers/multimedia_viewer.mustache new file mode 100644 index 0000000000..75a401845d --- /dev/null +++ b/docdoku-server/docdoku-server-viewer-multimedia/src/main/resources/com/docdoku/server/viewers/multimedia_viewer.mustache @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/docdoku-server/pom.xml b/docdoku-server/pom.xml new file mode 100644 index 0000000000..a41b380e03 --- /dev/null +++ b/docdoku-server/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + com.docdoku + docdoku-plm + 2.5-SNAPSHOT + + docdoku-server + pom + docdoku-server Java EE 7 + + docdoku-server-ext + docdoku-server-converter-step + docdoku-server-converter-dae + docdoku-server-converter-obj + docdoku-server-converter-all + docdoku-server-converter-ifc + docdoku-server-converter-catia + docdoku-server-converter-catia-product + docdoku-server-scorm + docdoku-server-office-doc + docdoku-server-viewer-image + docdoku-server-viewer-multimedia + docdoku-server-ejb + docdoku-server-rest + docdoku-server-ear + + \ No newline at end of file diff --git a/docdoku-server/server.properties b/docdoku-server/server.properties new file mode 100644 index 0000000000..8f9fce0007 --- /dev/null +++ b/docdoku-server/server.properties @@ -0,0 +1 @@ +server.contextRoot= \ No newline at end of file diff --git a/docdoku-web-front/.bowerrc b/docdoku-web-front/.bowerrc new file mode 100644 index 0000000000..ba0accc5a3 --- /dev/null +++ b/docdoku-web-front/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "app/bower_components" +} diff --git a/docdoku-web-front/.editorconfig b/docdoku-web-front/.editorconfig new file mode 100644 index 0000000000..8a80734f0c --- /dev/null +++ b/docdoku-web-front/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 4 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/docdoku-web-front/.gitattributes b/docdoku-web-front/.gitattributes new file mode 100644 index 0000000000..2125666142 --- /dev/null +++ b/docdoku-web-front/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/docdoku-web-front/.gitignore b/docdoku-web-front/.gitignore new file mode 100644 index 0000000000..b618e5e92e --- /dev/null +++ b/docdoku-web-front/.gitignore @@ -0,0 +1,18 @@ +node_modules/ +bower_components/ +dist/ +target/ +test/temp +.sass-cache +.tmp +app/document-management/main.css +app/product-management/main.css +app/product-structure/main.css +app/visualization/main.css +app/change-management/main.css +app/workspace-management/main.css +app/account-management/main.css +app/documents/main.css +app/parts/main.css +app/main/main.css +app/download/main.css diff --git a/docdoku-web-front/.jshintrc b/docdoku-web-front/.jshintrc new file mode 100644 index 0000000000..9a00d74ee4 --- /dev/null +++ b/docdoku-web-front/.jshintrc @@ -0,0 +1,37 @@ +{ + "node": true, + "browser": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "indent": 4, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "smarttabs": true, + "jquery": true, + "globals": { + "Docdokuplm": true, + "docdokuplm": true, + "_": false, + "Backbone": false, + "JST": false, + "beforeEach": false, + "describe": false, + "it": false, + "assert": true, + "expect": true, + "should": true, + "require": false, + "define": false + } +} diff --git a/docdoku-web-front/.jshintrc.default b/docdoku-web-front/.jshintrc.default new file mode 100644 index 0000000000..9a00d74ee4 --- /dev/null +++ b/docdoku-web-front/.jshintrc.default @@ -0,0 +1,37 @@ +{ + "node": true, + "browser": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "indent": 4, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "smarttabs": true, + "jquery": true, + "globals": { + "Docdokuplm": true, + "docdokuplm": true, + "_": false, + "Backbone": false, + "JST": false, + "beforeEach": false, + "describe": false, + "it": false, + "assert": true, + "expect": true, + "should": true, + "require": false, + "define": false + } +} diff --git a/docdoku-web-front/.yo-rc.json b/docdoku-web-front/.yo-rc.json new file mode 100644 index 0000000000..93d510d173 --- /dev/null +++ b/docdoku-web-front/.yo-rc.json @@ -0,0 +1,12 @@ +{ + "generator-backbone": { + "appPath": "app", + "appName": "Docdokuplm", + "coffee": false, + "testFramework": "mocha", + "templateFramework": "lodash", + "compassBootstrap": false, + "includeRequireJS": true + }, + "generator-mocha": {} +} \ No newline at end of file diff --git a/docdoku-web-front/Gruntfile.js b/docdoku-web-front/Gruntfile.js new file mode 100644 index 0000000000..a2037b1807 --- /dev/null +++ b/docdoku-web-front/Gruntfile.js @@ -0,0 +1,55 @@ +'use strict'; + +module.exports = function (grunt) { + + var config = { + clean: { + options: { + force: true + } + }, + cssmin: { + options: { + keepSpecialComments: 0 + } + }, + copy:{}, + less:{}, + usemin:{}, + htmlmin:{}, + requirejs:{}, + uglify:{} + }; + + require('time-grunt')(grunt); + require('load-grunt-tasks')(grunt); + + function initModule(module){ + module.loadConf(config, grunt); + module.loadTasks(grunt); + return module; + } + + initModule(require('./grunt/dev/server-front')); + initModule(require('./grunt/dev/tests')); + initModule(require('./grunt/dev/jshint')); + initModule(require('./grunt/tasks/copy')); + initModule(require('./grunt/tasks/build')); + + initModule(require('./grunt/modules/account-management')); + initModule(require('./grunt/modules/change-management')); + initModule(require('./grunt/modules/document-management')); + initModule(require('./grunt/modules/documents')); + initModule(require('./grunt/modules/download')); + initModule(require('./grunt/modules/main')); + initModule(require('./grunt/modules/parts')); + initModule(require('./grunt/modules/product-management')); + initModule(require('./grunt/modules/product-structure')); + initModule(require('./grunt/modules/visualization')); + initModule(require('./grunt/modules/workspace-management')); + + grunt.initConfig(config); + + grunt.registerTask('default',['jshint','build']); + +}; diff --git a/docdoku-web-front/README.md b/docdoku-web-front/README.md new file mode 100644 index 0000000000..ec2fb73c73 --- /dev/null +++ b/docdoku-web-front/README.md @@ -0,0 +1,3 @@ +# DocDokuPLM web client + +Read the development guide on [this page](https://github.com/docdoku/docdoku-plm/wiki/Development-Guide) diff --git a/docdoku-web-front/app/account-management/index.html b/docdoku-web-front/app/account-management/index.html new file mode 100644 index 0000000000..dfbdca3712 --- /dev/null +++ b/docdoku-web-front/app/account-management/index.html @@ -0,0 +1,29 @@ + + + + + + DocDokuPLM - Account management + + + + + + + + + + + + + +
+
+
+
+
+ + + + + diff --git a/docdoku-web-front/app/account-management/js/app.js b/docdoku-web-front/app/account-management/js/app.js new file mode 100644 index 0000000000..d67c5568e9 --- /dev/null +++ b/docdoku-web-front/app/account-management/js/app.js @@ -0,0 +1,34 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/content.html', + 'views/edit-account' +], function (Backbone, Mustache, template, EditAccountView) { + 'use strict'; + var AppView = Backbone.View.extend({ + + el: '#content', + + events: { + }, + + initialize: function () { + }, + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n + })).show(); + return this; + }, + + editAccount:function(){ + var view = new EditAccountView(); + this.$('#account-management-content').html(view.render().el); + } + + }); + + return AppView; +}); diff --git a/docdoku-web-front/app/account-management/js/router.js b/docdoku-web-front/app/account-management/js/router.js new file mode 100644 index 0000000000..d91b8c440e --- /dev/null +++ b/docdoku-web-front/app/account-management/js/router.js @@ -0,0 +1,21 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/common/singleton_decorator' +], +function (Backbone, singletonDecorator) { + 'use strict'; + var Router = Backbone.Router.extend({ + routes: { + '': 'editAccount' + }, + + editAccount:function(){ + App.appView.render(); + App.headerView.render(); + App.appView.editAccount(); + } + }); + + return singletonDecorator(Router); +}); diff --git a/docdoku-web-front/app/account-management/js/templates/content.html b/docdoku-web-front/app/account-management/js/templates/content.html new file mode 100644 index 0000000000..47e862e66e --- /dev/null +++ b/docdoku-web-front/app/account-management/js/templates/content.html @@ -0,0 +1,9 @@ +
+ +
+ +
diff --git a/docdoku-web-front/app/account-management/js/templates/edit-account.html b/docdoku-web-front/app/account-management/js/templates/edit-account.html new file mode 100644 index 0000000000..f11af1e101 --- /dev/null +++ b/docdoku-web-front/app/account-management/js/templates/edit-account.html @@ -0,0 +1,82 @@ +
+ +
+
+
+

{{i18n.ACCOUNT_EDITION}}

+
+
+ +
+ {{account.login}} +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ + + +
+
+ +
+ +
+
+ +
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+ + +
diff --git a/docdoku-web-front/app/account-management/js/views/edit-account.js b/docdoku-web-front/app/account-management/js/views/edit-account.js new file mode 100644 index 0000000000..d79f4791e8 --- /dev/null +++ b/docdoku-web-front/app/account-management/js/views/edit-account.js @@ -0,0 +1,122 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/edit-account.html', + 'common-objects/models/timezone', + 'common-objects/models/language', + 'common-objects/models/user', + 'common-objects/views/alert' +], function (Backbone, Mustache, template, TimeZone, Language, User, AlertView) { + 'use strict'; + + var EditAccountView = Backbone.View.extend({ + events:{ + 'click .toggle-password-update':'togglePasswordUpdate', + 'submit #account_edition_form':'onSubmitForm', + 'click .logout':'logout' + }, + + render:function(){ + + this.enablePasswordUpdate = false; + var _this = this; + + TimeZone.getTimeZones() + .then(function(timeZones){ + _this.timeZones = timeZones; + }) + .then(Language.getLanguages) + .then(function(languages){ + _this.languages = languages; + }) + .then(function(){ + + _this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + account:App.config.account, + timeZones: _this.timeZones, + languages: _this.languages + })); + _this.$notifications = _this.$('.notifications'); + _this.$('#account-language option').each(function(){ + $(this).attr('selected', $(this).val() === App.config.account.language); + $(this).text(App.config.i18n.LANGUAGES[$(this).val()]); + }); + _this.$('#account-timezone option').each(function(){ + $(this).attr('selected', $(this).val() === App.config.account.timeZone); + }); + + }); + + return this; + }, + + onSubmitForm:function(e){ + + var account = { + name:this.$('#account-name').val().trim(), + email:this.$('#account-email').val().trim(), + language:this.$('#account-language').val(), + timeZone:this.$('#account-timezone').val() + }; + + if(this.enablePasswordUpdate){ + var newPassword = this.$('#account-password').val().trim(); + var confirmedPassword = this.$('#account-confirm-password').val().trim(); + if(newPassword === confirmedPassword){ + account.newPassword = newPassword; + }else{ + this.$notifications.append(new AlertView({ + type: 'error', + message: App.config.i18n.PASSWORD_NOT_CONFIRMED + }).render().$el); + e.preventDefault(); + return false; + } + } + + User.updateAccount(account) + .then(this.onUpdateSuccess.bind(this),this.onError.bind(this)); + + e.preventDefault(); + return false; + }, + + onError:function(error){ + this.$notifications.append(new AlertView({ + type: 'error', + message: error.responseText + }).render().$el); + }, + + onUpdateSuccess:function(account){ + var needReload = window.localStorage.locale !== account.language; + if(window.localStorage.locale !== account.language){ + window.localStorage.locale = 'unset'; + } + + this.$notifications.append(new AlertView({ + type: 'success', + title: App.config.i18n.ACCOUNT_UPDATED, + message: needReload ? App.config.i18n.NEED_PAGE_RELOAD_CHANGED_LANG : '' + }).render().$el); + }, + + togglePasswordUpdate:function(){ + this.enablePasswordUpdate = !this.enablePasswordUpdate; + this.$('.password-update').toggle(this.enablePasswordUpdate); + this.$('#account-password').attr('required',this.enablePasswordUpdate); + this.$('#account-confirm-password').attr('required',this.enablePasswordUpdate); + }, + + logout:function(){ + delete localStorage.jwt; + $.get(App.config.contextPath + '/api/auth/logout').complete(function () { + window.location.href = App.config.contextPath + '/?logout=true'; + }); + } + }); + + return EditAccountView; +}); diff --git a/docdoku-web-front/app/account-management/main.js b/docdoku-web-front/app/account-management/main.js new file mode 100644 index 0000000000..97f0b220e2 --- /dev/null +++ b/docdoku-web-front/app/account-management/main.js @@ -0,0 +1,125 @@ +/*global _,require,window*/ + +var App = { + debug:false, + config:{ + login: '', + groups: [], + contextPath: '', + locale: window.localStorage.getItem('locale') || 'en', + needAuthentication:true + } +}; + +App.log=function(message){ + 'use strict'; + if(App.debug){ + window.console.log(message); + } +}; + +require.config({ + + baseUrl: 'js', + + shim: { + jqueryUI: { deps: ['jquery'], exports: 'jQuery' }, + effects: { deps: ['jquery'], exports: 'jQuery' }, + popoverUtils: { deps: ['jquery'], exports: 'jQuery' }, + inputValidity: { deps: ['jquery'], exports: 'jQuery' }, + bootstrap: { deps: ['jquery', 'jqueryUI'], exports: 'jQuery' }, + bootbox: { deps: ['jquery'], exports: 'jQuery' }, + datatables: { deps: ['jquery'], exports: 'jQuery' }, + unmask: { deps: ['jquery'], exports: 'jQuery' }, + unmaskConfig: { deps: ['unmask','jquery'], exports: 'jQuery' }, + bootstrapSwitch: { deps: ['jquery'], exports: 'jQuery'}, + bootstrapDatepicker: {deps: ['jquery','bootstrap'], exports: 'jQuery'}, + backbone: { deps: ['underscore', 'jquery'], exports: 'Backbone'}, + datePickerLang: { deps: ['bootstrapDatepicker'], exports: 'jQuery'} + }, + + paths: { + jquery: '../../bower_components/jquery/jquery', + backbone: '../../bower_components/backbone/backbone', + underscore: '../../bower_components/underscore/underscore', + mustache: '../../bower_components/mustache/mustache', + text: '../../bower_components/requirejs-text/text', + i18n: '../../bower_components/requirejs-i18n/i18n', + buzz: '../../bower_components/buzz/dist/buzz', + bootstrap: '../../bower_components/bootstrap/docs/assets/js/bootstrap', + bootbox:'../../bower_components/bootbox/bootbox', + datatables: '../../bower_components/datatables/media/js/jquery.dataTables', + jqueryUI: '../../bower_components/jqueryui/ui/jquery-ui', + unmask:'../../bower_components/jquery-maskedinput/dist/jquery.maskedinput', + bootstrapSwitch:'../../bower_components/bootstrap-switch/static/js/bootstrap-switch', + bootstrapDatepicker:'../../bower_components/bootstrap-datepicker/js/bootstrap-datepicker', + date:'../../bower_components/date.format/date.format', + unorm:'../../bower_components/unorm/lib/unorm', + moment:'../../bower_components/moment/min/moment-with-locales', + momentTimeZone:'../../bower_components/moment-timezone/builds/moment-timezone-with-data', + unmaskConfig:'../../js/utils/jquery.maskedinput-config', + localization: '../../js/localization', + modules: '../../js/modules', + 'common-objects': '../../js/common-objects', + effects: '../../js/utils/effects', + popoverUtils: '../../js/utils/popover.utils', + inputValidity: '../../js/utils/input-validity', + datatablesOsortExt: '../../js/utils/datatables.oSort.ext', + utilsprototype: '../../js/utils/utils.prototype', + userPopover: '../../js/modules/user-popover-module/app', + async: '../../bower_components/async/lib/async', + datePickerLang: '../../bower_components/bootstrap-datepicker/js/locales/bootstrap-datepicker.fr' + }, + + deps: [ + 'jquery', + 'underscore', + 'date', + 'bootstrap', + 'bootbox', + 'bootstrapSwitch', + 'jqueryUI', + 'effects', + 'popoverUtils', + 'inputValidity', + 'datatables', + 'datatablesOsortExt', + 'unmaskConfig', + 'utilsprototype', + 'datePickerLang' + ], + config: { + i18n: { + locale: (function(){ + 'use strict'; + try{ + return App.config.locale; + }catch(ex){ + return 'en'; + } + })() + } + } +}); + +require(['common-objects/contextResolver','i18n!localization/nls/common','i18n!localization/nls/account-management'], + function (ContextResolver, commonStrings, workspaceManagementStrings) { + + 'use strict'; + + App.config.i18n = _.extend(commonStrings,workspaceManagementStrings); + + ContextResolver.resolveServerProperties() + .then(ContextResolver.resolveAccount) + .then(ContextResolver.resolveWorkspaces) + .then(function buildView(){ + require(['backbone','app','router','common-objects/views/header','modules/all'],function(Backbone, AppView, Router,HeaderView,Modules){ + App.appView = new AppView(); + App.headerView = new HeaderView(); + App.headerView.setCoWorkersView(Modules.CoWorkersAccessModuleView); + App.router = Router.getInstance(); + Backbone.history.start(); + }); + }); + }); + diff --git a/docdoku-web-front/app/change-management/index.html b/docdoku-web-front/app/change-management/index.html new file mode 100644 index 0000000000..67362e19ef --- /dev/null +++ b/docdoku-web-front/app/change-management/index.html @@ -0,0 +1,29 @@ + + + + + + DocDokuPLM - Change management + + + + + + + + + + + + + +
+
+
+
+
+ + + + + diff --git a/docdoku-web-front/app/change-management/js/app.js b/docdoku-web-front/app/change-management/js/app.js new file mode 100644 index 0000000000..057fe5c5c2 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/app.js @@ -0,0 +1,34 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/models/workspace', + 'text!templates/content.html' +], function (Backbone, Mustache, Workspace, template) { + 'use strict'; + var AppView = Backbone.View.extend({ + el: '#content', + + events: {}, + + initialize: function () { + this.model = new Workspace({id: App.config.workspaceId}); + }, + + render: function () { + this.$el.html(Mustache.render(template, {model: this.model, i18n: App.config.i18n})); + this.$content = this.$('#change-management-content'); + + App.$changeManagementMenu = this.$('#change-management-menu'); + App.$changeManagementMenu.customResizable({ + containment: this.$el + }); + + this.$el.show(); + return this; + } + + }); + + return AppView; +}); diff --git a/docdoku-web-front/app/change-management/js/collections/change_issue_collection.js b/docdoku-web-front/app/change-management/js/collections/change_issue_collection.js new file mode 100644 index 0000000000..d944c74b2b --- /dev/null +++ b/docdoku-web-front/app/change-management/js/collections/change_issue_collection.js @@ -0,0 +1,15 @@ +/*global define,App*/ +define([ + 'backbone', + 'models/change_issue' +], function (Backbone,ChangeIssueModel) { + 'use strict'; + var ChangeIssueListCollection = Backbone.Collection.extend({ + model: ChangeIssueModel, + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/changes/issues'; + } + }); + + return ChangeIssueListCollection; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/change-management/js/collections/change_order_collection.js b/docdoku-web-front/app/change-management/js/collections/change_order_collection.js new file mode 100644 index 0000000000..ff9c988569 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/collections/change_order_collection.js @@ -0,0 +1,15 @@ +/*global define,App*/ +define([ + 'backbone', + 'models/change_order' +], function (Backbone,ChangeOrderModel) { + 'use strict'; + var ChangeOrderListCollection = Backbone.Collection.extend({ + model: ChangeOrderModel, + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/changes/orders'; + } + }); + + return ChangeOrderListCollection; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/change-management/js/collections/change_request_collection.js b/docdoku-web-front/app/change-management/js/collections/change_request_collection.js new file mode 100644 index 0000000000..36d4f24064 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/collections/change_request_collection.js @@ -0,0 +1,15 @@ +/*global define,App*/ +define([ + 'backbone', + 'models/change_request' +], function (Backbone,ChangeRequestModel) { + 'use strict'; + var ChangeRequestListCollection = Backbone.Collection.extend({ + model: ChangeRequestModel, + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/changes/requests'; + } + }); + + return ChangeRequestListCollection; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/change-management/js/collections/milestone_collection.js b/docdoku-web-front/app/change-management/js/collections/milestone_collection.js new file mode 100644 index 0000000000..96a83c42cb --- /dev/null +++ b/docdoku-web-front/app/change-management/js/collections/milestone_collection.js @@ -0,0 +1,15 @@ +/*global define,App*/ +define([ + 'backbone', + 'models/milestone' +], function (Backbone,MilestoneModel) { + 'use strict'; + var MilestoneListCollection = Backbone.Collection.extend({ + model: MilestoneModel, + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/changes/milestones'; + } + }); + + return MilestoneListCollection; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/change-management/js/collections/roles.js b/docdoku-web-front/app/change-management/js/collections/roles.js new file mode 100644 index 0000000000..e98d9f71be --- /dev/null +++ b/docdoku-web-front/app/change-management/js/collections/roles.js @@ -0,0 +1,31 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/role' +], function (Backbone, Role) { + 'use strict'; + var RoleList = Backbone.Collection.extend({ + model: Role, + + className: 'RoleList', + + comparator: function (a, b) { + // sort roles by name + var nameA = a.get('name'); + var nameB = b.get('name'); + + if (nameA === nameB) { + return 0; + } + return (nameA < nameB) ? -1 : 1; + }, + + + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/roles/'; + } + + }); + + return RoleList; +}); diff --git a/docdoku-web-front/app/change-management/js/collections/roles_in_use.js b/docdoku-web-front/app/change-management/js/collections/roles_in_use.js new file mode 100644 index 0000000000..57f20760d4 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/collections/roles_in_use.js @@ -0,0 +1,17 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/role' +], function (Backbone, Role) { + 'use strict'; + var RoleInUseList = Backbone.Collection.extend({ + model: Role, + + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/roles/inuse'; + } + + }); + + return RoleInUseList; +}); diff --git a/docdoku-web-front/app/change-management/js/models/change_issue.js b/docdoku-web-front/app/change-management/js/models/change_issue.js new file mode 100644 index 0000000000..73f5e62769 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/models/change_issue.js @@ -0,0 +1,16 @@ +/*global define,App*/ +define([ + 'models/change_item' +], function (ChangeItemModel) { + 'use strict'; + var ChangeIssueModel = ChangeItemModel.extend({ + urlRoot: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/changes/issues'; + }, + getInitiator: function () { + return this.get('initiator'); + } + }); + + return ChangeIssueModel; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/change-management/js/models/change_item.js b/docdoku-web-front/app/change-management/js/models/change_item.js new file mode 100644 index 0000000000..5994784288 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/models/change_item.js @@ -0,0 +1,200 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'common-objects/utils/date', + 'common-objects/utils/acl-checker' +], function (Backbone, Date, ACLChecker) { + 'use strict'; + var ChangeItemModel = Backbone.Model.extend({ + priorities: { + LOW: 'LOW', + MEDIUM: 'MEDIUM', + HIGH: 'HIGH', + EMERGENCY: 'EMERGENCY' + }, + + categories: { + ADAPTIVE: 'ADAPTIVE', + CORRECTIVE: 'CORRECTIVE', + PERFECTIVE: 'PERFECTIVE', + PREVENTIVE: 'PREVENTIVE', + OTHER: 'OTHER' + }, + + initialize: function () { + _.bindAll(this); + }, + + getId: function () { + return this.get('id'); + }, + + getName: function () { + return this.get('name'); + }, + + getAuthor: function () { + return this.get('author'); + }, + + getAuthorName: function () { + return this.get('authorName'); + }, + + getAssignee: function () { + return this.get('assignee'); + }, + + getAssigneeName: function () { + return this.get('assigneeName'); + }, + + getCreationDate: function () { + return this.get('creationDate'); + }, + + getFormattedCreationDate: function () { + return Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getCreationDate() + ); + }, + + getDescription: function () { + return this.get('description'); + }, + + getPriority: function () { + return this.get('priority'); + }, + + getCategory: function () { + return this.get('category'); + }, + + getAffectedDocuments: function () { + return this.get('affectedDocuments'); + }, + + getAffectedParts: function () { + return this.get('affectedParts'); + }, + + getTags: function () { + return this.get('tags'); + }, + + addTags: function (tags) { + $.ajax({ + context: this, + type: 'POST', + url: this.url() + '/tags', + data: JSON.stringify({tags:tags}), + contentType: 'application/json; charset=utf-8', + success: function () { + } + }); + }, + + removeTag: function (tag, callback) { + $.ajax({ + type: 'DELETE', + url: this.url() + '/tags/' + tag, + success: function () { + callback(); + } + }); + }, + + removeTags: function (tags, callback) { + var baseUrl = this.url() + '/tags/'; + var count = 0; + var total = _(tags).length; + _(tags).each(function (tag) { + $.ajax({ + type: 'DELETE', + url: baseUrl + tag, + success: function () { + count++; + if (count >= total) { + callback(); + } + } + }); + }); + + }, + + saveAffectedDocuments: function (documents, callback) { + $.ajax({ + context: this, + type: 'PUT', + url: this.url() + '/affected-documents', + data: JSON.stringify({documents:documents}), + contentType: 'application/json; charset=utf-8', + success: function () { + if (callback) { + callback(); + } + } + }); + }, + + saveAffectedParts: function (parts, callback) { + $.ajax({ + context: this, + type: 'PUT', + url: this.url() + '/affected-parts', + data: JSON.stringify({parts:parts}), + contentType: 'application/json; charset=utf-8', + success: function () { + if (callback) { + callback(); + } + } + }); + }, + + getACL: function () { + return this.get('acl'); + }, + + updateACL: function (args) { + $.ajax({ + type: 'PUT', + url: this.url() + '/acl', + data: JSON.stringify(args.acl), + contentType: 'application/json; charset=utf-8', + success: args.success, + error: args.error + }); + }, + + hasACLForCurrentUser: function () { + return this.getACLPermissionForCurrentUser() !== false; + }, + + isForbidden: function () { + return this.getACLPermissionForCurrentUser() === 'FORBIDDEN'; + }, + + isReadOnly: function () { + return this.getACLPermissionForCurrentUser() === 'READ_ONLY'; + }, + + isFullAccess: function () { + return this.getACLPermissionForCurrentUser() === 'FULL_ACCESS'; + }, + + getACLPermissionForCurrentUser: function () { + return ACLChecker.getPermission(this.getACL()); + }, + + isWritable: function () { + return this.get('writable'); + } + + }); + + return ChangeItemModel; +}); diff --git a/docdoku-web-front/app/change-management/js/models/change_order.js b/docdoku-web-front/app/change-management/js/models/change_order.js new file mode 100644 index 0000000000..016e5e0777 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/models/change_order.js @@ -0,0 +1,37 @@ +/*global $,define,App*/ +define([ + 'models/change_item' +], function (ChangeItemModel) { + 'use strict'; + var ChangeOrderModel = ChangeItemModel.extend({ + urlRoot: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/changes/orders'; + }, + + getMilestoneId: function () { + return this.get('milestoneId'); + }, + + getAddressedChangeRequests: function () { + return this.get('addressedChangeRequests'); + }, + + saveAffectedRequests: function (requests, callback) { + $.ajax({ + context: this, + type: 'PUT', + url: this.url() + '/affected-requests', + data: JSON.stringify({requests:requests}), + contentType: 'application/json; charset=utf-8', + success: function () { + this.fetch(); + if (callback) { + callback(); + } + } + }); + } + }); + + return ChangeOrderModel; +}); diff --git a/docdoku-web-front/app/change-management/js/models/change_request.js b/docdoku-web-front/app/change-management/js/models/change_request.js new file mode 100644 index 0000000000..06d370df03 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/models/change_request.js @@ -0,0 +1,36 @@ +/*global $,define,App*/ +define([ + 'models/change_item' +], function (ChangeItemModel) { + 'use strict'; + var ChangeRequestModel = ChangeItemModel.extend({ + urlRoot: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/changes/requests'; + }, + getMilestoneId: function () { + return this.get('milestoneId'); + }, + + getAddressedChangeIssues: function () { + return this.get('addressedChangeIssues'); + }, + + saveAffectedIssues: function (issues, callback) { + $.ajax({ + context: this, + type: 'PUT', + url: this.url() + '/affected-issues', + data: JSON.stringify({issues:issues}), + contentType: 'application/json; charset=utf-8', + success: function () { + this.fetch(); + if (callback) { + callback(); + } + } + }); + } + }); + + return ChangeRequestModel; +}); diff --git a/docdoku-web-front/app/change-management/js/models/milestone.js b/docdoku-web-front/app/change-management/js/models/milestone.js new file mode 100644 index 0000000000..824b812e3a --- /dev/null +++ b/docdoku-web-front/app/change-management/js/models/milestone.js @@ -0,0 +1,100 @@ +/*global $,_,define,App*/ +define([ + 'backbone', + 'common-objects/utils/date', + 'common-objects/utils/acl-checker' +], function (Backbone, Date, ACLChecker) { + 'use strict'; + var MilestoneModel = Backbone.Model.extend({ + + urlRoot: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/changes/milestones'; + }, + + initialize: function () { + _.bindAll(this); + }, + getId: function () { + return this.get('id'); + }, + + getTitle: function () { + return this.get('title'); + }, + + getDueDate: function () { + return this.get('dueDate'); + }, + + getDueDateToPrint: function () { + return (this.getDueDate()) ? Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getDueDate() + ) : null; + }, + + getDueDateDatePicker: function () { + return (this.getDueDate()) ? Date.formatTimestamp( + App.config.i18n._DATE_PICKER_DATE_FORMAT, + this.getDueDate() + ) : null; + }, + + getDescription: function () { + return this.get('description'); + }, + + getWorkspaceId: function () { + return this.get('workspaceId'); + }, + + getNumberOfRequests: function () { + return this.get('numberOfRequests'); + }, + + getNumberOfOrders: function () { + return this.get('numberOfOrders'); + }, + + getACL: function () { + return this.get('acl'); + }, + + updateACL: function (args) { + $.ajax({ + type: 'PUT', + url: this.url() + '/acl', + data: JSON.stringify(args.acl), + contentType: 'application/json; charset=utf-8', + success: args.success, + error: args.error + }); + }, + + hasACLForCurrentUser: function () { + return this.getACLPermissionForCurrentUser() !== false; + }, + + isForbidden: function () { + return this.getACLPermissionForCurrentUser() === 'FORBIDDEN'; + }, + + isReadOnly: function () { + return this.getACLPermissionForCurrentUser() === 'READ_ONLY'; + }, + + isFullAccess: function () { + return this.getACLPermissionForCurrentUser() === 'FULL_ACCESS'; + }, + + getACLPermissionForCurrentUser: function () { + return ACLChecker.getPermission(this.getACL()); + }, + + isWritable: function () { + return this.get('writable'); + } + }); + + return MilestoneModel; +}); diff --git a/docdoku-web-front/app/change-management/js/router.js b/docdoku-web-front/app/change-management/js/router.js new file mode 100644 index 0000000000..102655a898 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/router.js @@ -0,0 +1,136 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'common-objects/common/singleton_decorator', + 'views/nav/workflow_nav', + 'views/nav/task_nav', + 'views/nav/milestone_nav', + 'views/nav/change_issue_nav', + 'views/nav/change_request_nav', + 'views/nav/change_order_nav', + 'views/workflows/workflow_model_editor' +], +function (Backbone,singletonDecorator, WorkflowNavView, TaskNavView, MilestoneNavView, ChangeIssueNavView, ChangeRequestNavView, ChangeOrderNavView, WorkflowModelEditorView) { + 'use strict'; + var Router = Backbone.Router.extend({ + routes: { + ':workspaceId/workflows': 'workflows', + ':workspaceId/milestones': 'milestones', + ':workspaceId/issues': 'issues', + ':workspaceId/requests': 'requests', + ':workspaceId/orders': 'orders', + ':workspaceId/workflow-model-editor/:workflowModelId': 'workflowModelEditor', + ':workspaceId/workflow-model-editor': 'workflowModelEditorNew', + ':workspaceId/tasks/:taskId': 'task', + ':workspaceId/workflow/:workflowId': 'workflow', + ':workspaceId': 'workflows', + ':workspaceId/*path': 'workflows' + }, + + executeOrReload:function(workspaceId,fn){ + if(workspaceId !== App.config.workspaceId && decodeURIComponent(workspaceId).trim() !== App.config.workspaceId) { + location.reload(); + }else{ + fn.bind(this).call(); + } + }, + + initNavViews: function () { + WorkflowNavView.getInstance(); + MilestoneNavView.getInstance(); + ChangeIssueNavView.getInstance(); + ChangeOrderNavView.getInstance(); + ChangeRequestNavView.getInstance(); + TaskNavView.getInstance(); + }, + + cleanContent: function () { + WorkflowNavView.getInstance().cleanView(); + MilestoneNavView.getInstance().cleanView(); + ChangeIssueNavView.getInstance().cleanView(); + ChangeOrderNavView.getInstance().cleanView(); + ChangeRequestNavView.getInstance().cleanView(); + TaskNavView.getInstance().cleanView(); + }, + + workflows: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + WorkflowNavView.getInstance().showContent(); + }); + }, + milestones: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + MilestoneNavView.getInstance().showContent(); + }); + }, + issues: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + this.cleanContent(); + ChangeIssueNavView.getInstance().showContent(); + }); + }, + requests: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + this.cleanContent(); + ChangeRequestNavView.getInstance().showContent(); + }); + }, + orders: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + this.cleanContent(); + ChangeOrderNavView.getInstance().showContent(); + }); + }, + + workflowModelEditor: function (workspaceId, workflowModelId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + if (!_.isUndefined(this.workflowModelEditorView)) { + this.workflowModelEditorView.unbindAllEvents(); + } + + this.workflowModelEditorView = new WorkflowModelEditorView({ + workflowModelId: decodeURI(workflowModelId) + }); + + this.workflowModelEditorView.render(); + }); + }, + + workflowModelEditorNew: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + if (!_.isUndefined(this.workflowModelEditorView)) { + this.workflowModelEditorView.unbindAllEvents(); + } + + this.workflowModelEditorView = new WorkflowModelEditorView(); + + this.workflowModelEditorView.render(); + }); + }, + + task:function(workspaceId, taskId){ + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + this.cleanContent(); + TaskNavView.getInstance().showTaskContent(taskId); + }); + }, + + workflow:function(workspaceId, workflowId){ + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + this.cleanContent(); + TaskNavView.getInstance().showWorkflowContent(workflowId); + }); + } + }); + Router = singletonDecorator(Router); + return Router; +}); diff --git a/docdoku-web-front/app/change-management/js/templates/change-issues/change_issue_content.html b/docdoku-web-front/app/change-management/js/templates/change-issues/change_issue_content.html new file mode 100644 index 0000000000..cdd914787e --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/change-issues/change_issue_content.html @@ -0,0 +1,10 @@ +
+ + {{> deleteButton}} + {{> tagsButton}} + {{> aclButton}} +
+
+
diff --git a/docdoku-web-front/app/change-management/js/templates/change-issues/change_issue_creation.html b/docdoku-web-front/app/change-management/js/templates/change-issues/change_issue_creation.html new file mode 100644 index 0000000000..50591e75db --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/change-issues/change_issue_creation.html @@ -0,0 +1,83 @@ + diff --git a/docdoku-web-front/app/change-management/js/templates/change-issues/change_issue_edition.html b/docdoku-web-front/app/change-management/js/templates/change-issues/change_issue_edition.html new file mode 100644 index 0000000000..aa539a208f --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/change-issues/change_issue_edition.html @@ -0,0 +1,121 @@ + diff --git a/docdoku-web-front/app/change-management/js/templates/change-issues/change_issue_list_item.html b/docdoku-web-front/app/change-management/js/templates/change-issues/change_issue_list_item.html new file mode 100644 index 0000000000..ebd1cba2fe --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/change-issues/change_issue_list_item.html @@ -0,0 +1,15 @@ + +{{model.getName}} +{{model.getAssigneeName}} +{{model.getPriority}} +{{model.getCategory}} +{{model.getAuthorName}} +{{model.getFormattedCreationDate}} + + {{#model.isReadOnly}} + + {{/model.isReadOnly}} + {{#model.isFullAccess}} + + {{/model.isFullAccess}} + diff --git a/docdoku-web-front/app/change-management/js/templates/change-orders/change_order_content.html b/docdoku-web-front/app/change-management/js/templates/change-orders/change_order_content.html new file mode 100644 index 0000000000..5758c4edbd --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/change-orders/change_order_content.html @@ -0,0 +1,10 @@ +
+ + {{> deleteButton}} + {{> tagsButton}} + {{> aclButton}} +
+
+
diff --git a/docdoku-web-front/app/change-management/js/templates/change-orders/change_order_creation.html b/docdoku-web-front/app/change-management/js/templates/change-orders/change_order_creation.html new file mode 100644 index 0000000000..0638a6d40c --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/change-orders/change_order_creation.html @@ -0,0 +1,90 @@ + diff --git a/docdoku-web-front/app/change-management/js/templates/change-orders/change_order_edition.html b/docdoku-web-front/app/change-management/js/templates/change-orders/change_order_edition.html new file mode 100644 index 0000000000..4878018897 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/change-orders/change_order_edition.html @@ -0,0 +1,126 @@ + diff --git a/docdoku-web-front/app/change-management/js/templates/change-orders/change_order_list_item.html b/docdoku-web-front/app/change-management/js/templates/change-orders/change_order_list_item.html new file mode 100644 index 0000000000..b5f28790ee --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/change-orders/change_order_list_item.html @@ -0,0 +1,15 @@ + +{{model.getName}} +{{model.getAssigneeName}} +{{model.getPriority}} +{{model.getCategory}} +{{model.getAuthorName}} +{{model.getFormattedCreationDate}} + + {{#model.isReadOnly}} + + {{/model.isReadOnly}} + {{#model.isFullAccess}} + + {{/model.isFullAccess}} + diff --git a/docdoku-web-front/app/change-management/js/templates/change-requests/change_request_content.html b/docdoku-web-front/app/change-management/js/templates/change-requests/change_request_content.html new file mode 100644 index 0000000000..cef8718894 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/change-requests/change_request_content.html @@ -0,0 +1,10 @@ +
+ + {{> deleteButton}} + {{> tagsButton}} + {{> aclButton}} +
+
+
diff --git a/docdoku-web-front/app/change-management/js/templates/change-requests/change_request_creation.html b/docdoku-web-front/app/change-management/js/templates/change-requests/change_request_creation.html new file mode 100644 index 0000000000..576481b2a6 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/change-requests/change_request_creation.html @@ -0,0 +1,91 @@ + diff --git a/docdoku-web-front/app/change-management/js/templates/change-requests/change_request_edition.html b/docdoku-web-front/app/change-management/js/templates/change-requests/change_request_edition.html new file mode 100644 index 0000000000..a7f1503b44 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/change-requests/change_request_edition.html @@ -0,0 +1,126 @@ + diff --git a/docdoku-web-front/app/change-management/js/templates/change-requests/change_request_list_item.html b/docdoku-web-front/app/change-management/js/templates/change-requests/change_request_list_item.html new file mode 100644 index 0000000000..46f81d33c0 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/change-requests/change_request_list_item.html @@ -0,0 +1,15 @@ + +{{model.getName}} +{{model.getAssigneeName}} +{{model.getPriority}} +{{model.getCategory}} +{{model.getAuthorName}} +{{model.getFormattedCreationDate}} + + {{#model.isReadOnly}} + + {{/model.isReadOnly}} + {{#model.isFullAccess}} + + {{/model.isFullAccess}} + diff --git a/docdoku-web-front/app/change-management/js/templates/change_item_list.html b/docdoku-web-front/app/change-management/js/templates/change_item_list.html new file mode 100644 index 0000000000..2dbe1e9bd7 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/change_item_list.html @@ -0,0 +1,13 @@ + + + + {{i18n.NAME}} + {{i18n.CHANGE_ITEM_ASSIGNEE}} + {{i18n.CHANGE_ITEM_PRIORITY}} + {{i18n.CHANGE_ITEM_CATEGORY}} + {{i18n.AUTHOR}} + {{i18n.CREATION_DATE}} + {{i18n.ACL}} + + + diff --git a/docdoku-web-front/app/change-management/js/templates/content.html b/docdoku-web-front/app/change-management/js/templates/content.html new file mode 100644 index 0000000000..ec27f538fa --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/content.html @@ -0,0 +1,11 @@ +
+ +
+
diff --git a/docdoku-web-front/app/change-management/js/templates/milestones/milestone_content.html b/docdoku-web-front/app/change-management/js/templates/milestones/milestone_content.html new file mode 100644 index 0000000000..2366b7064f --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/milestones/milestone_content.html @@ -0,0 +1,9 @@ +
+ + {{> deleteButton}} + {{> aclButton}} +
+
+
diff --git a/docdoku-web-front/app/change-management/js/templates/milestones/milestone_creation.html b/docdoku-web-front/app/change-management/js/templates/milestones/milestone_creation.html new file mode 100644 index 0000000000..3f6323de6f --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/milestones/milestone_creation.html @@ -0,0 +1,51 @@ + diff --git a/docdoku-web-front/app/change-management/js/templates/milestones/milestone_edition.html b/docdoku-web-front/app/change-management/js/templates/milestones/milestone_edition.html new file mode 100644 index 0000000000..1deebe2728 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/milestones/milestone_edition.html @@ -0,0 +1,78 @@ + diff --git a/docdoku-web-front/app/change-management/js/templates/milestones/milestone_list.html b/docdoku-web-front/app/change-management/js/templates/milestones/milestone_list.html new file mode 100644 index 0000000000..08f8ce01d6 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/milestones/milestone_list.html @@ -0,0 +1,11 @@ + + + + {{i18n.TITLE}} + {{i18n.DUE_DATE}} + {{i18n.REQUESTS_NUMBER}} + {{i18n.ORDERS_NUMBER}} + {{i18n.ACL}} + + + diff --git a/docdoku-web-front/app/change-management/js/templates/milestones/milestone_list_item.html b/docdoku-web-front/app/change-management/js/templates/milestones/milestone_list_item.html new file mode 100644 index 0000000000..c91e5df0a9 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/milestones/milestone_list_item.html @@ -0,0 +1,13 @@ + +{{model.getTitle}} +{{model.getDueDateToPrint}} +{{model.getNumberOfRequests}} +{{model.getNumberOfOrders}} + + {{#model.isReadOnly}} + + {{/model.isReadOnly}} + {{#model.isFullAccess}} + + {{/model.isFullAccess}} + diff --git a/docdoku-web-front/app/change-management/js/templates/nav/change_issue_nav.html b/docdoku-web-front/app/change-management/js/templates/nav/change_issue_nav.html new file mode 100644 index 0000000000..43b807f911 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/nav/change_issue_nav.html @@ -0,0 +1,6 @@ +
+ + + {{i18n.ISSUES}} + +
diff --git a/docdoku-web-front/app/change-management/js/templates/nav/change_order_nav.html b/docdoku-web-front/app/change-management/js/templates/nav/change_order_nav.html new file mode 100644 index 0000000000..53f0c08e51 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/nav/change_order_nav.html @@ -0,0 +1,6 @@ +
+ + + {{i18n.ORDERS}} + +
diff --git a/docdoku-web-front/app/change-management/js/templates/nav/change_request_nav.html b/docdoku-web-front/app/change-management/js/templates/nav/change_request_nav.html new file mode 100644 index 0000000000..2508869152 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/nav/change_request_nav.html @@ -0,0 +1,6 @@ +
+ + + {{i18n.REQUESTS}} + +
diff --git a/docdoku-web-front/app/change-management/js/templates/nav/milestone_nav.html b/docdoku-web-front/app/change-management/js/templates/nav/milestone_nav.html new file mode 100644 index 0000000000..3c5e2b83c5 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/nav/milestone_nav.html @@ -0,0 +1,6 @@ +
+ + + {{i18n.MILESTONES}} + +
diff --git a/docdoku-web-front/app/change-management/js/templates/nav/workflow_nav.html b/docdoku-web-front/app/change-management/js/templates/nav/workflow_nav.html new file mode 100644 index 0000000000..e6523aa7b4 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/nav/workflow_nav.html @@ -0,0 +1,6 @@ +
+ + + {{i18n.WORKFLOWS}} + +
diff --git a/docdoku-web-front/app/change-management/js/templates/tasks/task.html b/docdoku-web-front/app/change-management/js/templates/tasks/task.html new file mode 100644 index 0000000000..4d8f5f9a60 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/tasks/task.html @@ -0,0 +1 @@ +
diff --git a/docdoku-web-front/app/change-management/js/templates/tasks/task_list.html b/docdoku-web-front/app/change-management/js/templates/tasks/task_list.html new file mode 100644 index 0000000000..424c656559 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/tasks/task_list.html @@ -0,0 +1,24 @@ + + + + + + + + + + + + + {{#tasks}} + + + + + + + + {{/tasks}} + + +
{{i18n.TITLE}}{{i18n.HOLDER}}{{i18n.AUTHOR}}{{i18n.STATUS}}
{{title}}{{holderReference}}-{{holderVersion}}-{{targetIteration}}{{worker.name}}{{status}}
diff --git a/docdoku-web-front/app/change-management/js/templates/workflows/activity_model_editor.html b/docdoku-web-front/app/change-management/js/templates/workflows/activity_model_editor.html new file mode 100644 index 0000000000..a51ff7de09 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/workflows/activity_model_editor.html @@ -0,0 +1,19 @@ +
+
+ + + + + + +
+ + +
+
+
    +
+ +
\ No newline at end of file diff --git a/docdoku-web-front/app/change-management/js/templates/workflows/roles_modal.html b/docdoku-web-front/app/change-management/js/templates/workflows/roles_modal.html new file mode 100644 index 0000000000..6453abddb1 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/workflows/roles_modal.html @@ -0,0 +1,29 @@ + diff --git a/docdoku-web-front/app/change-management/js/templates/workflows/task_model_editor.html b/docdoku-web-front/app/change-management/js/templates/workflows/task_model_editor.html new file mode 100644 index 0000000000..34da4a8076 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/workflows/task_model_editor.html @@ -0,0 +1,32 @@ +
+
+ + +

+ {{#task.title}}{{task.title}}{{/task.title}} + {{^task.title}}{{i18n.TASK_NAME_PLACEHOLDER}}{{/task.title}} +

+ + +
+
+ + +
+ + + + + + +
+
diff --git a/docdoku-web-front/app/change-management/js/templates/workflows/workflow_content_list.html b/docdoku-web-front/app/change-management/js/templates/workflows/workflow_content_list.html new file mode 100644 index 0000000000..87d4b065f1 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/workflows/workflow_content_list.html @@ -0,0 +1,12 @@ +
+ + {{> deleteButton}} + {{> aclButton}} + +
+
+
diff --git a/docdoku-web-front/app/change-management/js/templates/workflows/workflow_list.html b/docdoku-web-front/app/change-management/js/templates/workflows/workflow_list.html new file mode 100644 index 0000000000..73854d4572 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/workflows/workflow_list.html @@ -0,0 +1,11 @@ + + + + {{i18n.REFERENCE}} + {{i18n.FINAL_STATE_PLACEHOLDER}} + {{i18n.AUTHOR}} + {{i18n.CREATION_DATE}} + {{i18n.ACL}} + + + diff --git a/docdoku-web-front/app/change-management/js/templates/workflows/workflow_list_item.html b/docdoku-web-front/app/change-management/js/templates/workflows/workflow_list_item.html new file mode 100644 index 0000000000..e7792bac94 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/workflows/workflow_list_item.html @@ -0,0 +1,13 @@ + +{{model.id}} +{{model.finalLifeCycleState}} +{{model.author.name}} +{{model.creationDate}} + + {{#model.isReadOnly}} + + {{/model.isReadOnly}} + {{#model.isFullAccess}} + + {{/model.isFullAccess}} + diff --git a/docdoku-web-front/app/change-management/js/templates/workflows/workflow_model_copy.html b/docdoku-web-front/app/change-management/js/templates/workflows/workflow_model_copy.html new file mode 100644 index 0000000000..1d27fbaf61 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/workflows/workflow_model_copy.html @@ -0,0 +1,16 @@ + diff --git a/docdoku-web-front/app/change-management/js/templates/workflows/workflow_model_editor.html b/docdoku-web-front/app/change-management/js/templates/workflows/workflow_model_editor.html new file mode 100644 index 0000000000..a592f96d7c --- /dev/null +++ b/docdoku-web-front/app/change-management/js/templates/workflows/workflow_model_editor.html @@ -0,0 +1,39 @@ +
+ + + + {{#workflowId}} + + {{/workflowId}} +
+
+
+
    +
  • +
    + + +
    +
  • +
  • + +
  • +
  • +
    + + +
    +
  • +
+
diff --git a/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_content.js b/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_content.js new file mode 100644 index 0000000000..a39131a523 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_content.js @@ -0,0 +1,153 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'collections/change_issue_collection', + 'text!templates/change-issues/change_issue_content.html', + 'views/change-issues/change_issue_list', + 'views/change-issues/change_issue_creation', + 'common-objects/views/tags/tags_management', + 'common-objects/views/security/acl_edit', + 'common-objects/views/alert', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/tags_button.html', + 'text!common-objects/templates/buttons/ACL_button.html' +], function (Backbone, Mustache, ChangeIssueCollection, template, ChangeIssueListView, ChangeIssueCreationView, TagsManagementView, ACLEditView, AlertView, deleteButton, tagsButton, aclButton) { + 'use strict'; + var ChangeIssueContentView = Backbone.View.extend({ + + events: { + 'click button.new-issue': 'newIssue', + 'click button.delete': 'deleteIssue', + 'click button.edit-acl': 'actionEditAcl', + 'click button.tags': 'actionTags' + }, + + partials: { + deleteButton: deleteButton, + tagsButton: tagsButton, + aclButton: aclButton + }, + + initialize: function () { + _.bindAll(this); + this.collection = new ChangeIssueCollection(); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n}, this.partials)); + + this.bindDomElements(); + + this.listView = new ChangeIssueListView({ + el: this.$('#issue_table'), + collection: this.collection + }).render(); + + this.listView.on('delete-button:display', this.changeDeleteButtonDisplay); + this.listView.on('acl-button:display', this.changeAclButtonDisplay); + this.listView.on('error', this.onError); + this.tagsButton.show(); + + this.$el.on('remove', this.listView.remove); + return this; + }, + + bindDomElements: function () { + this.deleteButton = this.$('.delete'); + this.aclButton = this.$('.edit-acl'); + this.tagsButton = this.$('.tags'); + this.$notifications = this.$('.notifications').first(); + }, + + newIssue: function () { + var self = this; + var issueCreationView = new ChangeIssueCreationView({ + collection: self.collection + }); + window.document.body.appendChild(issueCreationView.render().el); + issueCreationView.openModal(); + + }, + + deleteIssue: function () { + this.listView.deleteSelectedIssues(); + }, + + actionTags: function () { + var changeIssuesChecked = new Backbone.Collection(); + + + this.listView.eachChecked(function (view) { + changeIssuesChecked.push(view.model); + }); + + var tagsManagementView = new TagsManagementView({ + collection: changeIssuesChecked + }); + window.document.body.appendChild(tagsManagementView.el); + tagsManagementView.show(); + + return false; + }, + + actionEditAcl: function () { + var modelChecked = this.listView.getChecked(); + + if (modelChecked) { + var self = this; + modelChecked.fetch(); + var aclEditView = new ACLEditView({ + editMode: true, + acl: modelChecked.getACL() + }); + + aclEditView.setTitle(modelChecked.getName()); + window.document.body.appendChild(aclEditView.render().el); + + aclEditView.openModal(); + aclEditView.on('acl:update', function () { + var acl = aclEditView.toList(); + modelChecked.updateACL({ + acl: acl || {userEntries: {}, groupEntries: {}}, + success: function () { + modelChecked.set('acl', acl); + aclEditView.closeModal(); + self.listView.redraw(); + }, + error: function(model, error){ + aclEditView.onError(model, error); + } + }); + + }); + + } + }, + + changeDeleteButtonDisplay: function (state) { + if (state) { + this.deleteButton.show(); + } else { + this.deleteButton.hide(); + } + }, + changeAclButtonDisplay: function (state) { + if (state) { + this.aclButton.show(); + } else { + this.aclButton.hide(); + } + }, + + onError: function(model, error) { + var errorMessage = error ? error.responseText : model; + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + } + }); + + return ChangeIssueContentView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_creation.js b/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_creation.js new file mode 100644 index 0000000000..ab4925d0b4 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_creation.js @@ -0,0 +1,169 @@ +/*global _,define,App,window*/ +define([ + 'backbone', + 'mustache', + 'text!templates/change-issues/change_issue_creation.html', + 'models/change_issue', + 'common-objects/collections/users', + 'common-objects/views/linked/linked_documents', + 'common-objects/collections/linked/linked_document_collection', + 'common-objects/views/linked/linked_parts', + 'common-objects/collections/linked/linked_part_collection' +], +function (Backbone, Mustache, template, ChangeIssueModel, UserList, LinkedDocumentsView, LinkedDocumentCollection, LinkedPartsView, LinkedPartCollection) { + 'use strict'; + var ChangeIssueCreationView = Backbone.View.extend({ + events: { + 'click .modal-footer .btn-primary': 'interceptSubmit', + 'submit #issue_creation_form': 'onSubmitForm', + 'hidden #issue_creation_modal': 'onHidden', + 'close-modal-request':'closeModal' + }, + + initialize: function () { + this._subViews = []; + this.model = new ChangeIssueModel(); + _.bindAll(this); + this.$el.on('remove', this.removeSubviews); // Remove cascade + }, + + removeSubviews: function () { + _(this._subViews).invoke('remove'); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + new UserList().fetch({success: this.fillUserList}); + this.fillPriorityList(); + this.fillCategoryList(); + this.linkManagement(); + + this.$inputIssueName.customValidity(App.config.i18n.REQUIRED_FIELD); + + return this; + }, + + fillUserList: function (list) { + var self = this; + list.each(function (user) { + self.$inputIssueAssignee.append(''); + }); + }, + fillPriorityList: function () { + var self = this; + _.each(this.model.priorities, function(priority){ + self.$inputIssuePriority.append(''); + }); + }, + fillCategoryList: function () { + var self = this; + _.each(this.model.categories, function(category){ + self.$inputIssueCategory.append(''); + }); + }, + + linkManagement: function () { + var that = this; + var $affectedDocumentsLinkZone = this.$('#documents-affected-links'); + + that._affectedDocumentsCollection = new LinkedDocumentCollection(); + var linkedDocumentsView = new LinkedDocumentsView({ + editMode: true, + commentEditable: false, + collection: that._affectedDocumentsCollection + }).render(); + + that._subViews.push(linkedDocumentsView); + $affectedDocumentsLinkZone.html(linkedDocumentsView.el); + + var $affectedPartsLinkZone = this.$('#parts-affected-links'); + + that._affectedPartsCollection = new LinkedPartCollection(); + var linkedPartsView = new LinkedPartsView({ + editMode: true, + collection: that._affectedPartsCollection + }).render(); + + that._subViews.push(linkedPartsView); + $affectedPartsLinkZone.html(linkedPartsView.el); + + }, + + bindDomElements: function () { + this.$modal = this.$('#issue_creation_modal'); + this.$inputIssueName = this.$('#inputIssueName'); + this.$inputIssueDescription = this.$('#inputIssueDescription'); + this.$inputIssuePriority = this.$('#inputIssuePriority'); + this.$inputIssueAssignee = this.$('#inputIssueAssignee'); + this.$inputIssueCategory = this.$('#inputIssueCategory'); + this.$inputIssueInitiator = this.$('#inputIssueInitiator'); + }, + + interceptSubmit : function(){ + this.isValid = ! this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + + if(this.isValid){ + var data = { + name: this.$inputIssueName.val(), + description: this.$inputIssueDescription.val(), + author: App.config.login, + assignee: this.$inputIssueAssignee.val(), + priority: this.$inputIssuePriority.val(), + category: this.$inputIssueCategory.val(), + initiator: this.$inputIssueInitiator.val() + }; + + this.model.save(data, { + success: this.onIssueCreated, + error: this.onError, + wait: true + }); + } + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onIssueCreated: function (model) { + this.collection.push(model); + this.updateAffectedDocuments(model); + this.updateAffectedParts(model); + this.closeModal(); + }, + + onError: function (model, error) { + window.alert(App.config.i18n.CREATION_ERROR + ' : ' + error.responseText); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + updateAffectedDocuments: function (model) { + if (this._affectedDocumentsCollection.length) { + model.saveAffectedDocuments(this._affectedDocumentsCollection); + } + }, + + updateAffectedParts: function (model) { + if (this._affectedPartsCollection.length) { + model.saveAffectedParts(this._affectedPartsCollection); + } + } + }); + + return ChangeIssueCreationView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_edition.js b/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_edition.js new file mode 100644 index 0000000000..f618ff1fd3 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_edition.js @@ -0,0 +1,214 @@ +/*global _,define,App,window*/ +define([ + 'backbone', + 'mustache', + 'text!templates/change-issues/change_issue_edition.html', + 'common-objects/collections/users', + 'common-objects/utils/date', + 'common-objects/models/tag', + 'common-objects/views/tags/tag', + 'common-objects/views/linked/linked_documents', + 'common-objects/collections/linked/linked_document_collection', + 'common-objects/views/linked/linked_parts', + 'common-objects/collections/linked/linked_part_collection' + ], + function (Backbone, Mustache, template, UserList, Date, Tag, TagView, LinkedDocumentsView, LinkedDocumentCollection, LinkedPartsView, LinkedPartCollection) { + 'use strict'; + var ChangeIssueEditionView = Backbone.View.extend({ + events: { + 'submit #issue_edition_form': 'onSubmitForm', + 'hidden #issue_edition_modal': 'onHidden', + 'close-modal-request':'closeModal' + }, + + initialize: function () { + this.tagsToRemove = []; + this._subViews = []; + _.bindAll(this); + this.$el.on('remove', this.removeSubviews); // Remove cascade + }, + + removeSubviews: function () { + _(this._subViews).invoke('remove'); + }, + + render: function () { + this.removeSubviews(); + this.editMode = this.model.isWritable(); + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, model: this.model})); + this.bindDomElements(); + this.bindUserPopover(); + Date.dateHelper(this.$('.date-popover')); + new UserList().fetch({success: this.fillUserList}); + this.fillPriorityList(); + this.fillCategoryList(); + this.initValue(); + this.tagManagement(); + this.linkManagement(); + return this; + }, + + fillUserList: function (list) { + var self = this; + list.each(function (user) { + self.$inputIssueAssignee.append(''); + }); + this.$inputIssueAssignee.val(this.model.getAssignee()); + }, + fillPriorityList: function () { + var self = this; + _.each(this.model.priorities, function(priority){ + self.$inputIssuePriority.append(''); + }); + this.$inputIssuePriority.val(this.model.getPriority()); + }, + fillCategoryList: function () { + var self = this; + _.each(this.model.categories, function(category){ + self.$inputIssueCategory.append(''); + }); + this.$inputIssueCategory.val(this.model.getCategory()); + }, + + tagManagement: function () { + var that = this; + + if (this.model.attributes.tags.length) { + + var $tagsZone = this.$('.master-tags-list'); + _.each(that.model.attributes.tags, function (tagLabel) { + var tagView; + var tagViewParams = { + model: new Tag({id: tagLabel, label: tagLabel}), + isAdded: that.editMode, + clicked: function () { + that.tagsToRemove.push(tagLabel); + tagView.$el.remove(); + } + }; + + tagView = new TagView(tagViewParams).render(); + that._subViews.push(tagView); + + $tagsZone.append(tagView.el); + }); + + } + }, + + linkManagement: function () { + var that = this; + var affectedDocuments = this.model.getAffectedDocuments(); + var $affectedDocumentsLinkZone = this.$('#documents-affected-links'); + + that._affectedDocumentsCollection = new LinkedDocumentCollection(affectedDocuments); + var linkedDocumentsView = new LinkedDocumentsView({ + editMode: that.editMode, + commentEditable:false, + collection: that._affectedDocumentsCollection + }).render(); + + that._subViews.push(linkedDocumentsView); + $affectedDocumentsLinkZone.html(linkedDocumentsView.el); + + var affectedParts = this.model.getAffectedParts(); + var $affectedPartsLinkZone = this.$('#parts-affected-links'); + + that._affectedPartsCollection = new LinkedPartCollection(affectedParts); + var linkedPartsView = new LinkedPartsView({ + editMode: that.editMode, + collection: that._affectedPartsCollection + }).render(); + + that._subViews.push(linkedPartsView); + $affectedPartsLinkZone.html(linkedPartsView.el); + + }, + + bindDomElements: function () { + this.$modal = this.$('#issue_edition_modal'); + this.$inputIssueName = this.$('#inputIssueName'); + this.$inputIssueInitiator = this.$('#inputIssueInitiator'); + this.$inputIssueDescription = this.$('#inputIssueDescription'); + this.$inputIssuePriority = this.$('#inputIssuePriority'); + this.$inputIssueAssignee = this.$('#inputIssueAssignee'); + this.$inputIssueCategory = this.$('#inputIssueCategory'); + this.$inputIssueInitiator = this.$('#inputIssueInitiator'); + this.$authorLink = this.$('.author-popover'); + }, + + bindUserPopover: function () { + this.$authorLink.userPopover(this.model.getAuthor(), this.model.getId(), 'right'); + }, + + initValue: function () { + this.$inputIssueName.val(this.model.getName()); + this.$inputIssueInitiator.val(this.model.getInitiator()); + this.$inputIssueDescription.val(this.model.getDescription()); + }, + + onSubmitForm: function (e) { + var data = { + description: this.$inputIssueDescription.val(), + author: App.config.login, + assignee: this.$inputIssueAssignee.val(), + priority: this.$inputIssuePriority.val(), + category: this.$inputIssueCategory.val(), + initiator: this.$inputIssueInitiator.val() + }; + + this.model.save(data, { + success: this.closeModal, + error: this.onError, + wait: true + }); + + this.deleteClickedTags(); // Delete tags if needed + this.updateAffectedDocuments(); + this.updateAffectedParts(); + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onError: function (model, error) { + window.alert(App.config.i18n.EDITION_ERROR + ' : ' + error.responseText); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + deleteClickedTags: function () { + if (this.tagsToRemove.length) { + var that = this; + this.model.removeTags(this.tagsToRemove, function () { + if (that.model.collection.parent) { + if (_.contains(that.tagsToRemove, that.model.collection.parent.id)) { + that.model.collection.remove(that.model); + } + } + }); + } + }, + + updateAffectedDocuments: function () { + this.model.saveAffectedDocuments(this._affectedDocumentsCollection); + }, + + updateAffectedParts: function () { + this.model.saveAffectedParts(this._affectedPartsCollection); + } + }); + + return ChangeIssueEditionView; + }); diff --git a/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_list.js b/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_list.js new file mode 100644 index 0000000000..06bed131ea --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_list.js @@ -0,0 +1,192 @@ +/*global _,define,bootbox,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/change_item_list.html', + 'views/change-issues/change_issue_list_item' +], function (Backbone, Mustache, template, ChangeIssueListItemView) { + 'use strict'; + var ChangeIssueListView = Backbone.View.extend({ + events: { + 'click .toggle-checkboxes': 'toggleSelection' + }, + + removeSubviews: function () { + _(this.listItemViews).invoke('remove'); // Invoke remove for each views in listItemViews + }, + + initialize: function () { + _.bindAll(this); + this.listenTo(this.collection, 'reset', this.resetList); + this.listenTo(this.collection, 'add', this.addNewIssue); + this.listItemViews = []; + this.$el.on('remove', this.removeSubviews); + }, + + render: function () { + this.collection.fetch({reset: true}); + return this; + }, + + bindDomElements: function () { + this.$items = this.$('.items'); + this.$checkbox = this.$('.toggle-checkboxes'); + }, + + resetList: function () { + if (this.oTable) { + this.oTable.fnDestroy(); + } + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + this.listItemViews = []; + var that = this; + this.collection.each(function (model) { + that.addIssue(model); + }); + this.dataTable(); + + }, + + addNewIssue: function (model) { + this.addIssue(model, true); + this.redraw(); + }, + + addIssue: function (model, effect) { + var view = new ChangeIssueListItemView({model: model}).render(); + this.listItemViews.push(view); + this.$items.append(view.$el); + view.on('selectionChanged', this.onSelectionChanged); + view.on('rendered', this.redraw); + if (effect) { + view.$el.highlightEffect(); + } + }, + + removeIssue: function (model) { + var viewToRemove = _(this.listItemViews).select(function (view) { + return view.model === model; + })[0]; + + if (viewToRemove) { + this.listItemViews = _(this.listItemViews).without(viewToRemove); + var row = viewToRemove.$el.get(0); + this.oTable.fnDeleteRow(this.oTable.fnGetPosition(row)); + viewToRemove.remove(); + } + this.redraw(); + }, + + toggleSelection: function () { + if (this.$checkbox.is(':checked')) { + _(this.listItemViews).each(function (view) { + view.check(); + }); + } else { + _(this.listItemViews).each(function (view) { + view.unCheck(); + }); + } + this.onSelectionChanged(); + }, + + onSelectionChanged: function () { + var checkedViewsLength = _(this.listItemViews).select(function (view) { + return view.isChecked(); + }).length; + + if (checkedViewsLength <= 0) { // No Issue Selected + this.trigger('delete-button:display', false); + this.trigger('acl-button:display', false); + } else if (checkedViewsLength === 1) { // One Issue Selected + this.trigger('delete-button:display', true); + this.trigger('acl-button:display', true); + } else { // Several Issue Selected + this.trigger('delete-button:display', true); + this.trigger('acl-button:display', false); + } + }, + + getChecked: function () { + var model = null; + _(this.listItemViews).each(function (view) { + if (view.isChecked()) { + model = view.model; + } + }); + return model; + }, + + eachChecked: function (callback) { + _(this.listItemViews).each(function (view) { + if (view.isChecked()) { + callback(view); + } + }); + }, + + deleteSelectedIssues: function () { + var _this = this; + bootbox.confirm(App.config.i18n.CONFIRM_DELETE_ISSUE, function(result){ + if(result){ + _(_this.listItemViews).each(function (view) { + if (view.isChecked()) { + view.model.destroy({ + wait:true, + dataType: 'text', // server doesn't send a json hash in the response body + success: function () { + _this.removeIssue(view.model); + _this.onSelectionChanged(); + }, + error: function (model, err) { + _this.trigger('error', model, err); + _this.onSelectionChanged(); + } + }); + } + }); + } + }); + }, + + redraw: function () { + + this.dataTable(); + this.eachChecked(function (view) { + view.unCheck(); + }); + + }, + + dataTable: function () { + var oldSort = [ + [1, 'asc'] + ]; + if (this.oTable) { + oldSort = this.oTable.fnSettings().aaSorting; + this.oTable.fnDestroy(); + } + this.oTable = this.$el.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + { 'bSortable': false, 'aTargets': [ 0, 5 ] }, + { 'sType': App.config.i18n.DATE_SORT, 'aTargets': [6] }, + { 'sType': 'strip_html', 'aTargets': [1] } + ] + }); + this.$el.parent().find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + } + + }); + + return ChangeIssueListView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_list_item.js b/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_list_item.js new file mode 100644 index 0000000000..629293ae4f --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-issues/change_issue_list_item.js @@ -0,0 +1,74 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/change-issues/change_issue_list_item.html', + 'views/change-issues/change_issue_edition', + 'common-objects/utils/date' +], function (Backbone, Mustache, template, ChangeIssueEditionView,date) { + 'use strict'; + var ChangeIssueListItemView = Backbone.View.extend({ + + events: { + 'click input[type=checkbox]': 'selectionChanged', + 'click td.change_issue_name': 'openEditionView' + }, + + tagName: 'tr', + + initialize: function () { + this._isChecked = false; + this.listenTo(this.model, 'change', this.render); + }, + + render: function () { + this.$el.html(Mustache.render(template, {model: this.model, i18n: App.config.i18n})); + this.$checkbox = this.$('input[type=checkbox]'); + this.bindUserPopover(); + date.dateHelper(this.$('.date-popover')); + this.trigger('rendered', this); + return this; + }, + + selectionChanged: function () { + this._isChecked = this.$checkbox.prop('checked'); + this.trigger('selectionChanged', this); + }, + + isChecked: function () { + return this._isChecked; + }, + + check: function () { + this.$checkbox.prop('checked', true); + this._isChecked = true; + this.trigger('selectionChanged', this); + }, + + unCheck: function () { + this.$checkbox.prop('checked', false); + this._isChecked = false; + this.trigger('selectionChanged', this); + }, + + bindUserPopover: function () { + this.$('.author-popover').userPopover(this.model.getAuthor(), this.model.getName(), 'left'); + this.$('.assigned-user-popover').userPopover(this.model.getAssignee(), this.model.getName(), 'left'); + }, + + openEditionView: function () { + var _this = this; + + this.model.fetch().success(function () { + var editionView = new ChangeIssueEditionView({ + collection: _this.collection, + model: _this.model + }); + window.document.body.appendChild(editionView.render().el); + editionView.openModal(); + }); + } + }); + + return ChangeIssueListItemView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/change-orders/change_order_content.js b/docdoku-web-front/app/change-management/js/views/change-orders/change_order_content.js new file mode 100644 index 0000000000..8af1260f97 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-orders/change_order_content.js @@ -0,0 +1,155 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'collections/change_order_collection', + 'text!templates/change-orders/change_order_content.html', + 'views/change-orders/change_order_list', + 'views/change-orders/change_order_creation', + 'common-objects/views/tags/tags_management', + 'common-objects/views/security/acl_edit', + 'common-objects/views/alert', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/tags_button.html', + 'text!common-objects/templates/buttons/ACL_button.html' +], function (Backbone, Mustache, ChangeOrderCollection, template, ChangeOrderListView, ChangeOrderCreationView, TagsManagementView, ACLEditView, AlertView, deleteButton, tagsButton, aclButton) { + 'use strict'; + var ChangeOrderContentView = Backbone.View.extend({ + events: { + 'click button.new-order': 'newOrder', + 'click button.delete': 'deleteOrder', + 'click button.edit-acl': 'actionEditAcl', + 'click button.tags': 'actionTags' + }, + + partials: { + deleteButton: deleteButton, + tagsButton: tagsButton, + aclButton: aclButton + }, + + initialize: function () { + _.bindAll(this); + this.collection = new ChangeOrderCollection(); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n}, this.partials)); + + this.bindDomElements(); + + this.listView = new ChangeOrderListView({ + el: this.$('#order_table'), + collection: this.collection + }).render(); + + this.listView.on('delete-button:display', this.changeDeleteButtonDisplay); + this.listView.on('acl-button:display', this.changeAclButtonDisplay); + this.listView.on('error', this.onError); + this.tagsButton.show(); + + this.$el.on('remove', this.listView.remove); + return this; + }, + + bindDomElements: function () { + this.deleteButton = this.$('.delete'); + this.aclButton = this.$('.edit-acl'); + this.tagsButton = this.$('.tags'); + this.$notifications = this.$el.find('.notifications').first(); + }, + + newOrder: function () { + var self = this; + var orderCreationView = new ChangeOrderCreationView({ + collection: self.collection + }); + window.document.body.appendChild(orderCreationView.render().el); + orderCreationView.openModal(); + + }, + + deleteOrder: function () { + this.listView.deleteSelectedOrders(); + }, + + actionTags: function () { + var changeOrdersChecked = new Backbone.Collection(); + + + this.listView.eachChecked(function (view) { + changeOrdersChecked.push(view.model); + }); + + var tagsManagementView = new TagsManagementView({ + collection: changeOrdersChecked + }); + window.document.body.appendChild(tagsManagementView.el); + tagsManagementView.show(); + + + return false; + }, + + actionEditAcl: function () { + var modelChecked = this.listView.getChecked(); + + if (modelChecked) { + var self = this; + modelChecked.fetch(); + var aclEditView = new ACLEditView({ + editMode: true, + acl: modelChecked.getACL() + }); + + aclEditView.setTitle(modelChecked.getName()); + window.document.body.appendChild(aclEditView.render().el); + + aclEditView.openModal(); + aclEditView.on('acl:update', function () { + var acl = aclEditView.toList(); + modelChecked.updateACL({ + acl: acl || {userEntries: {}, groupEntries: {}}, + success: function () { + modelChecked.set('acl', acl); + aclEditView.closeModal(); + self.listView.redraw(); + }, + error: function(model, error){ + aclEditView.onError(model, error); + } + }); + + }); + + } + }, + + changeDeleteButtonDisplay: function (state) { + if (state) { + this.deleteButton.show(); + } else { + this.deleteButton.hide(); + } + }, + + changeAclButtonDisplay: function (state) { + if (state) { + this.aclButton.show(); + } else { + this.aclButton.hide(); + } + }, + + onError: function(model, error) { + var errorMessage = error ? error.responseText : model; + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + } + + }); + + return ChangeOrderContentView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/change-orders/change_order_creation.js b/docdoku-web-front/app/change-management/js/views/change-orders/change_order_creation.js new file mode 100644 index 0000000000..1fcb6c1444 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-orders/change_order_creation.js @@ -0,0 +1,198 @@ +/*global _,define,App,window*/ +define([ + 'backbone', + 'mustache', + 'text!templates/change-orders/change_order_creation.html', + 'models/change_order', + 'common-objects/collections/users', + 'collections/milestone_collection', + 'common-objects/views/linked/linked_documents', + 'common-objects/collections/linked/linked_document_collection', + 'common-objects/views/linked/linked_parts', + 'common-objects/collections/linked/linked_part_collection', + 'common-objects/views/linked/linked_requests', + 'common-objects/collections/linked/linked_change_item_collection' +], function (Backbone, Mustache, template, ChangeOrderModel, UserList, MilestoneList, LinkedDocumentsView, LinkedDocumentCollection, LinkedPartsView, LinkedPartCollection,LinkedRequestsView,LinkedChangeItemCollection) { + 'use strict'; + var ChangeOrderCreationView = Backbone.View.extend({ + events: { + 'click .modal-footer .btn-primary': 'interceptSubmit', + 'submit #order_creation_form': 'onSubmitForm', + 'hidden #order_creation_modal': 'onHidden', + 'close-modal-request':'closeModal' + }, + + initialize: function () { + this._subViews = []; + this.model = new ChangeOrderModel(); + _.bindAll(this); + this.$el.on('remove', this.removeSubviews); // Remove cascade + }, + + removeSubviews: function () { + _(this._subViews).invoke('remove'); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + new UserList().fetch({success: this.fillUserList}); + new MilestoneList().fetch({success: this.fillMilestoneList}); + this.fillPriorityList(); + this.fillCategoryList(); + this.linkManagement(); + this.$inputOrderName.customValidity(App.config.i18n.REQUIRED_FIELD); + return this; + }, + + fillMilestoneList: function (list) { + var self = this; + if (list) { + list.each(function (milestone) { + self.$inputOrderMilestone.append(''); + }); + } + }, + fillUserList: function (list) { + var self = this; + if (list) { + list.each(function (user) { + self.$inputOrderAssignee.append(''); + }); + } + }, + fillPriorityList: function () { + var self = this; + _.each(this.model.priorities, function(priority){ + self.$inputOrderPriority.append(''); + }); + }, + fillCategoryList: function () { + var self = this; + _.each(this.model.categories, function(category){ + self.$inputOrderCategory.append(''); + }); + }, + + linkManagement: function () { + var that = this; + var $affectedDocumentsLinkZone = this.$('#documents-affected-links'); + + that._affectedDocumentsCollection = new LinkedDocumentCollection(); + that._linkedDocumentsView = new LinkedDocumentsView({ + editMode: true, + commentEditable:false, + collection: that._affectedDocumentsCollection + }).render(); + + that._subViews.push(that._linkedDocumentsView); + $affectedDocumentsLinkZone.html(that._linkedDocumentsView.el); + + var $affectedPartsLinkZone = this.$('#parts-affected-links'); + + that._affectedPartsCollection = new LinkedPartCollection(); + that._linkedPartsView = new LinkedPartsView({ + editMode: true, + collection: that._affectedPartsCollection + }).render(); + + that._subViews.push(that._linkedPartsView); + $affectedPartsLinkZone.html(that._linkedPartsView.el); + + var $affectedRequestsLinkZone = this.$('#requests-affected-links'); + + that._affectedRequestsCollection = new LinkedChangeItemCollection(); + var linkedRequestsView = new LinkedRequestsView({ + editMode: true, + collection: that._affectedRequestsCollection, + linkedPartsView: that._linkedPartsView, + linkedDocumentsView: that._linkedDocumentsView + }).render(); + + that._subViews.push(linkedRequestsView); + $affectedRequestsLinkZone.html(linkedRequestsView.el); + + }, + + bindDomElements: function () { + this.$modal = this.$('#order_creation_modal'); + this.$inputOrderName = this.$('#inputOrderName'); + this.$inputOrderDescription = this.$('#inputOrderDescription'); + this.$inputOrderMilestone = this.$('#inputOrderMilestone'); + this.$inputOrderPriority = this.$('#inputOrderPriority'); + this.$inputOrderAssignee = this.$('#inputOrderAssignee'); + this.$inputOrderCategory = this.$('#inputOrderCategory'); + }, + + interceptSubmit : function(){ + this.isValid = ! this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + if(this.isValid){ + var data = { + name: this.$inputOrderName.val(), + description: this.$inputOrderDescription.val(), + author: App.config.login, + assignee: this.$inputOrderAssignee.val(), + priority: this.$inputOrderPriority.val(), + category: this.$inputOrderCategory.val(), + milestoneId: parseInt(this.$inputOrderMilestone.val(), 10) + }; + + this.model.save(data, { + success: this.onOrderCreated, + error: this.error, + wait: true + }); + } + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onOrderCreated: function (model) { + this.collection.push(model); + this.updateAffectedDocuments(model); + this.updateAffectedParts(model); + this.updateAffectedRequests(model); + this.closeModal(); + }, + + onError: function (model, error) { + window.alert(App.config.i18n.CREATION_ERROR + ' : ' + error.responseText); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + updateAffectedDocuments: function (model) { + if (this._affectedDocumentsCollection.length) { + model.saveAffectedDocuments(this._affectedDocumentsCollection); + } + }, + + updateAffectedParts: function (model) { + if (this._affectedPartsCollection.length) { + model.saveAffectedParts(this._affectedPartsCollection); + } + }, + + updateAffectedRequests: function (model) { + if (this._affectedRequestsCollection.length) { + model.saveAffectedRequests(this._affectedRequestsCollection); + } + } + }); + + return ChangeOrderCreationView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/change-orders/change_order_edition.js b/docdoku-web-front/app/change-management/js/views/change-orders/change_order_edition.js new file mode 100644 index 0000000000..ad1c1a329a --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-orders/change_order_edition.js @@ -0,0 +1,246 @@ +/*global _,define,App,window*/ +define([ + 'backbone', + 'mustache', + 'text!templates/change-orders/change_order_edition.html', + 'models/change_order', + 'common-objects/collections/users', + 'collections/milestone_collection', + 'common-objects/utils/date', + 'common-objects/models/tag', + 'common-objects/views/tags/tag', + 'common-objects/views/linked/linked_documents', + 'common-objects/collections/linked/linked_document_collection', + 'common-objects/views/linked/linked_parts', + 'common-objects/collections/linked/linked_part_collection', + 'common-objects/views/linked/linked_requests', + 'common-objects/collections/linked/linked_change_item_collection' + ], + function (Backbone, Mustache, template, ChangeOrderModel, UserList, MilestoneList, Date, Tag, TagView, LinkedDocumentsView, LinkedDocumentCollection, LinkedPartsView, LinkedPartCollection, LinkedRequestsView, LinkedChangeItemCollection) { + 'use strict'; + var ChangeOrderEditionView = Backbone.View.extend({ + events: { + 'submit #order_edition_form': 'onSubmitForm', + 'hidden #order_edition_modal': 'onHidden', + 'close-modal-request':'closeModal' + }, + + + initialize: function () { + this.tagsToRemove = []; + this._subViews = []; + _.bindAll(this); + this.$el.on('remove', this.removeSubviews); // Remove cascade + }, + + removeSubviews: function () { + _(this._subViews).invoke('remove'); + }, + + render: function () { + this.removeSubviews(); + this.editMode = this.model.isWritable(); + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, model: this.model})); + this.bindDomElements(); + this.bindUserPopover(); + Date.dateHelper(this.$('.date-popover')); + new UserList().fetch({success: this.fillUserList}); + new MilestoneList().fetch({success: this.fillMilestoneList}); + this.fillPriorityList(); + this.fillCategoryList(); + this.initValue(); + this.tagManagement(); + this.linkManagement(); + return this; + }, + + fillMilestoneList: function (list) { + var self = this; + if (list) { + list.each(function (milestone) { + self.$inputOrderMilestone.append(''); + }); + } + this.$inputOrderMilestone.val(this.model.getMilestoneId()); + }, + fillUserList: function (list) { + var self = this; + list.each(function (user) { + self.$inputOrderAssignee.append(''); + }); + this.$inputOrderAssignee.val(this.model.getAssignee()); + }, + fillPriorityList: function () { + var self = this; + _.each(this.model.priorities, function(priority){ + self.$inputOrderPriority.append(''); + }); + this.$inputOrderPriority.val(this.model.getPriority()); + }, + fillCategoryList: function () { + var self = this; + _.each(this.model.categories, function(category){ + self.$inputOrderCategory.append(''); + }); + this.$inputOrderCategory.val(this.model.getCategory()); + }, + + tagManagement: function () { + var that = this; + + if (this.model.attributes.tags.length) { + + var $tagsZone = this.$('.master-tags-list'); + _.each(that.model.attributes.tags, function (tagLabel) { + var tagView; + var tagViewParams = { + model: new Tag({id: tagLabel, label: tagLabel}), + isAdded: that.editMode, + clicked: function () { + that.tagsToRemove.push(tagLabel); + tagView.$el.remove(); + } + }; + + tagView = new TagView(tagViewParams).render(); + that._subViews.push(tagView); + + $tagsZone.append(tagView.el); + }); + + } + }, + + linkManagement: function () { + var that = this; + var affectedDocuments = this.model.getAffectedDocuments(); + var $affectedDocumentsLinkZone = this.$('#documents-affected-links'); + + that._affectedDocumentsCollection = new LinkedDocumentCollection(affectedDocuments); + that._linkedDocumentsView = new LinkedDocumentsView({ + editMode: that.editMode, + commentEditable:false, + collection: that._affectedDocumentsCollection + }).render(); + + that._subViews.push(that._linkedDocumentsView); + $affectedDocumentsLinkZone.html(that._linkedDocumentsView.el); + + var affectedParts = this.model.getAffectedParts(); + var $affectedPartsLinkZone = this.$('#parts-affected-links'); + + that._affectedPartsCollection = new LinkedPartCollection(affectedParts); + that._linkedPartsView = new LinkedPartsView({ + editMode: that.editMode, + collection: that._affectedPartsCollection + }).render(); + + that._subViews.push(that._linkedPartsView); + $affectedPartsLinkZone.html(that._linkedPartsView.el); + + var affectedRequests = this.model.getAddressedChangeRequests(); + var $affectedRequestsLinkZone = this.$('#requests-affected-links'); + + that._affectedRequestsCollection = new LinkedChangeItemCollection(affectedRequests); + var linkedRequestsView = new LinkedRequestsView({ + editMode: that.editMode, + collection: that._affectedRequestsCollection, + linkedPartsView: that._linkedPartsView, + linkedDocumentsView: that._linkedDocumentsView + }).render(); + + that._subViews.push(linkedRequestsView); + $affectedRequestsLinkZone.html(linkedRequestsView.el); + + }, + + bindDomElements: function () { + this.$modal = this.$('#order_edition_modal'); + this.$inputOrderName = this.$('#inputOrderName'); + this.$inputOrderDescription = this.$('#inputOrderDescription'); + this.$inputOrderMilestone = this.$('#inputOrderMilestone'); + this.$inputOrderPriority = this.$('#inputOrderPriority'); + this.$inputOrderAssignee = this.$('#inputOrderAssignee'); + this.$inputOrderCategory = this.$('#inputOrderCategory'); + this.$authorLink = this.$('.author-popover'); + }, + + bindUserPopover: function () { + this.$authorLink.userPopover(this.model.getAuthor(), this.model.getId(), 'right'); + }, + + initValue: function () { + this.$inputOrderName.val(this.model.getName()); + this.$inputOrderDescription.val(this.model.getDescription()); + }, + + onSubmitForm: function (e) { + var data = { + description: this.$inputOrderDescription.val(), + author: App.config.login, + assignee: this.$inputOrderAssignee.val(), + priority: this.$inputOrderPriority.val(), + category: this.$inputOrderCategory.val(), + milestoneId: parseInt(this.$inputOrderMilestone.val(), 10) + }; + + this.model.save(data, { + success: this.closeModal, + error: this.error, + wait: true + }); + + this.deleteClickedTags(); // Delete tags if needed + this.updateAffectedDocuments(); + this.updateAffectedParts(); + this.updateAffectedRequests(); + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onError: function (model, error) { + window.alert(App.config.i18n.EDITION_ERROR + ' : ' + error.responseText); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + deleteClickedTags: function () { + if (this.tagsToRemove.length) { + var that = this; + this.model.removeTags(this.tagsToRemove, function () { + if (that.model.collection.parent) { + if (_.contains(that.tagsToRemove, that.model.collection.parent.id)) { + that.model.collection.remove(that.model); + } + } + }); + } + }, + + updateAffectedDocuments: function () { + this.model.saveAffectedDocuments(this._affectedDocumentsCollection); + }, + + updateAffectedParts: function () { + this.model.saveAffectedParts(this._affectedPartsCollection); + }, + + updateAffectedRequests: function () { + this.model.saveAffectedRequests(this._affectedRequestsCollection); + } + }); + + return ChangeOrderEditionView; + }); diff --git a/docdoku-web-front/app/change-management/js/views/change-orders/change_order_list.js b/docdoku-web-front/app/change-management/js/views/change-orders/change_order_list.js new file mode 100644 index 0000000000..c4f2707ec1 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-orders/change_order_list.js @@ -0,0 +1,182 @@ +/*global _,define,bootbox,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/change_item_list.html', + 'views/change-orders/change_order_list_item' +], function (Backbone, Mustache, template, ChangeOrderListItemView) { + 'use strict'; + var ChangeOrderListView = Backbone.View.extend({ + + + + events: { + 'click .toggle-checkboxes': 'toggleSelection' + }, + + removeSubviews: function () { + _(this.listItemViews).invoke('remove'); // Invoke remove for each views in listItemViews + }, + + initialize: function () { + _.bindAll(this); + this.listenTo(this.collection, 'reset', this.resetList); + this.listenTo(this.collection, 'add', this.addNewOrder); + this.listItemViews = []; + this.$el.on('remove', this.removeSubviews); + }, + + render: function () { + this.collection.fetch({reset: true}); + return this; + }, + + bindDomElements: function () { + this.$items = this.$('.items'); + this.$checkbox = this.$('.toggle-checkboxes'); + }, + + resetList: function () { + if (this.oTable) { + this.oTable.fnDestroy(); + } + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + this.listItemViews = []; + var that = this; + this.collection.each(function (model) { + that.addOrder(model); + }); + this.dataTable(); + }, + + addNewOrder: function (model) { + this.addOrder(model, true); + this.redraw(); + }, + + addOrder: function (model, effect) { + var view = new ChangeOrderListItemView({model: model}).render(); + this.listItemViews.push(view); + this.$items.append(view.$el); + view.on('selectionChanged', this.onSelectionChanged); + view.on('rendered', this.redraw); + if (effect) { + view.$el.highlightEffect(); + } + }, + + removeOrder: function (model) { + var viewToRemove = _(this.listItemViews).select(function (view) { + return view.model === model; + })[0]; + + if (viewToRemove) { + this.listItemViews = _(this.listItemViews).without(viewToRemove); + var row = viewToRemove.$el.get(0); + this.oTable.fnDeleteRow(this.oTable.fnGetPosition(row)); + viewToRemove.remove(); + } + this.redraw(); + }, + + toggleSelection: function () { + _(this.listItemViews).invoke((this.$checkbox.is(':checked')) ? 'check' : 'unCheck'); // Check a list item view if its checkbox is checked + this.onSelectionChanged(); + }, + + onSelectionChanged: function () { + var checkedViews = _(this.listItemViews).select(function (view) { + return view.isChecked(); + }); + + if (checkedViews.length <= 0) { // No Order Selected + this.trigger('delete-button:display', false); + this.trigger('acl-button:display', false); + } else if (checkedViews.length === 1) { // One Order Selected + this.trigger('delete-button:display', true); + this.trigger('acl-button:display', true); + } else { // Several Order Selected + this.trigger('delete-button:display', true); + this.trigger('acl-button:display', false); + } + }, + + getChecked: function () { + var model = null; + _(this.listItemViews).each(function (view) { + if (view.isChecked()) { + model = view.model; + } + }); + return model; + }, + + eachChecked: function (callback) { + _(this.listItemViews).each(function (view) { + if (view.isChecked()) { + callback(view); + } + }); + }, + + deleteSelectedOrders: function () { + var _this = this; + bootbox.confirm(App.config.i18n.CONFIRM_DELETE_ORDER, function(result){ + if(result){ + _(_this.listItemViews).each(function (view) { + if (view.isChecked()) { + view.model.destroy({ + wait:true, + dataType: 'text', // server doesn't send a json hash in the response body + success: function () { + _this.removeOrder(view.model); + _this.onSelectionChanged(); + }, + error: function (model, err) { + _this.trigger('error', model, err); + _this.onSelectionChanged(); + } + }); + } + }); + } + }); + }, + + redraw: function () { + this.dataTable(); + this.eachChecked(function (view) { + view.unCheck(); + }); + }, + + dataTable: function () { + var oldSort = [ + [1, 'asc'] + ]; + if (this.oTable) { + oldSort = this.oTable.fnSettings().aaSorting; + this.oTable.fnDestroy(); + } + this.oTable = this.$el.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + { 'bSortable': false, 'aTargets': [ 0, 5 ] }, + { 'sType': 'strip_html', 'aTargets': [1] } + ] + }); + this.$el.parent().find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + } + }); + + return ChangeOrderListView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/change-orders/change_order_list_item.js b/docdoku-web-front/app/change-management/js/views/change-orders/change_order_list_item.js new file mode 100644 index 0000000000..82472e7cb8 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-orders/change_order_list_item.js @@ -0,0 +1,74 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/change-orders/change_order_list_item.html', + 'views/change-orders/change_order_edition', + 'common-objects/utils/date' +], function (Backbone, Mustache, template, ChangeOrderEditionView, date) { + 'use strict'; + var ChangeOrderListItemView = Backbone.View.extend({ + + events: { + 'click input[type=checkbox]': 'selectionChanged', + 'click td.change_order_name': 'openEditionView' + }, + + tagName: 'tr', + + initialize: function () { + this._isChecked = false; + this.listenTo(this.model, 'change', this.render); + }, + + render: function () { + this.$el.html(Mustache.render(template, {model: this.model, i18n: App.config.i18n})); + this.$checkbox = this.$('input[type=checkbox]'); + this.bindUserPopover(); + date.dateHelper(this.$('.date-popover')); + this.trigger('rendered', this); + return this; + }, + + selectionChanged: function () { + this._isChecked = this.$checkbox.prop('checked'); + this.trigger('selectionChanged', this); + }, + + isChecked: function () { + return this._isChecked; + }, + + check: function () { + this.$checkbox.prop('checked', true); + this._isChecked = true; + this.trigger('selectionChanged', this); + }, + + unCheck: function () { + this.$checkbox.prop('checked', false); + this._isChecked = false; + this.trigger('selectionChanged', this); + }, + + bindUserPopover: function () { + this.$('.author-popover').userPopover(this.model.getAuthor(), this.model.getName(), 'left'); + this.$('.assigned-user-popover').userPopover(this.model.getAssignee(), this.model.getName(), 'left'); + }, + + openEditionView: function () { + var _this = this; + + this.model.fetch().success(function () { + var editionView = new ChangeOrderEditionView({ + collection: _this.collection, + model: _this.model + }); + window.document.body.appendChild(editionView.render().el); + editionView.openModal(); + }); + } + }); + + return ChangeOrderListItemView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/change-requests/change_request_content.js b/docdoku-web-front/app/change-management/js/views/change-requests/change_request_content.js new file mode 100644 index 0000000000..816d326ce5 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-requests/change_request_content.js @@ -0,0 +1,155 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'collections/change_request_collection', + 'text!templates/change-requests/change_request_content.html', + 'views/change-requests/change_request_list', + 'views/change-requests/change_request_creation', + 'common-objects/views/tags/tags_management', + 'common-objects/views/security/acl_edit', + 'common-objects/views/alert', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/tags_button.html', + 'text!common-objects/templates/buttons/ACL_button.html' +], function (Backbone, Mustache, ChangeRequestCollection, template, ChangeRequestListView, ChangeRequestCreationView, TagsManagementView, ACLEditView, AlertView, deleteButton, tagsButton, aclButton) { + 'use strict'; + var ChangeRequestContentView = Backbone.View.extend({ + + + events: { + 'click button.new-request': 'newRequest', + 'click button.delete': 'deleteRequest', + 'click button.edit-acl': 'actionEditAcl', + 'click button.tags': 'actionTags' + }, + + partials: { + deleteButton: deleteButton, + tagsButton: tagsButton, + aclButton: aclButton + }, + + initialize: function () { + _.bindAll(this); + this.collection = new ChangeRequestCollection(); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n}, this.partials)); + + this.bindDomElements(); + + this.listView = new ChangeRequestListView({ + el: this.$('#request_table'), + collection: this.collection + }).render(); + + this.listView.on('delete-button:display', this.changeDeleteButtonDisplay); + this.listView.on('acl-button:display', this.changeAclButtonDisplay); + this.listView.on('error', this.onError); + this.tagsButton.show(); + + this.$el.on('remove', this.listView.remove); + return this; + }, + + bindDomElements: function () { + this.deleteButton = this.$('.delete'); + this.aclButton = this.$('.edit-acl'); + this.tagsButton = this.$('.tags'); + this.$notifications = this.$('.notifications').first(); + }, + + newRequest: function () { + var self = this; + var requestCreationView = new ChangeRequestCreationView({ + collection: self.collection + }); + window.document.body.appendChild(requestCreationView.render().el); + requestCreationView.openModal(); + + }, + + deleteRequest: function () { + this.listView.deleteSelectedRequests(); + }, + + actionTags: function () { + var changeRequestsChecked = new Backbone.Collection(); + + + this.listView.eachChecked(function (view) { + changeRequestsChecked.push(view.model); + }); + + var tagsManagementView = new TagsManagementView({ + collection: changeRequestsChecked + }); + window.document.body.appendChild(tagsManagementView.el); + tagsManagementView.show(); + + return false; + }, + + actionEditAcl: function () { + var modelChecked = this.listView.getChecked(); + + if (modelChecked) { + var self = this; + modelChecked.fetch(); + var aclEditView = new ACLEditView({ + editMode: true, + acl: modelChecked.getACL() + }); + + aclEditView.setTitle(modelChecked.getName()); + window.document.body.appendChild(aclEditView.render().el); + + aclEditView.openModal(); + aclEditView.on('acl:update', function () { + var acl = aclEditView.toList(); + modelChecked.updateACL({ + acl: acl || {userEntries: {}, groupEntries: {}}, + success: function () { + modelChecked.set('acl', acl); + aclEditView.closeModal(); + self.listView.redraw(); + }, + error: function(model, error){ + aclEditView.onError(model, error); + } + }); + + }); + + } + }, + + changeDeleteButtonDisplay: function (state) { + if (state) { + this.deleteButton.show(); + } else { + this.deleteButton.hide(); + } + }, + changeAclButtonDisplay: function (state) { + if (state) { + this.aclButton.show(); + } else { + this.aclButton.hide(); + } + }, + + onError: function(model, error) { + var errorMessage = error ? error.responseText : model; + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + + } + }); + + return ChangeRequestContentView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/change-requests/change_request_creation.js b/docdoku-web-front/app/change-management/js/views/change-requests/change_request_creation.js new file mode 100644 index 0000000000..5f6e2dd205 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-requests/change_request_creation.js @@ -0,0 +1,201 @@ +/*global _,define,App,window*/ +define([ + 'backbone', + 'mustache', + 'text!templates/change-requests/change_request_creation.html', + 'models/change_request', + 'common-objects/collections/users', + 'collections/milestone_collection', + 'common-objects/views/linked/linked_documents', + 'common-objects/collections/linked/linked_document_collection', + 'common-objects/views/linked/linked_parts', + 'common-objects/collections/linked/linked_part_collection', + 'common-objects/views/linked/linked_issues', + 'common-objects/collections/linked/linked_change_item_collection' +], function (Backbone, Mustache, template, ChangeRequestModel, UserList, MilestoneList, LinkedDocumentsView, LinkedDocumentCollection, LinkedPartsView, LinkedPartCollection, LinkedIssuesView, LinkedChangeItemCollection) { + 'use strict'; + var ChangeRequestCreationView = Backbone.View.extend({ + events: { + 'click .modal-footer .btn-primary': 'interceptSubmit', + 'submit #request_creation_form': 'onSubmitForm', + 'hidden #request_creation_modal': 'onHidden', + 'close-modal-request':'closeModal' + }, + + initialize: function () { + this._subViews = []; + this.model = new ChangeRequestModel(); + _.bindAll(this); + this.$el.on('remove', this.removeSubviews); // Remove cascade + }, + + removeSubviews: function () { + _(this._subViews).invoke('remove'); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + new UserList().fetch({success: this.fillUserList}); + new MilestoneList().fetch({success: this.fillMilestoneList}); + this.fillPriorityList(); + this.fillCategoryList(); + this.linkManagement(); + this.$inputRequestName.customValidity(App.config.i18n.REQUIRED_FIELD); + + return this; + }, + + fillMilestoneList: function (list) { + var self = this; + if (list) { + list.each(function (milestone) { + self.$inputRequestMilestone.append(''); + }); + } + }, + fillUserList: function (list) { + var self = this; + if (list) { + list.each(function (user) { + self.$inputRequestAssignee.append(''); + }); + } + }, + fillPriorityList: function () { + var self = this; + _.each(this.model.priorities, function(priority){ + self.$inputRequestPriority.append(''); + }); + }, + fillCategoryList: function () { + var self = this; + _.each(this.model.categories, function(category){ + self.$inputRequestCategory.append(''); + }); + }, + + linkManagement: function () { + var that = this; + var $affectedDocumentsLinkZone = this.$('#documents-affected-links'); + + that._affectedDocumentsCollection = new LinkedDocumentCollection(); + that._linkedDocumentsView = new LinkedDocumentsView({ + editMode: true, + commentEditable:false, + collection: that._affectedDocumentsCollection + }).render(); + + that._subViews.push(that._linkedDocumentsView); + $affectedDocumentsLinkZone.html(that._linkedDocumentsView.el); + + var $affectedPartsLinkZone = this.$('#parts-affected-links'); + + that._affectedPartsCollection = new LinkedPartCollection(); + that._linkedPartsView = new LinkedPartsView({ + editMode: true, + collection: that._affectedPartsCollection + }).render(); + + that._subViews.push(that._linkedPartsView); + $affectedPartsLinkZone.html(that._linkedPartsView.el); + + var $affectedIssuesLinkZone = this.$('#issues-affected-links'); + + that._affectedIssuesCollection = new LinkedChangeItemCollection(); + var linkedIssuesView = new LinkedIssuesView({ + editMode: true, + collection: that._affectedIssuesCollection, + linkedPartsView: that._linkedPartsView, + linkedDocumentsView: that._linkedDocumentsView + }).render(); + + that._subViews.push(linkedIssuesView); + $affectedIssuesLinkZone.html(linkedIssuesView.el); + + }, + + bindDomElements: function () { + this.$modal = this.$('#request_creation_modal'); + this.$inputRequestName = this.$('#inputRequestName'); + this.$inputRequestDescription = this.$('#inputRequestDescription'); + this.$inputRequestMilestone = this.$('#inputRequestMilestone'); + this.$inputRequestPriority = this.$('#inputRequestPriority'); + this.$inputRequestAssignee = this.$('#inputRequestAssignee'); + this.$inputRequestCategory = this.$('#inputRequestCategory'); + }, + + interceptSubmit : function(){ + this.isValid = ! this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + + if(this.isValid){ + var data = { + name: this.$inputRequestName.val(), + description: this.$inputRequestDescription.val(), + author: App.config.login, + assignee: this.$inputRequestAssignee.val(), + priority: this.$inputRequestPriority.val(), + category: this.$inputRequestCategory.val(), + milestoneId: parseInt(this.$inputRequestMilestone.val(), 10) + }; + + this.model.save(data, { + success: this.onRequestCreated, + error: this.error, + wait: true + }); + } + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onRequestCreated: function (model) { + this.collection.push(model); + this.updateAffectedDocuments(model); + this.updateAffectedParts(model); + this.updateAffectedIssues(model); + this.closeModal(); + }, + + onError: function (model, error) { + window.alert(App.config.i18n.CREATION_ERROR + ' : ' + error.responseText); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + updateAffectedDocuments: function (model) { + if (this._affectedDocumentsCollection.length) { + model.saveAffectedDocuments(this._affectedDocumentsCollection); + } + }, + + updateAffectedParts: function (model) { + if (this._affectedPartsCollection.length) { + model.saveAffectedParts(this._affectedPartsCollection); + } + }, + + updateAffectedIssues: function (model) { + if (this._affectedIssuesCollection.length) { + model.saveAffectedIssues(this._affectedIssuesCollection); + } + } + }); + + return ChangeRequestCreationView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/change-requests/change_request_edition.js b/docdoku-web-front/app/change-management/js/views/change-requests/change_request_edition.js new file mode 100644 index 0000000000..101a1efb7d --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-requests/change_request_edition.js @@ -0,0 +1,246 @@ +/*global _,define,App,window*/ +define([ + 'backbone', + 'mustache', + 'text!templates/change-requests/change_request_edition.html', + 'models/change_request', + 'common-objects/collections/users', + 'collections/milestone_collection', + 'common-objects/utils/date', + 'common-objects/models/tag', + 'common-objects/views/tags/tag', + 'common-objects/views/linked/linked_documents', + 'common-objects/collections/linked/linked_document_collection', + 'common-objects/views/linked/linked_parts', + 'common-objects/collections/linked/linked_part_collection', + 'common-objects/views/linked/linked_issues', + 'common-objects/collections/linked/linked_change_item_collection' + ], + function (Backbone, Mustache, template, ChangeRequestModel, UserList, MilestoneList, Date, Tag, TagView, LinkedDocumentsView, LinkedDocumentCollection, LinkedPartsView, LinkedPartCollection, LinkedIssuesView, LinkedChangeItemCollection) { + 'use strict'; + var ChangeRequestEditionView = Backbone.View.extend({ + events: { + 'submit #request_edition_form': 'onSubmitForm', + 'hidden #request_edition_modal': 'onHidden', + 'close-modal-request':'closeModal' + }, + + + initialize: function () { + this.tagsToRemove = []; + this._subViews = []; + _.bindAll(this); + this.$el.on('remove', this.removeSubviews); // Remove cascade + }, + + removeSubviews: function () { + _(this._subViews).invoke('remove'); + }, + + render: function () { + this.removeSubviews(); + this.editMode = this.model.isWritable(); + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, model: this.model})); + this.bindDomElements(); + this.bindUserPopover(); + Date.dateHelper(this.$('.date-popover')); + new UserList().fetch({success: this.fillUserList}); + new MilestoneList().fetch({success: this.fillMilestoneList}); + this.fillPriorityList(); + this.fillCategoryList(); + this.initValue(); + this.tagManagement(); + this.linkManagement(); + return this; + }, + + fillMilestoneList: function (list) { + var self = this; + if (list) { + list.each(function (milestone) { + self.$inputRequestMilestone.append(''); + }); + } + this.$inputRequestMilestone.val(this.model.getMilestoneId()); + }, + fillUserList: function (list) { + var self = this; + list.each(function (user) { + self.$inputRequestAssignee.append(''); + }); + this.$inputRequestAssignee.val(this.model.getAssignee()); + }, + fillPriorityList: function () { + var self = this; + _.each(this.model.priorities, function(priority){ + self.$inputRequestPriority.append(''); + }); + this.$inputRequestPriority.val(this.model.getPriority()); + }, + fillCategoryList: function () { + var self = this; + _.each(this.model.categories, function(category){ + self.$inputRequestCategory.append(''); + }); + this.$inputRequestCategory.val(this.model.getCategory()); + }, + + tagManagement: function () { + var that = this; + + if (this.model.attributes.tags.length) { + + var $tagsZone = this.$('.master-tags-list'); + _.each(that.model.attributes.tags, function (tagLabel) { + var tagView; + var tagViewParams = { + model: new Tag({id: tagLabel, label: tagLabel}), + isAdded: that.editMode, + clicked: function () { + that.tagsToRemove.push(tagLabel); + tagView.$el.remove(); + } + }; + + tagView = new TagView(tagViewParams).render(); + that._subViews.push(tagView); + + $tagsZone.append(tagView.el); + }); + + } + }, + + linkManagement: function () { + var that = this; + var affectedDocuments = this.model.getAffectedDocuments(); + var $affectedDocumentsLinkZone = this.$('#documents-affected-links'); + + that._affectedDocumentsCollection = new LinkedDocumentCollection(affectedDocuments); + that._linkedDocumentsView = new LinkedDocumentsView({ + editMode: that.editMode, + commentEditable:false, + collection: that._affectedDocumentsCollection + }).render(); + + that._subViews.push(that._linkedDocumentsView); + $affectedDocumentsLinkZone.html(that._linkedDocumentsView.el); + + var affectedParts = this.model.getAffectedParts(); + var $affectedPartsLinkZone = this.$('#parts-affected-links'); + + that._affectedPartsCollection = new LinkedPartCollection(affectedParts); + that._linkedPartsView = new LinkedPartsView({ + editMode: that.editMode, + collection: that._affectedPartsCollection + }).render(); + + that._subViews.push(that._linkedPartsView); + $affectedPartsLinkZone.html(that._linkedPartsView.el); + + var affectedIssues = this.model.getAddressedChangeIssues(); + var $affectedIssuesLinkZone = this.$('#issues-affected-links'); + + that._affectedIssuesCollection = new LinkedChangeItemCollection(affectedIssues); + var linkedIssuesView = new LinkedIssuesView({ + editMode: that.editMode, + collection: that._affectedIssuesCollection, + linkedPartsView: that._linkedPartsView, + linkedDocumentsView: that._linkedDocumentsView + }).render(); + + that._subViews.push(linkedIssuesView); + $affectedIssuesLinkZone.html(linkedIssuesView.el); + + }, + + bindDomElements: function () { + this.$modal = this.$('#request_edition_modal'); + this.$inputRequestName = this.$('#inputRequestName'); + this.$inputRequestDescription = this.$('#inputRequestDescription'); + this.$inputRequestMilestone = this.$('#inputRequestMilestone'); + this.$inputRequestPriority = this.$('#inputRequestPriority'); + this.$inputRequestAssignee = this.$('#inputRequestAssignee'); + this.$inputRequestCategory = this.$('#inputRequestCategory'); + this.$authorLink = this.$('.author-popover'); + }, + + bindUserPopover: function () { + this.$authorLink.userPopover(this.model.getAuthor(), this.model.getId(), 'right'); + }, + + initValue: function () { + this.$inputRequestName.val(this.model.getName()); + this.$inputRequestDescription.val(this.model.getDescription()); + }, + + onSubmitForm: function (e) { + var data = { + description: this.$inputRequestDescription.val(), + author: App.config.login, + assignee: this.$inputRequestAssignee.val(), + priority: this.$inputRequestPriority.val(), + category: this.$inputRequestCategory.val(), + milestoneId: parseInt(this.$inputRequestMilestone.val(), 10) + }; + + this.model.save(data, { + success: this.closeModal, + error: this.error, + wait: true + }); + + this.deleteClickedTags(); // Delete tags if needed + this.updateAffectedDocuments(); + this.updateAffectedParts(); + this.updateAffectedIssues(); + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onError: function (model, error) { + window.alert(App.config.i18n.EDITION_ERROR + ' : ' + error.responseText); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + deleteClickedTags: function () { + if (this.tagsToRemove.length) { + var that = this; + this.model.removeTags(this.tagsToRemove, function () { + if (that.model.collection.parent) { + if (_.contains(that.tagsToRemove, that.model.collection.parent.id)) { + that.model.collection.remove(that.model); + } + } + }); + } + }, + + updateAffectedDocuments: function () { + this.model.saveAffectedDocuments(this._affectedDocumentsCollection); + }, + + updateAffectedParts: function () { + this.model.saveAffectedParts(this._affectedPartsCollection); + }, + + updateAffectedIssues: function () { + this.model.saveAffectedIssues(this._affectedIssuesCollection); + } + }); + + return ChangeRequestEditionView; + }); diff --git a/docdoku-web-front/app/change-management/js/views/change-requests/change_request_list.js b/docdoku-web-front/app/change-management/js/views/change-requests/change_request_list.js new file mode 100644 index 0000000000..c310e54788 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-requests/change_request_list.js @@ -0,0 +1,182 @@ +/*global _,define,bootbox,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/change_item_list.html', + 'views/change-requests/change_request_list_item' +], function (Backbone, Mustache, template, ChangeRequestListItemView) { + 'use strict'; + var ChangeRequestListView = Backbone.View.extend({ + + + + events: { + 'click .toggle-checkboxes': 'toggleSelection' + }, + + removeSubviews: function () { + _(this.listItemViews).invoke('remove'); // Invoke remove for each views in listItemViews + }, + + initialize: function () { + _.bindAll(this); + this.listenTo(this.collection, 'reset', this.resetList); + this.listenTo(this.collection, 'add', this.addNewRequest); + this.listItemViews = []; + this.$el.on('remove', this.removeSubviews); + }, + + render: function () { + this.collection.fetch({reset: true}); + return this; + }, + + bindDomElements: function () { + this.$items = this.$('.items'); + this.$checkbox = this.$('.toggle-checkboxes'); + }, + + resetList: function () { + if (this.oTable) { + this.oTable.fnDestroy(); + } + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + this.listItemViews = []; + var that = this; + this.collection.each(function (model) { + that.addRequest(model); + }); + this.dataTable(); + }, + + addNewRequest: function (model) { + this.addRequest(model, true); + this.redraw(); + }, + + addRequest: function (model, effect) { + var view = new ChangeRequestListItemView({model: model}).render(); + this.listItemViews.push(view); + this.$items.append(view.$el); + view.on('selectionChanged', this.onSelectionChanged); + view.on('rendered', this.redraw); + if (effect) { + view.$el.highlightEffect(); + } + }, + + removeRequest: function (model) { + var viewToRemove = _(this.listItemViews).select(function (view) { + return view.model === model; + })[0]; + + if (viewToRemove) { + this.listItemViews = _(this.listItemViews).without(viewToRemove); + var row = viewToRemove.$el.get(0); + this.oTable.fnDeleteRow(this.oTable.fnGetPosition(row)); + viewToRemove.remove(); + } + this.redraw(); + }, + + toggleSelection: function () { + _(this.listItemViews).invoke((this.$checkbox.is(':checked')) ? 'check' : 'unCheck'); // Check a list item view if its checkbox is checked + this.onSelectionChanged(); + }, + + onSelectionChanged: function () { + var checkedViews = _(this.listItemViews).select(function (view) { + return view.isChecked(); + }); + + if (checkedViews.length <= 0) { // No Request Selected + this.trigger('delete-button:display', false); + this.trigger('acl-button:display', false); + } else if (checkedViews.length === 1) { // One Request Selected + this.trigger('delete-button:display', true); + this.trigger('acl-button:display', true); + } else { // Several Request Selected + this.trigger('delete-button:display', true); + this.trigger('acl-button:display', false); + } + }, + + getChecked: function () { + var model = null; + _(this.listItemViews).each(function (view) { + if (view.isChecked()) { + model = view.model; + } + }); + return model; + }, + + eachChecked: function (callback) { + _(this.listItemViews).each(function (view) { + if (view.isChecked()) { + callback(view); + } + }); + }, + + deleteSelectedRequests: function () { + var _this = this; + bootbox.confirm(App.config.i18n.CONFIRM_DELETE_REQUEST, function(result){ + if(result){ + _(_this.listItemViews).each(function (view) { + if (view.isChecked()) { + view.model.destroy({ + wait:true, + dataType: 'text', // server doesn't send a json hash in the response body + success: function () { + _this.removeRequest(view.model); + _this.onSelectionChanged(); + }, + error: function (model, err) { + _this.trigger('error', model, err); + _this.onSelectionChanged(); + } + }); + } + }); + } + }); + }, + + redraw: function () { + this.dataTable(); + this.eachChecked(function (view) { + view.unCheck(); + }); + }, + + dataTable: function () { + var oldSort = [ + [1, 'asc'] + ]; + if (this.oTable) { + oldSort = this.oTable.fnSettings().aaSorting; + this.oTable.fnDestroy(); + } + this.oTable = this.$el.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + { 'bSortable': false, 'aTargets': [ 0, 5 ] }, + { 'sType': 'strip_html', 'aTargets': [1] } + ] + }); + this.$el.parent().find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + } + }); + + return ChangeRequestListView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/change-requests/change_request_list_item.js b/docdoku-web-front/app/change-management/js/views/change-requests/change_request_list_item.js new file mode 100644 index 0000000000..3df4388358 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/change-requests/change_request_list_item.js @@ -0,0 +1,74 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/change-requests/change_request_list_item.html', + 'views/change-requests/change_request_edition', + 'common-objects/utils/date' +], function (Backbone, Mustache, template, ChangeRequestEditionView,date) { + 'use strict'; + var ChangeRequestListItemView = Backbone.View.extend({ + + events: { + 'click input[type=checkbox]': 'selectionChanged', + 'click td.change_request_name': 'openEditionView' + }, + + tagName: 'tr', + + initialize: function () { + this._isChecked = false; + this.listenTo(this.model, 'change', this.render); + }, + + render: function () { + this.$el.html(Mustache.render(template, {model: this.model, i18n: App.config.i18n})); + this.$checkbox = this.$('input[type=checkbox]'); + this.bindUserPopover(); + date.dateHelper(this.$('.date-popover')); + this.trigger('rendered', this); + return this; + }, + + selectionChanged: function () { + this._isChecked = this.$checkbox.prop('checked'); + this.trigger('selectionChanged', this); + }, + + isChecked: function () { + return this._isChecked; + }, + + check: function () { + this.$checkbox.prop('checked', true); + this._isChecked = true; + this.trigger('selectionChanged', this); + }, + + unCheck: function () { + this.$checkbox.prop('checked', false); + this._isChecked = false; + this.trigger('selectionChanged', this); + }, + + bindUserPopover: function () { + this.$('.author-popover').userPopover(this.model.getAuthor(), this.model.getName(), 'left'); + this.$('.assigned-user-popover').userPopover(this.model.getAssignee(), this.model.getName(), 'left'); + }, + + openEditionView: function () { + var _this = this; + + this.model.fetch().success(function () { + var editionView = new ChangeRequestEditionView({ + collection: _this.collection, + model: _this.model + }); + window.document.body.appendChild(editionView.render().el); + editionView.openModal(); + }); + } + }); + + return ChangeRequestListItemView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/milestones/milestone_content.js b/docdoku-web-front/app/change-management/js/views/milestones/milestone_content.js new file mode 100644 index 0000000000..12ac1d3a7b --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/milestones/milestone_content.js @@ -0,0 +1,128 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'collections/milestone_collection', + 'text!templates/milestones/milestone_content.html', + 'views/milestones/milestone_list', + 'views/milestones/milestone_creation', + 'common-objects/views/security/acl_edit', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/ACL_button.html', + 'common-objects/views/alert' +], function (Backbone, Mustache, MilestoneCollection, template, MilestoneListView, MilestoneCreationView, ACLEditView, deleteButton, aclButton, AlertView) { + 'use strict'; + var MilestoneContentView = Backbone.View.extend({ + events: { + 'click button.new-milestone': 'newMilestone', + 'click button.delete': 'deleteMilestone', + 'click button.edit-acl': 'actionEditAcl' + }, + + partials: { + deleteButton: deleteButton, + aclButton: aclButton + }, + + initialize: function () { + _.bindAll(this); + this.collection = new MilestoneCollection(); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n}, this.partials)); + + this.bindDomElements(); + + this.listView = new MilestoneListView({ + el: this.$('#milestone_table'), + collection: this.collection + }).render(); + + this.listView.on('delete-button:display', this.changeDeleteButtonDisplay); + this.listView.on('acl-button:display', this.changeAclButtonDisplay); + this.listView.on('error', this.onError); + + return this; + }, + + bindDomElements: function () { + this.deleteButton = this.$('.delete'); + this.aclButton = this.$('.edit-acl'); + this.$notifications = this.$('.notifications'); + }, + + newMilestone: function () { + var self = this; + var milestoneCreationView = new MilestoneCreationView({ + collection: self.collection + }); + window.document.body.appendChild(milestoneCreationView.render().el); + milestoneCreationView.openModal(); + }, + + deleteMilestone: function () { + this.listView.deleteSelectedMilestones(); + }, + + actionEditAcl: function () { + var modelChecked = this.listView.getChecked(); + + if (modelChecked) { + var self = this; + modelChecked.fetch(); + var aclEditView = new ACLEditView({ + editMode: true, + acl: modelChecked.getACL() + }); + + aclEditView.setTitle(modelChecked.getTitle()); + window.document.body.appendChild(aclEditView.render().el); + + aclEditView.openModal(); + aclEditView.on('acl:update', function () { + var acl = aclEditView.toList(); + modelChecked.updateACL({ + acl: acl || {userEntries: {}, groupEntries: {}}, + success: function () { + modelChecked.set('acl', acl); + aclEditView.closeModal(); + self.listView.redraw(); + }, + error: function(model, error){ + aclEditView.onError(model, error); + } + }); + + }); + } + }, + + changeDeleteButtonDisplay: function (state) { + if (state) { + this.deleteButton.show(); + } else { + this.deleteButton.hide(); + } + }, + + changeAclButtonDisplay: function (state) { + if (state) { + this.aclButton.show(); + } else { + this.aclButton.hide(); + } + }, + + onError: function(model, error) { + var errorMessage = error ? error.responseText : model; + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + } + + }); + + return MilestoneContentView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/milestones/milestone_creation.js b/docdoku-web-front/app/change-management/js/views/milestones/milestone_creation.js new file mode 100644 index 0000000000..91b5bf99fd --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/milestones/milestone_creation.js @@ -0,0 +1,92 @@ +/*global _,define,App,window*/ +define([ + 'backbone', + 'mustache', + 'text!templates/milestones/milestone_creation.html', + 'models/milestone', + 'common-objects/utils/date' +], function (Backbone, Mustache, template, MilestoneModel,date) { + 'use strict'; + var MilestoneCreationView = Backbone.View.extend({ + events: { + 'click .modal-footer .btn-primary': 'interceptSubmit', + 'submit #milestone_creation_form': 'onSubmitForm', + 'hidden #milestone_creation_modal': 'onHidden', + 'shown #milestone_creation_modal': 'onShown' + }, + + + initialize: function () { + _.bindAll(this); + this.model = new MilestoneModel(); + }, + + render: function () { + this.$el.html(Mustache.render(template, { + timeZone:App.config.timeZone, + language : App.config.locale, + i18n: App.config.i18n + })); + this.bindDomElements(); + this.$('input[required]').customValidity(App.config.i18n.REQUIRED_FIELD); + return this; + }, + + bindDomElements: function () { + this.$modal = this.$('#milestone_creation_modal'); + this.$inputMilestoneTitle = this.$('#inputMilestoneTitle'); + this.$inputMilestoneDescription = this.$('#inputMilestoneDescription'); + this.$inputMilestoneDueDate = this.$('#inputMilestoneDueDate'); + }, + interceptSubmit : function(){ + this.isValid = ! this.$('#milestone_creation_form').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + if(this.isValid) { + var data = { + title: this.$inputMilestoneTitle.val(), + description: this.$inputMilestoneDescription.val(), + dueDate: date.toUTCWithTimeZoneOffset(this.$inputMilestoneDueDate.val()) + }; + + this.model.save(data, { + success: this.onMilestoneCreated, + error: this.onError, + wait: true + }); + } + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onMilestoneCreated: function (model) { + this.collection.push(model); + this.closeModal(); + }, + + onError: function (model, error) { + window.alert(App.config.i18n.CREATION_ERROR + ' : ' + error.responseText); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onShown: function () { + this.$modal.addClass('ready'); + }, + + onHidden: function () { + this.remove(); + } + }); + + return MilestoneCreationView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/milestones/milestone_edition.js b/docdoku-web-front/app/change-management/js/views/milestones/milestone_edition.js new file mode 100644 index 0000000000..5b8a13d6c6 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/milestones/milestone_edition.js @@ -0,0 +1,134 @@ +/*global _,define,App,window*/ +define([ + 'backbone', + 'mustache', + 'text!templates/milestones/milestone_edition.html', + 'common-objects/views/linked/linked_requests', + 'common-objects/collections/linked/linked_change_item_collection', + 'common-objects/views/linked/linked_orders', + 'common-objects/utils/date' +], function (Backbone, Mustache, template, LinkedRequestsView, LinkedChangeItemCollection, LinkedOrdersView, date) { + 'use strict'; + var MilestoneEditionView = Backbone.View.extend({ + events: { + 'submit #milestone_edition_form': 'onSubmitForm', + 'hidden #milestone_edition_modal': 'onHidden' + }, + + + initialize: function () { + this._subViews = []; + this.model.fetch(); + _.bindAll(this); + this.$el.on('remove', this.removeSubviews); // Remove cascade + }, + + removeSubviews: function () { + _(this._subViews).invoke('remove'); + }, + + render: function () { + var hasRequest = this.model.getNumberOfRequests() > 0; + var hasOrder = this.model.getNumberOfOrders() > 0; + this.removeSubviews(); + this.editMode = this.model.isWritable(); + this.$el.html(Mustache.render(template, { + timeZone: App.config.timeZone, + language : App.config.locale, + i18n: App.config.i18n, + hasRequest: hasRequest, + hasOrder: hasOrder, + model: this.model + })); + this.bindDomElements(); + this.initValue(); + this.linkManagement(); + return this; + }, + + bindDomElements: function () { + this.$modal = this.$('#milestone_edition_modal'); + this.$inputMilestoneTitle = this.$('#inputMilestoneTitle'); + this.$inputMilestoneDescription = this.$('#inputMilestoneDescription'); + this.$inputMilestoneDueDate = this.$('#inputMilestoneDueDate'); + }, + + initValue: function () { + this.$inputMilestoneTitle.val(this.model.getTitle()); + this.$inputMilestoneDueDate.val(this.model.getDueDateDatePicker()); + this.$inputMilestoneDescription.val(this.model.getDescription()); + }, + + linkManagement: function () { + var that = this; + var $affectedRequestsLinkZone = this.$('#requests-affected-links'); + + var affectedRequestsCollection = new LinkedChangeItemCollection(); + affectedRequestsCollection.url = that.model.url() + '/requests'; + affectedRequestsCollection.fetch({ + success: function () { + var linkedRequestsView = new LinkedRequestsView({ + editMode: false, + collection: affectedRequestsCollection + }).render(); + + that._subViews.push(linkedRequestsView); + $affectedRequestsLinkZone.html(linkedRequestsView.el); + } + }); + + var $affectedOrdersLinkZone = this.$('#orders-affected-links'); + + var affectedOrdersCollection = new LinkedChangeItemCollection(); + affectedOrdersCollection.url = that.model.url() + '/orders'; + affectedOrdersCollection.fetch({ + success: function () { + var linkedOrdersView = new LinkedOrdersView({ + editMode: false, + collection: affectedOrdersCollection + }).render(); + + that._subViews.push(linkedOrdersView); + $affectedOrdersLinkZone.html(linkedOrdersView.el); + } + }); + + }, + + onSubmitForm: function (e) { + var data = { + title: this.$inputMilestoneTitle.val(), + description: this.$inputMilestoneDescription.val(), + dueDate: date.toUTCWithTimeZoneOffset(this.$inputMilestoneDueDate.val()) + }; + + this.model.save(data, { + success: this.closeModal, + error: this.onError, + wait: true + }); + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onError: function (model, error) { + window.alert(App.config.i18n.EDITION_ERROR + ' : ' + error.responseText); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + } + }); + + return MilestoneEditionView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/milestones/milestone_list.js b/docdoku-web-front/app/change-management/js/views/milestones/milestone_list.js new file mode 100644 index 0000000000..571849b775 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/milestones/milestone_list.js @@ -0,0 +1,194 @@ +/*global _,define,App,bootbox*/ +define([ + 'backbone', + 'mustache', + 'text!templates/milestones/milestone_list.html', + 'views/milestones/milestone_list_item' +], function (Backbone, Mustache, template, MilestoneListItemView) { + 'use strict'; + var MilestoneListView = Backbone.View.extend({ + + events: { + 'click .toggle-checkboxes': 'toggleSelection' + }, + + initialize: function () { + _.bindAll(this); + this.listenTo(this.collection, 'reset', this.resetList); + this.listenTo(this.collection, 'add', this.addNewMilestone); + this.listItemViews = []; + }, + + render: function () { + this.collection.fetch({reset: true}); + return this; + }, + + bindDomElements: function () { + this.$items = this.$('.items'); + this.$checkbox = this.$('.toggle-checkboxes'); + }, + + resetList: function () { + if (this.oTable) { + this.oTable.fnDestroy(); + } + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + this.listItemViews = []; + var that = this; + this.collection.each(function (model) { + that.addMilestone(model); + }); + this.dataTable(); + }, + + addNewMilestone: function (model) { + this.addMilestone(model, true); + this.redraw(); + }, + + addMilestone: function (model, effect) { + var view = this.addMilestoneView(model); + if (effect) { + view.$el.highlightEffect(); + } + }, + + removeMilestone: function (model) { + this.removeMilestoneView(model); + this.redraw(); + }, + + removeMilestoneView: function (model) { + var viewToRemove = _(this.listItemViews).select(function (view) { + return view.model === model; + })[0]; + + if (viewToRemove) { + this.listItemViews = _(this.listItemViews).without(viewToRemove); + var row = viewToRemove.$el.get(0); + this.oTable.fnDeleteRow(this.oTable.fnGetPosition(row)); + viewToRemove.remove(); + } + }, + + addMilestoneView: function (model) { + var view = new MilestoneListItemView({model: model}).render(); + this.listItemViews.push(view); + this.$items.append(view.$el); + view.on('selectionChanged', this.onSelectionChanged); + view.on('rendered', this.redraw); + return view; + }, + + toggleSelection: function () { + if (this.$checkbox.is(':checked')) { + _(this.listItemViews).each(function (view) { + view.check(); + }); + } else { + _(this.listItemViews).each(function (view) { + view.unCheck(); + }); + } + this.onSelectionChanged(); + }, + + onSelectionChanged: function () { + var checkedViewsLength = _(this.listItemViews).select(function (view) { + return view.isChecked(); + }).length; + + if (checkedViewsLength <= 0) { // No Milestone Selected + this.trigger('delete-button:display', false); + this.trigger('acl-button:display', false); + } else if (checkedViewsLength === 1) { // One Milestone Selected + this.trigger('delete-button:display', true); + this.trigger('acl-button:display', true); + } else { // Several Milestone Selectes + this.trigger('delete-button:display', true); + this.trigger('acl-button:display', false); + } + }, + + getChecked: function () { + var model = null; + _(this.listItemViews).each(function (view) { + if (view.isChecked()) { + model = view.model; + } + }); + return model; + }, + + eachChecked: function (callback) { + _(this.listItemViews).each(function (view) { + if (view.isChecked()) { + callback(view); + } + }); + }, + + deleteSelectedMilestones: function () { + var that = this; + bootbox.confirm(App.config.i18n.CONFIRM_DELETE_ISSUE, function(result){ + if(result){ + _(that.listItemViews).each(function (view) { + if (view.isChecked()) { + view.model.destroy({ + wait:true, + dataType: 'text', // server doesn't send a json hash in the response body + success: function () { + that.removeMilestone(view.model); + that.onSelectionChanged(); + }, + error: function (model, err) { + that.trigger('error', model, err); + that.onSelectionChanged(); + } + }); + } + }); + } + }); + + }, + + redraw: function () { + this.dataTable(); + this.eachChecked(function (view) { + view.unCheck(); + }); + }, + + dataTable: function () { + var oldSort = [ + [1, 'asc'] + ]; + if (this.oTable) { + oldSort = this.oTable.fnSettings().aaSorting; + this.oTable.fnDestroy(); + } + this.oTable = this.$el.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + { 'bSortable': false, 'aTargets': [ 0, 5 ] }, + { 'sType': 'strip_html', 'aTargets': [1] } + ] + }); + this.$el.parent().find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + } + + }); + + return MilestoneListView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/milestones/milestone_list_item.js b/docdoku-web-front/app/change-management/js/views/milestones/milestone_list_item.js new file mode 100644 index 0000000000..ed21ace5b0 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/milestones/milestone_list_item.js @@ -0,0 +1,66 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/milestones/milestone_list_item.html', + 'views/milestones/milestone_edition', + 'common-objects/utils/date' +], function (Backbone, Mustache, template, MilestoneEditionView,date) { + 'use strict'; + var MilestoneListItemView = Backbone.View.extend({ + + events: { + 'click input[type=checkbox]': 'selectionChanged', + 'click td.milestone_title': 'openEditionView' + }, + + tagName: 'tr', + + initialize: function () { + this._isChecked = false; + this.listenTo(this.model, 'change', this.render); + }, + + render: function () { + this.$el.html(Mustache.render(template, {model: this.model, i18n: App.config.i18n})); + this.$checkbox = this.$('input[type=checkbox]'); + this.trigger('rendered', this); + date.dateHelper(this.$('.date-popover')); + return this; + }, + + selectionChanged: function () { + this._isChecked = this.$checkbox.prop('checked'); + this.trigger('selectionChanged', this); + }, + + isChecked: function () { + return this._isChecked; + }, + + check: function () { + this.$checkbox.prop('checked', true); + this._isChecked = true; + this.trigger('selectionChanged', this); + }, + + unCheck: function () { + this.$checkbox.prop('checked', false); + this._isChecked = false; + this.trigger('selectionChanged', this); + }, + + openEditionView: function () { + var that = this; + this.model.fetch(); + var editionView = new MilestoneEditionView({ + collection: that.collection, + model: that.model + }); + window.document.body.appendChild(editionView.render().el); + editionView.openModal(); + } + }); + + return MilestoneListItemView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/nav/change_issue_nav.js b/docdoku-web-front/app/change-management/js/views/nav/change_issue_nav.js new file mode 100644 index 0000000000..d8ce926b2f --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/nav/change_issue_nav.js @@ -0,0 +1,45 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'text!templates/nav/change_issue_nav.html', + 'views/change-issues/change_issue_content' +], function (Backbone, Mustache, singletonDecorator, template, ChangeIssueContentView) { + 'use strict'; + var ChangeIssueNavView = Backbone.View.extend({ + el: '#issue-nav', + + initialize: function () { + this.render(); + this.contentView = undefined; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId})); + }, + + setActive: function () { + if (App.$changeManagementMenu) { + App.$changeManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + + showContent: function () { + this.setActive(); + this.cleanView(); + this.contentView = new ChangeIssueContentView().render(); + App.appView.$content.html(this.contentView.el); + }, + + cleanView: function () { + if (this.contentView) { + this.contentView.remove(); + } + } + }); + + ChangeIssueNavView = singletonDecorator(ChangeIssueNavView); + return ChangeIssueNavView; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/change-management/js/views/nav/change_order_nav.js b/docdoku-web-front/app/change-management/js/views/nav/change_order_nav.js new file mode 100644 index 0000000000..109a039245 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/nav/change_order_nav.js @@ -0,0 +1,45 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'text!templates/nav/change_order_nav.html', + 'views/change-orders/change_order_content' +], function (Backbone, Mustache, singletonDecorator, template, ChangeOrderContentView) { + 'use strict'; + var ChangeOrderNavView = Backbone.View.extend({ + el: '#order-nav', + + initialize: function () { + this.render(); + this.contentView = undefined; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId})); + }, + + setActive: function () { + if (App.$changeManagementMenu) { + App.$changeManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + + showContent: function () { + this.setActive(); + this.cleanView(); + this.contentView = new ChangeOrderContentView().render(); + App.appView.$content.html(this.contentView.el); + }, + + cleanView: function () { + if (this.contentView) { + this.contentView.remove(); + } + } + }); + + ChangeOrderNavView = singletonDecorator(ChangeOrderNavView); + return ChangeOrderNavView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/nav/change_request_nav.js b/docdoku-web-front/app/change-management/js/views/nav/change_request_nav.js new file mode 100644 index 0000000000..c1bec61de6 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/nav/change_request_nav.js @@ -0,0 +1,45 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'text!templates/nav/change_request_nav.html', + 'views/change-requests/change_request_content' +], function (Backbone, Mustache, singletonDecorator, template, ChangeRequestContentView) { + 'use strict'; + var ChangeRequestNavView = Backbone.View.extend({ + el: '#request-nav', + + initialize: function () { + this.render(); + this.contentView = undefined; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId})); + }, + + setActive: function () { + if (App.$changeManagementMenu) { + App.$changeManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + + showContent: function () { + this.setActive(); + this.cleanView(); + this.contentView = new ChangeRequestContentView().render(); + App.appView.$content.html(this.contentView.el); + }, + + cleanView: function () { + if (this.contentView) { + this.contentView.remove(); + } + } + }); + + var ChangeRequestView = singletonDecorator(ChangeRequestNavView); + return ChangeRequestView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/nav/milestone_nav.js b/docdoku-web-front/app/change-management/js/views/nav/milestone_nav.js new file mode 100644 index 0000000000..0a1d09347e --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/nav/milestone_nav.js @@ -0,0 +1,45 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'text!templates/nav/milestone_nav.html', + 'views/milestones/milestone_content' +], function (Backbone, Mustache, singletonDecorator, template, MilestoneContentView) { + 'use strict'; + var MilestoneNavView = Backbone.View.extend({ + el: '#milestone-nav', + + initialize: function () { + this.render(); + this.contentView = undefined; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId})); + }, + + setActive: function () { + if (App.$changeManagementMenu) { + App.$changeManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + + showContent: function () { + this.setActive(); + this.cleanView(); + this.contentView = new MilestoneContentView().render(); + App.appView.$content.html(this.contentView.el); + }, + + cleanView: function () { + if (this.contentView) { + this.contentView.remove(); + } + } + }); + + MilestoneNavView = singletonDecorator(MilestoneNavView); + return MilestoneNavView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/nav/task_nav.js b/docdoku-web-front/app/change-management/js/views/nav/task_nav.js new file mode 100644 index 0000000000..5cf083c874 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/nav/task_nav.js @@ -0,0 +1,49 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'views/tasks/task_content_list', + 'views/tasks/task' +], function (Backbone, Mustache, singletonDecorator, TaskListView, TaskView) { + 'use strict'; + var TaskNavView = Backbone.View.extend({ + + initialize: function () { + this.contentView = undefined; + }, + + render: function () { + }, + + setActive: function () { + if (App.$changeManagementMenu) { + App.$changeManagementMenu.find('.active').removeClass('active'); + } + }, + + showTaskContent: function (taskId) { + this.setActive(); + this.cleanView(); + this.contentView = new TaskView(); + this.contentView.renderTask(taskId); + App.appView.$content.html(this.contentView.el); + }, + + showWorkflowContent: function (workflowId) { + this.setActive(); + this.cleanView(); + this.contentView = new TaskView(); + this.contentView.renderWorkflow(workflowId); + App.appView.$content.html(this.contentView.el); + }, + cleanView: function () { + if (this.contentView) { + this.contentView.remove(); + } + } + }); + + TaskNavView = singletonDecorator(TaskNavView); + return TaskNavView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/nav/workflow_nav.js b/docdoku-web-front/app/change-management/js/views/nav/workflow_nav.js new file mode 100644 index 0000000000..a14dd6ac87 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/nav/workflow_nav.js @@ -0,0 +1,46 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'views/workflows/workflow_content_list', + 'text!templates/nav/workflow_nav.html' +], function (Backbone, Mustache, singletonDecorator, WorkflowContentListView, template) { + 'use strict'; + var WorkflowNavView = Backbone.View.extend({ + + el: '#workflow-nav', + + initialize: function () { + this.render(); + this.contentView = undefined; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId})); + }, + + setActive: function () { + if (App.$changeManagementMenu) { + App.$changeManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + + showContent: function () { + this.setActive(); + this.cleanView(); + this.contentView = new WorkflowContentListView(); + this.contentView.render(); + App.appView.$content.html(this.contentView.el); + }, + + cleanView: function () { + if (this.contentView) { + this.contentView.remove(); + } + } + }); + WorkflowNavView = singletonDecorator(WorkflowNavView); + return WorkflowNavView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/tasks/task.js b/docdoku-web-front/app/change-management/js/views/tasks/task.js new file mode 100644 index 0000000000..268144fd9a --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/tasks/task.js @@ -0,0 +1,52 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/tasks/task.html', + 'common-objects/views/workflow/lifecycle' +], function (Backbone, Mustache, template, LifecycleView) { + 'use strict'; + var TaskView = Backbone.View.extend({ + + initialize: function () { + + }, + + renderTask: function (taskId) { + var _this = this; + $.getJSON(App.config.contextPath+'/api/workspaces/'+App.config.workspaceId+'/tasks/'+taskId) + .then(function(task){ + _this.task = task; + _this.renderWorkflow(task.workflowId); + }); + return this; + }, + + renderWorkflow:function(workflowId){ + var _this = this; + $.getJSON(App.config.contextPath+'/api/workspaces/'+App.config.workspaceId+'/workflow-instances/'+workflowId) + .then(function(workflow){ + _this.workflow = workflow; + _this.render(); + }); + }, + + render:function(){ + this.$el.html(Mustache.render(template, {task:this.task, workflow:this.workflow, i18n: App.config.i18n})); + + this.lifecycleView = new LifecycleView() + .setWorkflow(this.workflow) + .render(); + + var _this = this; + this.lifecycleView.on('lifecycle:change', function () { + _this.lifecycleView.displayWorkflow(_this.workflow); + _this.renderWorkflow(_this.workflow.id); + }); + + this.$('.workflow-detail').append(this.lifecycleView.$el); + } + + }); + return TaskView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/tasks/task_content_list.js b/docdoku-web-front/app/change-management/js/views/tasks/task_content_list.js new file mode 100644 index 0000000000..fcfc4cdbfd --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/tasks/task_content_list.js @@ -0,0 +1,26 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/tasks/task_list.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var TaskListView = Backbone.View.extend({ + events: {}, + + initialize: function () { + + }, + + render: function () { + var _this = this; + $.getJSON(App.config.contextPath+'/api/workspaces/'+App.config.workspaceId+'/tasks/'+App.config.login+'/assigned') + .then(function(tasks){ + _this.$el.html(Mustache.render(template, {tasks:tasks,i18n: App.config.i18n})); + }); + return this; + } + + }); + return TaskListView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/workflows/activity_model_editor.js b/docdoku-web-front/app/change-management/js/views/workflows/activity_model_editor.js new file mode 100644 index 0000000000..a9308db9cb --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/workflows/activity_model_editor.js @@ -0,0 +1,233 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/workflows/activity_model_editor.html', + 'common-objects/models/workflow/activity_model', + 'common-objects/models/task_model', + 'views/workflows/task_model_editor' +], function (Backbone, Mustache, template, ActivityModel, TaskModel, TaskModelEditorView) { + 'use strict'; + var ActivityModelEditorView = Backbone.View.extend({ + tagName: 'li', + className: 'activity-section', + + events: { + 'click button.add-task': 'addTaskAction', + 'click button.switch-activity': 'switchActivityAction', + 'click button.delete-activity': 'deleteActivityAction', + 'change input.activity-state': 'lifeCycleStateChanged', + 'change input.tasksToComplete': 'tasksToCompleteChanged', + 'change select.relaunchActivitySelector': 'changeRelaunchActivityStep' + }, + + initialize: function () { + this.subviews = []; + + var switchModeTitle; + switch (this.model.get('type')) { + case 'SEQUENTIAL': + switchModeTitle = App.config.i18n.GOTO_PARALLEL_MODE; + break; + case 'PARALLEL': + switchModeTitle = App.config.i18n.GOTO_SEQUENTIAL_MODE; + break; + } + + this.template = Mustache.render(template, {cid: this.model.cid, activity: this.model.attributes, switchModeTitle: switchModeTitle, i18n: App.config.i18n}); + + this.model.attributes.taskModels.bind('add', this.addOneTask, this); + this.model.attributes.taskModels.bind('remove', this.removeOneTask, this); + + this.on('activities-order:changed', this.populateRelaunchActivitySelector); + this.on('activities:removed', this.populateRelaunchActivitySelector); + + }, + render: function () { + this.$el.html(this.template); + this.bindDomElements(); + this.addAllTask(); + this.populateRelaunchActivitySelector(); + return this; + }, + bindDomElements: function () { + var self = this; + + this.activityDiv = this.$('div.activity'); + + this.buttonSwitchActivity = this.$('button.switch-activity'); + + this.inputLifeCycleState = this.$('input.activity-state'); + + this.inputTasksToComplete = this.$('input.tasksToComplete'); + + this.relaunchActivitySelector = this.$('.relaunchActivitySelector'); + + this.relaunchActivitySelectorWrapper = this.$('.relaunchActivitySelector-wrapper'); + + this.tasksUL = this.$('ul.task-list'); + this.tasksUL.sortable({ + handle: 'i.fa.fa-bars', + tolerance: 'pointer', + start: function (event, ui) { + ui.item.oldPosition = ui.item.index(); + }, + stop: function (event, ui) { + self.taskPositionChanged(ui.item.oldPosition, ui.item.index()); + } + }); + }, + + addAllTask: function () { + this.model.attributes.taskModels.each(this.addOneTask, this); + }, + + addOneTask: function (taskModel) { + var _this = this; + + this.updateMaxTasksToComplete(); + + var taskModelEditorView = new TaskModelEditorView({ + model: taskModel, + roles: _this.options.roles, + newRoles: _this.options.newRoles + }); + + _this.subviews.push(taskModelEditorView); + taskModelEditorView.render(); + _this.tasksUL.append(taskModelEditorView.el); + }, + + removeOneTask: function () { + this.updateMaxTasksToComplete(); + + var cntTasks = this.model.get('taskModels').length; + + if (this.inputTasksToComplete.val() > cntTasks) { + this.inputTasksToComplete.val(cntTasks); + this.tasksToCompleteChanged(); + } + }, + + updateMaxTasksToComplete: function () { + this.inputTasksToComplete.attr({ + MAX: this.model.get('taskModels').length + }); + }, + + addTaskAction: function () { + this.inputTasksToComplete.val(parseInt(this.inputTasksToComplete.val(), 10) + 1); + this.model.attributes.taskModels.add(new TaskModel()); + this.tasksToCompleteChanged(); + return false; + }, + + switchActivityAction: function () { + switch (this.model.get('type')) { + case 'SEQUENTIAL': + this.model.set({ + type: 'PARALLEL' + }); + this.activityDiv.removeClass('SEQUENTIAL'); + this.activityDiv.addClass('PARALLEL'); + this.buttonSwitchActivity.attr({title: App.config.i18n.GOTO_SEQUENTIAL_MODE}); + break; + case 'PARALLEL': + this.model.set({ + type: 'SEQUENTIAL' + }); + this.activityDiv.removeClass('PARALLEL'); + this.activityDiv.addClass('SEQUENTIAL'); + this.buttonSwitchActivity.attr({title: App.config.i18n.GOTO_PARALLEL_MODE}); + break; + } + return false; + }, + + deleteActivityAction: function () { + this.model.collection.remove(this.model); + this.unbindAllEvents(); + this.remove(); + this.model.destroy(); + }, + + tasksToCompleteChanged: function () { + var tasksToCompleteValue = parseInt(this.inputTasksToComplete.val(), 10); + var maxTaskToComplete = this.model.attributes.taskModels.length; + if (tasksToCompleteValue <= 0) { + this.inputTasksToComplete.val(1); + this.model.set({ + tasksToComplete: 1 + }); + } else if (tasksToCompleteValue > maxTaskToComplete) { + this.inputTasksToComplete.val(maxTaskToComplete); + this.model.set({ + tasksToComplete: maxTaskToComplete + }); + } else { + this.model.set({ + tasksToComplete: tasksToCompleteValue + }); + } + }, + + taskPositionChanged: function (oldPosition, newPosition) { + var taskModel = this.model.attributes.taskModels.at(oldPosition); + this.model.attributes.taskModels.remove(taskModel, {silent: true}); + this.model.attributes.taskModels.add(taskModel, {silent: true, at: newPosition}); + }, + + lifeCycleStateChanged: function () { + this.model.set({ + lifeCycleState: this.inputLifeCycleState.val() + }); + }, + + populateRelaunchActivitySelector: function () { + var that = this; + this.relaunchActivitySelector.empty(); + this.relaunchActivitySelector.append(''); + var stepCount = 0; + + var modelIndex = this.model.collection.indexOf(this.model); + + this.model.collection.each(function (activity) { + var activityIndex = activity.collection.indexOf(activity); + if (activityIndex < modelIndex) { + if(activity.get('lifeCycleState')){ + that.relaunchActivitySelector.append(''); + } + stepCount++; + } + }); + + if (!stepCount) { + this.relaunchActivitySelectorWrapper.hide(); + } else { + this.relaunchActivitySelectorWrapper.show(); + } + + if (this.model.get('relaunchStep') !== null) { + this.relaunchActivitySelector.val(this.model.get('relaunchStep')); + } + + }, + + unbindAllEvents: function () { + _.each(this.subviews, function (subview) { + subview.unbindAllEvents(); + }); + this.undelegateEvents(); + }, + + changeRelaunchActivityStep: function (e) { + if (e.target.value === -1) { + this.model.set('relaunchStep', null); + } else { + this.model.set('relaunchStep', e.target.value); + } + } + + }); + return ActivityModelEditorView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/workflows/roles_modal_view.js b/docdoku-web-front/app/change-management/js/views/workflows/roles_modal_view.js new file mode 100644 index 0000000000..6825f1cbec --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/workflows/roles_modal_view.js @@ -0,0 +1,188 @@ +/*global _,define,App*/ +define([ + 'mustache', + 'common-objects/views/components/modal', + 'text!templates/workflows/roles_modal.html', + 'common-objects/models/role', + 'collections/roles', + 'collections/roles_in_use', + 'common-objects/views/workflow/role_item_view', + 'common-objects/views/alert', + 'common-objects/collections/users', + 'common-objects/collections/user_groups' +],function (Mustache, ModalView, template, Role, RolesList, RoleInUseList, RoleItemView, AlertView, UserList, UserGroupList) { + 'use strict'; + var RolesModalView = ModalView.extend({ + initialize: function () { + ModalView.prototype.initialize.apply(this, arguments); + this.events['submit #form-new-role'] = 'onSubmitNewRole'; + this.events['submit #form-roles'] = 'onSubmitForm'; + }, + + render: function () { + var _this = this; + + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n + })); + this.bindDomElement(); + + this.$roleViews = this.$('#form-roles'); + this.$newRoleName = this.$('input.role-name'); + + this.$newRoleName.customValidity(App.config.i18n.REQUIRED_FIELD); + + this.userList = new UserList(); + this.groupList = new UserGroupList(); + + if(!this.collection){ + this.collection = new RolesList(); + } + + this.rolesInUse = new RoleInUseList(); + + this.groupList.fetch({reset: true, success: function () { + _this.userList.fetch({reset: true, success: function () { + _this.createRoleViews(); + _this.rolesInUse.fetch({reset: true}); + }}); + }}); + + this.rolesToDelete = []; + + this.listenTo(this.collection, 'add', this.onModelAddedToCollection); + + return this; + }, + + bindDomElement: function(){ + this.$notifications = this.$el.find('.notifications').first(); + }, + + onError:function(model, error){ + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + this.collection.fetch(); + }, + + onSubmitNewRole: function (e) { + if(this.$newRoleName.val().trim() && !this.collection.findWhere({name:this.$newRoleName.val()})){ + this.collection.add({ + workspaceId: App.config.workspaceId, + name: this.$newRoleName.val(), + defaultAssignedUsers: [], + defaultAssignedGroups: [] + }); + this.resetNewRoleForm(); + } + e.preventDefault(); + e.stopPropagation(); + return false; + }, + onSubmitForm: function (e) { + var _this = this; + var toSave = this.collection.length; + var toDelete = this.rolesToDelete.length; + + // Update models which has changed + this.collection.each(function (model) { + if(!_.contains(_this.rolesToDelete, model)){ + model.save(null,{ + success: function(){ + toSave--; + if(!toDelete && !toSave){ + _this.hide(); + } + }, + error: _this.onError + }); + }else{ + toSave--; + } + }); + + // Delete roles marked for delete + _.each(this.rolesToDelete, function (model) { + model.destroy({ + dataType: 'text', // server doesn't send a json hash in the response body + success: function(){ + toDelete--; + if(!toDelete && !toSave){ + _this.hide(); + } + }, + error: _this.onError + }); + }); + + if(!toDelete && !toSave){ + _this.hide(); + } + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + createRoleViews: function () { + var _this = this; + this.roleViews = []; + this.collection.each(function (model) { + _this.addRoleView(model); + }); + }, + + resetNewRoleForm: function () { + this.$newRoleName.val(''); + }, + + onModelAddedToCollection: function (model) { + var _this = this; + this.addRoleView(model, function(){ + _this.collection.remove(model); + }); + }, + + addRoleView: function (model,onRemove) { + var _this = this; + var onViewRemove = (_.isFunction(onRemove)) ? onRemove : function () { + _this.rolesToDelete.push(model); + }; + + var modelCanBeRemoved = this.checkRemovable(model); + var view = new RoleItemView({ + model: model, + userList: this.userList, + groupList: this.groupList, + nullable: true, + removable: modelCanBeRemoved, + onError: _this.onError + }).render(); + this.roleViews.push(view); + this.$roleViews.append(view.$el); + this.addSubView(view); + + view.on('view:removed', onViewRemove); + }, + + checkRemovable: function (pModel) { + var removable = true; + this.rolesInUse.each(function (model) { + if (pModel.getName() === model.getName()) { + removable = false; + } + }); + return removable; + }, + + hidden: function () { + this.collection.fetch({reset:true}); + this.destroy(); + } + }); + return RolesModalView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/workflows/task_model_editor.js b/docdoku-web-front/app/change-management/js/views/workflows/task_model_editor.js new file mode 100644 index 0000000000..c2848554e0 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/workflows/task_model_editor.js @@ -0,0 +1,157 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/models/task_model', + 'common-objects/models/role', + 'text!templates/workflows/task_model_editor.html' +], function (Backbone, Mustache, TaskModel, Role, template) { + 'use strict'; + var TaskModelEditorView = Backbone.View.extend({ + + tagName: 'li', + + className: 'task-section', + + events: { + 'click button.delete-task': 'deleteTaskAction', + 'click p.task-name': 'gotoUnfoldState', + 'click i.fa-minus': 'gotoFoldState', + 'click .new-role': 'newRoleAction', + 'click .add-role': 'addRoleAction', + 'change input.task-name': 'titleChanged', + 'change textarea.instructions': 'instructionsChanged', + 'change select.role': 'roleSelected' + }, + + States: { + FOLD: 0, + UNFOLD: 1 + }, + + initialize: function () { + this.isNewRole = false; + this.roles = this.options.roles; + this.roles.on('add', this.onRoleAdd, this); + this.newRoles = this.options.newRoles; + + this.state = this.States.FOLD; + if (_.isUndefined(this.model.get('role'))) { + this.model.set({ + role: this.options.roles.at(0) + }); + } + }, + render: function () { + var _this = this; + this.template = Mustache.render(template, { + cid: this.model.cid, + task: this.model.attributes, + roles: this.roles.pluck('name'), + i18n: App.config.i18n + }); + this.$el.html(this.template); + this.bindDomElements(); + + // Select right role + _.each(this.roles.models, function (role) { + if (_this.model.get('role') && _this.model.get('role').get('name') === role.get('name')) { + _this.roleSelect.val(role.get('name')); + } + }); + return this; + }, + bindDomElements: function () { + this.divTask = this.$('div.task'); + this.taskContent = this.$('.task-content'); + this.pTitle = this.$('p.task-name'); + this.inputTitle = this.$('input.task-name'); + this.textareaInstructions = this.$('textarea.instructions'); + this.roleInput = this.$('.role-input'); + this.roleSelect = this.$('.role-select'); + }, + + titleChanged: function () { + this.model.set({ + title: this.inputTitle.val() + }); + if (this.inputTitle.val().length === 0) { + this.pTitle.html(App.config.i18n.TASK_NAME_PLACEHOLDER); + } + else { + this.pTitle.html(this.inputTitle.val()); + } + }, + instructionsChanged: function () { + this.model.set({ + instructions: this.textareaInstructions.val() + }); + }, + onRoleAdd: function (model) { + this.roleSelect.append(''); + }, + + + deleteTaskAction: function () { + this.model.collection.remove(this.model); + this.unbindAllEvents(); + this.remove(); + }, + + newRoleAction: function () { + this.roleInput.val(''); + this.taskContent.addClass('new-role'); + return false; + }, + addRoleAction: function () { + var roleName = this.roleInput.val(); + if (roleName) { + var selectedRole = this.roles.findWhere({name: roleName}); + if (!selectedRole) { + selectedRole = new Role({ + workspaceId: App.config.workspaceId, + name: roleName, + defaultAssignedUsers: [], + defaultAssignedGroups: [] + }); + this.newRoles.push(selectedRole); + this.roles.add(selectedRole); + } + this.roleSelect.val(roleName); + this.model.set({ + role: selectedRole + }); + } + this.taskContent.removeClass('new-role'); + return false; + }, + roleSelected: function (e) { + var nameSelected = e.target.value; + var roleSelected = _.find(this.roles.models, function (role) { + return nameSelected === role.get('name'); + }); + this.model.set({ + role: roleSelected + }); + }, + + gotoFoldState: function () { + this.state = this.States.FOLD; + this.divTask.removeClass('unfold'); + this.divTask.addClass('fold'); + this.inputTitle.prop('readonly', true); + }, + gotoUnfoldState: function () { + this.state = this.States.UNFOLD; + this.divTask.removeClass('fold'); + this.divTask.addClass('unfold'); + this.inputTitle.prop('readonly', false); + }, + + unbindAllEvents: function () { + this.undelegateEvents(); + } + + }); + return TaskModelEditorView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/workflows/workflow_content_list.js b/docdoku-web-front/app/change-management/js/views/workflows/workflow_content_list.js new file mode 100644 index 0000000000..b0c8731b7a --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/workflows/workflow_content_list.js @@ -0,0 +1,200 @@ +/*global define,bootbox,App*/ +define([ + 'require', + 'backbone', + 'mustache', + 'collections/roles', + 'views/workflows/workflow_list', + 'views/workflows/roles_modal_view', + 'common-objects/views/security/acl_edit', + 'text!templates/workflows/workflow_content_list.html', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/ACL_button.html', + 'common-objects/views/alert' +], function (require, Backbone, Mustache, RoleList, WorkflowListView, RolesModalView,ACLEditView, template, deleteButton,aclButton, AlertView) { + 'use strict'; + var WorkflowContentListView = Backbone.View.extend({ + + events:{ + 'click .actions .new':'actionNew', + 'click .actions .delete':'actionDelete', + 'click .actions .roles': 'actionRoles', + 'click .actions .edit-acl': 'onEditAcl' + }, + + partials: { + deleteButton: deleteButton, + aclButton: aclButton + }, + + initialize: function () { + this.rolesList = new RoleList(); + }, + + render:function(){ + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId},this.partials)); + this.bindDomElement(); + this.listView = new WorkflowListView({ + el: this.$('.workflow-table') + }); + this.listView.on('selectionChange', this.selectionChanged.bind(this)); + this.bindCollections(); + }, + destroyed: function () { + this.$el.html(''); + }, + + bindDomElement : function(){ + this.$notifications = this.$('.notifications'); + this.$newWorflowBtn = this.$('.actions .new'); + this.$aclButton = this.$('.actions .edit-acl'); + this.$deleteButton = this.$('.actions .delete'); + + }, + bindCollections : function(){ + this.listenTo(this.listView.collection, 'reset', this.onWorkflowsListChange); + this.listView.collection.fetch({reset: true}); + this.listenTo(this.rolesList, 'reset', this.onRolesListChange); + this.rolesList.fetch({reset: true}); + this.listenTo(this.listView.collection, 'change', this.onWorkflowsListChange); + }, + + selectionChanged: function () { + + var checkedViews = this.listView.checkedViews(); + switch (checkedViews.length) { + case 0: + this.onNoWorkflowSelected(); + break; + case 1: + this.onOneWorkflowSelected(checkedViews[0].model); + break; + default: + this.onSeveralWorkflowSelected(); + break; + } + }, + + actionNew: function () { + this.$notifications.html(''); + App.router.navigate(App.config.workspaceId + '/workflow-model-editor', {trigger: true}); + return false; + }, + actionDelete: function () { + var _this = this; + + bootbox.confirm(App.config.i18n.CONFIRM_DELETE_WORKFLOW, function(result) { + if (result) { + + _this.listView.eachChecked(function (view) { + view.model.destroy({ + wait:true, + dataType: 'text', + success: function () { + _this.listView.redraw(); + _this.onNoWorkflowSelected(); + }, + error:_this.onError.bind(_this) + }); + }); + } + }); + return false; + }, + actionRoles: function () { + this.$notifications.html(''); + new RolesModalView({ + collection: this.rolesList + }).show(); + }, + + onRolesListChange: function(){ + var _this = this; + var isRolesListEmpty = _this.rolesList.length === 0; + if (isRolesListEmpty) { + _this.$notifications.append(new AlertView({ + type: 'warning', + message: App.config.i18n.WARNING_ANY_ROLE + }).render().$el); + + _this.$newWorflowBtn.attr('disabled', 'disabled'); + } else { + _this.$newWorflowBtn.removeAttr('disabled'); + } + }, + onWorkflowsListChange: function(){ + var _this = this; + var isWorkflowListEmpty = _this.listView.collection.length === 0; + if (isWorkflowListEmpty) { + _this.$notifications.append(new AlertView({ + type: 'info', + message: App.config.i18n.ADVICE_CREATE_WORKFLOW + }).render().$el); + } + }, + + onNoWorkflowSelected: function () { + this.$aclButton.hide(); + this.$deleteButton.hide(); + }, + + onOneWorkflowSelected: function () { + this.$aclButton.show(); + this.$deleteButton.show(); + + }, + onSeveralWorkflowSelected: function () { + this.$aclButton.hide(); + this.$deleteButton.show(); + }, + onEditAcl: function () { + + var self = this; + + var workflowSelected; + + this.listView.eachChecked(function (view) { + workflowSelected = view.model; + }); + + var aclEditView = new ACLEditView({ + editMode: true, + acl: workflowSelected.get('acl') + }); + + aclEditView.setTitle(workflowSelected.getId()); + window.document.body.appendChild(aclEditView.render().el); + + aclEditView.openModal(); + aclEditView.on('acl:update', function () { + + var acl = aclEditView.toList(); + + workflowSelected.updateWorkflowACL({ + acl: acl || {userEntries: {}, groupEntries: {}}, + success: function () { + workflowSelected.set('acl', acl); + aclEditView.closeModal(); + self.listView.redraw(); + }, + error: function(model, error){ + aclEditView.onError(model, error); + } + }); + }); + + return false; + }, + + onError: function(model, error) { + var errorMessage = error ? error.responseText : model; + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + } + + }); + + return WorkflowContentListView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/workflows/workflow_list.js b/docdoku-web-front/app/change-management/js/views/workflows/workflow_list.js new file mode 100644 index 0000000000..c4bac9fc09 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/workflows/workflow_list.js @@ -0,0 +1,57 @@ +/*global define,App*/ +define([ + 'common-objects/collections/workflow_models', + 'common-objects/views/documents/checkbox_list', + 'views/workflows/workflow_list_item', + 'text!templates/workflows/workflow_list.html' +], function (WorkflowList, CheckboxListView, WorkflowListItemView, template) { + 'use strict'; + var WorkflowListView = CheckboxListView.extend({ + + template: template, + + collection: function () { + return new WorkflowList(); + }, + itemViewFactory: function (model) { + return new WorkflowListItemView({ + model: model + }); + }, + rendered: function () { + this.once('_ready', this.dataTable); + }, + redraw: function () { + this.dataTable(); + }, + dataTable: function () { + var oldSort = [ + [0, 'asc'] + ]; + if (this.oTable) { + oldSort = this.oTable.fnSettings().aaSorting; + this.oTable.fnDestroy(); + } + + this.oTable = this.$el.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + { 'bSortable': false, 'aTargets': [ 0,5 ] }, + { 'sType': App.config.i18n.DATE_SORT, 'aTargets': [4] } + ] + }); + + this.$el.parent().find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + } + + }); + return WorkflowListView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/workflows/workflow_list_item.js b/docdoku-web-front/app/change-management/js/views/workflows/workflow_list_item.js new file mode 100644 index 0000000000..1e33a0cad0 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/workflows/workflow_list_item.js @@ -0,0 +1,59 @@ +/*global define,App*/ +define([ + 'require', + 'common-objects/utils/date', + 'common-objects/views/documents/checkbox_list_item', + 'text!templates/workflows/workflow_list_item.html' +], function (require, date, CheckboxListItemView, template) { + 'use strict'; + var WorkflowListItemView = CheckboxListItemView.extend({ + + template: template, + + tagName: 'tr', + + initialize: function () { + this._isChecked = false; + CheckboxListItemView.prototype.initialize.apply(this, arguments); + this.events['click .reference'] = this.actionEdit; + this.events['click input[type=checkbox]'] = this.selectionChanged; + }, + + modelToJSON: function () { + var data = this.model.toJSON(); + + data.creationDate = date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + data.creationDate + ); + + if (this.model.hasACLForCurrentUser()) { + data.isReadOnly = this.model.isReadOnly(); + data.isFullAccess = this.model.isFullAccess(); + } + return data; + }, + + rendered: function () { + CheckboxListItemView.prototype.rendered.apply(this, arguments); + this.$('.author-popover').userPopover(this.model.attributes.author.login, this.model.id, 'left'); + date.dateHelper(this.$('.date-popover')); + this.$checkbox = this.$('input[type=checkbox]'); + }, + + actionEdit: function () { + var url = encodeURI(App.config.workspaceId + '/workflow-model-editor/' + this.model.id); + App.router.navigate(url, {trigger: true}); + }, + unCheck: function () { + this.$checkbox.prop('checked', false); + this._isChecked = false; + }, + selectionChanged: function () { + this._isChecked = this.$checkbox.prop('checked'); + this.trigger('selectionChanged', this); + } + + }); + return WorkflowListItemView; +}); diff --git a/docdoku-web-front/app/change-management/js/views/workflows/workflow_model_copy.js b/docdoku-web-front/app/change-management/js/views/workflows/workflow_model_copy.js new file mode 100644 index 0000000000..cec3dcbcce --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/workflows/workflow_model_copy.js @@ -0,0 +1,78 @@ +/*global define,App,_*/ +define([ + 'backbone', + 'mustache', + 'require', + 'text!templates/workflows/workflow_model_copy.html' +], function (Backbone, Mustache, require, template) { + 'use strict'; + var WorkflowModelCopyView = Backbone.View.extend({ + + events: { + 'click #save-copy-workflow-btn': 'saveCopyAction', + 'click #cancel-copy-workflow-btn': 'closeModalAction', + 'click a.close': 'closeModalAction', + 'hidden #modal-copy-workflow': 'onHidden' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workflow: this.model.attributes})); + this.bindDomElements(); + return this; + }, + + bindDomElements: function () { + this.inputWorkflowCopyName = this.$('#workflow-copy-name'); + this.$modal = this.$('#modal-copy-workflow'); + }, + + saveCopyAction: function () { + var self = this; + var reference = this.inputWorkflowCopyName.val(); + + if (reference !== null && reference !== '') { + delete this.model.id; + this.model.save( + { + reference: reference, + finalLifeCycleState: self.model.get('finalLifeCycleState') + }, + { + success: function () { + self.closeModalAction(); + self.goToWorkflows(); + }, + error: function (model, xhr) { + console.error('Error while saving workflow "' + model.attributes.reference + '" : ' + xhr.responseText); + self.inputWorkflowCopyName.focus(); + } + } + ); + } + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModalAction: function () { + this.$modal.modal('hide'); + }, + + onHidden:function(){ + this.remove(); + }, + + goToWorkflows: function () { + App.router.navigate(App.config.workspaceId + '/workflows', {trigger: true}); + } + + }); + + return WorkflowModelCopyView; + +}); diff --git a/docdoku-web-front/app/change-management/js/views/workflows/workflow_model_editor.js b/docdoku-web-front/app/change-management/js/views/workflows/workflow_model_editor.js new file mode 100644 index 0000000000..5ee5931863 --- /dev/null +++ b/docdoku-web-front/app/change-management/js/views/workflows/workflow_model_editor.js @@ -0,0 +1,250 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'require', + 'collections/roles', + 'text!templates/workflows/workflow_model_editor.html', + 'views/workflows/workflow_model_copy', + 'views/workflows/activity_model_editor', + 'common-objects/models/workflow/workflow_model', + 'common-objects/models/workflow/activity_model', + 'common-objects/views/alert' +], function (Backbone, Mustache, require, Roles, template, WorkflowModelCopyView, ActivityModelEditorView, WorkflowModel, ActivityModel, AlertView) { + 'use strict'; + var WorkflowModelEditorView = Backbone.View.extend({ + el: '#change-management-content', + + events: { + 'click .actions #cancel-workflow': 'cancelAction', + 'click .actions #save-workflow': 'saveAction', + 'click .actions #copy-workflow': 'copyAction', + 'click button#add-activity': 'addActivityAction' + }, + + initialize: function () { + this.subviews = []; // store subview to clean them on view remove + if(!this.roles) { + this.roles = this.options.roles ? this.options.roles : new Roles(); + } + this.newRoles = []; + this.listenTo(this.roles, 'reset', this.onRolesListFetch); + }, + + render: function () { + var _this = this; + var templateOptions = { + i18n: App.config.i18n + }; + if(!_.isUndefined(this.options.workflowModelId)){ + templateOptions.workflowId = this.options.workflowModelId; + } + + this.template = Mustache.render(template,templateOptions); + this.$el.html(this.template); + this.bindDomElements(); + + // Init model & collection at first render + if(!this.model){ + this.roles.fetch({ + reset: true, + success:function(){ + _this.initModel(); + } + }); + } + + return this; + }, + bindDomElements: function () { + var _this = this; + this.inputWorkflowName = this.$('input#workflow-name'); + this.inputFinalState = this.$('input#final-state'); + this.liAddActivitySection = this.$('li#add-activity-section'); + this.$notifications = this.$el.find('.notifications').first(); + this.$saveBtn = this.$el.find('#save-workflow').first(); + + this.activitiesUL = this.$('ul#activity-list'); + this.activitiesUL.sortable({ + items: 'li.activity-section', + handle: '.activity-topbar', + tolerance: 'pointer', + start: function (event, ui) { + ui.item.oldPosition = ui.item.index(); + }, + stop: function (event, ui) { + _this.activityPositionChanged(ui.item.oldPosition - 1, ui.item.index() - 1); + } + }); + }, + + initModel:function(){ + var _this = this; + if(_.isUndefined(this.options.workflowModelId)) { + this.model = new WorkflowModel(); + this.model.attributes.activityModels.bind('add', this.addActivity, this); + + this.$notifications.append(new AlertView({ + type: 'info', + message: App.config.i18n.WARNING_ACTIVITIES_MISSING + }).render().$el); + + this.$notifications.append(new AlertView({ + type: 'info', + message: App.config.i18n.WARNING_FINAL_STATE_MISSING + }).render().$el); + + } else { + this.model = new WorkflowModel({ + id: this.options.workflowModelId + }); + this.model.fetch({ + success: function(){ + _this.onModelFetch(); + } + }); + } + }, + onModelFetch: function(){ + + if(!this.model.get('finalLifeCycleState')){ + this.$notifications.append(new AlertView({ + type: 'info', + message: App.config.i18n.WARNING_FINAL_STATE_MISSING + }).render().$el); + } + + this.inputFinalState.val(this.model.get('finalLifeCycleState')); + this.model.attributes.activityModels.bind('add', this.addActivity, this); + this.model.attributes.activityModels.each(this.addActivity, this); + }, + onRolesListFetch: function(){ + var _this = this; + this.isRolesListEmpty = _this.roles.length === 0; + if (this.isRolesListEmpty) { + this.$notifications.append(new AlertView({ + type: 'warning', + message: App.config.i18n.WARNING_ANY_ROLE + }).render().$el); + } + }, + onError:function(model, error){ + var errorMessage = error ? error.responseText : model; + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + addActivity: function (activityModel) { + var _this = this; + var activityModelEditorView = new ActivityModelEditorView({ + model: activityModel, + roles: _this.roles, + newRoles: _this.newRoles + }); + _this.subviews.push(activityModelEditorView); + activityModelEditorView.render(); + _this.liAddActivitySection.before(activityModelEditorView.el); + _this.listenTo(activityModel, 'change', function () { + _.each(_this.subviews, function (subview) { + subview.trigger('activities-order:changed'); + }); + }); + + _this.listenTo(activityModel, 'destroy', function () { + _this.subviews = _(_this.subviews).without(activityModelEditorView); + _.each(_this.subviews, function (subview) { + subview.trigger('activities:removed'); + }); + }); + }, + + activityPositionChanged: function (oldPosition, newPosition) { + var activityModel = this.model.attributes.activityModels.at(oldPosition); + this.model.attributes.activityModels.remove(activityModel, {silent: true}); + this.model.attributes.activityModels.add(activityModel, {silent: true, at: newPosition}); + _.each(this.subviews, function (subview) { + subview.trigger('activities-order:changed'); + }); + }, + + addActivityAction: function () { + this.model.attributes.activityModels.add(new ActivityModel()); + return false; + }, + goToWorkflows: function () { + App.router.navigate(App.config.workspaceId + '/workflows', {trigger: true}); + }, + cancelAction: function () { + this.goToWorkflows(); + return false; + }, + saveAction: function () { + var _this = this; + var reference = this.inputWorkflowName.val(); + + if (reference) { + if(this.newRoles.length){ + _.each(this.newRoles, function(role){ + role.save(null,{ + success:function(){ + _this.saveModel(reference); + }, + error: function (model, xhr) { + _this.onError(model,xhr); + } + }); + }); + } else { + this.saveModel(reference); + } + + } else { + this.inputWorkflowName.focus(); + this.onError(App.config.i18n.ERROR_WORKFLOW_REFERENCE_MISSING); + } + + return false; + }, + saveModel:function(reference){ + var _this = this; + this.model.save( + { + reference: reference, + finalLifeCycleState: _this.inputFinalState.val() + }, + { + success: function () { + _this.goToWorkflows(); + }, + error: function (model, xhr) { + _this.onError(model,xhr); + _this.inputWorkflowName.focus(); + } + } + ); + }, + copyAction: function () { + + var workflowModelCopyView = new WorkflowModelCopyView({ + model: this.model + }); + + window.document.body.appendChild(workflowModelCopyView.render().el); + + workflowModelCopyView.openModal(); + + return false; + }, + + unbindAllEvents: function () { + _.each(this.subviews, function (subview) { + subview.unbindAllEvents(); + }); + this.undelegateEvents(); + } + }); + + return WorkflowModelEditorView; +}); diff --git a/docdoku-web-front/app/change-management/main.js b/docdoku-web-front/app/change-management/main.js new file mode 100644 index 0000000000..1c2198fc23 --- /dev/null +++ b/docdoku-web-front/app/change-management/main.js @@ -0,0 +1,126 @@ +/*global _,require,window*/ +var workspace = /^#([^\/]+)/.exec(window.location.hash); +if(!workspace){ + location.href = '../404?url='+window.location.href; + throw new Error('Cannot parse workspace in url'); +} +var App = { + debug:false, + config:{ + workspaceId: decodeURIComponent(workspace[1]).trim() || null, + login: '', + groups: [], + contextPath: '', + locale: window.localStorage.getItem('locale') || 'en', + needAuthentication:true + } +}; + +App.log=function(message){ + 'use strict'; + if(App.debug){ + window.console.log(message); + } +}; + +require.config({ + + baseUrl: 'js', + + shim: { + jqueryUI: { deps: ['jquery'], exports: 'jQuery' }, + effects: { deps: ['jquery'], exports: 'jQuery' }, + popoverUtils: { deps: ['jquery'], exports: 'jQuery' }, + inputValidity: { deps: ['jquery'], exports: 'jQuery' }, + bootstrap: { deps: ['jquery', 'jqueryUI'], exports: 'jQuery' }, + bootbox: { deps: ['jquery'], exports: 'jQuery' }, + datatables: { deps: ['jquery'], exports: 'jQuery' }, + bootstrapSwitch: {deps: ['jquery'], exports: 'jQuery'}, + bootstrapDatepicker: {deps: ['jquery','bootstrap'], exports: 'jQuery'}, + backbone: {deps: ['underscore', 'jquery'],exports: 'Backbone'}, + datePickerLang: { deps: ['bootstrapDatepicker'], exports: 'jQuery'}, + selectize: { deps: ['jquery'], exports: 'jQuery' } + }, + + paths: { + jquery: '../../bower_components/jquery/jquery', + backbone: '../../bower_components/backbone/backbone', + underscore: '../../bower_components/underscore/underscore', + mustache: '../../bower_components/mustache/mustache', + text: '../../bower_components/requirejs-text/text', + i18n: '../../bower_components/requirejs-i18n/i18n', + buzz: '../../bower_components/buzz/dist/buzz', + bootstrap: '../../bower_components/bootstrap/docs/assets/js/bootstrap', + bootbox:'../../bower_components/bootbox/bootbox', + datatables: '../../bower_components/datatables/media/js/jquery.dataTables', + jqueryUI: '../../bower_components/jqueryui/ui/jquery-ui', + bootstrapSwitch:'../../bower_components/bootstrap-switch/static/js/bootstrap-switch', + bootstrapDatepicker:'../../bower_components/bootstrap-datepicker/js/bootstrap-datepicker', + date:'../../bower_components/date.format/date.format', + unorm:'../../bower_components/unorm/lib/unorm', + moment:'../../bower_components/moment/min/moment-with-locales', + momentTimeZone:'../../bower_components/moment-timezone/builds/moment-timezone-with-data', + localization: '../../js/localization', + modules: '../../js/modules', + 'common-objects': '../../js/common-objects', + userPopover: '../../js/modules/user-popover-module/app', + effects: '../../js//utils/effects', + popoverUtils: '../../js/utils/popover.utils', + datatablesOsortExt: '../../js/utils/datatables.oSort.ext', + utilsprototype: '../../js/utils/utils.prototype', + inputValidity: '../../js/utils/input-validity', + datePickerLang: '../../bower_components/bootstrap-datepicker/js/locales/bootstrap-datepicker.fr', + selectize: '../../bower_components/selectize/dist/js/standalone/selectize' + }, + + deps: [ + 'jquery', + 'underscore', + 'date', + 'bootstrap', + 'bootbox', + 'bootstrapSwitch', + 'jqueryUI', + 'effects', + 'popoverUtils', + 'datatables', + 'datatablesOsortExt', + 'utilsprototype', + 'inputValidity', + 'datePickerLang', + 'selectize' + ], + config: { + i18n: { + locale: (function(){ + 'use strict'; + try{ + return App.config.locale; + }catch(ex){ + return 'en'; + } + })() + } + } +}); + + +require(['common-objects/contextResolver','i18n!localization/nls/common','i18n!localization/nls/change-management'], +function (ContextResolver, commonStrings, changeManagementStrings) { + 'use strict'; + App.config.i18n = _.extend(commonStrings,changeManagementStrings); + ContextResolver.resolveServerProperties() + .then(ContextResolver.resolveAccount) + .then(ContextResolver.resolveWorkspaces) + .then(ContextResolver.resolveGroups) + .then(ContextResolver.resolveUser) + .then(function(){ + require(['backbone','app','router','common-objects/views/header','modules/all'],function(Backbone, AppView, Router,HeaderView,Modules){ + App.appView = new AppView().render(); + App.headerView = new HeaderView().render(); + App.router = Router.getInstance(); + App.coworkersView = new Modules.CoWorkersAccessModuleView().render(); + Backbone.history.start(); + }); + }); +}); diff --git a/docdoku-web-front/app/document-management/index.html b/docdoku-web-front/app/document-management/index.html new file mode 100644 index 0000000000..1e77e517f2 --- /dev/null +++ b/docdoku-web-front/app/document-management/index.html @@ -0,0 +1,29 @@ + + + + + + DocDokuPLM - Document management + + + + + + + + + + + + + +
+
+
+
+
+ + + + + diff --git a/docdoku-web-front/app/document-management/js/app.js b/docdoku-web-front/app/document-management/js/app.js new file mode 100644 index 0000000000..a98e47b02d --- /dev/null +++ b/docdoku-web-front/app/document-management/js/app.js @@ -0,0 +1,65 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/models/workspace', + 'common-objects/collections/baselines', + 'views/baselines/baseline_select_view', + 'text!templates/content.html' +], function (Backbone, Mustache, Workspace, Baselines, BaselineSelectView, template) { + 'use strict'; + var AppView = Backbone.View.extend({ + + el: '#content', + + events: { + 'click button.newBaseline':'createBaseline' + }, + + initialize: function () { + App.config.documentConfigSpec = 'latest'; + this.model = new Workspace({id: App.config.workspaceId}); + }, + + render: function () { + + this.$el.html(Mustache.render(template, {model: this.model, i18n: App.config.i18n})); + App.$documentManagementMenu = this.$('#document-management-menu'); + App.$documentManagementContent = this.$('#document-management-content'); + + this.bindDomElements(); + this.renderSubView(); + + App.$documentManagementMenu.customResizable({ + containment: this.$el + }); + this.listenEvents(); + this.$el.show(); + return this; + }, + + bindDomElements:function(){ + this.$linksNav = this.$('.nav-header.links-nav'); + }, + + renderSubView:function(){ + this.baselinesCollection = new Baselines({},{type:'document'}); + App.baselineSelectView = new BaselineSelectView({el:'#config_spec_container',type: 'document', collection: this.baselinesCollection}).render(); + App.baselineSelectView.showMenu(); + }, + + listenEvents:function(){ + App.baselineSelectView.on('config_spec:changed', this.onConfigSpecChange,this); + }, + + onConfigSpecChange:function(configSpec){ + App.router.navigate(App.config.workspaceId+'/configspec/'+configSpec+'/folders', {trigger:true, replace:false}); + }, + + isReadOnly:function(){ + return App.config.documentConfigSpec!=='latest'; + } + }); + + return AppView; +}); diff --git a/docdoku-web-front/app/document-management/js/collections/checked_out_document.js b/docdoku-web-front/app/document-management/js/collections/checked_out_document.js new file mode 100644 index 0000000000..6a8f9a6467 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/collections/checked_out_document.js @@ -0,0 +1,25 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/document/document_revision' +], function (Backbone, DocumentRevision) { + 'use strict'; + var TagDocumentList = Backbone.Collection.extend({ + + model: DocumentRevision, + + className: 'CheckedOutDocumentList', + + url: function () { + var baseUrl = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/checkedouts'; + return baseUrl + '/' + App.config.login + '/documents'; + }, + + comparator: function (documentRevision) { + return documentRevision.get('id'); + } + + }); + + return TagDocumentList; +}); diff --git a/docdoku-web-front/app/document-management/js/collections/checkedout_document.js b/docdoku-web-front/app/document-management/js/collections/checkedout_document.js new file mode 100644 index 0000000000..be2632fed5 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/collections/checkedout_document.js @@ -0,0 +1,20 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/document/document_revision' +], function (Backbone, DocumentRevision) { + 'use strict'; + var CheckedoutDocumentList = Backbone.Collection.extend({ + + model: DocumentRevision, + + className: 'CheckedoutDocumentList', + + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/documents/checkedout'; + } + + }); + + return CheckedoutDocumentList; +}); diff --git a/docdoku-web-front/app/document-management/js/collections/folder.js b/docdoku-web-front/app/document-management/js/collections/folder.js new file mode 100644 index 0000000000..9567e8ad2d --- /dev/null +++ b/docdoku-web-front/app/document-management/js/collections/folder.js @@ -0,0 +1,55 @@ +/*global define,App*/ +define([ + 'backbone', + 'models/folder' +], function (Backbone,Folder) { + 'use strict'; + + //TODO : rename the file to FolderList + var FolderList = Backbone.Collection.extend({ + + model: Folder, + + className: 'FolderList', + + url: function () { + var baseUrl = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/folders'; + if (this.parent) { + return baseUrl + '/' + this.parent.get('id') + '/folders?configSpec='+App.config.documentConfigSpec; + } else { + return baseUrl+'?configSpec='+App.config.documentConfigSpec; + } + }, + + parse: function (data) { + if (!this.parent && App.config.documentConfigSpec === 'latest') { + // inject the user home folder + data.unshift({ + id: App.config.workspaceId + ':~' + App.config.login, + name: '~' + App.config.login, + path: App.config.workspaceId, + home: true + }); + } + return data; + }, + comparator: function (folderA, folderB) { + // sort folders by name + var nameA = folderA.get('name'); + var nameB = folderB.get('name'); + + if (folderB.get('home')) { + return 1; + } + if (folderA.get('home')) { + return -1; + } + if (nameA === nameB) { + return 0; + } + return (nameA < nameB) ? -1 : 1; + } + }); + + return FolderList; +}); diff --git a/docdoku-web-front/app/document-management/js/collections/folder_document.js b/docdoku-web-front/app/document-management/js/collections/folder_document.js new file mode 100644 index 0000000000..d59790c79c --- /dev/null +++ b/docdoku-web-front/app/document-management/js/collections/folder_document.js @@ -0,0 +1,27 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/document/document_revision' +], function (Backbone, DocumentRevision) { + 'use strict'; + var FolderDocumentList = Backbone.Collection.extend({ + + model: DocumentRevision, + + url: function () { + var baseUrl = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId; + if (this.parent) { + return baseUrl + '/folders' + '/' + this.parent.id + '/documents?configSpec='+App.config.documentConfigSpec; + } else { + return baseUrl + '/folders' + '/' + App.config.workspaceId + '/documents?configSpec='+App.config.documentConfigSpec; + } + }, + + comparator: function (documentRevision) { + return documentRevision.get('id'); + } + + }); + FolderDocumentList.className = 'FolderDocumentList'; + return FolderDocumentList; +}); diff --git a/docdoku-web-front/app/document-management/js/collections/search_document.js b/docdoku-web-front/app/document-management/js/collections/search_document.js new file mode 100644 index 0000000000..3c55fc9bc7 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/collections/search_document.js @@ -0,0 +1,24 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/document/document_revision' +], function (Backbone, DocumentRevision) { + 'use strict'; + var SearchDocumentList = Backbone.Collection.extend({ + model: DocumentRevision, + + className: 'SearchDocumentList', + + setQuery: function (query) { + this.query = query; + return this; + }, + + url: function () { + var baseUrl = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId; + return baseUrl + '/documents/search?configSpec='+App.config.documentConfigSpec+ '&'+this.query ; + } + }); + + return SearchDocumentList; +}); diff --git a/docdoku-web-front/app/document-management/js/collections/tag_document.js b/docdoku-web-front/app/document-management/js/collections/tag_document.js new file mode 100644 index 0000000000..6469f84322 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/collections/tag_document.js @@ -0,0 +1,25 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/document/document_revision' +], function (Backbone, DocumentRevision) { + 'use strict'; + var TagDocumentList = Backbone.Collection.extend({ + + model: DocumentRevision, + + className: 'TagDocumentList', + + url: function () { + var tagsUrl = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/tags'; + return tagsUrl + '/' + encodeURIComponent(this.parent.get('label')) + '/documents?configSpec='+App.config.documentConfigSpec; + }, + + comparator: function (documentRevision) { + return documentRevision.get('id'); + } + + }); + + return TagDocumentList; +}); diff --git a/docdoku-web-front/app/document-management/js/collections/task_document.js b/docdoku-web-front/app/document-management/js/collections/task_document.js new file mode 100644 index 0000000000..0bebf9f4d5 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/collections/task_document.js @@ -0,0 +1,32 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/document/document_revision' +], function (Backbone, DocumentRevision) { + 'use strict'; + var TaskDocumentList = Backbone.Collection.extend({ + + model: DocumentRevision, + + className: 'TaskDocumentList', + + setFilterStatus: function (status) { + this.filterStatus = status; + }, + + url: function () { + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/tasks/' + App.config.login + '/documents/'; + if (this.filterStatus) { + url += '?filter=' + this.filterStatus; + } + return url; + }, + + comparator: function (documentRevision) { + return documentRevision.get('id'); + } + + }); + + return TaskDocumentList; +}); diff --git a/docdoku-web-front/app/document-management/js/collections/template.js b/docdoku-web-front/app/document-management/js/collections/template.js new file mode 100644 index 0000000000..93625724b4 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/collections/template.js @@ -0,0 +1,19 @@ +/*global define,App*/ +define([ + 'backbone', + 'models/template', + 'common-objects/common/singleton_decorator' +], function (Backbone, Template, singletonDecorator) { + 'use strict'; + var TemplateList = Backbone.Collection.extend({ + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/document-templates'; + }, + model: Template + }); + + TemplateList = singletonDecorator(TemplateList); + TemplateList.className = 'TemplateList'; + + return TemplateList; +}); diff --git a/docdoku-web-front/app/document-management/js/models/folder.js b/docdoku-web-front/app/document-management/js/models/folder.js new file mode 100644 index 0000000000..1ebfafa6fc --- /dev/null +++ b/docdoku-web-front/app/document-management/js/models/folder.js @@ -0,0 +1,29 @@ +/*global $,define,App*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var Folder = Backbone.Model.extend({ + defaults: { + home: false + }, + initialize: function () { + this.className = 'Folder'; + }, + getPath: function () { + return this.get('path'); + }, + getName: function () { + return this.get('name'); + }, + url: function () { + if (this.get('id')) { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/folders/' + this.get('id'); + } else if (this.collection) { + return this.collection.url; + } + }, + moveTo:function(other){ + return $.ajax({method:'PUT',contentType:'application/json',url:this.url()+'/move',data:JSON.stringify(other)}); + } + }); + return Folder; +}); diff --git a/docdoku-web-front/app/document-management/js/models/template.js b/docdoku-web-front/app/document-management/js/models/template.js new file mode 100644 index 0000000000..9969b082ce --- /dev/null +++ b/docdoku-web-front/app/document-management/js/models/template.js @@ -0,0 +1,97 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'common-objects/collections/file/attached_file_collection', + 'common-objects/utils/acl-checker', + 'common-objects/utils/date', +], function (Backbone, AttachedFileCollection, ACLChecker, Date) { + 'use strict'; + var Template = Backbone.Model.extend({ + initialize: function () { + this.className = 'Template'; + }, + + parse: function (response) { + var filesMapping = _.map(response.attachedFiles, function (fullName) { + return { + 'fullName': fullName, + shortName: _.last(fullName.split('/')), + created: true + }; + }); + response.attachedFiles = new AttachedFileCollection(filesMapping); + return response; + }, + + defaults: { + attachedFiles: [] + }, + + toJSON: function () { + return this.clone().set({ + attributeTemplates: _.reject(this.get('attributeTemplates'), + function (attribute) { + return attribute.name === ''; + } + ) + }, {silent: true}).attributes; + }, + + getId: function () { + return this.get('id'); + }, + + getAttachedFiles: function () { + return this.get('attachedFiles'); + }, + getUploadBaseUrl: function () { + return App.config.contextPath + '/api/files/' + this.get('workspaceId') + '/document-templates/' + this.get('id') + '/'; + }, + + isAttributesLocked: function () { + return this.get('attributesLocked'); + }, + hasACLForCurrentUser: function () { + return this.getACLPermissionForCurrentUser() !== false; + }, + + isForbidden: function () { + return this.getACLPermissionForCurrentUser() === 'FORBIDDEN'; + }, + + isReadOnly: function () { + return this.getACLPermissionForCurrentUser() === 'READ_ONLY'; + }, + + isFullAccess: function () { + return this.getACLPermissionForCurrentUser() === 'FULL_ACCESS'; + }, + + getACLPermissionForCurrentUser: function () { + return ACLChecker.getPermission(this.get('acl')); + }, + updateACL: function (args) { + $.ajax({ + type: 'PUT', + url: this.url() + '/acl', + data: JSON.stringify(args.acl), + contentType: 'application/json; charset=utf-8', + success: args.success, + error: args.error + }); + }, + + getModificationDate: function() { + return this.get('modificationDate'); + }, + + getFormattedModificationDate: function () { + return Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getModificationDate() + ); + } + + }); + return Template; +}); diff --git a/docdoku-web-front/app/document-management/js/router.js b/docdoku-web-front/app/document-management/js/router.js new file mode 100644 index 0000000000..add76b9663 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/router.js @@ -0,0 +1,135 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/common/singleton_decorator', + 'views/folder_nav', + 'views/tag_nav', + 'views/search_nav', + 'views/template_nav', + 'views/baselines/baseline_nav', + 'views/checkedout_nav', + 'views/task_nav' +], +function (Backbone, singletonDecorator, FolderNavView, TagNavView, SearchNavView, TemplateNavView, BaselineNavView, CheckedoutNavView, TaskNavView) { + 'use strict'; + var Router = Backbone.Router.extend({ + routes: { + ':workspaceId/(configspec/:configSpec/)folders': 'folders', + ':workspaceId/(configspec/:configSpec/)folders/*path': 'folder', + ':workspaceId/tags': 'tags', + ':workspaceId/tags/:id': 'tag', + ':workspaceId/templates': 'templates', + ':workspaceId/baselines': 'baselines', + ':workspaceId/checkedouts': 'checkedouts', + ':workspaceId/tasks': 'tasks', + ':workspaceId/tasks/:filter': 'tasks', + ':workspaceId/search/:query': 'search', + ':workspaceId': 'home', + ':workspaceId/*path': 'home' + }, + + executeOrReload:function(workspaceId,fn){ + if(workspaceId !== App.config.workspaceId && decodeURIComponent(workspaceId).trim() !== App.config.workspaceId) { + location.reload(); + }else{ + fn.bind(this).call(); + } + }, + initNavViews: function () { + FolderNavView.getInstance(); + TagNavView.getInstance(); + TemplateNavView.getInstance(); + BaselineNavView.getInstance(); + CheckedoutNavView.getInstance(); + SearchNavView.getInstance(); + TaskNavView.getInstance(); + }, + + folders: function (workspaceId,configSpec) { + if(!configSpec){ + return this.home(workspaceId); + } + App.config.documentConfigSpec = configSpec || 'latest'; + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + this.configSpecAdaptMenu(configSpec); + FolderNavView.getInstance().show(); + }); + }, + folder: function (workspaceId, configSpec, path) { + if(!configSpec){ + return this.navigate(workspaceId+ '/configspec/latest/folders/'+ path,{trigger:true}); + } + App.config.documentConfigSpec = configSpec || 'latest'; + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + this.configSpecAdaptMenu(configSpec); + FolderNavView.getInstance().show(decodeURIComponent(path)); + }); + }, + tags: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + TagNavView.getInstance().toggle(); + }); + }, + tag: function (workspaceId, id) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + TagNavView.getInstance().show(id); + }); + }, + templates: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + TemplateNavView.getInstance().showContent(); + }); + }, + baselines: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + BaselineNavView.getInstance().showContent(); + }); + }, + checkedouts: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + CheckedoutNavView.getInstance().showContent(); + }); + }, + tasks: function (workspaceId, filter) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + TaskNavView.getInstance().showContent(filter); + }); + }, + search: function (workspaceId, query) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + SearchNavView.getInstance().showContent(query); + }); + }, + + home:function(workspaceId){ + this.navigate(workspaceId+'/configspec/latest/folders',{trigger:true}); + }, + + configSpecAdaptMenu:function(configSpec){ + + // Hide/show some nav view when you selected a baseline + var isLatest = configSpec === 'latest'; + + TagNavView.getInstance().$el.toggle(isLatest); + TemplateNavView.getInstance().$el.toggle(isLatest); + CheckedoutNavView.getInstance().$el.toggle(isLatest); + TaskNavView.getInstance().$el.toggle(isLatest); + SearchNavView.getInstance().$el.toggle(isLatest); + App.appView.$linksNav.toggle(isLatest); + + FolderNavView.getInstance().refresh(); + } + }); + + + return singletonDecorator(Router); +}); diff --git a/docdoku-web-front/app/document-management/js/templates/baselines/baseline_content.html b/docdoku-web-front/app/document-management/js/templates/baselines/baseline_content.html new file mode 100644 index 0000000000..eecaca716a --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/baselines/baseline_content.html @@ -0,0 +1,6 @@ +
+ {{> snapButton}} + {{> deleteButton}} +
+ +
diff --git a/docdoku-web-front/app/document-management/js/templates/baselines/baseline_creation_view.html b/docdoku-web-front/app/document-management/js/templates/baselines/baseline_creation_view.html new file mode 100644 index 0000000000..13bf02c961 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/baselines/baseline_creation_view.html @@ -0,0 +1,40 @@ + diff --git a/docdoku-web-front/app/document-management/js/templates/baselines/baseline_detail.html b/docdoku-web-front/app/document-management/js/templates/baselines/baseline_detail.html new file mode 100644 index 0000000000..5f90269ffe --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/baselines/baseline_detail.html @@ -0,0 +1,56 @@ + diff --git a/docdoku-web-front/app/document-management/js/templates/baselines/baseline_list.html b/docdoku-web-front/app/document-management/js/templates/baselines/baseline_list.html new file mode 100644 index 0000000000..583fa76b8d --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/baselines/baseline_list.html @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + +
{{i18n.NAME}}{{i18n.DESCRIPTION}}{{i18n.CREATION_DATE}}
diff --git a/docdoku-web-front/app/document-management/js/templates/baselines/baseline_list_item.html b/docdoku-web-front/app/document-management/js/templates/baselines/baseline_list_item.html new file mode 100644 index 0000000000..b27acb27be --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/baselines/baseline_list_item.html @@ -0,0 +1,16 @@ + + +{{model.getName}} +{{model.getDescription}} +{{model.getFormattedCreationDate}} + + + + + + + + + + + diff --git a/docdoku-web-front/app/document-management/js/templates/baselines/baseline_nav.html b/docdoku-web-front/app/document-management/js/templates/baselines/baseline_nav.html new file mode 100644 index 0000000000..781049e5c0 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/baselines/baseline_nav.html @@ -0,0 +1,7 @@ +
+ + + {{i18n.DOCUMENTS_COLLECTIONS}} + +
+ diff --git a/docdoku-web-front/app/document-management/js/templates/baselines/baseline_select.html b/docdoku-web-front/app/document-management/js/templates/baselines/baseline_select.html new file mode 100644 index 0000000000..980142d79b --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/baselines/baseline_select.html @@ -0,0 +1,12 @@ +{{i18n.CONFIG_SPEC}} +
+ + +
+ diff --git a/docdoku-web-front/app/document-management/js/templates/checked_out_document_list.html b/docdoku-web-front/app/document-management/js/templates/checked_out_document_list.html new file mode 100644 index 0000000000..38a5cf294c --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/checked_out_document_list.html @@ -0,0 +1,9 @@ +
+ {{> searchForm}} + {{> deleteButton}} + {{> checkoutButtonGroup}} + {{> tagsButton}} + {{> aclButton}} +
+
+
diff --git a/docdoku-web-front/app/document-management/js/templates/checkedout_nav.html b/docdoku-web-front/app/document-management/js/templates/checkedout_nav.html new file mode 100644 index 0000000000..9f51e6420a --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/checkedout_nav.html @@ -0,0 +1,10 @@ +
+ + + + {{i18n.CHECKOUTS}} + + + + +
diff --git a/docdoku-web-front/app/document-management/js/templates/content.html b/docdoku-web-front/app/document-management/js/templates/content.html new file mode 100644 index 0000000000..5162ecb914 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/content.html @@ -0,0 +1,15 @@ +
+ +
+
+
diff --git a/docdoku-web-front/app/document-management/js/templates/document/document_new.html b/docdoku-web-front/app/document-management/js/templates/document/document_new.html new file mode 100644 index 0000000000..11a13d42bb --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/document/document_new.html @@ -0,0 +1,69 @@ + diff --git a/docdoku-web-front/app/document-management/js/templates/document/document_new_version.html b/docdoku-web-front/app/document-management/js/templates/document/document_new_version.html new file mode 100644 index 0000000000..f724a6a11b --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/document/document_new_version.html @@ -0,0 +1,52 @@ + diff --git a/docdoku-web-front/app/document-management/js/templates/document/document_template_select.html b/docdoku-web-front/app/document-management/js/templates/document/document_template_select.html new file mode 100644 index 0000000000..b2326a08f3 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/document/document_template_select.html @@ -0,0 +1,11 @@ +
+ + +
+ +
+
diff --git a/docdoku-web-front/app/document-management/js/templates/document_list.html b/docdoku-web-front/app/document-management/js/templates/document_list.html new file mode 100644 index 0000000000..d9992c1049 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/document_list.html @@ -0,0 +1,21 @@ + + + + + {{i18n.REFERENCE}} + {{i18n.VERSION}} + {{i18n.ITERATION}} + {{i18n.TYPE}} + {{i18n.TITLE}} + {{i18n.AUTHOR}} + {{i18n.MODIFICATION_DATE}} + {{i18n.LIFECYCLE_STATE}} + {{i18n.CHECKOUT_BY}} + {{i18n.ACL}} + + + + + + + diff --git a/docdoku-web-front/app/document-management/js/templates/document_list_item.html b/docdoku-web-front/app/document-management/js/templates/document_list_item.html new file mode 100644 index 0000000000..d169df5498 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/document_list_item.html @@ -0,0 +1,42 @@ + + + + {{#model.isCheckout}}{{#model.isCheckoutByConnectedUser}}{{/model.isCheckoutByConnectedUser}}{{^model.isCheckoutByConnectedUser}}{{/model.isCheckoutByConnectedUser}}{{/model.isCheckout}}{{^model.isCheckout}}{{#model.isReleased}}{{/model.isReleased}}{{^model.isReleased}}{{#model.isObsolete}}{{/model.isObsolete}}{{^model.isObsolete}}{{/model.isObsolete}}{{/model.isReleased}}{{/model.isCheckout}}{{model.reference}} + +{{model.version}} + + {{#model.lastIteration}}{{model.lastIteration.iteration}}{{/model.lastIteration}}{{^model.lastIteration}}-{{/model.lastIteration}} + +{{model.type}} +{{model.title}} +{{model.author.name}} +{{model.lastIteration.modificationDate}} +{{model.lifeCycleState}} +{{model.checkOutUser.name}} + + {{#model.isReadOnly}} + + {{/model.isReadOnly}} + {{#model.isFullAccess}} + + {{/model.isFullAccess}} + + + + + + + + + + + + {{#model.hasAttachedFiles}} + + {{/model.hasAttachedFiles}} + diff --git a/docdoku-web-front/app/document-management/js/templates/folder_document_list.html b/docdoku-web-front/app/document-management/js/templates/folder_document_list.html new file mode 100644 index 0000000000..b7dd85b800 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/folder_document_list.html @@ -0,0 +1,20 @@ +
+ {{^isReadOnly}} + {{> searchForm}} + + + + {{> deleteButton}} + {{> checkoutButtonGroup}} + {{> tagsButton}} + {{> aclButton}} + {{> newVersionButton}} + {{> releaseButton}} + {{> obsoleteButton}} + {{/isReadOnly}} + +
+
+
diff --git a/docdoku-web-front/app/document-management/js/templates/folder_edit.html b/docdoku-web-front/app/document-management/js/templates/folder_edit.html new file mode 100644 index 0000000000..c867cab751 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/folder_edit.html @@ -0,0 +1,21 @@ + diff --git a/docdoku-web-front/app/document-management/js/templates/folder_list_item.html b/docdoku-web-front/app/document-management/js/templates/folder_list_item.html new file mode 100644 index 0000000000..73e3710c9e --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/folder_list_item.html @@ -0,0 +1,27 @@ +
+ + + + {{model.name}} + + + {{^isReadOnly}} + + {{/isReadOnly}} +
+
    diff --git a/docdoku-web-front/app/document-management/js/templates/folder_nav.html b/docdoku-web-front/app/document-management/js/templates/folder_nav.html new file mode 100644 index 0000000000..63e19d4cef --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/folder_nav.html @@ -0,0 +1,21 @@ +
    + + + {{i18n.FOLDERS}} + + + {{^isReadOnly}} + + {{/isReadOnly}} + +
    +
      diff --git a/docdoku-web-front/app/document-management/js/templates/folder_new.html b/docdoku-web-front/app/document-management/js/templates/folder_new.html new file mode 100644 index 0000000000..3eeee846c2 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/folder_new.html @@ -0,0 +1,21 @@ + diff --git a/docdoku-web-front/app/document-management/js/templates/search_document_advanced_form.html b/docdoku-web-front/app/document-management/js/templates/search_document_advanced_form.html new file mode 100644 index 0000000000..270bdffd3a --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/search_document_advanced_form.html @@ -0,0 +1,154 @@ + diff --git a/docdoku-web-front/app/document-management/js/templates/search_document_form.html b/docdoku-web-front/app/document-management/js/templates/search_document_form.html new file mode 100644 index 0000000000..a8f3df9d0f --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/search_document_form.html @@ -0,0 +1,5 @@ +
      + + + +
      \ No newline at end of file diff --git a/docdoku-web-front/app/document-management/js/templates/search_document_list.html b/docdoku-web-front/app/document-management/js/templates/search_document_list.html new file mode 100644 index 0000000000..f86eb7864f --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/search_document_list.html @@ -0,0 +1,14 @@ +
      + {{^isReadOnly}} + {{> searchForm}} + {{> deleteButton}} + {{> checkoutButtonGroup}} + {{> tagsButton}} + {{> aclButton}} + {{> newVersionButton}} + {{> releaseButton}} + {{> obsoleteButton}} + {{/isReadOnly}} +
      +
      +
      diff --git a/docdoku-web-front/app/document-management/js/templates/search_nav.html b/docdoku-web-front/app/document-management/js/templates/search_nav.html new file mode 100644 index 0000000000..cb7c56e061 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/search_nav.html @@ -0,0 +1,6 @@ +
      + + + {{i18n.SEARCH}} + +
      diff --git a/docdoku-web-front/app/document-management/js/templates/status_filter.html b/docdoku-web-front/app/document-management/js/templates/status_filter.html new file mode 100644 index 0000000000..e96272235c --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/status_filter.html @@ -0,0 +1,8 @@ +
      + + +
      ​ \ No newline at end of file diff --git a/docdoku-web-front/app/document-management/js/templates/tag_document_list.html b/docdoku-web-front/app/document-management/js/templates/tag_document_list.html new file mode 100644 index 0000000000..034922bb62 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/tag_document_list.html @@ -0,0 +1,17 @@ +
      + {{> searchForm}} + {{^isReadOnly}} + + {{> deleteButton}} + {{> checkoutButtonGroup}} + {{> tagsButton}} + {{> aclButton}} + {{> newVersionButton}} + {{> releaseButton}} + {{> obsoleteButton}} + {{/isReadOnly}} +
      +
      +
      diff --git a/docdoku-web-front/app/document-management/js/templates/tag_list_item.html b/docdoku-web-front/app/document-management/js/templates/tag_list_item.html new file mode 100644 index 0000000000..f45bc9dc45 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/tag_list_item.html @@ -0,0 +1,20 @@ +
      + + + {{model.label}} + + + +
      diff --git a/docdoku-web-front/app/document-management/js/templates/tag_nav.html b/docdoku-web-front/app/document-management/js/templates/tag_nav.html new file mode 100644 index 0000000000..4535e418de --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/tag_nav.html @@ -0,0 +1,7 @@ +
      + + + {{i18n.TAGS}} + +
      +
        diff --git a/docdoku-web-front/app/document-management/js/templates/task_document_list.html b/docdoku-web-front/app/document-management/js/templates/task_document_list.html new file mode 100644 index 0000000000..73fb2261d9 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/task_document_list.html @@ -0,0 +1,13 @@ +
        + {{> searchForm}} + {{> deleteButton}} + {{> checkoutButtonGroup}} + {{> tagsButton}} + {{> aclButton}} + {{> newVersionButton}} + {{> releaseButton}} + {{> obsoleteButton}} +
        +
        +{{> statusFilter}} +
        diff --git a/docdoku-web-front/app/document-management/js/templates/task_nav.html b/docdoku-web-front/app/document-management/js/templates/task_nav.html new file mode 100644 index 0000000000..5365902a0b --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/task_nav.html @@ -0,0 +1,6 @@ +
        + + + {{i18n.TASKS}} + +
        \ No newline at end of file diff --git a/docdoku-web-front/app/document-management/js/templates/template_content_list.html b/docdoku-web-front/app/document-management/js/templates/template_content_list.html new file mode 100644 index 0000000000..07886e6f46 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/template_content_list.html @@ -0,0 +1,11 @@ +
        + + + {{> deleteButton}} + {{> aclButton}} +
        +
        diff --git a/docdoku-web-front/app/document-management/js/templates/template_list.html b/docdoku-web-front/app/document-management/js/templates/template_list.html new file mode 100644 index 0000000000..8e34d1aa4b --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/template_list.html @@ -0,0 +1,14 @@ + + + + {{i18n.REFERENCE}} + {{i18n.TYPE}} + {{i18n.MASK}} + {{i18n.AUTHOR}} + {{i18n.CREATION_DATE}} + {{i18n.MODIFICATION_DATE}} + {{i18n.ACL}} + + + + diff --git a/docdoku-web-front/app/document-management/js/templates/template_list_item.html b/docdoku-web-front/app/document-management/js/templates/template_list_item.html new file mode 100644 index 0000000000..16c355119f --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/template_list_item.html @@ -0,0 +1,20 @@ + +{{model.id}} +{{model.documentType}} +{{model.mask}} +{{model.author.name}} +{{model.creationDate}} +{{model.modificationDate}} + + {{#model.isReadOnly}} + + {{/model.isReadOnly}} + {{#model.isFullAccess}} + + {{/model.isFullAccess}} + + + {{#model.hasAttachedFiles}} + + {{/model.hasAttachedFiles}} + diff --git a/docdoku-web-front/app/document-management/js/templates/template_nav.html b/docdoku-web-front/app/document-management/js/templates/template_nav.html new file mode 100644 index 0000000000..bcd269ace2 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/template_nav.html @@ -0,0 +1,7 @@ +
        + + + {{i18n.TEMPLATES}} + +
        + diff --git a/docdoku-web-front/app/document-management/js/templates/template_new.html b/docdoku-web-front/app/document-management/js/templates/template_new.html new file mode 100644 index 0000000000..c0f14814a4 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/templates/template_new.html @@ -0,0 +1,95 @@ + diff --git a/docdoku-web-front/app/document-management/js/views/advanced_search.js b/docdoku-web-front/app/document-management/js/views/advanced_search.js new file mode 100644 index 0000000000..ff281e4799 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/advanced_search.js @@ -0,0 +1,213 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/search_document_advanced_form.html', + 'common-objects/collections/users', + 'common-objects/views/attributes/attribute_list', + 'collections/template', + 'common-objects/utils/date', + 'common-objects/collections/lovs' +], function (Backbone,Mustache, template, Users, DocumentAttributeListView, Templates, date,LOVCollection) { + 'use strict'; + var AdvancedSearchView = Backbone.View.extend({ + + events: { + 'hidden #advanced_search_modal': 'onHidden', + 'submit #advanced_search_form': 'onSubmitForm', + 'click #search-add-attributes': 'addAttribute', + 'change #template-attributes-helper': 'changeAttributes' + }, + + lovs : new LOVCollection(), + + initialize: function () { + _.bindAll(this); + + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, timeZone:App.config.timeZone})); + this.bindDomElements(); + this.fillInputs(); + this.initAttributesView(); + return this; + }, + + initAttributesView: function () { + + this.attributes = new Backbone.Collection(); + + var that = this; + this.lovs.fetch().success(function(){ + that.attributesView = new DocumentAttributeListView({ + collection: that.attributes, + lovs : that.lovs, + displayOnly: true + }); + + that.$('#attributes-list').html(that.attributesView.$el); + }); + + }, + + fillInputs: function () { + + var that = this; + + this.users = new Users(); + this.users.fetch({reset: true, success: function () { + that.users.each(function (user) { + that.$author.append(''); + }); + }}); + + this.templates = new Templates(); + this.types = []; + this.templatesId = []; + this.templates.fetch({reset: true, success: function () { + that.templates.each(function (template) { + var type = template.get('documentType'); + if (!_.contains(that.types, type) && type) { + that.types.push(type); + that.$type.append(''); + } + var templateId = template.get('id'); + if (!_.contains(that.templatesId, templateId) && templateId) { + that.templatesId.push(type); + that.$templatesId.append(''); + } + }); + }}); + + }, + + addAttribute: function () { + this.attributes.add({ + name: '', + type: 'TEXT', + value: '' + }); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + onSubmitForm: function () { + var queryString = this.constructQueryString(); + if (queryString) { + App.router.navigate(App.config.workspaceId + '/search/' + encodeURIComponent(queryString), {trigger: true}); + this.closeModal(); + } + return false; + }, + + bindDomElements: function () { + this.$modal = this.$('#advanced_search_modal'); + this.$id = this.$('#search-id'); + this.$title = this.$('#search-title'); + this.$type = this.$('#search-type'); + this.$version = this.$('#search-version'); + this.$author = this.$('#search-author'); + this.$tags = this.$('#search-tags'); + this.$content = this.$('#search-content'); + this.$createdFrom = this.$('#search-creation-from'); + this.$createdTo = this.$('#search-creation-to'); + this.$modifiedFrom = this.$('#search-modification-from'); + this.$modifiedTo = this.$('#search-modification-to'); + this.$templatesId = this.$('#template-attributes-helper'); + }, + + changeAttributes: function (e) { + if (e.target.value) { + var search = _.where(this.templates.models, {id: e.target.value}); + if (search[0]) { + this.attributes.reset(search[0].get('attributeTemplates')); + } + } else { + this.attributes.reset(); + } + }, + + constructQueryString: function () { + + var id = this.$id.val(); + var title = this.$title.val(); + var type = this.$type.val(); + var version = this.$version.val(); + var author = this.$author.val(); + var tags = this.$tags.val().replace(/ /g, ''); + var content = this.$content.val(); + var createdFrom = this.$createdFrom.val(); + var createdTo = this.$createdTo.val(); + var modifiedFrom = this.$modifiedFrom.val(); + var modifiedTo = this.$modifiedTo.val(); + + var queryString = ''; + + if (id) { + queryString += '&id=' + id; + } + if (title) { + queryString += '&title=' + title; + } + if (type) { + queryString += '&type=' + type; + } + if (version) { + queryString += '&version=' + version; + } + if (author) { + queryString += '&author=' + author; + } + if (tags) { + queryString += '&tags=' + tags; + } + if (content) { + queryString += '&content=' + content; + } + if (createdFrom) { + queryString += '&createdFrom=' + date.toUTCWithTimeZoneOffset(createdFrom); + } + if (createdTo) { + queryString += '&createdTo=' + date.toUTCWithTimeZoneOffset(createdTo); + } + if (modifiedFrom) { + queryString += '&modifiedFrom=' + date.toUTCWithTimeZoneOffset(modifiedFrom); + } + if (modifiedTo) { + queryString += '&modifiedTo=' + date.toUTCWithTimeZoneOffset(modifiedTo); + } + + if (this.attributes.length) { + queryString += '&attributes='; + this.attributes.each(function (attribute) { + var type = attribute.get('type'); + var name = attribute.get('name'); + var value = attribute.get('value'); + value = type === 'BOOLEAN' ? (value ? 'true' : 'false') : value; + value = type === 'LOV' ? attribute.get('items')[value].name : value; + queryString += type + ':' + name + ':' + value + ';'; + }); + // remove last '+' + queryString = queryString.substr(0, queryString.length - 1); + } + + return queryString; + + } + + }); + + return AdvancedSearchView; + +}); diff --git a/docdoku-web-front/app/document-management/js/views/baselines/baseline_content.js b/docdoku-web-front/app/document-management/js/views/baselines/baseline_content.js new file mode 100644 index 0000000000..5a4b308d8d --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/baselines/baseline_content.js @@ -0,0 +1,112 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/collections/baselines', + 'text!templates/baselines/baseline_content.html', + 'views/baselines/baseline_list', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/snap_button.html', + 'common-objects/views/alert', + 'views/baselines/baseline_creation_view' +], function (Backbone, Mustache, BaselinesCollection, template, BaselinesListView, deleteButton, snapButton, AlertView, BaselineCreationView) { + 'use strict'; + + var BaselineContentView = Backbone.View.extend({ + partials: { + deleteButton: deleteButton, + snapButton:snapButton + }, + + events: { + 'click button.delete': 'deleteBaseline', + 'click button.new-baseline': 'createBaseline' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n}, this.partials)); + this.bindDomElements(); + this.createBaselineButton.show(); + + this.bindEvent(); + this.createBaselineView(); + return this; + }, + + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.deleteButton = this.$('.delete'); + this.createBaselineButton = this.$('.new-baseline'); + }, + + + // TODO: determine if this is still useful + bindEvent: function(){ + var _this = this; + this.delegateEvents(); + }, + + createBaseline: function () { + var baselineCreationView = new BaselineCreationView({collection:this.listView.collection}); + window.document.body.appendChild(baselineCreationView.render().el); + baselineCreationView.on('warning', this.onWarning); + baselineCreationView.openModal(); + }, + + createBaselineView: function () { + if (this.listView) { + this.listView.remove(); + this.changeDeleteButtonDisplay(false); + } + + this.listView = new BaselinesListView({ + collection: new BaselinesCollection({}, {type:'document'}) + }).render(); + + this.$el.append(this.listView.el); + this.listView.on('error', this.onError); + this.listView.on('warning', this.onWarning); + this.listView.on('delete-button:display', this.changeDeleteButtonDisplay); + }, + + deleteBaseline: function () { + this.listView.deleteSelectedBaselines(); + }, + + changeDeleteButtonDisplay: function (state) { + this.deleteButton.toggle(state); + }, + + onError:function(model, error){ + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + onWarning:function(model, error){ + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'warning', + message: errorMessage + }).render().$el); + }, + + onInfo:function(message){ + this.$notifications.append(new AlertView({ + type: 'info', + message: message + }).render().$el); + } + + }); + + return BaselineContentView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/baselines/baseline_creation_view.js b/docdoku-web-front/app/document-management/js/views/baselines/baseline_creation_view.js new file mode 100644 index 0000000000..6b770766e2 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/baselines/baseline_creation_view.js @@ -0,0 +1,86 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/collections/baselines', + 'text!templates/baselines/baseline_creation_view.html', + 'common-objects/views/alert' +], function (Backbone, Mustache, Baselines, template, AlertView) { + + 'use strict'; + + var BaselineCreationView = Backbone.View.extend({ + + events: { + 'submit #baseline_creation_form': 'onSubmitForm', + 'hidden #baseline_creation_modal': 'onHidden' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + this.$inputBaselineName.customValidity(App.config.i18n.REQUIRED_FIELD); + + return this; + }, + + bindDomElements: function () { + this.$modal = this.$('#baseline_creation_modal'); + this.$notifications = this.$el.find('.notifications').first(); + this.$inputBaselineName = this.$('#inputBaselineName'); + this.$inputBaselineDescription = this.$('#inputBaselineDescription'); + }, + + onSubmitForm: function (e) { + this.$notifications.empty(); + var data = { + name: this.$inputBaselineName.val(), + description: this.$inputBaselineDescription.val() + }; + + this.collection.create(data, { + wait: true, + success: this.onBaselineCreated, + error: this.onError + }); + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onBaselineCreated: function (e) { + if (e.message) { + this.trigger('warning', e.message); + } + this.closeModal(); + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + } + }); + + return BaselineCreationView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/baselines/baseline_detail_view.js b/docdoku-web-front/app/document-management/js/views/baselines/baseline_detail_view.js new file mode 100644 index 0000000000..2f02d98461 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/baselines/baseline_detail_view.js @@ -0,0 +1,52 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/baselines/baseline_detail.html', + 'common-objects/utils/date' +], function (Backbone, Mustache, template, date) { + 'use strict'; + var BaselineDetailView = Backbone.View.extend({ + + events: { + 'hidden #baseline_detail_modal': 'onHidden', + 'close-modal-request': 'closeModal' + }, + + initialize: function () { + + }, + + render: function () { + var that = this; + that.$el.html(Mustache.render(template, {i18n: App.config.i18n, model: that.model})); + that.bindDomElements(); + date.dateHelper(this.$('.date-popover')); + that.openModal(); + + window.document.body.appendChild(this.el); + return this; + }, + + bindDomElements: function () { + this.$notifications = this.$('.notifications'); + this.$modal = this.$('#baseline_detail_modal'); + this.$tabs = this.$('.nav-tabs li'); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + } + + }); + + return BaselineDetailView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/baselines/baseline_list.js b/docdoku-web-front/app/document-management/js/views/baselines/baseline_list.js new file mode 100644 index 0000000000..04011e131a --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/baselines/baseline_list.js @@ -0,0 +1,194 @@ +/*global _,define,bootbox,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/baselines/baseline_list.html', + 'views/baselines/baseline_list_item' +], function (Backbone, Mustache, template, BaselineListItemView) { + 'use strict'; + + var BaselineListView = Backbone.View.extend({ + + events: { + 'click .toggle-checkboxes': 'toggleSelection' + }, + + removeSubviews: function () { + _(this.listItemViews).invoke('remove'); // Invoke remove for each views in listItemViews + this.listItemViews = []; + }, + + initialize: function () { + _.bindAll(this); + this.listenTo(this.collection, 'reset', this.resetList); + this.listenTo(this.collection, 'add', this.addNewDocumentBaseline); + this.listItemViews = []; + this.$el.on('remove', this.removeSubviews); + }, + + render: function () { + this.collection.fetch({reset: true}); + return this; + }, + + bindDomElements: function () { + this.$table = this.$('#document_baseline_table'); + this.$items = this.$('.items'); + this.$checkbox = this.$('.toggle-checkboxes'); + }, + + resetList: function () { + if (this.oTable) { + this.oTable.fnDestroy(); + } + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + this.listItemViews = []; + var that = this; + this.collection.each(function (model) { + that.addDocumentBaseline(model); + }); + this.dataTable(); + }, + + addNewDocumentBaseline: function (model) { + this.addDocumentBaseline(model, true); + this.redraw(); + }, + + addDocumentBaseline: function (model, effect) { + var view = this.addDocumentBaselineView(model); + if (effect) { + view.$el.highlightEffect(); + } + }, + + removeDocumentBaseline: function (model) { + this.removeDocumentBaselineView(model); + this.redraw(); + }, + + removeDocumentBaselineView: function (model) { + var viewToRemove = _(this.listItemViews).select(function (view) { + return view.model === model; + })[0]; + + if (viewToRemove) { + this.listItemViews = _(this.listItemViews).without(viewToRemove); + var row = viewToRemove.$el.get(0); + this.oTable.fnDeleteRow(this.oTable.fnGetPosition(row)); + viewToRemove.remove(); + } + }, + + addDocumentBaselineView: function (model) { + var view = new BaselineListItemView({model: model}).render(); + this.listItemViews.push(view); + this.$items.append(view.$el); + view.on('selectionChanged', this.onSelectionChanged); + view.on('rendered', this.redraw); + return view; + }, + + toggleSelection: function () { + if (this.$checkbox.is(':checked')) { + _(this.listItemViews).each(function (view) { + view.check(); + }); + } else { + _(this.listItemViews).each(function (view) { + view.unCheck(); + }); + } + this.onSelectionChanged(); + }, + + onSelectionChanged: function () { + var checkedViews = _(this.listItemViews).select(function (view) { + return view.isChecked(); + }); + + if (checkedViews.length <= 0) { + this.onNoDocumentBaselineSelected(); + } else if (checkedViews.length === 1) { + this.onOneDocumentBaselineSelected(); + } else { + this.onSeveralDocumentBaselinesSelected(); + } + + }, + + onNoDocumentBaselineSelected: function () { + this.trigger('delete-button:display', false); + }, + + onOneDocumentBaselineSelected: function () { + this.trigger('delete-button:display', true); + }, + + onSeveralDocumentBaselinesSelected: function () { + this.trigger('delete-button:display', true); + }, + + getSelectedBaseline:function(){ + var selectedView = _.select(this.listItemViews,function(view){ + return view.isChecked(); + })[0]; + return selectedView ? selectedView.model : null; + }, + + deleteSelectedBaselines: function () { + var _this = this; + bootbox.confirm(App.config.i18n.CONFIRM_DELETE_DOCUMENTS_COLLECTIONS, function(result){ + if(result){ + _(_this.listItemViews).each(function (view) { + if (view.isChecked()) { + view.model.destroy({ + dataType: 'text', // server doesn't send a json hash in the response body + success: function () { + _this.removeDocumentBaseline(view.model); + _this.onSelectionChanged(); + }, + error: function (model, err) { + _this.trigger('error',model,err); + _this.onSelectionChanged(); + } + }); + } + }); + } + }); + }, + redraw: function () { + this.dataTable(); + }, + dataTable: function () { + var oldSort = [ + [1, 'asc'] + ]; + if (this.oTable) { + oldSort = this.oTable.fnSettings().aaSorting; + this.oTable.fnDestroy(); + } + this.oTable = this.$table.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + { 'bSortable': false, 'aTargets': [ 0 ] }, + { 'sType': App.config.i18n.DATE_SORT, 'aTargets': [3] } + ] + }); + this.$el.find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + } + + }); + + return BaselineListView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/baselines/baseline_list_item.js b/docdoku-web-front/app/document-management/js/views/baselines/baseline_list_item.js new file mode 100644 index 0000000000..27a1763a3f --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/baselines/baseline_list_item.js @@ -0,0 +1,85 @@ +/*global define*/ +define([ + 'backbone', + 'mustache', + 'text!templates/baselines/baseline_list_item.html', + 'views/baselines/baseline_detail_view', + 'common-objects/utils/date' +], function (Backbone, Mustache, template, BaselineDetailView, date) { + 'use strict'; + var BaselineListItemView = Backbone.View.extend({ + events: { + 'click input[type=checkbox]': 'selectionChanged', + 'click td.reference': 'openDetailView' + }, + + tagName: 'tr', + + initialize: function () { + this._isChecked = false; + }, + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + model: this.model, + zipUrl: this.model.getZipUrl() + })); + this.$checkbox = this.$('input[type=checkbox]'); + this.bindUserPopover(); + date.dateHelper(this.$('.date-popover')); + this.trigger('rendered', this); + + var zipUrl = this.model.getZipUrl(); + this.$('.download-zip').popover({ + title: ''+App.config.i18n.DOWNLOAD_ZIP+'
        ', + animation: true, + html: true, + trigger: 'manual', + content: ''+App.config.i18n.WITHOUT_LINKS+' | ' + + ''+App.config.i18n.WITH_LINKS+'', + placement: 'top' + }).click(function (e) { + $(this).popover('show'); + e.stopPropagation(); + e.preventDefault(); + return false; + }); + + return this; + }, + + selectionChanged: function () { + this._isChecked = this.$checkbox.prop('checked'); + this.trigger('selectionChanged', this); + }, + + isChecked: function () { + return this._isChecked; + }, + + check: function () { + this.$checkbox.prop('checked', true); + this._isChecked = true; + }, + + unCheck: function () { + this.$checkbox.prop('checked', false); + this._isChecked = false; + }, + + openDetailView: function () { + var model = this.model; + model.fetch().success(function () { + new BaselineDetailView({model: model, isForBaseline: true}).render(); + }); + }, + + bindUserPopover: function () { + //this.$('.author-popover').userPopover(this.model.getAuthorLogin(), App.config.i18n.BASELINE, 'left'); + } + + }); + + return BaselineListItemView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/baselines/baseline_nav.js b/docdoku-web-front/app/document-management/js/views/baselines/baseline_nav.js new file mode 100644 index 0000000000..4a676c6246 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/baselines/baseline_nav.js @@ -0,0 +1,52 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'views/baselines/baseline_content', + 'text!templates/baselines/baseline_nav.html' +], function (Backbone, Mustache, singletonDecorator, BaselineContentView, template) { + + 'use strict'; + + var BaselineNavView = Backbone.View.extend({ + + el: '#baseline-nav', + + initialize: function () { + this.render(); + this.contentView = undefined; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId})); + }, + + setActive: function () { + if (App.$documentManagementMenu) { + App.$documentManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + + showContent: function () { + this.setActive(); + this.cleanView(); + if(!this.contentView){ + this.contentView = new BaselineContentView(); + } + this.contentView.render(); + App.$documentManagementContent.html(this.contentView.el); + }, + + cleanView: function () { + if (this.contentView) { + this.contentView.undelegateEvents(); + App.$documentManagementContent.html(''); + } + } + }); + + BaselineNavView = singletonDecorator(BaselineNavView); + return BaselineNavView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/baselines/baseline_select_view.js b/docdoku-web-front/app/document-management/js/views/baselines/baseline_select_view.js new file mode 100644 index 0000000000..50f97b0cb8 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/baselines/baseline_select_view.js @@ -0,0 +1,129 @@ +/*global define,bootbox,App,window*/ +define([ + 'backbone', + 'mustache', + 'common-objects/collections/baselines', + 'text!templates/baselines/baseline_select.html', + 'views/baselines/baseline_creation_view' +], function (Backbone, Mustache, Baselines, template, BaselineCreationView) { + 'use strict'; + + var BaselineSelectView = Backbone.View.extend({ + events:{ + 'change select' : 'onSelectorChanged', + 'click button.newBaseline':'createBaseline', + 'click button.deleteBaseline':'deleteBaseline' + }, + + initialize:function(){ + this.type = 'document'; + if(!this.collection){ + var data = { + type : this.type + }; + this.collection = new Baselines({},data); + } + this.listenToOnce(this.collection,'reset',this.onCollectionReset); + }, + + render:function(){ + this.$el.html(Mustache.render(template, {i18n:App.config.i18n})); + this.bindDomElements(); + this.hideMenu(); + this.collection.fetch({reset:true}); + return this ; + }, + + bindDomElements:function(){ + this.$select = this.$('select'); + this.$menu = this.$('.ConfigSpecSelector-menu'); + this.$newBaselineBtn = this.$('.btn.newBaseline'); + this.$deleteBaselineBtn = this.$('.btn.deleteBaseline'); + if(App.config.documentConfigSpec==='latest' || App.config.documentConfigSpec==='released'){ + this.$deleteBaselineBtn.attr('disabled', 'disabled'); + this.$deleteBaselineBtn.hide(); + } + }, + + onCollectionReset:function(){ + this.onCollectionChange(); + this.listenTo(this.collection,'change',this.onCollectionChange); + }, + + onCollectionChange:function(){ + var that = this ; + if(this.$select){ + this.$select.find('option').remove(); + + this.collection.getLastReleaseRevision({ + success : function (lastReleaseRevision){ + if(lastReleaseRevision){ + that.$select.prepend(''); + } + } + }); + + this.$select.append(''); + this.collection.each(function(baseline){ + that.$select.append(''); + }); + } + if(App.config.documentConfigSpec){ + this.$select.val(App.config.documentConfigSpec); + } + }, + + onSelectorChanged:function(e){ + this.trigger('config_spec:changed', e.target.value); + if(e.target.value==='latest' || e.target.value==='released'){ + this.$deleteBaselineBtn.attr('disabled', 'disabled'); + this.$newBaselineBtn.removeAttr('disabled'); + this.$deleteBaselineBtn.hide(); + }else{ + this.$deleteBaselineBtn.show(); + this.$deleteBaselineBtn.removeAttr('disabled'); + this.$newBaselineBtn.attr('disabled', 'disabled'); + } + }, + + createBaseline:function(){ + var baselineCreationView = new BaselineCreationView({collection: this.collection}); + window.document.body.appendChild(baselineCreationView.render().el); + baselineCreationView.openModal(); + }, + + deleteBaseline:function(){ + var that = this; + + bootbox.confirm(App.config.i18n.DELETE_SELECTION_QUESTION, function(result){ + if(result){ + that.collection.each(function(baseline){ + if(parseInt(that.$select.val(),10)===baseline.getId()){ + baseline.destroy({ + dataType: 'text', // server doesn't send a json hash in the response body + success:function(){ + that.$select.find('option[value='+baseline.getId()+']').remove(); + that.$select.val('latest').change(); + }, + error:function(model,err){ + window.alert(err.responseText); + } + }); + } + }); + } + }); + }, + + showMenu: function(){ + this.$menu.show(); + }, + + hideMenu: function(){ + this.$menu.hide(); + } + + }); + + return BaselineSelectView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/checked_out_document_list.js b/docdoku-web-front/app/document-management/js/views/checked_out_document_list.js new file mode 100644 index 0000000000..123fb428ad --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/checked_out_document_list.js @@ -0,0 +1,38 @@ +/*global define*/ +define([ + 'collections/checked_out_document', + 'views/content_document_list', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/checkout_button_group.html', + 'text!common-objects/templates/buttons/tags_button.html', + 'text!common-objects/templates/buttons/new_version_button.html', + 'text!common-objects/templates/buttons/ACL_button.html', + 'text!templates/search_document_form.html', + 'text!templates/checked_out_document_list.html' +], function (CheckedOutDocumentList, ContentDocumentListView, deleteButton, checkoutButtonGroup, tagsButton, newVersionButton, aclButton, searchForm, template) { + 'use strict'; + var CheckedOutDocumentListView = ContentDocumentListView.extend({ + + template: template, + + partials: { + deleteButton: deleteButton, + checkoutButtonGroup: checkoutButtonGroup, + tagsButton: tagsButton, + searchForm: searchForm, + aclButton: aclButton + }, + + collection: function () { + return new CheckedOutDocumentList(); + + }, + initialize: function () { + ContentDocumentListView.prototype.initialize.apply(this, arguments); + if (this.model) { + this.collection.parent = this.model; + } + } + }); + return CheckedOutDocumentListView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/checkedout_nav.js b/docdoku-web-front/app/document-management/js/views/checkedout_nav.js new file mode 100644 index 0000000000..e4fa8849d7 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/checkedout_nav.js @@ -0,0 +1,61 @@ +/*global define,App,$*/ +define([ + 'common-objects/common/singleton_decorator', + 'common-objects/views/base', + 'views/checked_out_document_list', + 'text!templates/checkedout_nav.html', + 'backbone' +], function (singletonDecorator, BaseView, CheckedoutContentListView, template, Backbone) { + + 'use strict'; + + var CheckedOutNavView = BaseView.extend({ + + template: template, + el: '#checked-out-nav', + initialize: function () { + BaseView.prototype.initialize.apply(this, arguments); + Backbone.Events.on('document:iterationChange', this.refreshCount, this); + this.render(); + }, + + rendered:function(){ + this.refreshCount(); + }, + + refreshCount:function(){ + var that = this; + $.ajax({ + context :this, + type:'GET', + url:App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/documents/countCheckedOut' + }).success(function(data){ + var numberOfItem = data.count; + var badge = that.$('.badge.nav-checkedOut-number-item'); + badge.html(numberOfItem); + /* if(numberOfItem === 0){ + badge.addClass('badge-success'); + badge.removeClass('badge-info'); + } else{ + badge.addClass('badge-info'); + badge.removeClass('badge-success'); + }*/ + }); + }, + + setActive: function () { + if (App.$documentManagementMenu) { + App.$documentManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + showContent: function () { + this.setActive(); + this.addSubView( + new CheckedoutContentListView() + ).render(); + } + }); + CheckedOutNavView = singletonDecorator(CheckedOutNavView); + return CheckedOutNavView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/content.js b/docdoku-web-front/app/document-management/js/views/content.js new file mode 100644 index 0000000000..538aa35940 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/content.js @@ -0,0 +1,22 @@ +/*global define*/ +define([ + 'common-objects/views/base' +], function (BaseView) { + 'use strict'; + var ContentView = BaseView.extend({ + el: '#document-management-content', + initialize: function () { + BaseView.prototype.initialize.apply(this, arguments); + // destroy previous content view if any + if (ContentView._instance) { + ContentView._instance.destroy(); + } + // keep track of the created content view + ContentView._instance = this; + }, + destroyed: function () { + this.$el.html(''); + } + }); + return ContentView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/content_document_list.js b/docdoku-web-front/app/document-management/js/views/content_document_list.js new file mode 100644 index 0000000000..0c49f18643 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/content_document_list.js @@ -0,0 +1,536 @@ +/*global _,define,bootbox,App,window*/ +define([ + 'backbone', + 'views/content', + 'views/document_list', + 'views/document/document_new_version', + 'views/advanced_search', + 'common-objects/views/prompt', + 'common-objects/views/security/acl_edit', + 'common-objects/views/tags/tags_management', + 'common-objects/views/alert', + 'async' +], function (Backbone, ContentView, DocumentListView, DocumentNewVersionView, AdvancedSearchView, PromptView, ACLEditView, TagsManagementView, AlertView, async) { + 'use strict'; + var ContentDocumentListView = ContentView.extend({ + + initialize: function () { + ContentView.prototype.initialize.apply(this, arguments); + this.events['click .actions .checkout'] = 'actionCheckout'; + this.events['click .actions .undocheckout'] = 'actionUndocheckout'; + this.events['click .actions .checkin'] = 'actionCheckin'; + this.events['click .actions .delete'] = 'actionDelete'; + this.events['click .actions .tags'] = 'actionTags'; + this.events['click .actions .new-version'] = 'actionNewVersion'; + this.events['click .actions .new-release'] = 'releaseDocuments'; + this.events['click .actions .mark-as-obsolete'] = 'markAsObsolete'; + this.events['submit .actions #document-search-form'] = 'onQuickSearch'; + this.events['click .actions .advanced-search-button'] = 'onAdvancedSearchButton'; + this.events['click .actions .edit-acl'] = 'onEditAcl'; + Backbone.Events.on('folder-delete:error',this.onError); + }, + + rendered: function () { + + this.checkoutGroup = this.$('.actions .checkout-group'); + this.checkoutButton = this.$('.checkout'); + this.undoCheckoutButton = this.$('.undocheckout'); + this.checkinButton = this.$('.checkin'); + this.deleteButton = this.$('.actions .delete'); + this.tagsButton = this.$('.actions .tags'); + this.newVersionButton = this.$('.actions .new-version'); + this.releaseButton = this.$('.actions .new-release'); + this.obsoleteButton = this.$('.actions .mark-as-obsolete'); + this.aclButton = this.$('.actions .edit-acl'); + this.notifications = this.$('>.notifications'); + + this.tagsButton.show(); + + this.listView = this.addSubView( + new DocumentListView({ + el: '#list-' + this.cid, + collection: this.collection + }) + ); + var self = this; + this.collection.fetch({reset: true}).error(function(err) { + self.onError(null,err); + }); + + this.listenTo(this.listView, 'selectionChange', this.onStateChange); + this.listenTo(this.collection, 'change', this.onStateChange); + this.listenTo(this.collection, 'add', this.highlightAddedView); + + this.$('.tabs').tabs(); + }, + + onStateChange: function () { + + var checkedViews = this.listView.checkedViews(); + + switch (checkedViews.length) { + case 0: + this.onNoDocumentSelected(); + break; + case 1: + this.onOneDocumentSelected(checkedViews[0].model); + break; + default: + this.onSeveralDocumentsSelected(); + break; + } + + }, + + onNoDocumentSelected: function () { + this.deleteButton.hide(); + this.checkoutGroup.hide(); + //this.tagsButton.show(); + this.newVersionButton.toggle(false); + this.changeReleaseButtonDisplay(false); + this.changeObsoleteButtonDisplay(false); + this.aclButton.hide(); + }, + + onOneDocumentSelected: function (document) { + this.deleteButton.show(); + this.checkoutGroup.css('display', 'inline-block'); + + if (document.isCheckout()) { + this.newVersionButton.toggle(false); + if (document.isCheckoutByConnectedUser()) { + var canUndo = document.getLastIteration().get('iteration') > 1; + this.updateCheckoutGroupButtons(false, canUndo, true); + } else { + this.updateCheckoutGroupButtons(false, false, false); + } + } else if (document.getReleaseAuthor()) { + this.newVersionButton.toggle(true); + this.changeCheckoutGroupDisplay(false); + this.changeReleaseButtonDisplay(false); + if (document.isObsolete()) { + this.changeObsoleteButtonDisplay(false); + } else { + this.changeObsoleteButtonDisplay(true); + } + } else { + this.changeReleaseButtonDisplay(true); + this.newVersionButton.toggle(true); + this.updateCheckoutGroupButtons(true, false, false); + } + + + if ((App.config.workspaceAdmin || document.attributes.author.login === App.config.login)) { + this.aclButton.show(); + } + + }, + areAllDocumentsCheckedOut: function () { + var isCheckedOut = true; + this.listView.eachChecked(function (view) { + if (!view.model.isCheckout()) { + isCheckedOut = false; + } + }); + return isCheckedOut; + }, + areAllDocumentsNotCheckedOut: function () { + var isNotCheckedOut = true; + this.listView.eachChecked(function (view) { + if (view.model.isCheckout()) { + isNotCheckedOut = false; + } + }); + return isNotCheckedOut; + }, + areAllDocumentsCheckedOutByConnectedUser: function () { + var isCheckedOutByMe = true; + this.listView.eachChecked(function (view) { + if (!view.model.isCheckoutByConnectedUser()) { + isCheckedOutByMe = false; + } + }); + return isCheckedOutByMe; + }, + isNotThefirstIteration: function () { + var notFirstIteration = true; + this.listView.eachChecked(function (view) { + if (view.model.getLastIteration().get('iteration') <= 1) { + notFirstIteration = false; + } + }); + return notFirstIteration; + }, + + areSelectedDocumentsReleasable: function () { + var areAllDocumentsReleasable = true; + this.listView.eachChecked(function (view) { + if (view.model.isCheckout() || view.model.isReleased() || view.model.isObsolete()) { + areAllDocumentsReleasable = false; + } + }); + return areAllDocumentsReleasable; + }, + areSelectedDocumentsCheckoutable: function () { + var areAllDocumentsCheckoutable = true; + this.listView.eachChecked(function (view) { + if (view.model.isReleased() || view.model.isCheckout() || view.model.isObsolete()) { + areAllDocumentsCheckoutable = false; + } + }); + return areAllDocumentsCheckoutable; + }, + + updateActionButtonsDisplay: function () { + if (this.areAllDocumentsCheckedOut()) { + if (this.areAllDocumentsCheckedOutByConnectedUser()) { + this.updateCheckoutGroupButtons(false, this.isNotThefirstIteration(), true); + } else { + this.updateCheckoutGroupButtons(false, false, false); + } + } else if (this.areAllDocumentsNotCheckedOut()) { + this.changeObsoleteButtonDisplay(false); + this.changeReleaseButtonDisplay(this.areSelectedDocumentsReleasable()); + if (this.areSelectedDocumentsCheckoutable()) { + this.updateCheckoutGroupButtons(true, false, false); + } else { + this.changeCheckoutGroupDisplay(false); + } + } else { + this.updateCheckoutGroupButtons(false, false, false); + this.changeReleaseButtonDisplay(false); + this.changeObsoleteButtonDisplay(false); + } + }, + onSeveralDocumentsSelected: function () { + this.deleteButton.show(); + this.newVersionButton.toggle(false); + this.checkoutGroup.css('display', 'inline-block'); + this.updateActionButtonsDisplay(); + this.aclButton.hide(); + }, + + updateCheckoutGroupButtons: function (canCheckout, canUndo, canCheckin) { + this.changeCheckoutGroupDisplay(true); + this.checkoutButton.prop('disabled', !canCheckout); + this.undoCheckoutButton.prop('disabled', !canUndo); + this.checkinButton.prop('disabled', !canCheckin); + }, + + changeCheckoutGroupDisplay: function (state) { + this.checkoutGroup.toggle(state); + }, + + changeReleaseButtonDisplay: function (state) { + this.releaseButton.toggle(state); + }, + + changeObsoleteButtonDisplay: function(state) { + this.obsoleteButton.toggle(state); + }, + + actionCheckout: function () { + + var self = this; + + var selectedDocuments = this.listView.checkedViews(); + + var queueCheckOut= async.queue(function(docView,callback){ + docView.model.checkout().success(callback); + }); + + queueCheckOut.drain = function(){ + self.multipleCheckInCheckOutDone(selectedDocuments); + }; + + queueCheckOut.push(selectedDocuments); + return false; + }, + + actionUndocheckout: function () { + var self = this; + bootbox.confirm(App.config.i18n.UNDO_CHECKOUT_QUESTION, function (result) { + if (result) { + + var selectedDocuments = self.listView.checkedViews(); + var queueUndoCheckOut= async.queue(function(docView,callback){ + docView.model.undocheckout().success(callback); + }); + queueUndoCheckOut.drain = function(){ + self.multipleCheckInCheckOutDone(selectedDocuments); + }; + + queueUndoCheckOut.push(selectedDocuments); + } + }); + return false; + }, + + actionCheckin: function () { + var selectedDocuments = this.listView.checkedViews(); + var selectedDocumentsWithoutNote = 0; + + _.each(selectedDocuments, function (selectedDocView) { + if (!selectedDocView.model.getLastIteration().get('revisionNote')) { + selectedDocumentsWithoutNote++; + } + }); + + var self = this; + + if (selectedDocumentsWithoutNote > 0) { + var promptView = new PromptView(); + + if (selectedDocuments.length > 1) { + promptView.setPromptOptions(App.config.i18n.REVISION_NOTE, App.config.i18n.DOCUMENT_REVISION_NOTE_PROMPT_LABEL, App.config.i18n.REVISION_NOTE_PROMPT_OK, App.config.i18n.REVISION_NOTE_PROMPT_CANCEL); + } else { + promptView.setPromptOptions(App.config.i18n.REVISION_NOTE, App.config.i18n.REVISION_NOTE_PROMPT_LABEL, App.config.i18n.REVISION_NOTE_PROMPT_OK, App.config.i18n.REVISION_NOTE_PROMPT_CANCEL); + } + + promptView.specifyInput('textarea'); + window.document.body.appendChild(promptView.render().el); + promptView.openModal(); + + this.listenTo(promptView, 'prompt-ok', function (args) { + + var iterationNote = args[0]; + if(_.isEqual(iterationNote, '')){ + iterationNote = null; + } + + var queueCheckIn = async.queue(function(docView, callback) { + var revisionNote; + if (iterationNote) { + revisionNote = docView.model.getLastIteration().get('revisionNote'); + if (!revisionNote) { + revisionNote = iterationNote; + } + } + + docView.model.getLastIteration().save({ + revisionNote: revisionNote + }).success(function(){ + docView.model.checkin().success(callback); + }); + }); + + queueCheckIn.drain = function(){ + self.multipleCheckInCheckOutDone(selectedDocuments); + }; + + queueCheckIn.push(selectedDocuments); + }); + + this.listenTo(promptView, 'prompt-cancel', function () { + var queueCheckIn= async.queue(function(docView,callback){ + docView.model.checkin().success(callback); + }); + + queueCheckIn.drain = function(){ + self.multipleCheckInCheckOutDone(selectedDocuments); + }; + + queueCheckIn.push(selectedDocuments); + }); + + } else { + var queueCheckIn = async.queue(function(docView, callback) { + docView.model.getLastIteration().save().success(function(){ + docView.model.checkin().success(callback); + }); + }); + + queueCheckIn.drain = function(){ + self.multipleCheckInCheckOutDone(selectedDocuments); + }; + + queueCheckIn.push(selectedDocuments); + } + + return false; + }, + + multipleCheckInCheckOutDone: function (selectedDocuments) { + var that = this; + this.collection.fetch({reset: true}).success(function(){ + that.listView.checkCheckboxes(selectedDocuments); + }); + Backbone.Events.trigger('document:iterationChange'); + }, + + actionDelete: function () { + var that = this; + + bootbox.confirm(App.config.i18n.DELETE_SELECTION_QUESTION, function (result) { + if (result) { + + var checkedViews = that.listView.checkedViews(); + var requestsToBeDone = checkedViews.length; + var requestsDone = 0; + + var onRequestOver = function () { + if (++requestsDone === requestsToBeDone) { + that.listView.redraw(); + that.collection.fetch(); + that.onStateChange(); + Backbone.Events.trigger('document:iterationChange'); + } + }; + + that.listView.eachChecked(function (view) { + view.model.destroy({ + wait: true, + dataType: 'text', // server doesn't send a json hash in the response body + success: onRequestOver, + error: function (model, err) { + that.onError(model, err); + onRequestOver(); + } + }); + }); + } + }); + + return false; + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + + this.notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + actionTags: function () { + var self = this; + var documentsChecked = new Backbone.Collection(); + + + this.listView.eachChecked(function (view) { + documentsChecked.push(view.model); + }); + + + self.addSubView( + new TagsManagementView({ + collection: documentsChecked + }) + ).show(); + + + return false; + + }, + + actionNewVersion: function () { + + var documentChecked = null; + + this.listView.eachChecked(function (view) { + documentChecked = view.model; + }); + + var newVersionView = new DocumentNewVersionView({ + model: documentChecked + }); + + window.document.body.appendChild(newVersionView.render().el); + + newVersionView.openModal(); + + return false; + + }, + + releaseDocuments: function () { + var that = this; + bootbox.confirm(App.config.i18n.RELEASE_SELECTION_QUESTION, function (result) { + if (result) { + that.listView.eachChecked(function (view) { + view.model.release(); + }); + } + }); + }, + + markAsObsolete: function () { + var that = this; + bootbox.confirm(App.config.i18n.MARK_DOCUMENT_AS_OBSOLETE_QUESTION, function(result){ + if(result){ + that.listView.eachChecked(function (view) { + view.model.markAsObsolete(); + }); + } + }); + }, + + onQuickSearch: function (e) { + + if (e.target.children[0].value) { + App.router.navigate(App.config.workspaceId + '/search/q=' + e.target.children[0].value, {trigger: true}); + } + + return false; + }, + + onAdvancedSearchButton: function () { + var advancedSearchView = new AdvancedSearchView(); + window.document.body.appendChild(advancedSearchView.render().el); + advancedSearchView.openModal(); + }, + + onEditAcl: function () { + + var that = this; + var documentChecked; + + this.listView.eachChecked(function (view) { + documentChecked = view.model; + }); + + if (documentChecked) { + + var aclEditView = new ACLEditView({ + editMode: true, + acl: documentChecked.get('acl') + }); + + aclEditView.setTitle(documentChecked.getReference()); + window.document.body.appendChild(aclEditView.render().el); + + aclEditView.openModal(); + aclEditView.on('acl:update', function () { + + var acl = aclEditView.toList(); + + documentChecked.updateACL({ + acl: acl || {userEntries: {}, groupEntries: {}}, + success: function () { + documentChecked.set('acl', acl); + aclEditView.closeModal(); + that.listView.redraw(); + }, + error: function () { + window.alert(App.config.i18n.EDITION_ERROR); + } + }); + }); + } + return false; + }, + + highlightAddedView: function (model) { + this.listView.redraw(); + var addedView = _.find(this.listView.subViews, function (view) { + return view.model === model; + }); + if (addedView) { + addedView.$el.highlightEffect(); + } + } + + }); + return ContentDocumentListView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/document/document_new.js b/docdoku-web-front/app/document-management/js/views/document/document_new.js new file mode 100644 index 0000000000..69bd136a4b --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/document/document_new.js @@ -0,0 +1,137 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/views/components/modal', + 'common-objects/views/attributes/attributes', + 'views/document/document_template_list', + 'common-objects/views/workflow/workflow_list', + 'common-objects/views/workflow/workflow_mapping', + 'common-objects/views/security/acl', + 'text!templates/document/document_new.html' +], function (Backbone, Mustache, ModalView, AttributesView, DocumentTemplateListView, DocumentWorkflowListView, DocumentWorkflowMappingView, ACLView, template) { + 'use strict'; + var DocumentNewView = ModalView.extend({ + + template: template, + + initialize: function () { + ModalView.prototype.initialize.apply(this, arguments); + this.events['click .modal-footer button.btn-primary'] = 'interceptSubmit'; + this.events['submit #form-' + this.cid] = 'onSubmitForm'; + }, + + rendered: function () { + + this.workflowsView = this.addSubView( + new DocumentWorkflowListView({ + el: '#workflows-' + this.cid + }) + ); + + this.workflowsMappingView = this.addSubView( + new DocumentWorkflowMappingView({ + el: '#workflows-mapping-' + this.cid + }) + ); + + this.workflowsView.on('workflow:change', this.workflowsMappingView.updateMapping); + + this.attributesView = this.addSubView( + new AttributesView({ + el: '#tab-attributes-' + this.cid + }) + ); + + this.attributesView.render(); + + this.templatesView = this.addSubView( + new DocumentTemplateListView({ + el: '#templates-' + this.cid, + workflowsView: this.workflowsView, + attributesView: this.attributesView + }) + ); + + this.templatesView.collection.fetch({reset: true}); + + this.workspaceMembershipsView = new ACLView({ + el: this.$('#acl-mapping-' + this.cid), + editMode: true + }).render(); + + this.$('input.reference').customValidity(App.config.i18n.REQUIRED_FIELD); + + }, + + interceptSubmit:function(){ + this.isValid = !this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function () { + + if (this.isValid) { + var workflow = this.workflowsView.selected(); + var template = this.templatesView.selected(); + var acl = this.workspaceMembershipsView.toList(); + + var data = { + reference: this.$('#form-' + this.cid + ' .reference').val(), + title: this.$('#form-' + this.cid + ' .title').val(), + description: this.$('#form-' + this.cid + ' .description').val(), + workflowModelId: workflow ? workflow.get('id') : null, + templateId: template ? template.get('id') : null, + roleMapping: workflow ? this.workflowsMappingView.toResolvedList() : null, + acl: acl + }; + + + + this.collection.create(data, { + success: this.success, + error: this.error, + wait: true + }); + } + + return false; + }, + + success: function (model) { + var that = this; + model.getLastIteration().save({ + instanceAttributes: this.attributesView.collection.toJSON() + }, { + success: function () { + if(that.options.autoAddTag){ + model.addTags([that.options.autoAddTag]).success(function(){ + that.hide(); + model.fetch(); + }); + }else{ + that.hide(); + model.fetch(); + } + Backbone.Events.trigger('document:iterationChange'); + }, + error: this.error + }); + }, + + error: function (model, error) { + this.collection.remove(model); + if (error.responseText) { + this.alert({ + type: 'error', + message: error.responseText + }); + } else { + console.error(error); + } + } + + }); + + return DocumentNewView; + +}); diff --git a/docdoku-web-front/app/document-management/js/views/document/document_new_version.js b/docdoku-web-front/app/document-management/js/views/document/document_new_version.js new file mode 100644 index 0000000000..b35e546fa6 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/document/document_new_version.js @@ -0,0 +1,87 @@ +/*global define,App,_*/ +define([ + 'backbone', + 'mustache', + 'text!templates/document/document_new_version.html', + 'common-objects/views/workflow/workflow_mapping', + 'common-objects/views/workflow/workflow_list', + 'common-objects/views/security/acl_clone_edit' +], function (Backbone, Mustache, template, DocumentWorkflowMappingView, DocumentWorkflowListView, ACLView) { + + 'use strict'; + + var DocumentsNewVersionView = Backbone.View.extend({ + + events: { + 'click #create-new-version-btn': 'createNewVersionAction', + 'click #cancel-new-version-btn': 'closeModalAction', + 'click a.close': 'closeModalAction', + 'hidden #new-version-modal':'onHidden' + }, + + initialize:function(){ + _.bindAll(this); + }, + + render: function () { + + this.template = Mustache.render(template, {i18n: App.config.i18n, document: this.model.attributes}); + + this.$el.html(this.template); + + this.bindDomElements(); + + this.workflowsView = new DocumentWorkflowListView(); + this.newVersionWorkflowDiv.html(this.workflowsView.el); + + this.workflowsMappingView = new DocumentWorkflowMappingView({ + el: this.$('#workflows-mapping') + }); + + this.workflowsView.on('workflow:change', this.workflowsMappingView.updateMapping); + + this.aclView = new ACLView({ + el: this.$('#acl-mapping'), + editMode: true, + acl: this.model.get('acl') + }).render(); + + this.$('.tabs').tabs(); + + return this; + }, + + bindDomElements: function () { + this.newVersionWorkflowDiv = this.$('#new-version-workflow'); + this.inputNewVersionTitle = this.$('#new-version-title'); + this.textAreaNewVersionDescription = this.$('#new-version-description'); + this.$modal = this.$('#new-version-modal'); + }, + + createNewVersionAction: function () { + if(this.workflowsMappingView.isValid()){ + this.model.createNewVersion(this.inputNewVersionTitle.val(), this.textAreaNewVersionDescription.val(), this.workflowsView.selected(), this.workflowsMappingView.toResolvedList(), this.aclView.toList()); + this.closeModalAction(); + }else{ + this.$('.tabs').find('li:eq(2) a').tab('show'); + } + + }, + + openModal:function(){ + this.$modal.modal('show'); + }, + + closeModalAction: function () { + this.$modal.modal('hide'); + }, + + onHidden:function(){ + this.remove(); + } + + }); + + return DocumentsNewVersionView; + +}); diff --git a/docdoku-web-front/app/document-management/js/views/document/document_template_list.js b/docdoku-web-front/app/document-management/js/views/document/document_template_list.js new file mode 100644 index 0000000000..91f04babce --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/document/document_template_list.js @@ -0,0 +1,102 @@ +/*global define,$*/ +define([ + 'collections/template', + 'common-objects/views/base', + 'text!templates/document/document_template_select.html' +], function (TemplateList, BaseView, template) { + 'use strict'; + var DocumentTemplateListView = BaseView.extend({ + template: template, + + collection: function () { + return TemplateList.getInstance(); + }, + + initialize: function (options) { + BaseView.prototype.initialize.apply(this, arguments); + this.workflowsView = options.workflowsView; + this.attributesView = options.attributesView; + this.events.change = 'changed'; + }, + + collectionReset: function () { + this.render(); + }, + + collectionToJSON: function () { + var data = BaseView.prototype.collectionToJSON.call(this); + // Insert the empty option + data.unshift({ + id: '' + }); + return data; + }, + + selected: function () { + var id = this.$('#select-' + this.cid).val(); + return this.collection.get(id); + }, + + changed: function () { + // Reset reference field + this.parentView.$el.find('input.reference:first') + .unmask() + .val(''); + + // Insert Template attributes if any + var collection = []; + var template = this.selected(); + + if (template) { + var attributes = template.get('attributeTemplates'); + for (var i = attributes.length - 1; i >= 0; i--) { + collection.unshift({ + type: attributes[i].attributeType, + name: attributes[i].name, + mandatory: attributes[i].mandatory, + value: '', + lovName:attributes[i].lovName, + locked:attributes[i].locked + }); + } + if (template.get('mask')) { + // Set field mask + this.showMask(template); + } + if (template.get('idGenerated')) { + this.generateId(template); + } + + this.workflowsView.setValue(template.get('workflowModelId')); + + this.attributesView.setAttributesLocked(template.isAttributesLocked()); + this.attributesView.render(); + + } else { + this.workflowsView.setValue(null); + } + + this.attributesView.collection.reset(collection); + }, + + showMask: function (template) { + var elId = this.parentView.$el.find('input.reference:first'); + var mask = template.get('mask');//.replace(/#/g, '9'); + elId.mask(mask); + }, + + generateId: function (template) { + var elId = this.parentView.$el.find('input.reference:first'); + // Get the next id from the webservice if any + $.getJSON(template.url() + '/generate_id', function (data) { + if (data) { + elId.val(data.id); + } + }, 'html'); // TODO: fix the webservice return type (actualy: json) + } + + }); + + return DocumentTemplateListView; + +}); diff --git a/docdoku-web-front/app/document-management/js/views/document_list.js b/docdoku-web-front/app/document-management/js/views/document_list.js new file mode 100644 index 0000000000..0a83993284 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/document_list.js @@ -0,0 +1,68 @@ +/*global define,App,_*/ +define([ + 'common-objects/views/documents/checkbox_list', + 'views/document_list_item', + 'text!templates/document_list.html' +], function (CheckboxListView, DocumentListItemView, template) { + 'use strict'; + var DocumentListView = CheckboxListView.extend({ + + template: template, + + itemViewFactory: function (model) { + model.on('change', this.redraw); + return new DocumentListItemView({ + model: model + }); + }, + rendered: function () { + this.once('_ready', function() { + this.dataTable(); + this.trigger('selectionChange'); // TODO rename event name accordingly to patterns ('entity:verb') + }); + + }, + redraw: function () { + this.dataTable(); + }, + dataTable: function () { + var oldSort = []; + if (this.oTable) { + oldSort = this.oTable.fnSettings().aaSorting; + this.oTable.fnDestroy(); + } + + this.oTable = this.$el.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + { 'bSortable': false, 'aTargets': [ 0, 1, 11, 12, 13, 14, 15 ] }, + { 'sType': App.config.i18n.DATE_SORT, 'aTargets': [8] }, + { 'sType': 'strip_html', 'aTargets': [2] } + ] + }); + + this.$el.parent().find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + }, + + checkCheckboxes: function (selectedDocuments) { + var that = this; + _.each(selectedDocuments, function(selectedView) { + _.each(that.subViews, function(view) { + if(selectedView.model.getId() === view.model.getId()) { + view.check(); + } + }); + }); + } + + }); + return DocumentListView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/document_list_item.js b/docdoku-web-front/app/document-management/js/views/document_list_item.js new file mode 100644 index 0000000000..3a851b939b --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/document_list_item.js @@ -0,0 +1,180 @@ +/*global $,define,App*/ +define([ + 'backbone', + 'common-objects/utils/date', + 'common-objects/views/documents/checkbox_list_item', + 'common-objects/views/document/document_iteration', + 'text!templates/document_list_item.html', + 'common-objects/views/share/share_entity' +], function (Backbone, date, CheckboxListItemView, IterationView, template, ShareView) { + 'use strict'; + var DocumentListItemView = CheckboxListItemView.extend({ + + template: template, + + tagName: 'tr', + + initialize: function () { + CheckboxListItemView.prototype.initialize.apply(this, arguments); + + // jQuery creates it's own event object, and it doesn't have a + // dataTransfer property yet. This adds dataTransfer to the event object. + $.event.props.push('dataTransfer'); + + this.events['click .reference'] = this.actionEdit; + this.events['click .state-subscription'] = this.toggleStateSubscription; + this.events['click .iteration-subscription'] = this.toggleIterationSubscription; + this.events['click .document-master-share i'] = this.shareDocument; + this.events['click .document-attached-files i'] = this.openDocumentModal; + this.events['dragstart a.dochandle'] = this.dragStart; + this.events['dragend a.dochandle'] = this.dragEnd; + this.events['dragstart td.doc-ref'] = this.dragStart; + this.events['dragend td.doc-ref'] = this.dragEnd; + + }, + + modelToJSON: function () { + + var data = this.model.toJSON(); + data.reference = this.model.getReference(); + + if (this.model.hasIterations()) { + data.lastIteration = this.model.getLastIteration().toJSON(); + data.lastIteration.modificationDate = date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + data.lastIteration.modificationDate + ); + } + + if (this.model.hasACLForCurrentUser()) { + data.isReadOnly = this.model.isReadOnly(); + data.isFullAccess = this.model.isFullAccess(); + } + + data.isCheckoutByConnectedUser = this.model.isCheckoutByConnectedUser(); + data.isCheckout = this.model.isCheckout(); + if (this.model.getLastIteration()){ + data.hasAttachedFiles = this.model.getLastIteration().getAttachedFiles().length; + } + + data.isReleased = this.model.isReleased(); + data.isObsolete = this.model.isObsolete(); + + return data; + }, + + rendered: function () { + CheckboxListItemView.prototype.rendered.apply(this, arguments); + + if (this.model.isStateChangedSubscribed()) { + this.$('.state-subscription').addClass('fa-bell').attr('title', App.config.i18n.UNSUBSCRIBE_STATE_CHANGE); + } else { + this.$('.state-subscription').addClass('fa-bell-o').attr('title', App.config.i18n.SUBSCRIBE_STATE_CHANGE); + } + + if (this.model.isIterationChangedSubscribed()) { + this.$('.iteration-subscription').addClass('fa-bell').attr('title', App.config.i18n.UNSUBSCRIBE_ITERATION_CHANGE); + } else { + this.$('.iteration-subscription').addClass('fa-bell-o').attr('title', App.config.i18n.SUBSCRIBE_ITERATION_CHANGE); + } + + this.$('.author-popover').userPopover(this.model.attributes.author.login, this.model.id, 'left'); + + if (this.model.isCheckout()) { + this.$('.checkout-user-popover').userPopover(this.model.getCheckoutUser().login, this.model.id, 'left'); + } + + date.dateHelper(this.$('.date-popover')); + this.bindDescriptionPopover(); + this.$el.attr('title',this.model.getReference()); + + }, + + bindDescriptionPopover: function() { + if(this.model.getDescription() !== undefined && this.model.getDescription() !== null && this.model.getDescription() !== '') { + var self = this; + this.$('.reference.doc-ref') + .popover({ + title: App.config.i18n.DESCRIPTION, + html: true, + content: self.model.getDescription(), + trigger: 'hover', + placement: 'top', + container: 'body' + }); + } + }, + + dragStart: function (e) { + var that = this; + this.$el.addClass('moving'); + + Backbone.Events.on('document-moved', function () { + Backbone.Events.off('document-moved'); + Backbone.Events.off('document-error-moved'); + that.model.collection.remove(that.model); + }); + Backbone.Events.on('document-error-moved', function () { + Backbone.Events.off('document-moved'); + Backbone.Events.off('document-error-moved'); + that.$el.removeClass('moving'); + }); + var data = JSON.stringify(this.model.toJSON()); + var img = document.createElement('img'); + img.src = App.config.contextPath + '/images/icon-action-document-move.png'; + e.dataTransfer.setDragImage(img, 0, 0); + e.dataTransfer.setData('document:text/plain', data); + e.dataTransfer.dropEffect = 'none'; + e.dataTransfer.effectAllowed = 'copyMove'; + return e; + }, + + dragEnd: function (e) { + if (e.dataTransfer.dropEffect === 'none') { + Backbone.Events.off('document-moved'); + Backbone.Events.off('document-error-moved'); + } + this.$el.removeClass('moving'); + }, + + actionEdit: function () { + var that = this; + this.model.fetch().success(function () { + new IterationView({ + model: that.model + }).show(); + }); + }, + + openDocumentModal: function(){ + var that = this; + this.model.fetch().success(function () { + var view = new IterationView({ + model: that.model + }); + view.show(); + view.activateFileTab(); + + }); + + }, + toggleStateSubscription: function () { + this.model.toggleStateSubscribe(this.model.isStateChangedSubscribed()); + }, + + toggleIterationSubscription: function () { + this.model.toggleIterationSubscribe(this.model.isIterationChangedSubscribed()); + }, + + shareDocument: function () { + var that = this; + var shareView = new ShareView({model: that.model, entityType: 'documents'}); + window.document.body.appendChild(shareView.render().el); + shareView.openModal(); + + } + + }); + + return DocumentListItemView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/folder_document_list.js b/docdoku-web-front/app/document-management/js/views/folder_document_list.js new file mode 100644 index 0000000000..1aa2669e72 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/folder_document_list.js @@ -0,0 +1,57 @@ +/*global define,App*/ +define([ + 'collections/folder_document', + 'views/content_document_list', + 'views/document/document_new', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/checkout_button_group.html', + 'text!common-objects/templates/buttons/tags_button.html', + 'text!common-objects/templates/buttons/import_button.html', + 'text!common-objects/templates/buttons/new_version_button.html', + 'text!common-objects/templates/buttons/release_button.html', + 'text!common-objects/templates/buttons/obsolete_button.html', + 'text!common-objects/templates/buttons/ACL_button.html', + 'text!templates/search_document_form.html', + 'text!templates/folder_document_list.html' +], function (FolderDocumentList, ContentDocumentListView, DocumentNewView, deleteButton, checkoutButtonGroup, tagsButton, importButton, newVersionButton, releaseButton, obsoleteButton, aclButton, searchForm, template) { + 'use strict'; + var FolderDocumentListView = ContentDocumentListView.extend({ + + template: template, + + partials: { + deleteButton: deleteButton, + checkoutButtonGroup: checkoutButtonGroup, + tagsButton: tagsButton, + newVersionButton: newVersionButton, + releaseButton: releaseButton, + obsoleteButton:obsoleteButton, + searchForm: searchForm, + aclButton: aclButton, + importButton: importButton + }, + collection: function () { + return new FolderDocumentList(); + }, + initialize: function () { + ContentDocumentListView.prototype.initialize.apply(this, arguments); + this.events['click .actions .new-document'] = 'actionNew'; + if (this.model) { + this.collection.parent = this.model; + } + this.templateExtraData = { + isReadOnly: App.appView.isReadOnly() + }; + }, + + actionNew: function () { + this.addSubView( + new DocumentNewView({ + collection: this.collection + }) + ).show(); + return false; + } + }); + return FolderDocumentListView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/folder_edit.js b/docdoku-web-front/app/document-management/js/views/folder_edit.js new file mode 100644 index 0000000000..6e2156df48 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/folder_edit.js @@ -0,0 +1,57 @@ +/*global define,App*/ +define([ + 'common-objects/views/components/modal', + 'text!templates/folder_edit.html' +], function (ModalView, template) { + + 'use strict'; + + var FolderEditView = ModalView.extend({ + template: template, + tagName: 'div', + initialize: function () { + ModalView.prototype.initialize.apply(this, arguments); + this.events['submit #edit-folder-form'] = 'onSubmitForm'; + }, + rendered: function () { + this.nameInput = this.$('input.name'); + this.nameInput.customValidity(App.config.i18n.REQUIRED_FIELD); + this.nameInput.val(this.model.get('name')); + this.previousName = this.model.get('name'); + }, + + onSubmitForm: function (e) { + + var name = this.nameInput.val() ? this.nameInput.val().trim():''; + + if (name && name !== this.model.get('name')) { + this.model.save({ + name: name + }, { + success: this.success, + error: this.error + }); + } + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + success: function (model, response) { + this.model.id = response.id; + this.hide(); + }, + error: function (model, error) { + this.model.set('name',this.previousName); + if (error.responseText) { + this.alert({ + type: 'error', + message: error.responseText + }); + } else { + console.error(error); + } + } + }); + return FolderEditView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/folder_list.js b/docdoku-web-front/app/document-management/js/views/folder_list.js new file mode 100644 index 0000000000..7c09056ac9 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/folder_list.js @@ -0,0 +1,16 @@ +/*global define*/ +define([ + 'require', + 'common-objects/views/components/collapsible_list' +], function (require, CollapsibleListView) { + 'use strict'; + var FolderListView = CollapsibleListView.extend({ + itemViewFactory: function (model) { + var FolderListItemView = require('views/folder_list_item'); // Circular dependency + return new FolderListItemView({ + model: model + }); + } + }); + return FolderListView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/folder_list_item.js b/docdoku-web-front/app/document-management/js/views/folder_list_item.js new file mode 100644 index 0000000000..b244581d20 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/folder_list_item.js @@ -0,0 +1,328 @@ +/*global $,_,define,bootbox,App*/ +define([ + 'backbone', + 'mustache', + 'require', + 'common-objects/models/document/document_revision', + 'models/folder', + 'collections/folder', + 'common-objects/views/components/list_item', + 'views/folder_list', + 'views/folder_document_list', + 'views/folder_new', + 'views/folder_edit', + 'text!templates/folder_list_item.html' +], function (Backbone,Mustache, require, DocumentRevision, Folder, FolderList, ListItemView, FolderListView, FolderDocumentListView, FolderNewView, FolderEditView, template) { + 'use strict'; + var FolderListItemView = ListItemView.extend({ + + template: template, + + tagName: 'li', + className: 'folder FolderNavListItem', + collection: function () { + return new FolderList(); + }, + initialize: function () { + ListItemView.prototype.initialize.apply(this, arguments); + // jQuery creates it's own event object, and it doesn't have a + // dataTransfer property yet. This adds dataTransfer to the event object. + $.event.props.push('dataTransfer'); + + this.isOpen = false; + this.templateExtraData = { + isReadOnly: App.appView.isReadOnly() + }; + if (this.model) { + this.collection.parent = this.model; + } + this.events = _.extend(this.events, { + 'click .header .new-folder': 'actionNewFolder', + 'click .header .edit': 'actionEdit', + 'click .header .delete': 'actionDelete', + 'mouseleave .header': 'hideActions', + 'dragenter >.nav-list-entry': 'onDragEnter', + 'dragover >.nav-list-entry': 'checkDrag', + 'dragleave >.nav-list-entry': 'onDragLeave', + 'dragstart >.nav-list-entry': 'onDragStart', + 'dragend >.nav-list-entry': 'onDragEnd', + 'drop >.nav-list-entry': 'onDrop' + }); + this.events['click [data-target="#items-' + this.cid + '"]'] = 'forceShow'; + this.events['click .status'] = 'toggle'; + this.bind('shown', this.shown); + this.bind('hidden', this.hidden); + }, + modelToJSON: function () { + var data = this.model.toJSON(); + if (data.id) { + data.path = data.id.replace(/^[^:]*:?/, ''); + this.modelPath = data.path; + } + return data; + }, + rendered: function () { + var isHome = this.model ? this.model.get('home') : false; + var isRoot = _.isUndefined(this.model); + if (isHome) { + this.$el.addClass('home'); + } + if (isRoot || isHome) { + this.$('.delete').remove(); + this.$('.edit').remove(); + } + + this.foldersView = this.addSubView( + new FolderListView({ + el: '#items-' + this.cid, + collection: this.collection + }) + ).render(); + + this.folderDiv = this.$('>.nav-list-entry'); + + }, + forceShow: function (e) { + var isRoot = _.isUndefined(this.model); + if (isRoot) { + this.show(); + } else { + this.showContent(); + this.navigate(); + } + e.stopPropagation(); + e.preventDefault(); + return false; + }, + show: function (routePath) { + if (routePath) { + this.listenToOnce(this.collection, 'reset', this.traverse.bind(this)); + } + this.routePath = routePath; + this.isOpen = true; + this.foldersView.show(); + this.trigger('shown'); + }, + shown: function () { + this.$el.addClass('open'); + if (!_.isUndefined(this.routePath)) { + // If from direct url access (address bar) + // show documents only if not traversed + var pattern = new RegExp('^' + this.modelPath); + if (this.routePath.match(pattern)) { + this.showContent(); + } + } else { + // If not from direct url access (click) + this.showContent(); + this.navigate(); + } + }, + showContent: function () { + this.setActive(); + this.addSubView(new FolderDocumentListView({ + model: this.model + })).render(); + }, + hide: function () { + this.isOpen = false; + this.foldersView.hide(); + this.trigger('hidden'); + }, + hidden: function () { + this.$el.removeClass('open'); + }, + navigate: function () { + var path = this.modelPath ? '/' + encodeURIComponent(this.modelPath) : ''; + App.router.navigate(App.config.workspaceId + '/configspec/' + App.config.documentConfigSpec + '/folders' + path, {trigger: false}); + }, + + toggle: function () { + if (this.isOpen) { + this.hide(); + } else { + this.show(); + } + return false; + }, + traverse: function () { + if (this.routePath) { + var routePath = this.routePath; + _.each(this.foldersView.subViews, function (view) { + var pattern = new RegExp('^' + view.modelPath); + if (routePath.match(pattern)) { + view.show(routePath); + } + }); + } + }, + + /** State */ + setActive: function () { + if (App.$documentManagementMenu) { + App.$documentManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.header').first().addClass('active'); + }, + isActive: function () { + return this.$el.find('.header').first().hasClass('active'); + }, + + /** Action */ + hideActions: function () { + // Prevents the actions menu to stay opened all the time + this.$el.find('.header .btn-group').first().removeClass('open'); + }, + actionNewFolder: function () { + this.hideActions(); + this.addSubView( + new FolderNewView({ + collection: this.collection + }) + ); + return false; + }, + actionEdit: function () { + this.hideActions(); + new FolderEditView({ + model: this.model + }).show(); + return false; + }, + actionDelete: function () { + this.hideActions(); + var that = this; + bootbox.confirm(App.config.i18n.DELETE_FOLDER_QUESTION, function(result){ + if(result){ + that.model.destroy({ + wait:true, + dataType: 'text', // server doesn't send a json hash in the response body, + success:function(){ + Backbone.Events.trigger('document:iterationChange'); + }, + error:function(model,res){ + Backbone.Events.trigger('folder-delete:error',model,res); + } + }); + } + }); + return false; + }, + + onDragEnter: function () { + var that = this; + + if (!this.isOpen) { + setTimeout(function () { + if (that.folderDiv.hasClass('move-doc-into')) { + that.isOpen = true; + that.foldersView.show(); + that.$el.addClass('open'); + } + }, 500); + } + }, + + checkDrag: function (e) { + e.dataTransfer.dropEffect = 'copy'; + this.folderDiv.addClass('move-doc-into'); + return this.isActive(); + }, + + onDragLeave: function (e) { + e.dataTransfer.dropEffect = 'none'; + this.folderDiv.removeClass('move-doc-into'); + }, + + onDragStart : function(e){ + + var data = JSON.stringify(this.model.toJSON()); + + if(!data || this.model.get('home')){ + e.preventDefault(); + e.stopPropagation(); + return false; + } + + this.$el.addClass('moving'); + var that = this; + Backbone.Events.on('folder-moved', function () { + Backbone.Events.off('folder-moved'); + Backbone.Events.off('folder-error-moved'); + that.remove(); + }); + Backbone.Events.on('folder-error-moved', function () { + Backbone.Events.off('folder-moved'); + Backbone.Events.off('folder-error-moved'); + that.$el.removeClass('moving'); + }); + + + e.dataTransfer.setData('folder:text/plain',data); + + var img = document.createElement('img'); + img.src = App.config.contextPath + '/images/icon-nav-folder-opened.png'; + e.dataTransfer.setDragImage(img, 0, 0); + + + e.dataTransfer.dropEffect = 'none'; + e.dataTransfer.effectAllowed = 'copyMove'; + return e; + + + }, + + onDragEnd : function(){ + + }, + + onDrop: function (e) { + if(e.dataTransfer.getData('document:text/plain')){ + this.moveDocument(e); + } else if(e.dataTransfer.getData('folder:text/plain')){ + this.moveFolder(e); + } + }, + + moveDocument: function(e){ + + var that = this; + var documentRevision = new DocumentRevision(JSON.parse(e.dataTransfer.getData('document:text/plain'))); + + var path = documentRevision.getWorkspace(); + if (this.model) { + path = this.model.getPath() + '/' + this.model.getName(); + } + documentRevision.moveInto(path, function () { + Backbone.Events.trigger('document-moved'); + that.folderDiv.removeClass('move-doc-into'); + that.folderDiv.highlightEffect(); + }, function () { + Backbone.Events.trigger('document-error-moved'); + that.folderDiv.removeClass('move-doc-into'); + + }); + + }, + + moveFolder: function(e){ + var data = JSON.parse(e.dataTransfer.getData('folder:text/plain')); + var model = this.model||{id:App.config.workspaceId}; + var folder = new Folder(data); + var that = this ; + + folder.moveTo(model).success(function(){ + Backbone.Events.trigger('folder-moved'); + that.folderDiv.removeClass('move-doc-into'); + that.folderDiv.highlightEffect(); + that.show(); + }).error(function(){ + Backbone.Events.trigger('folder-error-moved'); + that.folderDiv.removeClass('move-doc-into'); + }); + + } + + }); + return FolderListItemView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/folder_nav.js b/docdoku-web-front/app/document-management/js/views/folder_nav.js new file mode 100644 index 0000000000..321e2034e3 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/folder_nav.js @@ -0,0 +1,26 @@ +/*global define,App*/ +define([ + 'mustache', + 'common-objects/common/singleton_decorator', + 'views/folder_list_item', + 'text!templates/folder_nav.html' +], function (Mustache, singletonDecorator, FolderListItemView, template) { + 'use strict'; + var FolderNavView = FolderListItemView.extend({ + + template: template, + el: '#folder-nav', + initialize: function () { + FolderListItemView.prototype.initialize.apply(this, arguments); + this.render(); + }, + refresh:function(){ + this.templateExtraData = { + isReadOnly: App.appView.isReadOnly() + }; + this.render(); + } + }); + FolderNavView = singletonDecorator(FolderNavView); + return FolderNavView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/folder_new.js b/docdoku-web-front/app/document-management/js/views/folder_new.js new file mode 100644 index 0000000000..7b98353ff2 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/folder_new.js @@ -0,0 +1,58 @@ +/*global define,App*/ +define([ + 'common-objects/views/components/modal', + 'text!templates/folder_new.html' +], function (ModalView, template) { + + 'use strict'; + + var FolderNewView = ModalView.extend({ + template: template, + tagName: 'div', + initialize: function () { + ModalView.prototype.initialize.apply(this, arguments); + this.events['submit #new-folder-form'] = 'onSubmitForm'; + }, + rendered:function(){ + this.nameInput = this.$('input.name'); + this.nameInput.customValidity(App.config.i18n.REQUIRED_FIELD); + }, + + onSubmitForm: function (e) { + + var name = this.nameInput.val() ? this.nameInput.val().trim():''; + + if (name) { + this.collection.create({ + name: name + }, { + url: this.collection.url(), + success: this.success, + error: this.error + }); + } + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + success: function () { + this.hide(); + this.parentView.show(); + }, + error: function (model, error) { + if (error.responseText) { + this.alert({ + type: 'error', + message: error.responseText + }); + this.collection.remove([model]); + } else { + console.error(error); + } + this.collection.remove([model]); + } + }); + return FolderNewView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/search_document_list.js b/docdoku-web-front/app/document-management/js/views/search_document_list.js new file mode 100644 index 0000000000..c609433d9e --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/search_document_list.js @@ -0,0 +1,48 @@ +/*global define,App*/ +define([ + 'collections/search_document', + 'views/content_document_list', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/checkout_button_group.html', + 'text!common-objects/templates/buttons/tags_button.html', + 'text!common-objects/templates/buttons/new_version_button.html', + 'text!common-objects/templates/buttons/release_button.html', + 'text!common-objects/templates/buttons/obsolete_button.html', + 'text!common-objects/templates/buttons/ACL_button.html', + 'text!templates/search_document_form.html', + 'text!templates/search_document_list.html' +], function (SearchDocumentList, ContentDocumentListView, deleteButton, checkoutButtonGroup, tagsButton, newVersionButton, releaseButton, obsoleteButton, aclButton, searchForm, template) { + 'use strict'; + var SearchDocumentListView = ContentDocumentListView.extend({ + template: template, + + partials: { + deleteButton: deleteButton, + checkoutButtonGroup: checkoutButtonGroup, + tagsButton: tagsButton, + newVersionButton: newVersionButton, + releaseButton: releaseButton, + obsoleteButton:obsoleteButton, + searchForm: searchForm, + aclButton: aclButton + }, + + collection: function () { + return new SearchDocumentList().setQuery(this.options.query); + }, + + initialize: function () { + + ContentDocumentListView.prototype.initialize.apply(this, arguments); + + if (this.model) { + this.collection.parent = this.model; + } + + this.templateExtraData = { + isReadOnly: App.appView.isReadOnly() + }; + } + }); + return SearchDocumentListView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/search_nav.js b/docdoku-web-front/app/document-management/js/views/search_nav.js new file mode 100644 index 0000000000..fc50be1942 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/search_nav.js @@ -0,0 +1,43 @@ +/*global define,App*/ +define([ + 'common-objects/common/singleton_decorator', + 'common-objects/views/base', + 'views/search_document_list', + 'views/advanced_search', + 'text!templates/search_nav.html' +], function (singletonDecorator, BaseView, SearchDocumentListView, AdvancedSearchView, template) { + 'use strict'; + var SearchNavView = BaseView.extend({ + template: template, + + el: '#search-nav', + + initialize: function () { + BaseView.prototype.initialize.apply(this, arguments); + this.events['click div'] = 'onClick'; + this.render(); + }, + + setActive: function () { + if (App.$documentManagementMenu) { + App.$documentManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + + showContent: function (query) { + this.setActive(); + this.addSubView( + new SearchDocumentListView({query: query}) + ).render(); + }, + + onClick: function () { + var advancedSearchView = new AdvancedSearchView(); + window.document.body.appendChild(advancedSearchView.render().el); + advancedSearchView.openModal(); + } + }); + SearchNavView = singletonDecorator(SearchNavView); + return SearchNavView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/tag_document_list.js b/docdoku-web-front/app/document-management/js/views/tag_document_list.js new file mode 100644 index 0000000000..f30ca30b3e --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/tag_document_list.js @@ -0,0 +1,58 @@ +/*global define,App*/ +define([ + 'collections/tag_document', + 'views/content_document_list', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/checkout_button_group.html', + 'text!common-objects/templates/buttons/tags_button.html', + 'text!common-objects/templates/buttons/new_version_button.html', + 'text!common-objects/templates/buttons/release_button.html', + 'text!common-objects/templates/buttons/obsolete_button.html', + 'text!common-objects/templates/buttons/ACL_button.html', + 'text!templates/search_document_form.html', + 'text!templates/tag_document_list.html', + 'views/document/document_new' +], function (TagDocumentList, ContentDocumentListView, deleteButton, checkoutButtonGroup, tagsButton, newVersionButton, releaseButton, obsoleteButton, aclButton, searchForm, template, DocumentNewView) { + 'use strict'; + var TagDocumentListView = ContentDocumentListView.extend({ + + template: template, + + partials: { + deleteButton: deleteButton, + checkoutButtonGroup: checkoutButtonGroup, + tagsButton: tagsButton, + newVersionButton: newVersionButton, + releaseButton: releaseButton, + obsoleteButton:obsoleteButton, + searchForm: searchForm, + aclButton: aclButton + }, + + collection: function () { + return new TagDocumentList(); + + }, + initialize: function () { + ContentDocumentListView.prototype.initialize.apply(this, arguments); + this.events['click .actions .new-document'] = 'actionNew'; + + if (this.model) { + this.collection.parent = this.model; + } + this.templateExtraData = { + isReadOnly: App.appView.isReadOnly() + }; + }, + actionNew: function () { + this.addSubView( + new DocumentNewView({ + collection: this.collection, + autoAddTag:this.model + }) + ).show(); + return false; + } + }); + return TagDocumentListView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/tag_list.js b/docdoku-web-front/app/document-management/js/views/tag_list.js new file mode 100644 index 0000000000..bf7093d201 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/tag_list.js @@ -0,0 +1,37 @@ +/*global _,define*/ +define([ + 'common-objects/collections/tag', + 'common-objects/views/components/collapsible_list', + 'views/tag_list_item' +], function (TagList, CollapsibleListView, TagListItemView) { + 'use strict'; + var TagListView = CollapsibleListView.extend({ + itemViewFactory: function (model) { + return new TagListItemView({ + model: model + }); + }, + collection: function () { + return new TagList(); + }, + showTag: function (tag) { + this.tag = tag; + this.show(); + }, + shown: function () { + if (this.tag) { + var view = _.find( + _.values(this.subViews), + function (view) { + return this.tag === view.model.id; + }, + this + ); + if (view) { + view.showContent(); + } + } + } + }); + return TagListView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/tag_list_item.js b/docdoku-web-front/app/document-management/js/views/tag_list_item.js new file mode 100644 index 0000000000..1e65c2d255 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/tag_list_item.js @@ -0,0 +1,94 @@ +/*global define,bootbox,App,_*/ +define([ + 'common-objects/views/components/list_item', + 'views/tag_document_list', + 'text!templates/tag_list_item.html', + 'common-objects/models/document/document_revision' +], function (ListItemView, TagDocumentListView, template, DocumentRevision) { + 'use strict'; + var TagListItemView = ListItemView.extend({ + + template: template, + tagName: 'li', + className: 'tag', + initialize: function () { + ListItemView.prototype.initialize.apply(this, arguments); + this.events = _.extend(this.events, { + 'click .delete': 'actionDelete', + 'mouseleave .header': 'hideActions', + 'dragenter >.nav-list-entry': 'onDragEnter', + 'dragover >.nav-list-entry': 'checkDrag', + 'dragleave >.nav-list-entry': 'onDragLeave', + 'drop >.nav-list-entry': 'onDrop' + }); + + }, + rendered:function(){ + this.tagDiv= this.$('>.nav-list-entry'); + }, + hideActions: function () { + // Prevents the actions menu to stay opened all the time + this.$('.header .btn-group').first().removeClass('open'); + }, + setActive: function () { + if (App.$documentManagementMenu) { + App.$documentManagementMenu.find('.active').removeClass('active'); + } + this.$('.nav-list-entry').first().addClass('active'); + }, + showContent: function () { + this.setActive(); + this.addSubView( + new TagDocumentListView({ + model: this.model + }) + ).render(); + }, + actionDelete: function () { + this.hideActions(); + var that = this ; + bootbox.confirm(App.config.i18n.DELETE_TAG_QUESTION, function(result){ + if(result){ + that.model.destroy({ + dataType: 'text' // server doesn't send a json hash in the response body + }); + } + }); + return false; + }, + onDragEnter: function () { + this.tagDiv.hasClass('move-doc-into'); + }, + + checkDrag: function (e) { + e.dataTransfer.dropEffect = 'copy'; + this.tagDiv.addClass('move-doc-into'); + return false; + }, + + onDragLeave: function (e) { + e.dataTransfer.dropEffect = 'none'; + this.tagDiv.removeClass('move-doc-into'); + }, + + onDrop: function (e) { + if(e.dataTransfer.getData('document:text/plain')){ + this.tagDocument(e); + } + }, + + tagDocument : function(e) { + var that = this; + var documentRevision = new DocumentRevision(JSON.parse(e.dataTransfer.getData('document:text/plain'))); + + documentRevision.addTags([this.model]).success(function () { + that.tagDiv.removeClass('move-doc-into'); + that.tagDiv.highlightEffect(); + }).error(function () { + that.tagDiv.removeClass('move-doc-into'); + }); + + } + }); + return TagListItemView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/tag_nav.js b/docdoku-web-front/app/document-management/js/views/tag_nav.js new file mode 100644 index 0000000000..fe3278dfd0 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/tag_nav.js @@ -0,0 +1,70 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/common/singleton_decorator', + 'common-objects/views/base', + 'views/tag_list', + 'text!templates/tag_nav.html' +], function (Backbone,singletonDecorator, BaseView, TagListView, template) { + + 'use strict'; + + var TagNavView = BaseView.extend({ + template: template, + el: '#tag-nav', + initialize: function () { + BaseView.prototype.initialize.apply(this, arguments); + var toggleTarget = '[data-target="#items-' + this.cid + '"]'; + this.events['click ' + toggleTarget] = 'toggle'; + + var that = this; + + Backbone.Events.on('refreshTagNavViewCollection', function () { + that.tagsView.collection.fetch({reset: true}); + }); + + this.render(); + }, + rendered: function () { + this.tagsView = this.addSubView( + new TagListView({ + el: '#items-' + this.cid + }) + ); + this.tagsView.bind('shown', this.onTagsViewShown); + this.bind('shown', this.shown); + this.bind('hidden', this.hidden); + }, + show: function (tag) { + this.tag = tag; + this.isOpen = true; + this.tagsView.showTag(this.tag); + if (!this.tag) { + App.router.navigate(App.config.workspaceId + '/tags', {trigger: false}); + } + this.trigger('shown'); + }, + hide: function () { + this.isOpen = false; + this.tagsView.hide(); + App.router.navigate(App.config.workspaceId + '/tags', {trigger: false}); + this.trigger('hidden'); + }, + shown: function () { + this.$el.addClass('open'); + }, + hidden: function () { + this.$el.removeClass('open'); + }, + toggle: function () { + if (this.isOpen) { + this.hide(); + } else { + this.show(); + } + return false; + } + }); + TagNavView = singletonDecorator(TagNavView); + return TagNavView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/task_document_list.js b/docdoku-web-front/app/document-management/js/views/task_document_list.js new file mode 100644 index 0000000000..46d0151ff5 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/task_document_list.js @@ -0,0 +1,61 @@ +/*global define,App*/ +define([ + 'collections/task_document', + 'views/content_document_list', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/checkout_button_group.html', + 'text!common-objects/templates/buttons/tags_button.html', + 'text!common-objects/templates/buttons/new_version_button.html', + 'text!common-objects/templates/buttons/release_button.html', + 'text!common-objects/templates/buttons/obsolete_button.html', + 'text!common-objects/templates/buttons/ACL_button.html', + 'text!templates/search_document_form.html', + 'text!templates/status_filter.html', + 'text!templates/task_document_list.html' +], function (TaskDocumentList, ContentDocumentListView, deleteButton, checkoutButtonGroup, tagsButton, newVersionButton, releaseButton, obsoleteButton, aclButton, searchForm, statusFilter, template) { + 'use strict'; + var TaskDocumentListView = ContentDocumentListView.extend({ + + template: template, + + partials: { + deleteButton: deleteButton, + checkoutButtonGroup: checkoutButtonGroup, + tagsButton: tagsButton, + newVersionButton: newVersionButton, + releaseButton: releaseButton, + obsoleteButton:obsoleteButton, + searchForm: searchForm, + aclButton: aclButton, + statusFilter: statusFilter + }, + + collection: function () { + var c = new TaskDocumentList(); + c.setFilterStatus(this.options.filter); + return c; + }, + + initialize: function () { + ContentDocumentListView.prototype.initialize.apply(this, arguments); + if (this.model) { + this.collection.parent = this.model; + } + this.events['click button[value="all"]'] = 'allClicked'; + this.events['click button[value="in_progress"]'] = 'inProgressClicked'; + }, + rendered: function () { + ContentDocumentListView.prototype.rendered.apply(this, arguments); + var filter = this.options.filter || 'all'; + this.$('button.filter[value=' + filter + ']').addClass('active'); + }, + allClicked: function () { + window.location.hash = '#' + App.config.workspaceId + '/tasks'; + }, + inProgressClicked: function () { + window.location.hash = '#' + App.config.workspaceId + '/tasks/in_progress'; + } + + }); + return TaskDocumentListView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/task_nav.js b/docdoku-web-front/app/document-management/js/views/task_nav.js new file mode 100644 index 0000000000..1bf119ec5e --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/task_nav.js @@ -0,0 +1,39 @@ +/*global define,App*/ +define([ + 'common-objects/common/singleton_decorator', + 'common-objects/views/base', + 'views/task_document_list', + 'text!templates/task_nav.html' +], function (singletonDecorator, BaseView, TaskDocumentListView, template) { + + 'use strict'; + + var TaskNavView = BaseView.extend({ + + template: template, + + el: '#task-nav', + + initialize: function () { + BaseView.prototype.initialize.apply(this, arguments); + this.render(); + }, + + setActive: function () { + if (App.$documentManagementMenu) { + App.$documentManagementMenu.find('.active').removeClass('active'); + } + this.$('.nav-list-entry').first().addClass('active'); + }, + + showContent: function (filter) { + this.setActive(); + this.addSubView( + new TaskDocumentListView({filter: filter}) + ).render(); + } + }); + + TaskNavView = singletonDecorator(TaskNavView); + return TaskNavView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/template_content_list.js b/docdoku-web-front/app/document-management/js/views/template_content_list.js new file mode 100644 index 0000000000..c152489572 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/template_content_list.js @@ -0,0 +1,151 @@ +/*global define,bootbox,App*/ +define([ + 'collections/template', + 'views/content', + 'views/template_list', + 'views/template_new', + 'common-objects/views/security/acl_edit', + 'text!templates/template_content_list.html', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/ACL_button.html', + 'common-objects/views/alert', + 'common-objects/views/lov/lov_modal' +], function (TemplateList, ContentView, TemplateListView, TemplateNewView, ACLEditView, template, deleteButton, aclButton, AlertView, LOVModalView) { + 'use strict'; + var TemplateContentListView = ContentView.extend({ + + template: template, + + partials: { + deleteButton: deleteButton, + aclButton: aclButton + + }, + + collection: function () { + return TemplateList.getInstance(); + }, + initialize: function () { + ContentView.prototype.initialize.apply(this, arguments); + this.events['click .actions .new-template'] = 'actionNew'; + this.events['click .actions .delete'] = 'actionDelete'; + this.events['click .actions .edit-acl'] = 'onEditAcl'; + this.events['click .actions .list-lov'] = 'showLovs'; + }, + bindDomElement: function () { + this.$aclButton = this.$('.actions .edit-acl'); + this.$deleteButton = this.$('.actions .delete'); + + }, + rendered: function () { + this.listView = this.addSubView(new TemplateListView({ + el: '#list-' + this.cid, + collection: this.collection + })); + this.listView.collection.fetch({reset: true}); + this.listView.on('selectionChange', this.selectionChanged); + this.bindDomElement(); + this.selectionChanged(); + }, + selectionChanged: function () { + + var checkedViews = this.listView.checkedViews(); + switch (checkedViews.length) { + case 0: + this.onNoTemplateSelected(); + break; + case 1: + this.onOneTemplateSelected(checkedViews[0].model); + break; + default: + this.onSeveralTemplateSelected(); + break; + } + }, + onNoTemplateSelected: function () { + this.$aclButton.hide(); + this.$deleteButton.hide(); + }, + + onOneTemplateSelected: function () { + this.$aclButton.show(); + this.$deleteButton.show(); + + }, + onSeveralTemplateSelected: function () { + this.$aclButton.hide(); + this.$deleteButton.show(); + }, + onEditAcl: function () { + + var templateSelected; + + this.listView.eachChecked(function (view) { + templateSelected = view.model; + + }); + var self = this; + var aclEditView = new ACLEditView({ + editMode: true, + acl: templateSelected.get('acl') + }); + + aclEditView.setTitle(templateSelected.getId()); + window.document.body.appendChild(aclEditView.render().el); + + aclEditView.openModal(); + aclEditView.on('acl:update', function () { + + var acl = aclEditView.toList(); + + templateSelected.updateACL({ + acl: acl || {userEntries: {}, groupEntries: {}}, + success: function () { + templateSelected.set('acl', acl); + aclEditView.closeModal(); + self.listView.redraw(); + }, + error: function(error){ + aclEditView.onError(error); + } + }); + }); + + return false; + }, + + actionNew: function () { + this.addSubView( + new TemplateNewView({ + collection: this.collection + }) + ).show(); + this.listView.redraw(); + return false; + }, + actionDelete: function () { + var that = this; + + bootbox.confirm(App.config.i18n.DELETE_SELECTION_QUESTION, function (result) { + if (result) { + that.listView.eachChecked(function (view) { + view.model.destroy({ + dataType: 'text', // server doesn't send a json hash in the response body + success: function () { + that.listView.redraw(); + } + }); + }); + } + }); + + return false; + }, + showLovs: function () { + var lovmodal = new LOVModalView({}); + window.document.body.appendChild(lovmodal.render().el); + lovmodal.openModal(); + } + }); + return TemplateContentListView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/template_edit.js b/docdoku-web-front/app/document-management/js/views/template_edit.js new file mode 100644 index 0000000000..857c57e82f --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/template_edit.js @@ -0,0 +1,138 @@ +/*global define,App*/ +define([ + 'common-objects/utils/date', + 'common-objects/views/components/modal', + 'common-objects/views/workflow/workflow_list', + 'common-objects/views/attributes/template_new_attributes', + 'common-objects/views/file/file_list', + 'text!templates/template_new.html', +], function (date, ModalView, DocumentWorkflowListView, TemplateNewAttributesView, FileListView, template) { + 'use strict'; + var TemplateEditView = ModalView.extend({ + + template: template, + + initialize: function () { + this.templateExtraData = {modificationDate: this.model.getFormattedModificationDate()}; + ModalView.prototype.initialize.apply(this, arguments); + // destroy previous template edit view if any + if (TemplateEditView._instance) { + TemplateEditView._oldInstance = TemplateEditView._instance; + } + // keep track of the created template edit view + TemplateEditView._instance = this; + + this.events['click .modal-footer button.btn-primary'] = 'interceptSubmit'; + this.events['submit form'] = 'onSubmitForm'; + this.tabs = this.$('.nav-tabs li'); + }, + + rendered: function () { + this.workflowsView = this.addSubView( + new DocumentWorkflowListView({ + el: '#workflows-' + this.cid + }) + ); + + var _this = this; + this.workflowsView.collection.on('reset', function() { + _this.workflowsView.setValue(_this.model.get('workflowModelId')); + }); + + this.attributesView = this.addSubView( + new TemplateNewAttributesView({ + el: '#tab-attributes-' + this.cid, + attributesLocked: this.model.isAttributesLocked(), + editMode:true + }) + ); + this.attributesView.render(); + this.attributesView.collection.reset(this.model.get('attributeTemplates')); + + this.fileListView = new FileListView({ + deleteBaseUrl: this.model.url(), + uploadBaseUrl: this.model.getUploadBaseUrl(), + collection: this.model.get('attachedFiles'), + editMode: true + }).render(); + + // Add the fileListView to the tab + this.$('#tab-files-' + this.cid).append(this.fileListView.el); + + var $popoverLink = this.$('a#mask-help'); + + $popoverLink.popover({ + title: App.config.i18n.MASK, + placement: 'left', + html: true, + content: App.config.i18n.MASK_HELP.nl2br(), + trigger:'manual', + container:'.modal.new-template' + }).click(function(e){ + $popoverLink.popover('show'); + e.stopPropagation(); + e.preventDefault(); + return false; + }); + date.dateHelper(this.$('.date-popover')); + }, + + interceptSubmit:function(){ + this.isValid = !this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + + if (this.isValid) { + var workflow = this.workflowsView.selected(); + + this.model.unset('reference'); + this.model.save({ + documentType: this.$('#form-' + this.cid + ' .type').val(), + mask: this.$('#form-' + this.cid + ' .mask').val(), + idGenerated: this.$('#form-' + this.cid + ' .id-generated').is(':checked'), + workflowModelId: workflow ? workflow.get('id') : null, + attributeTemplates: this.attributesView.collection.toJSON(), + attributesLocked: this.attributesView.isAttributesLocked() + }, { + success: this.success, + error: this.error + }); + + // saving new files : nothing to do : it's already saved + // deleting unwanted files + this.fileListView.deleteFilesToDelete(); + } + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + cancelAction: function () { + this.fileListView.deleteNewFiles(); + }, + + activateTab: function (index) { + this.tabs.eq(index).children().tab('show'); + }, + activateFileTab: function(){ + this.activateTab(3); + }, + success: function () { + this.hide(); + }, + + error: function (model, error) { + if (error.responseText) { + this.alert({ + type: 'error', + message: error.responseText + }); + } else { + console.error(error); + } + } + }); + return TemplateEditView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/template_list.js b/docdoku-web-front/app/document-management/js/views/template_list.js new file mode 100644 index 0000000000..b356c81455 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/template_list.js @@ -0,0 +1,51 @@ +/*global define,App*/ +define([ + 'common-objects/views/documents/checkbox_list', + 'views/template_list_item', + 'text!templates/template_list.html' +], function (CheckboxListView, TemplateListItemView, template) { + 'use strict'; + var TemplateListView = CheckboxListView.extend({ + + template: template, + + itemViewFactory: function (model) { + model.on('change', this.redraw); + return new TemplateListItemView({ + model: model + }); + }, + rendered: function () { + this.once('_ready', this.dataTable); + }, + redraw: function () { + this.dataTable(); + }, + dataTable: function () { + var oldSort = [ + [0, 'asc'] + ]; + if (this.oTable) { + oldSort = this.oTable.fnSettings().aaSorting; + this.oTable.fnDestroy(); + } + this.oTable = this.$el.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + { 'bSortable': false, 'aTargets': [ 0, 6, 7 ] }, + { 'sType': App.config.i18n.DATE_SORT, 'aTargets': [5] } + ] + }); + this.$el.parent().find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + } + }); + return TemplateListView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/template_list_item.js b/docdoku-web-front/app/document-management/js/views/template_list_item.js new file mode 100644 index 0000000000..4baa00c2b9 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/template_list_item.js @@ -0,0 +1,76 @@ +/*global define,App,_*/ +define([ + 'common-objects/utils/date', + 'common-objects/views/documents/checkbox_list_item', + 'views/template_edit', + 'text!templates/template_list_item.html' +], function (date, CheckboxListItemView, TemplateEditView, template) { + + 'use strict'; + + var TemplateListItemView = CheckboxListItemView.extend({ + + template: template, + tagName: 'tr', + initialize: function () { + CheckboxListItemView.prototype.initialize.apply(this, arguments); + this.events['click .reference'] = this.actionEdit; + this.events['click .document-attached-files i'] = this.openDocumentTemplateModal; + }, + + rendered: function () { + CheckboxListItemView.prototype.rendered.apply(this, arguments); + this.$('.author-popover').userPopover(this.model.attributes.author.login, this.model.id, 'left'); + date.dateHelper(this.$('.date-popover')); + }, + + modelToJSON: function () { + var data = this.model.toJSON(); + // Format dates + if (!_.isUndefined(data.creationDate)) { + data.creationDate = date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + data.creationDate); + } + if (!_.isUndefined(data.modificationDate)) { + data.modificationDate = date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + data.modificationDate); + } + if (this.model.hasACLForCurrentUser()) { + data.isReadOnly = this.model.isReadOnly(); + data.isFullAccess = this.model.isFullAccess(); + } + data.hasAttachedFiles = this.model.getAttachedFiles().length; + return data; + }, + actionEdit: function () { + var that = this; + this.model.fetch().success(function () { + that.editView = that.addSubView( + new TemplateEditView({ + model: that.model + }) + ); + window.document.body.appendChild(that.editView.el); + }); + }, + + openDocumentTemplateModal: function () { + var that = this; + this.model.fetch().success(function () { + that.editView = that.addSubView( + new TemplateEditView({ + model: that.model + }) + ); + that.editView.activateFileTab(); + window.document.body.appendChild(that.editView.el); + + + + }); + } + }); + return TemplateListItemView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/template_nav.js b/docdoku-web-front/app/document-management/js/views/template_nav.js new file mode 100644 index 0000000000..ed7e318aab --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/template_nav.js @@ -0,0 +1,35 @@ +/*global define,App*/ +define([ + 'common-objects/common/singleton_decorator', + 'common-objects/views/base', + 'views/template_content_list', + 'text!templates/template_nav.html' +], function (singletonDecorator, BaseView, TemplateContentListView, template) { + + 'use strict'; + + var TemplateNavView = BaseView.extend({ + + template: template, + el: '#template-nav', + + initialize: function () { + BaseView.prototype.initialize.apply(this, arguments); + this.render(); + }, + setActive: function () { + if (App.$documentManagementMenu) { + App.$documentManagementMenu.find('.active').removeClass('active'); + } + this.$('.nav-list-entry').first().addClass('active'); + }, + showContent: function () { + this.setActive(); + this.addSubView( + new TemplateContentListView() + ).render(); + } + }); + TemplateNavView = singletonDecorator(TemplateNavView); + return TemplateNavView; +}); diff --git a/docdoku-web-front/app/document-management/js/views/template_new.js b/docdoku-web-front/app/document-management/js/views/template_new.js new file mode 100644 index 0000000000..d67b856cc6 --- /dev/null +++ b/docdoku-web-front/app/document-management/js/views/template_new.js @@ -0,0 +1,97 @@ +/*global define,App*/ +define([ + 'common-objects/views/components/modal', + 'common-objects/views/workflow/workflow_list', + 'common-objects/views/attributes/template_new_attributes', + 'text!templates/template_new.html' +], function (ModalView, DocumentWorkflowListView, TemplateNewAttributesView, template) { + 'use strict'; + var TemplateNewView = ModalView.extend({ + + template: template, + + initialize: function () { + ModalView.prototype.initialize.apply(this, arguments); + this.events['click .modal-footer button.btn-primary'] = 'interceptSubmit'; + this.events['submit form'] = 'onSubmitForm'; + }, + + rendered: function () { + + this.workflowsView = this.addSubView( + new DocumentWorkflowListView({ + el: '#workflows-' + this.cid + }) + ); + + this.attributesView = this.addSubView( + new TemplateNewAttributesView({ + el: '#tab-attributes-' + this.cid, + editMode: true + }) + ).render(); + + var $popoverLink = this.$('#mask-help'); + + $popoverLink.popover({ + title: App.config.i18n.MASK, + placement: 'left', + html: true, + trigger: 'manual', + content: App.config.i18n.MASK_HELP.nl2br(), + container:'.modal.new-template' + }).click(function(e){ + $popoverLink.popover('show'); + e.stopPropagation(); + e.preventDefault(); + return false; + }); + + this.$('input.reference').customValidity(App.config.i18n.REQUIRED_FIELD); + + }, + + interceptSubmit:function(){ + this.isValid = !this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + if (this.isValid) { + var workflow = this.workflowsView.selected(); + + this.collection.create({ + reference: this.$('#form-' + this.cid + ' .reference').val(), + documentType: this.$('#form-' + this.cid + ' .type').val(), + mask: this.$('#form-' + this.cid + ' .mask').val(), + idGenerated: this.$('#form-' + this.cid + ' .id-generated').is(':checked'), + workflowModelId: workflow ? workflow.get('id') : null, + attributeTemplates: this.attributesView.collection.toJSON(), + attributesLocked: this.attributesView.isAttributesLocked() + }, { + wait: true, + success: this.success, + error: this.error + }); + } + e.preventDefault(); + e.stopPropagation(); + + return false; + }, + success: function () { + this.hide(); + }, + error: function (model, error) { + this.collection.remove(model); + if (error.responseText) { + this.alert({ + type: 'error', + message: error.responseText + }); + } else { + console.error(error); + } + } + }); + return TemplateNewView; +}); diff --git a/docdoku-web-front/app/document-management/main.js b/docdoku-web-front/app/document-management/main.js new file mode 100644 index 0000000000..6c640b3648 --- /dev/null +++ b/docdoku-web-front/app/document-management/main.js @@ -0,0 +1,135 @@ +/*global _,require,window*/ +var workspace = /^#([^\/]+)/.exec(window.location.hash); +if(!workspace){ + location.href = '../404?url='+window.location.href; + throw new Error('Cannot parse workspace in url'); +} +var App = { + debug:false, + config:{ + workspaceId: decodeURIComponent(workspace[1]).trim() || null, + login: '', + groups: [], + contextPath: '', + locale: window.localStorage.getItem('locale') || 'en', + needAuthentication:true + } +}; + + +App.log=function(message){ + 'use strict'; + if(App.debug){ + window.console.log(message); + } +}; + +require.config({ + + baseUrl: 'js', + + shim: { + jqueryUI: { deps: ['jquery'], exports: 'jQuery' }, + effects: { deps: ['jquery'], exports: 'jQuery' }, + popoverUtils: { deps: ['jquery'], exports: 'jQuery' }, + inputValidity: { deps: ['jquery'], exports: 'jQuery' }, + bootstrap: { deps: ['jquery', 'jqueryUI'], exports: 'jQuery' }, + bootbox: { deps: ['jquery'], exports: 'jQuery' }, + datatables: { deps: ['jquery'], exports: 'jQuery' }, + unmask: { deps: ['jquery'], exports: 'jQuery' }, + unmaskConfig: { deps: ['unmask','jquery'], exports: 'jQuery' }, + bootstrapSwitch: { deps: ['jquery'], exports: 'jQuery'}, + bootstrapDatepicker: {deps: ['jquery','bootstrap'], exports: 'jQuery'}, + backbone: { deps: ['underscore', 'jquery'], exports: 'Backbone'}, + datePickerLang: { deps: ['bootstrapDatepicker'], exports: 'jQuery'}, + selectize: { deps: ['jquery'], exports: 'jQuery' } + }, + + paths: { + jquery: '../../bower_components/jquery/jquery', + backbone: '../../bower_components/backbone/backbone', + underscore: '../../bower_components/underscore/underscore', + mustache: '../../bower_components/mustache/mustache', + text: '../../bower_components/requirejs-text/text', + i18n: '../../bower_components/requirejs-i18n/i18n', + buzz: '../../bower_components/buzz/dist/buzz', + bootstrap: '../../bower_components/bootstrap/docs/assets/js/bootstrap', + bootbox:'../../bower_components/bootbox/bootbox', + datatables: '../../bower_components/datatables/media/js/jquery.dataTables', + jqueryUI: '../../bower_components/jqueryui/ui/jquery-ui', + unmask:'../../bower_components/jquery-maskedinput/dist/jquery.maskedinput', + bootstrapSwitch:'../../bower_components/bootstrap-switch/static/js/bootstrap-switch', + bootstrapDatepicker:'../../bower_components/bootstrap-datepicker/js/bootstrap-datepicker', + date:'../../bower_components/date.format/date.format', + unorm:'../../bower_components/unorm/lib/unorm', + moment:'../../bower_components/moment/min/moment-with-locales', + momentTimeZone:'../../bower_components/moment-timezone/builds/moment-timezone-with-data', + unmaskConfig:'../../js/utils/jquery.maskedinput-config', + localization: '../../js/localization', + modules: '../../js/modules', + 'common-objects': '../../js/common-objects', + effects: '../../js/utils/effects', + popoverUtils: '../../js/utils/popover.utils', + inputValidity: '../../js/utils/input-validity', + datatablesOsortExt: '../../js/utils/datatables.oSort.ext', + utilsprototype: '../../js/utils/utils.prototype', + userPopover: '../../js/modules/user-popover-module/app', + async: '../../bower_components/async/lib/async', + datePickerLang: '../../bower_components/bootstrap-datepicker/js/locales/bootstrap-datepicker.fr', + selectize: '../../bower_components/selectize/dist/js/standalone/selectize' + }, + + deps: [ + 'jquery', + 'underscore', + 'date', + 'bootstrap', + 'bootbox', + 'bootstrapSwitch', + 'jqueryUI', + 'effects', + 'popoverUtils', + 'inputValidity', + 'datatables', + 'datatablesOsortExt', + 'unmaskConfig', + 'utilsprototype', + 'datePickerLang', + 'selectize' + ], + config: { + i18n: { + locale: (function(){ + 'use strict'; + try{ + return App.config.locale; + }catch(ex){ + return 'en'; + } + })() + } + } +}); + +require(['common-objects/contextResolver','i18n!localization/nls/common','i18n!localization/nls/document-management'], +function (ContextResolver, commonStrings, documentManagementStrings) { + 'use strict'; + + App.config.i18n = _.extend(commonStrings,documentManagementStrings); + + + ContextResolver.resolveServerProperties() + .then(ContextResolver.resolveAccount) + .then(ContextResolver.resolveWorkspaces) + .then(ContextResolver.resolveGroups) + .then(ContextResolver.resolveUser) + .then(function buildView(){ + require(['backbone','app','router','common-objects/views/header','modules/all'],function(Backbone, AppView, Router,HeaderView,Modules){ + App.appView = new AppView().render(); + App.headerView = new HeaderView().render(); + App.router = Router.getInstance(); + App.coworkersView = new Modules.CoWorkersAccessModuleView().render(); + Backbone.history.start(); + }); + }); +}); diff --git a/docdoku-web-front/app/documents/index.html b/docdoku-web-front/app/documents/index.html new file mode 100644 index 0000000000..ab9bb09556 --- /dev/null +++ b/docdoku-web-front/app/documents/index.html @@ -0,0 +1,27 @@ + + + + + + DocDokuPLM - Document + + + + + + + + + + + + + +
        +
        +
        + + + + + diff --git a/docdoku-web-front/app/documents/js/app.js b/docdoku-web-front/app/documents/js/app.js new file mode 100644 index 0000000000..5dd9126fc3 --- /dev/null +++ b/docdoku-web-front/app/documents/js/app.js @@ -0,0 +1,88 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/document-permalink.html', + 'views/document-revision', + 'common-objects/views/not-found', + 'common-objects/views/prompt' +], function (Backbone, Mustache, template, DocumentRevisionView, NotFoundView, PromptView) { + 'use strict'; + + var AppView = Backbone.View.extend({ + + el: '#content', + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n + })).show(); + this.$notifications = this.$('.notifications'); + return this; + }, + + onDocumentFetched: function (document) { + this.$('.document-revision').html(new DocumentRevisionView().render(document).$el, null); + }, + + showDocumentRevision: function (workspace, documentId, documentVersion) { + $.getJSON(App.config.contextPath + '/api/shared/' + workspace + '/documents/' + documentId + '-' + documentVersion) + .then(this.onDocumentFetched.bind(this), this.onError.bind(this)); + }, + + showSharedEntity: function (uuid) { + this.uuid = uuid; + var password = this.password; + $.ajax({ + type: 'GET', + url: App.config.contextPath + '/api/shared/' + uuid + '/documents', + beforeSend: function setPassword(xhr) { + if (password) { + xhr.setRequestHeader('password', password); + } + } + }).then(this.onSharedEntityFetched.bind(this), this.onSharedEntityError.bind(this)); + }, + + onSharedEntityFetched: function (part) { + this.$('.document-revision').html(new DocumentRevisionView().render(part, this.uuid).$el); + }, + + onSharedEntityError: function (err) { + if (err.status === 404) { + this.$el.html(new NotFoundView().render(err).$el); + } + else if (err.status === 403 && err.getResponseHeader('Reason-Phrase') === 'password-protected') { + this.promptSharedEntityPassword(); + } + }, + + onError: function (err) { + if (err.status === 404) { + this.$el.html(new NotFoundView().render(err).$el); + } + else if (err.status === 403 || err.status === 401) { + window.location.href = App.config.contextPath + '/?denied=true&originURL=' + encodeURIComponent(window.location.pathname + window.location.hash); + } + }, + + promptSharedEntityPassword: function () { + + var _this = this; + var promptView = new PromptView(); + promptView.setPromptOptions(App.config.i18n.PROTECTED_RESOURCE, null, App.config.i18n.OK, App.config.i18n.CANCEL, null, App.config.i18n.PASSWORD, 'password'); + window.document.body.appendChild(promptView.render().el); + promptView.openModal(); + this.listenTo(promptView, 'prompt-ok', function (args) { + _this.password = args[0]; + _this.showSharedEntity(_this.uuid); + }); + this.listenTo(promptView, 'prompt-cancel', function () { + _this.password = null; + }); + } + + }); + + return AppView; +}); diff --git a/docdoku-web-front/app/documents/js/router.js b/docdoku-web-front/app/documents/js/router.js new file mode 100644 index 0000000000..d505f9ca0f --- /dev/null +++ b/docdoku-web-front/app/documents/js/router.js @@ -0,0 +1,24 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/common/singleton_decorator' +], +function (Backbone, singletonDecorator) { + 'use strict'; + var Router = Backbone.Router.extend({ + routes: { + ':workspaceId/:documentId/:documentVersion': 'showDocumentRevision', + ':uuid': 'showSharedEntity' + }, + + showDocumentRevision:function(workspace, documentId, documentVersion){ + App.appView.showDocumentRevision(workspace, documentId, documentVersion); + }, + + showSharedEntity:function(uuid){ + App.appView.showSharedEntity(uuid); + } + }); + + return singletonDecorator(Router); +}); diff --git a/docdoku-web-front/app/documents/js/templates/document-permalink.html b/docdoku-web-front/app/documents/js/templates/document-permalink.html new file mode 100644 index 0000000000..8bd693488c --- /dev/null +++ b/docdoku-web-front/app/documents/js/templates/document-permalink.html @@ -0,0 +1,2 @@ +
        +
        diff --git a/docdoku-web-front/app/documents/js/templates/document-revision.html b/docdoku-web-front/app/documents/js/templates/document-revision.html new file mode 100644 index 0000000000..328735f2c6 --- /dev/null +++ b/docdoku-web-front/app/documents/js/templates/document-revision.html @@ -0,0 +1,146 @@ +

        {{document.documentMasterId}}-{{document.version}}

        +
        + + + +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{#document.releaseAuthor}} + + + + + + + + + {{/document.releaseAuthor}} + {{#document.obsoleteAuthor}} + + + + + + + + + {{/document.obsoleteAuthor}} + +
        {{i18n.CREATION_DATE}}{{document.creationDate}}
        {{i18n.TYPE}}{{document.type}}
        {{i18n.TITLE}}{{document.title}}
        {{i18n.AUTHOR}}{{document.author.name}}
        {{i18n.LIFECYCLE_STATE}}{{document.lifeCycleState}}
        {{i18n.DESCRIPTION}}{{document.description}}
        {{i18n.TAGS}}{{document.tags}}
        {{i18n.RELEASED_BY}}{{document.releaseAuthor.name}}
        {{i18n.RELEASE_DATE}}{{document.releaseDate}}
        {{i18n.OBSOLETE_AUTHOR}}{{document.obsoleteAuthor.name}}
        {{i18n.OBSOLETE_DATE}}{{document.obsoleteDate}}
        + +
        + +
        + + + + + + + + + + + + + + + + + + + + + + + + + +
        {{i18n.ITERATION}}{{lastIteration.iteration}}
        {{i18n.REVISION_NOTE}}{{lastIteration.revisionNote}}
        {{i18n.REVISION_DATE}} + {{#document.checkOutUser}} + {{lastIteration.creationDate}} + {{/document.checkOutUser}} + {{^document.checkOutUser}} + {{lastIteration.checkInDate}} + {{/document.checkOutUser}} +
        {{i18n.MODIFICATION_DATE}}{{lastIteration.modificationDate}}
        {{i18n.AUTHOR}}{{lastIteration.author.name}}
        +
        + +
        + {{#lastIteration.instanceAttributes.length}} + + + + + + + + + {{#lastIteration.instanceAttributes}} + + + + + {{/lastIteration.instanceAttributes}} + +
        {{i18n.NAME}}{{i18n.VALUE}}
        {{name}}{{value}}
        + {{/lastIteration.instanceAttributes.length}} +
        + +
        +
        +
        + + +
        + +
        + +{{i18n.SEE_IN_DOCUMENT_MANAGEMENT}} diff --git a/docdoku-web-front/app/documents/js/views/document-revision.js b/docdoku-web-front/app/documents/js/views/document-revision.js new file mode 100644 index 0000000000..4fb8f5d1c1 --- /dev/null +++ b/docdoku-web-front/app/documents/js/views/document-revision.js @@ -0,0 +1,44 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/document-revision.html' +], function (Backbone, Mustache, template) { + 'use strict'; + + var DocumentRevisionView = Backbone.View.extend({ + render: function (document, uuid) { + + this.uuid = uuid; + this.document = document; + + var _this = this; + + var lastIteration = document.documentIterations[document.documentIterations.length-1]; + + document.encodedRoutePath = encodeURIComponent(document.routePath); + + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + contextPath:App.config.contextPath, + document:document, + lastIteration:lastIteration + })).show(); + + this.$accordion = this.$('#tab-document-files > .accordion'); + + _.each(lastIteration.attachedFiles,function(file){ + var url = App.config.contextPath+'/api/viewer?'; + if(uuid){ + url+= 'uuid=' + encodeURIComponent(uuid) + '&'; + } + $.get(url+'fileName='+encodeURIComponent(file)).then(function(data){ + _this.$accordion.append(data); + }); + }); + return this; + } + }); + + return DocumentRevisionView; +}); diff --git a/docdoku-web-front/app/documents/main.js b/docdoku-web-front/app/documents/main.js new file mode 100644 index 0000000000..ab929240b9 --- /dev/null +++ b/docdoku-web-front/app/documents/main.js @@ -0,0 +1,80 @@ +/*global _,require,window*/ + +var App = { + debug:false, + config:{ + locale: window.localStorage.getItem('locale') || 'en' + } +}; + +App.log=function(message){ + 'use strict'; + if(App.debug){ + window.console.log(message); + } +}; + +require.config({ + + baseUrl: 'js', + + shim: { + jqueryUI: { deps: ['jquery'], exports: 'jQuery' }, + bootstrap: { deps: ['jquery', 'jqueryUI'], exports: 'jQuery' }, + backbone: { deps: ['underscore', 'jquery'], exports: 'Backbone'} + + }, + + paths: { + jquery: '../../bower_components/jquery/jquery', + jqueryUI: '../../bower_components/jqueryui/ui/jquery-ui', + backbone: '../../bower_components/backbone/backbone', + underscore: '../../bower_components/underscore/underscore', + mustache: '../../bower_components/mustache/mustache', + text: '../../bower_components/requirejs-text/text', + i18n: '../../bower_components/requirejs-i18n/i18n', + bootstrap: '../../bower_components/bootstrap/docs/assets/js/bootstrap', + 'common-objects': '../../js/common-objects', + localization: '../../js/localization', + pluginDetect:'../../js/lib/plugin-detect' + }, + + deps: [ + 'jquery', + 'underscore', + 'bootstrap', + 'jqueryUI', + 'pluginDetect' + ], + config: { + i18n: { + locale: (function(){ + 'use strict'; + try{ + return App.config.locale; + }catch(ex){ + return 'en'; + } + })() + } + } +}); + +require(['common-objects/contextResolver','i18n!localization/nls/common','i18n!localization/nls/index'], + function (ContextResolver, commonStrings, indexStrings) { + 'use strict'; + + App.config.i18n = _.extend(commonStrings, indexStrings); + ContextResolver.resolveServerProperties() + .then(function buildView(){ + require(['backbone','app','router','common-objects/views/header'],function(Backbone, AppView, Router, HeaderView){ + App.appView = new AppView(); + App.headerView = new HeaderView(); + App.appView.render(); + App.headerView.render(); + App.router = Router.getInstance(); + Backbone.history.start(); + }); + }); + }); + diff --git a/docdoku-web-front/app/download/dplm/.gitkeep b/docdoku-web-front/app/download/dplm/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docdoku-web-front/app/download/index.html b/docdoku-web-front/app/download/index.html new file mode 100644 index 0000000000..78aedfb40b --- /dev/null +++ b/docdoku-web-front/app/download/index.html @@ -0,0 +1,27 @@ + + + + + + DocDokuPLM - Download DPLM + + + + + + + + + + + + + +
        +
        +
        + + + + + diff --git a/docdoku-web-front/app/download/js/app.js b/docdoku-web-front/app/download/js/app.js new file mode 100644 index 0000000000..56050261ee --- /dev/null +++ b/docdoku-web-front/app/download/js/app.js @@ -0,0 +1,22 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/content.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var AppView = Backbone.View.extend({ + + el: '#content', + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + contextPath:App.config.contextPath + })).show(); + return this; + } + }); + + return AppView; +}); diff --git a/docdoku-web-front/app/download/js/templates/content.html b/docdoku-web-front/app/download/js/templates/content.html new file mode 100644 index 0000000000..6b43f153a5 --- /dev/null +++ b/docdoku-web-front/app/download/js/templates/content.html @@ -0,0 +1,52 @@ +
        +

        {{i18n.DOWNLOAD}}

        +

        {{i18n.DPLM_CLIENT}}

        +

        {{i18n.DPLM_CLIENT_INSTALL_MESSAGE}}

        +

        {{i18n.CHOOSE_PLATFORM}}

        + +
        diff --git a/docdoku-web-front/app/download/main.js b/docdoku-web-front/app/download/main.js new file mode 100644 index 0000000000..2d04be610c --- /dev/null +++ b/docdoku-web-front/app/download/main.js @@ -0,0 +1,86 @@ +/*global _,require,window*/ + +var App = { + debug:false, + config:{ + login: '', + groups: [], + contextPath: '', + locale: window.localStorage.getItem('locale') || 'en' + } +}; + +App.log=function(message){ + 'use strict'; + if(App.debug){ + window.console.log(message); + } +}; + +require.config({ + + baseUrl: 'js', + + shim: { + jqueryUI: { deps: ['jquery'], exports: 'jQuery' }, + bootstrap: { deps: ['jquery', 'jqueryUI'], exports: 'jQuery' }, + backbone: { deps: ['underscore', 'jquery'], exports: 'Backbone'} + }, + + paths: { + jquery: '../../bower_components/jquery/jquery', + jqueryUI: '../../bower_components/jqueryui/ui/jquery-ui', + backbone: '../../bower_components/backbone/backbone', + underscore: '../../bower_components/underscore/underscore', + mustache: '../../bower_components/mustache/mustache', + text: '../../bower_components/requirejs-text/text', + i18n: '../../bower_components/requirejs-i18n/i18n', + bootstrap: '../../bower_components/bootstrap/docs/assets/js/bootstrap', + 'common-objects': '../../js/common-objects', + localization: '../../js/localization' + }, + + deps: [ + 'jquery', + 'underscore', + 'bootstrap', + 'jqueryUI' + ], + config: { + i18n: { + locale: (function(){ + 'use strict'; + try{ + return App.config.locale; + }catch(ex){ + return 'en'; + } + })() + } + } +}); + +require(['common-objects/contextResolver','i18n!localization/nls/common','i18n!localization/nls/download'], + function (ContextResolver, commonStrings, downloadStrings) { + + 'use strict'; + + App.config.i18n = _.extend(commonStrings, downloadStrings); + var load = function(){ + require(['backbone','app','common-objects/views/header'],function(Backbone, AppView, HeaderView){ + App.appView = new AppView().render(); + App.headerView = new HeaderView().render(); + }); + }; + ContextResolver.resolveServerProperties() + .then(ContextResolver.resolveAccount) + .then(function(){ + App.config.connected = true; + return ContextResolver.resolveWorkspaces(); + },function(response){ + App.config.connected = response.status !== 401; + load(); + }).then(load); + + }); + diff --git a/docdoku-web-front/app/fonts/FontAwesome.otf b/docdoku-web-front/app/fonts/FontAwesome.otf new file mode 100644 index 0000000000..81c9ad949b Binary files /dev/null and b/docdoku-web-front/app/fonts/FontAwesome.otf differ diff --git a/docdoku-web-front/app/fonts/fontawesome-webfont.eot b/docdoku-web-front/app/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000000..84677bc0c5 Binary files /dev/null and b/docdoku-web-front/app/fonts/fontawesome-webfont.eot differ diff --git a/docdoku-web-front/app/fonts/fontawesome-webfont.svg b/docdoku-web-front/app/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000000..d907b25ae6 --- /dev/null +++ b/docdoku-web-front/app/fonts/fontawesome-webfont.svg @@ -0,0 +1,520 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docdoku-web-front/app/fonts/fontawesome-webfont.ttf b/docdoku-web-front/app/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000000..96a3639cdd Binary files /dev/null and b/docdoku-web-front/app/fonts/fontawesome-webfont.ttf differ diff --git a/docdoku-web-front/app/fonts/fontawesome-webfont.woff b/docdoku-web-front/app/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000000..628b6a52a8 Binary files /dev/null and b/docdoku-web-front/app/fonts/fontawesome-webfont.woff differ diff --git a/docdoku-web-front/app/images/apple.gif b/docdoku-web-front/app/images/apple.gif new file mode 100644 index 0000000000..4aab59a31b Binary files /dev/null and b/docdoku-web-front/app/images/apple.gif differ diff --git a/docdoku-web-front/app/images/back_disabled.png b/docdoku-web-front/app/images/back_disabled.png new file mode 100644 index 0000000000..881de7976f Binary files /dev/null and b/docdoku-web-front/app/images/back_disabled.png differ diff --git a/docdoku-web-front/app/images/back_enabled.png b/docdoku-web-front/app/images/back_enabled.png new file mode 100644 index 0000000000..c608682b04 Binary files /dev/null and b/docdoku-web-front/app/images/back_enabled.png differ diff --git a/docdoku-web-front/app/images/back_enabled_hover.png b/docdoku-web-front/app/images/back_enabled_hover.png new file mode 100644 index 0000000000..d300f1064b Binary files /dev/null and b/docdoku-web-front/app/images/back_enabled_hover.png differ diff --git a/docdoku-web-front/app/images/download_image.png b/docdoku-web-front/app/images/download_image.png new file mode 100644 index 0000000000..341fd7e2fd Binary files /dev/null and b/docdoku-web-front/app/images/download_image.png differ diff --git a/docdoku-web-front/app/images/favicon.ico b/docdoku-web-front/app/images/favicon.ico new file mode 100644 index 0000000000..d2ca94c110 Binary files /dev/null and b/docdoku-web-front/app/images/favicon.ico differ diff --git a/docdoku-web-front/app/images/form-checkbox-checked.png b/docdoku-web-front/app/images/form-checkbox-checked.png new file mode 100644 index 0000000000..110f55e3fd Binary files /dev/null and b/docdoku-web-front/app/images/form-checkbox-checked.png differ diff --git a/docdoku-web-front/app/images/form-checkbox-dark-checked.png b/docdoku-web-front/app/images/form-checkbox-dark-checked.png new file mode 100644 index 0000000000..9d3e146c57 Binary files /dev/null and b/docdoku-web-front/app/images/form-checkbox-dark-checked.png differ diff --git a/docdoku-web-front/app/images/form-checkbox-dark.png b/docdoku-web-front/app/images/form-checkbox-dark.png new file mode 100644 index 0000000000..5fcf88eec8 Binary files /dev/null and b/docdoku-web-front/app/images/form-checkbox-dark.png differ diff --git a/docdoku-web-front/app/images/form-checkbox.png b/docdoku-web-front/app/images/form-checkbox.png new file mode 100644 index 0000000000..463f2c1551 Binary files /dev/null and b/docdoku-web-front/app/images/form-checkbox.png differ diff --git a/docdoku-web-front/app/images/forward_disabled.png b/docdoku-web-front/app/images/forward_disabled.png new file mode 100644 index 0000000000..6a6ded7de8 Binary files /dev/null and b/docdoku-web-front/app/images/forward_disabled.png differ diff --git a/docdoku-web-front/app/images/forward_enabled.png b/docdoku-web-front/app/images/forward_enabled.png new file mode 100644 index 0000000000..a4e6b5384b Binary files /dev/null and b/docdoku-web-front/app/images/forward_enabled.png differ diff --git a/docdoku-web-front/app/images/forward_enabled_hover.png b/docdoku-web-front/app/images/forward_enabled_hover.png new file mode 100644 index 0000000000..fc46c5ebf0 Binary files /dev/null and b/docdoku-web-front/app/images/forward_enabled_hover.png differ diff --git a/docdoku-web-front/app/images/glyphicons-halflings.png b/docdoku-web-front/app/images/glyphicons-halflings.png new file mode 100644 index 0000000000..92d4445dfd Binary files /dev/null and b/docdoku-web-front/app/images/glyphicons-halflings.png differ diff --git a/docdoku-web-front/app/images/help_image.png b/docdoku-web-front/app/images/help_image.png new file mode 100644 index 0000000000..ddb30fc4b7 Binary files /dev/null and b/docdoku-web-front/app/images/help_image.png differ diff --git a/docdoku-web-front/app/images/icon-action-configuration-new.png b/docdoku-web-front/app/images/icon-action-configuration-new.png new file mode 100755 index 0000000000..40ea387323 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-configuration-new.png differ diff --git a/docdoku-web-front/app/images/icon-action-delete.png b/docdoku-web-front/app/images/icon-action-delete.png new file mode 100644 index 0000000000..36d16667aa Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-delete.png differ diff --git a/docdoku-web-front/app/images/icon-action-document-acl.png b/docdoku-web-front/app/images/icon-action-document-acl.png new file mode 100644 index 0000000000..7d3fbc0a78 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-document-acl.png differ diff --git a/docdoku-web-front/app/images/icon-action-document-checkin.png b/docdoku-web-front/app/images/icon-action-document-checkin.png new file mode 100644 index 0000000000..f945c05577 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-document-checkin.png differ diff --git a/docdoku-web-front/app/images/icon-action-document-checkout.png b/docdoku-web-front/app/images/icon-action-document-checkout.png new file mode 100644 index 0000000000..7799a2c1b9 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-document-checkout.png differ diff --git a/docdoku-web-front/app/images/icon-action-document-edit.png b/docdoku-web-front/app/images/icon-action-document-edit.png new file mode 100755 index 0000000000..4ccc71be40 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-document-edit.png differ diff --git a/docdoku-web-front/app/images/icon-action-document-move.png b/docdoku-web-front/app/images/icon-action-document-move.png new file mode 100755 index 0000000000..1d9f2deafc Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-document-move.png differ diff --git a/docdoku-web-front/app/images/icon-action-document-new-release.png b/docdoku-web-front/app/images/icon-action-document-new-release.png new file mode 100644 index 0000000000..051c009b62 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-document-new-release.png differ diff --git a/docdoku-web-front/app/images/icon-action-document-new-version.png b/docdoku-web-front/app/images/icon-action-document-new-version.png new file mode 100644 index 0000000000..af2c20411e Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-document-new-version.png differ diff --git a/docdoku-web-front/app/images/icon-action-document-new.png b/docdoku-web-front/app/images/icon-action-document-new.png new file mode 100755 index 0000000000..9d0692dd9f Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-document-new.png differ diff --git a/docdoku-web-front/app/images/icon-action-document-tags.png b/docdoku-web-front/app/images/icon-action-document-tags.png new file mode 100755 index 0000000000..7cefe0fe14 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-document-tags.png differ diff --git a/docdoku-web-front/app/images/icon-action-document-undocheckout.png b/docdoku-web-front/app/images/icon-action-document-undocheckout.png new file mode 100644 index 0000000000..dec08d47c1 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-document-undocheckout.png differ diff --git a/docdoku-web-front/app/images/icon-action-duplicate.png b/docdoku-web-front/app/images/icon-action-duplicate.png new file mode 100644 index 0000000000..c1b030b826 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-duplicate.png differ diff --git a/docdoku-web-front/app/images/icon-action-import.png b/docdoku-web-front/app/images/icon-action-import.png new file mode 100644 index 0000000000..44f5c6f5b8 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-import.png differ diff --git a/docdoku-web-front/app/images/icon-action-issue-new.png b/docdoku-web-front/app/images/icon-action-issue-new.png new file mode 100644 index 0000000000..3107786de2 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-issue-new.png differ diff --git a/docdoku-web-front/app/images/icon-action-lov.png b/docdoku-web-front/app/images/icon-action-lov.png new file mode 100644 index 0000000000..713da8a92d Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-lov.png differ diff --git a/docdoku-web-front/app/images/icon-action-mark-as-obsolete.png b/docdoku-web-front/app/images/icon-action-mark-as-obsolete.png new file mode 100644 index 0000000000..65baedb8a7 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-mark-as-obsolete.png differ diff --git a/docdoku-web-front/app/images/icon-action-milestone-new.png b/docdoku-web-front/app/images/icon-action-milestone-new.png new file mode 100644 index 0000000000..bc10a806c0 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-milestone-new.png differ diff --git a/docdoku-web-front/app/images/icon-action-new-baseline.png b/docdoku-web-front/app/images/icon-action-new-baseline.png new file mode 100644 index 0000000000..5e5d18dbf1 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-new-baseline.png differ diff --git a/docdoku-web-front/app/images/icon-action-new-product-intances.png b/docdoku-web-front/app/images/icon-action-new-product-intances.png new file mode 100644 index 0000000000..eeb9284575 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-new-product-intances.png differ diff --git a/docdoku-web-front/app/images/icon-action-order-new.png b/docdoku-web-front/app/images/icon-action-order-new.png new file mode 100644 index 0000000000..10d661f848 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-order-new.png differ diff --git a/docdoku-web-front/app/images/icon-action-part-new.png b/docdoku-web-front/app/images/icon-action-part-new.png new file mode 100755 index 0000000000..09945c363f Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-part-new.png differ diff --git a/docdoku-web-front/app/images/icon-action-product-new.png b/docdoku-web-front/app/images/icon-action-product-new.png new file mode 100755 index 0000000000..eeb9284575 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-product-new.png differ diff --git a/docdoku-web-front/app/images/icon-action-request-new.png b/docdoku-web-front/app/images/icon-action-request-new.png new file mode 100644 index 0000000000..f20f0b8cb6 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-request-new.png differ diff --git a/docdoku-web-front/app/images/icon-action-search.png b/docdoku-web-front/app/images/icon-action-search.png new file mode 100755 index 0000000000..523da7f7be Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-search.png differ diff --git a/docdoku-web-front/app/images/icon-action-snap-latest-baseline.png b/docdoku-web-front/app/images/icon-action-snap-latest-baseline.png new file mode 100644 index 0000000000..72f13d6340 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-snap-latest-baseline.png differ diff --git a/docdoku-web-front/app/images/icon-action-snap-released-baseline.png b/docdoku-web-front/app/images/icon-action-snap-released-baseline.png new file mode 100644 index 0000000000..a15364d60b Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-snap-released-baseline.png differ diff --git a/docdoku-web-front/app/images/icon-action-template-new.png b/docdoku-web-front/app/images/icon-action-template-new.png new file mode 100755 index 0000000000..3da3b146a4 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-template-new.png differ diff --git a/docdoku-web-front/app/images/icon-action-udf.png b/docdoku-web-front/app/images/icon-action-udf.png new file mode 100644 index 0000000000..ba113e827a Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-udf.png differ diff --git a/docdoku-web-front/app/images/icon-action-workflow-new.png b/docdoku-web-front/app/images/icon-action-workflow-new.png new file mode 100644 index 0000000000..06acdf0fe0 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-workflow-new.png differ diff --git a/docdoku-web-front/app/images/icon-action-workflow-roles.png b/docdoku-web-front/app/images/icon-action-workflow-roles.png new file mode 100644 index 0000000000..71c379d260 Binary files /dev/null and b/docdoku-web-front/app/images/icon-action-workflow-roles.png differ diff --git a/docdoku-web-front/app/images/icon-menu-document-new.png b/docdoku-web-front/app/images/icon-menu-document-new.png new file mode 100644 index 0000000000..13e16ac8f2 Binary files /dev/null and b/docdoku-web-front/app/images/icon-menu-document-new.png differ diff --git a/docdoku-web-front/app/images/icon-menu-edit-delete.png b/docdoku-web-front/app/images/icon-menu-edit-delete.png new file mode 100644 index 0000000000..36d16667aa Binary files /dev/null and b/docdoku-web-front/app/images/icon-menu-edit-delete.png differ diff --git a/docdoku-web-front/app/images/icon-menu-edit.png b/docdoku-web-front/app/images/icon-menu-edit.png new file mode 100644 index 0000000000..316de6b399 Binary files /dev/null and b/docdoku-web-front/app/images/icon-menu-edit.png differ diff --git a/docdoku-web-front/app/images/icon-menu-folder-new.png b/docdoku-web-front/app/images/icon-menu-folder-new.png new file mode 100644 index 0000000000..b1a10af577 Binary files /dev/null and b/docdoku-web-front/app/images/icon-menu-folder-new.png differ diff --git a/docdoku-web-front/app/images/icon-nav-baselines.png b/docdoku-web-front/app/images/icon-nav-baselines.png new file mode 100644 index 0000000000..9f16f2d7ce Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-baselines.png differ diff --git a/docdoku-web-front/app/images/icon-nav-bullet-down.png b/docdoku-web-front/app/images/icon-nav-bullet-down.png new file mode 100644 index 0000000000..0b0ac9f811 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-bullet-down.png differ diff --git a/docdoku-web-front/app/images/icon-nav-bullet-right.png b/docdoku-web-front/app/images/icon-nav-bullet-right.png new file mode 100644 index 0000000000..8cc20a991d Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-bullet-right.png differ diff --git a/docdoku-web-front/app/images/icon-nav-checkedouts.png b/docdoku-web-front/app/images/icon-nav-checkedouts.png new file mode 100644 index 0000000000..969ba17b33 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-checkedouts.png differ diff --git a/docdoku-web-front/app/images/icon-nav-configuration.png b/docdoku-web-front/app/images/icon-nav-configuration.png new file mode 100644 index 0000000000..63c75da0ed Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-configuration.png differ diff --git a/docdoku-web-front/app/images/icon-nav-documents.png b/docdoku-web-front/app/images/icon-nav-documents.png new file mode 100644 index 0000000000..84b6d80756 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-documents.png differ diff --git a/docdoku-web-front/app/images/icon-nav-folder-opened.png b/docdoku-web-front/app/images/icon-nav-folder-opened.png new file mode 100644 index 0000000000..3788e879d0 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-folder-opened.png differ diff --git a/docdoku-web-front/app/images/icon-nav-folder.png b/docdoku-web-front/app/images/icon-nav-folder.png new file mode 100644 index 0000000000..84595187e6 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-folder.png differ diff --git a/docdoku-web-front/app/images/icon-nav-issues.png b/docdoku-web-front/app/images/icon-nav-issues.png new file mode 100644 index 0000000000..21535b0dd3 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-issues.png differ diff --git a/docdoku-web-front/app/images/icon-nav-milestones.png b/docdoku-web-front/app/images/icon-nav-milestones.png new file mode 100644 index 0000000000..71175f1285 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-milestones.png differ diff --git a/docdoku-web-front/app/images/icon-nav-orders.png b/docdoku-web-front/app/images/icon-nav-orders.png new file mode 100644 index 0000000000..94d7186c29 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-orders.png differ diff --git a/docdoku-web-front/app/images/icon-nav-part.png b/docdoku-web-front/app/images/icon-nav-part.png new file mode 100644 index 0000000000..faf11e2d8b Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-part.png differ diff --git a/docdoku-web-front/app/images/icon-nav-product-instances.png b/docdoku-web-front/app/images/icon-nav-product-instances.png new file mode 100644 index 0000000000..2ee105fcd3 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-product-instances.png differ diff --git a/docdoku-web-front/app/images/icon-nav-product.png b/docdoku-web-front/app/images/icon-nav-product.png new file mode 100644 index 0000000000..8ebe437c55 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-product.png differ diff --git a/docdoku-web-front/app/images/icon-nav-requests.png b/docdoku-web-front/app/images/icon-nav-requests.png new file mode 100644 index 0000000000..845fb58db8 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-requests.png differ diff --git a/docdoku-web-front/app/images/icon-nav-search.png b/docdoku-web-front/app/images/icon-nav-search.png new file mode 100755 index 0000000000..e769c556b9 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-search.png differ diff --git a/docdoku-web-front/app/images/icon-nav-tag.png b/docdoku-web-front/app/images/icon-nav-tag.png new file mode 100644 index 0000000000..b4f8925088 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-tag.png differ diff --git a/docdoku-web-front/app/images/icon-nav-tags.png b/docdoku-web-front/app/images/icon-nav-tags.png new file mode 100644 index 0000000000..b6a016a907 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-tags.png differ diff --git a/docdoku-web-front/app/images/icon-nav-tasks.png b/docdoku-web-front/app/images/icon-nav-tasks.png new file mode 100644 index 0000000000..2b3d930c1c Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-tasks.png differ diff --git a/docdoku-web-front/app/images/icon-nav-templates.png b/docdoku-web-front/app/images/icon-nav-templates.png new file mode 100644 index 0000000000..749b244def Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-templates.png differ diff --git a/docdoku-web-front/app/images/icon-nav-user-home.png b/docdoku-web-front/app/images/icon-nav-user-home.png new file mode 100644 index 0000000000..96b4a3a8e2 Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-user-home.png differ diff --git a/docdoku-web-front/app/images/icon-nav-workflows.png b/docdoku-web-front/app/images/icon-nav-workflows.png new file mode 100644 index 0000000000..73230c1c6c Binary files /dev/null and b/docdoku-web-front/app/images/icon-nav-workflows.png differ diff --git a/docdoku-web-front/app/images/icon-place-home.png b/docdoku-web-front/app/images/icon-place-home.png new file mode 100644 index 0000000000..a9db92f295 Binary files /dev/null and b/docdoku-web-front/app/images/icon-place-home.png differ diff --git a/docdoku-web-front/app/images/linux.gif b/docdoku-web-front/app/images/linux.gif new file mode 100644 index 0000000000..8799707077 Binary files /dev/null and b/docdoku-web-front/app/images/linux.gif differ diff --git a/docdoku-web-front/app/images/loader.gif b/docdoku-web-front/app/images/loader.gif new file mode 100644 index 0000000000..e2735a46e1 Binary files /dev/null and b/docdoku-web-front/app/images/loader.gif differ diff --git a/docdoku-web-front/app/images/minus.png b/docdoku-web-front/app/images/minus.png new file mode 100644 index 0000000000..5c3d8d460c Binary files /dev/null and b/docdoku-web-front/app/images/minus.png differ diff --git a/docdoku-web-front/app/images/pba.bin b/docdoku-web-front/app/images/pba.bin new file mode 100755 index 0000000000..bcdd643599 Binary files /dev/null and b/docdoku-web-front/app/images/pba.bin differ diff --git a/docdoku-web-front/app/images/pba.json b/docdoku-web-front/app/images/pba.json new file mode 100755 index 0000000000..7534959f63 --- /dev/null +++ b/docdoku-web-front/app/images/pba.json @@ -0,0 +1,24 @@ +{ + + "metadata" : + { + "formatVersion" : 3.1, + "sourceFile" : "pba.obj", + "generatedBy" : "OBJConverter", + "vertices" : 12886, + "faces" : 25792, + "normals" : 0, + "uvs" : 0, + "materials" : 0 + }, + + "materials": [ { + "DbgColor" : 5994395, + "DbgIndex" : 0, + "DbgName" : "default", + "vertexColors" : "face" + }], + + "buffers": "pba.bin" + +} diff --git a/docdoku-web-front/app/images/pdfIcon.png b/docdoku-web-front/app/images/pdfIcon.png new file mode 100644 index 0000000000..f718c9ed20 Binary files /dev/null and b/docdoku-web-front/app/images/pdfIcon.png differ diff --git a/docdoku-web-front/app/images/plm_logo.png b/docdoku-web-front/app/images/plm_logo.png new file mode 100644 index 0000000000..ba6738b742 Binary files /dev/null and b/docdoku-web-front/app/images/plm_logo.png differ diff --git a/docdoku-web-front/app/images/plm_logo2.png b/docdoku-web-front/app/images/plm_logo2.png new file mode 100644 index 0000000000..ee4245542e Binary files /dev/null and b/docdoku-web-front/app/images/plm_logo2.png differ diff --git a/docdoku-web-front/app/images/plus.png b/docdoku-web-front/app/images/plus.png new file mode 100644 index 0000000000..e48fd4aa4b Binary files /dev/null and b/docdoku-web-front/app/images/plus.png differ diff --git a/docdoku-web-front/app/images/preloader.gif b/docdoku-web-front/app/images/preloader.gif new file mode 100644 index 0000000000..00802994fb Binary files /dev/null and b/docdoku-web-front/app/images/preloader.gif differ diff --git a/docdoku-web-front/app/images/product-loader.gif b/docdoku-web-front/app/images/product-loader.gif new file mode 100644 index 0000000000..82cd119d7b Binary files /dev/null and b/docdoku-web-front/app/images/product-loader.gif differ diff --git a/docdoku-web-front/app/images/sidebar_bg.png b/docdoku-web-front/app/images/sidebar_bg.png new file mode 100644 index 0000000000..a40b424122 Binary files /dev/null and b/docdoku-web-front/app/images/sidebar_bg.png differ diff --git a/docdoku-web-front/app/images/sidebar_bg_2.png b/docdoku-web-front/app/images/sidebar_bg_2.png new file mode 100644 index 0000000000..2f46bd4d02 Binary files /dev/null and b/docdoku-web-front/app/images/sidebar_bg_2.png differ diff --git a/docdoku-web-front/app/images/sliderDot.png b/docdoku-web-front/app/images/sliderDot.png new file mode 100644 index 0000000000..67408019e8 Binary files /dev/null and b/docdoku-web-front/app/images/sliderDot.png differ diff --git a/docdoku-web-front/app/images/sort_asc.png b/docdoku-web-front/app/images/sort_asc.png new file mode 100644 index 0000000000..1205f6638c Binary files /dev/null and b/docdoku-web-front/app/images/sort_asc.png differ diff --git a/docdoku-web-front/app/images/sort_both.png b/docdoku-web-front/app/images/sort_both.png new file mode 100644 index 0000000000..18670406bc Binary files /dev/null and b/docdoku-web-front/app/images/sort_both.png differ diff --git a/docdoku-web-front/app/images/sort_desc.png b/docdoku-web-front/app/images/sort_desc.png new file mode 100644 index 0000000000..7824973cc6 Binary files /dev/null and b/docdoku-web-front/app/images/sort_desc.png differ diff --git a/docdoku-web-front/app/images/tag.png b/docdoku-web-front/app/images/tag.png new file mode 100644 index 0000000000..87b97db5a4 Binary files /dev/null and b/docdoku-web-front/app/images/tag.png differ diff --git a/docdoku-web-front/app/images/treeview-default-line.gif b/docdoku-web-front/app/images/treeview-default-line.gif new file mode 100755 index 0000000000..37114d3068 Binary files /dev/null and b/docdoku-web-front/app/images/treeview-default-line.gif differ diff --git a/docdoku-web-front/app/images/treeview-default.gif b/docdoku-web-front/app/images/treeview-default.gif new file mode 100755 index 0000000000..a12ac52ffe Binary files /dev/null and b/docdoku-web-front/app/images/treeview-default.gif differ diff --git a/docdoku-web-front/app/images/user_avatar.png b/docdoku-web-front/app/images/user_avatar.png new file mode 100644 index 0000000000..f744242108 Binary files /dev/null and b/docdoku-web-front/app/images/user_avatar.png differ diff --git a/docdoku-web-front/app/images/view.png b/docdoku-web-front/app/images/view.png new file mode 100644 index 0000000000..dc059fd4bb Binary files /dev/null and b/docdoku-web-front/app/images/view.png differ diff --git a/docdoku-web-front/app/images/windows.gif b/docdoku-web-front/app/images/windows.gif new file mode 100644 index 0000000000..5ce50da885 Binary files /dev/null and b/docdoku-web-front/app/images/windows.gif differ diff --git a/docdoku-web-front/app/images/workflow/parallel.png b/docdoku-web-front/app/images/workflow/parallel.png new file mode 100644 index 0000000000..d634b266d9 Binary files /dev/null and b/docdoku-web-front/app/images/workflow/parallel.png differ diff --git a/docdoku-web-front/app/images/workflow/parallel.svg b/docdoku-web-front/app/images/workflow/parallel.svg new file mode 100644 index 0000000000..e79a08c23f --- /dev/null +++ b/docdoku-web-front/app/images/workflow/parallel.svg @@ -0,0 +1,53 @@ + + + +image/svg+xml + + + + \ No newline at end of file diff --git a/docdoku-web-front/app/images/workflow/sequential.png b/docdoku-web-front/app/images/workflow/sequential.png new file mode 100644 index 0000000000..722264f406 Binary files /dev/null and b/docdoku-web-front/app/images/workflow/sequential.png differ diff --git a/docdoku-web-front/app/images/workflow/sequential.svg b/docdoku-web-front/app/images/workflow/sequential.svg new file mode 100644 index 0000000000..4c6f8452a2 --- /dev/null +++ b/docdoku-web-front/app/images/workflow/sequential.svg @@ -0,0 +1,53 @@ + + + +image/svg+xml + + + + \ No newline at end of file diff --git a/docdoku-web-front/app/images/workspace_image.png b/docdoku-web-front/app/images/workspace_image.png new file mode 100644 index 0000000000..22cbcd9c33 Binary files /dev/null and b/docdoku-web-front/app/images/workspace_image.png differ diff --git a/docdoku-web-front/app/img/glyphicons-halflings-white.png b/docdoku-web-front/app/img/glyphicons-halflings-white.png new file mode 100644 index 0000000000..3bf6484a29 Binary files /dev/null and b/docdoku-web-front/app/img/glyphicons-halflings-white.png differ diff --git a/docdoku-web-front/app/img/glyphicons-halflings.png b/docdoku-web-front/app/img/glyphicons-halflings.png new file mode 100644 index 0000000000..a996999320 Binary files /dev/null and b/docdoku-web-front/app/img/glyphicons-halflings.png differ diff --git a/docdoku-web-front/app/index.html b/docdoku-web-front/app/index.html new file mode 100644 index 0000000000..ec7b00a322 --- /dev/null +++ b/docdoku-web-front/app/index.html @@ -0,0 +1,27 @@ + + + + + + DocDokuPLM - Workspace management + + + + + + + + + + + + + +
        +
        +
        + + + + + diff --git a/docdoku-web-front/app/js/common-objects/collections/activity_models.js b/docdoku-web-front/app/js/common-objects/collections/activity_models.js new file mode 100644 index 0000000000..b2a890b8aa --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/activity_models.js @@ -0,0 +1,12 @@ +/*global define*/ +define([ + 'backbone', + 'common-objects/models/workflow/activity_model' +], function (Backbone, ActivityModel) { + 'use strict'; + var ActivityModels = Backbone.Collection.extend({ + model: ActivityModel + }); + + return ActivityModels; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/attribute_collection.js b/docdoku-web-front/app/js/common-objects/collections/attribute_collection.js new file mode 100644 index 0000000000..0c567bdcbb --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/attribute_collection.js @@ -0,0 +1,13 @@ +/*global define*/ +define([ + 'backbone', + 'common-objects/models/attribute' +], function (Backbone, AttributeModel) { + 'use strict'; + var AttributeCollection = Backbone.Collection.extend({ + model: AttributeModel, + className: 'AttributeCollection' + }); + + return AttributeCollection; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/baselines.js b/docdoku-web-front/app/js/common-objects/collections/baselines.js new file mode 100644 index 0000000000..505fea2160 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/baselines.js @@ -0,0 +1,66 @@ +/*global $,define,App*/ +define(['backbone', 'common-objects/models/product_baseline', 'common-objects/models/document_baseline'], + function (Backbone, ProductBaseline, DocumentBaseline) { + 'use strict'; + var Baselines = Backbone.Collection.extend({ + initialize: function (attributes, options) { + if (options) { + this.type = (options.type) ? options.type : 'product'; + if (this.type === 'product') { + this.productId = options.productId; + } + } + }, + + model: function (attrs, options) { + switch (options.collection.type) { + case 'document' : + return new DocumentBaseline(attrs, options); + case 'product' : + return new ProductBaseline(attrs, options); + default : + return null; + } + }, + + url: function () { + switch (this.type) { + case 'document' : + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/document-baselines/'; + case 'product' : + if (this.productId) { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.productId + '/baselines'; + } + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/baselines'; + default : + return ''; + } + }, + + getLastReleaseRevision: function (callback) { + var lastReleaseRevision = null; + if (this.productId) { + $.ajax({ + type: 'GET', + url: App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.productId + '/releases/last', + contentType: 'application/json; charset=utf-8', + success: function (data) { + lastReleaseRevision = data; + if (callback && callback.success) { + callback.success(data); + } + }, + error: function (data) { + if (callback && callback.error) { + callback.error(data); + } + } + }); + } + return lastReleaseRevision; + } + + }); + + return Baselines; + }); diff --git a/docdoku-web-front/app/js/common-objects/collections/document_iteration.js b/docdoku-web-front/app/js/common-objects/collections/document_iteration.js new file mode 100644 index 0000000000..ec0ee02f97 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/document_iteration.js @@ -0,0 +1,60 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'common-objects/models/document/document_iteration' +], function (Backbone,DocumentIteration) { + 'use strict'; + var DocumentIterationList = Backbone.Collection.extend({ + + model: DocumentIteration, + + setDocument: function (document) { + this.document = document; + }, + + url: function () { + if(this.document.getId()){ + if (App.config.documentConfigSpec) { + return this.baseUrl()+ '?configSpec='+App.config.documentConfigSpec; + } else { + return this.baseUrl(); + } + }else{ + return this.document.urlRoot()+ '/iterations'; + } + }, + + baseUrl: function(){ + return this.document.urlRoot()+ '/'+this.document.getId()+ '/iterations'; + }, + + comparator: function (documentIteration) { + return documentIteration.getDocKey(); + }, + + next: function (iteration) { + var index = this.indexOf(iteration); + return this.at(index + 1); + }, + + previous: function (iteration) { + var index = this.indexOf(iteration); + return this.at(index - 1); + }, + + hasNextIteration: function (iteration) { + return !_.isUndefined(this.next(iteration)); + }, + + hasPreviousIteration: function (iteration) { + return !_.isUndefined(this.previous(iteration)); + }, + + isLast: function (iteration) { + return this.last() === iteration; + } + + }); + + return DocumentIterationList; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/file/attached_file_collection.js b/docdoku-web-front/app/js/common-objects/collections/file/attached_file_collection.js new file mode 100644 index 0000000000..fca84c4cee --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/file/attached_file_collection.js @@ -0,0 +1,13 @@ +/*global define*/ +define([ + 'backbone', + 'common-objects/models/file/attached_file' +], function (Backbone, AttachedFileModel) { + 'use strict'; + var AttachedFileCollection = Backbone.Collection.extend({ + model: AttachedFileModel, + className: 'AttachedFileCollection' + }); + + return AttachedFileCollection; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/linked/linked_change_item_collection.js b/docdoku-web-front/app/js/common-objects/collections/linked/linked_change_item_collection.js new file mode 100644 index 0000000000..cf5a1c3a93 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/linked/linked_change_item_collection.js @@ -0,0 +1,18 @@ +/*global define*/ +define([ + 'backbone', + 'common-objects/models/linked/linked_change_item' +], function (Backbone, LinkedChangeItem) { + 'use strict'; + var LinkedChangeItemCollection = Backbone.Collection.extend({ + + model: LinkedChangeItem, + + comparator: function (linkedChangeItem) { + return linkedChangeItem.getName(); + } + + }); + + return LinkedChangeItemCollection; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/linked/linked_document_collection.js b/docdoku-web-front/app/js/common-objects/collections/linked/linked_document_collection.js new file mode 100644 index 0000000000..bddaa3ecdf --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/linked/linked_document_collection.js @@ -0,0 +1,18 @@ +/*global define*/ +define([ + 'backbone', + 'common-objects/models/linked/linked_document' +], function (Backbone, LinkedDocument) { + 'use strict'; + var LinkedDocumentCollection = Backbone.Collection.extend({ + + model: LinkedDocument, + + comparator: function (linkedDocument) { + return linkedDocument.getDocKey(); + } + + }); + + return LinkedDocumentCollection; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/linked/linked_document_iteration_collection.js b/docdoku-web-front/app/js/common-objects/collections/linked/linked_document_iteration_collection.js new file mode 100644 index 0000000000..ff9599fdd6 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/linked/linked_document_iteration_collection.js @@ -0,0 +1,18 @@ +/*global define*/ +define([ + 'backbone', + 'common-objects/models/linked/linked_document_iteration' +], function (Backbone, LinkedDocumentIteration) { + 'use strict'; + var LinkedDocumentIterationCollection = Backbone.Collection.extend({ + + model: LinkedDocumentIteration, + + comparator: function (linkedDocumentIteration) { + return linkedDocumentIteration.getDocKey(); + } + + }); + + return LinkedDocumentIterationCollection; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/linked/linked_part_collection.js b/docdoku-web-front/app/js/common-objects/collections/linked/linked_part_collection.js new file mode 100644 index 0000000000..8baef16b9c --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/linked/linked_part_collection.js @@ -0,0 +1,18 @@ +/*global define*/ +define([ + 'backbone', + 'common-objects/models/linked/linked_part' +], function (Backbone, LinkedPart) { + 'use strict'; + var LinkedPartCollection = Backbone.Collection.extend({ + + model: LinkedPart, + + comparator: function (linkedPart) { + return linkedPart.getPartKey(); + } + + }); + + return LinkedPartCollection; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/lovs.js b/docdoku-web-front/app/js/common-objects/collections/lovs.js new file mode 100644 index 0000000000..325006d77b --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/lovs.js @@ -0,0 +1,16 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/lov/lov' +], function (Backbone, lov) { + 'use strict'; + var LOVCollection = Backbone.Collection.extend({ + + model: lov, + + url: App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/lov' + + }); + + return LOVCollection; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/modification_notification_collection.js b/docdoku-web-front/app/js/common-objects/collections/modification_notification_collection.js new file mode 100644 index 0000000000..be7fff1bf2 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/modification_notification_collection.js @@ -0,0 +1,25 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'common-objects/models/modification_notification' +], function (Backbone, ModificationNotification) { + 'use strict'; + var ModificationNotificationCollection = Backbone.Collection.extend({ + + model: ModificationNotification, + className: 'ModificationNotificationCollection', + + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/notifications/'; + }, + + hasUnreadModificationNotifications: function () { + return _.select(this.models || [], function(notif) { + return !notif.isAcknowledged(); + }).length; + } + + }); + + return ModificationNotificationCollection; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/part_collection.js b/docdoku-web-front/app/js/common-objects/collections/part_collection.js new file mode 100644 index 0000000000..0a8eee3515 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/part_collection.js @@ -0,0 +1,107 @@ +/*global $,define,App*/ +define([ + 'backbone', + 'common-objects/models/part' +], function (Backbone, Part) { + 'use strict'; + var PartList = Backbone.Collection.extend({ + model: Part, + + className: 'PartList', + + setMainPart: function (part) { + this.part = part; + }, + initialize: function (start) { + + this.currentPage = 0; + this.pageCount = 0; + this.resultsPerPage = 20; + + this.urlBase = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts?start='; + + if (start) { + this.currentPage = start; + } + + }, + + setResultsPerPage: function (count) { + this.resultsPerPage = count; + }, + + fetchPageCount: function () { + var self = this; + $.ajax({ + url: App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/count', + success: function (data) { + self.pageCount = Math.ceil(data.count / self.resultsPerPage); + self.trigger('page-count:fetch'); + }, + error: function () { + self.trigger('page-count:fetch'); + } + }); + }, + + hasSeveralPages: function () { + return this.pageCount > 1; + }, + + setCurrentPage: function (page) { + this.currentPage = page; + return this; + }, + + getPageCount: function () { + return this.pageCount; + }, + + getCurrentPage: function () { + return this.currentPage + 1; + }, + + isLastPage: function () { + return this.currentPage >= this.pageCount - 1; + }, + + isFirstPage: function () { + return this.currentPage <= 0; + }, + + setFirstPage: function () { + if (!this.isFirstPage()) { + this.currentPage = 0; + } + return this; + }, + + setLastPage: function () { + if (!this.isLastPage()) { + this.currentPage = this.pageCount - 1; + } + return this; + }, + + setNextPage: function () { + if (!this.isLastPage()) { + this.currentPage++; + } + return this; + }, + + setPreviousPage: function () { + if (!this.isFirstPage()) { + this.currentPage--; + } + return this; + }, + + url: function () { + return this.urlBase + this.currentPage * this.resultsPerPage + '&length=' + this.resultsPerPage; + } + + }); + + return PartList; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/part_iteration_collection.js b/docdoku-web-front/app/js/common-objects/collections/part_iteration_collection.js new file mode 100644 index 0000000000..8486318549 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/part_iteration_collection.js @@ -0,0 +1,44 @@ +/*global _,define*/ +define([ + 'backbone', + 'common-objects/models/part_iteration' +], function (Backbone, PartIteration) { + 'use strict'; + var PartIterationList = Backbone.Collection.extend({ + + model: PartIteration, + + setPart: function (part) { + this.part = part; + }, + + url: function () { + return this.part.url() + '/iterations'; + }, + + next: function (iteration) { + var index = this.indexOf(iteration); + return this.at(index + 1); + }, + + previous: function (iteration) { + var index = this.indexOf(iteration); + return this.at(index - 1); + }, + + hasNextIteration: function (iteration) { + return !_.isUndefined(this.next(iteration)); + }, + + hasPreviousIteration: function (iteration) { + return !_.isUndefined(this.previous(iteration)); + }, + + isLast: function (iteration) { + return this.last() === iteration; + } + + }); + + return PartIterationList; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/part_search_collection.js b/docdoku-web-front/app/js/common-objects/collections/part_search_collection.js new file mode 100644 index 0000000000..920081aaca --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/part_search_collection.js @@ -0,0 +1,36 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/part' +], function (Backbone, Part) { + 'use strict'; + var PartList = Backbone.Collection.extend({ + + model: Part, + + className: 'PartList', + + setQuery: function (query) { + this.query = query; + }, + + initialize: function () { + this.urlBase = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/search/'; + }, + + fetchPageCount: function () { + return false; + }, + + hasSeveralPages: function () { + return false; + }, + + url: function () { + return this.urlBase + this.query; + } + + }); + + return PartList; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/path_data_iterations.js b/docdoku-web-front/app/js/common-objects/collections/path_data_iterations.js new file mode 100644 index 0000000000..c698c58549 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/path_data_iterations.js @@ -0,0 +1,51 @@ +/*global _,define*/ +define([ + 'backbone', + 'common-objects/models/path_data_iteration' +], function (Backbone, PathDataIteration) { + 'use strict'; + var PathDataIterations = Backbone.Collection.extend({ + + model: PathDataIteration, + + className: 'PathDataIterations', + + initialize: function () { + }, + + setPathDataMaster: function (pathDataMaster) { + this.pathDataMaster = pathDataMaster; + }, + + url: function () { + return this.pathDataMaster.url() + '/iterations'; + }, + + next: function (iteration) { + var index = this.indexOf(iteration); + return this.at(index + 1); + }, + + previous: function (iteration) { + var index = this.indexOf(iteration); + return this.at(index - 1); + }, + + hasNextIteration: function (iteration) { + return !_.isUndefined(this.next(iteration)); + }, + + hasPreviousIteration: function (iteration) { + return !_.isUndefined(this.previous(iteration)); + }, + + isLast: function (iteration) { + return this.last() === iteration; + } + + + }); + + return PathDataIterations; + +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/product_instance_iterations.js b/docdoku-web-front/app/js/common-objects/collections/product_instance_iterations.js new file mode 100644 index 0000000000..75bddb2813 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/product_instance_iterations.js @@ -0,0 +1,45 @@ +/*global _,define*/ +define(['backbone', 'common-objects/models/product_instance_iteration'], +function (Backbone, ProductInstanceIteration) { + + 'use strict'; + + var ProductInstanceIterations = Backbone.Collection.extend({ + + model: ProductInstanceIteration, + + className: 'ProductInstanceIterations', + + setProductInstanceMaster: function (productInstanceMaster) { + this.productInstanceMaster = productInstanceMaster; + }, + + url: function () { + return this.productInstanceMaster.url() + '/iterations'; + }, + + next: function (iteration) { + var index = this.indexOf(iteration); + return this.at(index + 1); + }, + + previous: function (iteration) { + var index = this.indexOf(iteration); + return this.at(index - 1); + }, + + hasNextIteration: function (iteration) { + return !_.isUndefined(this.next(iteration)); + }, + + hasPreviousIteration: function (iteration) { + return !_.isUndefined(this.previous(iteration)); + }, + + isLast: function (iteration) { + return this.last() === iteration; + } + + }); + return ProductInstanceIterations; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/product_instances.js b/docdoku-web-front/app/js/common-objects/collections/product_instances.js new file mode 100644 index 0000000000..5caded9d50 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/product_instances.js @@ -0,0 +1,23 @@ +/*global define,App*/ +define(['backbone', 'common-objects/models/product_instance'], +function (Backbone, ProductInstance) { + 'use strict'; + var ProductInstances = Backbone.Collection.extend({ + model: ProductInstance, + + initialize: function (attributes, options) { + if (options) { + this.productId = options.productId; + } + }, + + url: function () { + if (this.productId) { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.productId + '/product-instances'; + } + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/product-instances'; + } + + }); + return ProductInstances; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/collections/reachable_users.js b/docdoku-web-front/app/js/common-objects/collections/reachable_users.js new file mode 100644 index 0000000000..d3ae7f5b3d --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/reachable_users.js @@ -0,0 +1,15 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/user' +], function (Backbone, User) { + 'use strict'; + var Users = Backbone.Collection.extend({ + model: User, + url: function () { + return App.config.contextPath + '/api/workspaces/reachable-users'; + } + }); + + return Users; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/security/workspace_user_group_memberships.js b/docdoku-web-front/app/js/common-objects/collections/security/workspace_user_group_memberships.js new file mode 100644 index 0000000000..d456bff9c3 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/security/workspace_user_group_memberships.js @@ -0,0 +1,18 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/security/workspace_user_group_membership' +], function (Backbone, WorkspaceUserGroupMembership) { + 'use strict'; + var WorkspaceUserGroupMemberships = Backbone.Collection.extend({ + + model: WorkspaceUserGroupMembership, + + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/memberships/usergroups'; + } + + }); + + return WorkspaceUserGroupMemberships; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/collections/security/workspace_user_memberships.js b/docdoku-web-front/app/js/common-objects/collections/security/workspace_user_memberships.js new file mode 100644 index 0000000000..21e8f2d6e9 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/security/workspace_user_memberships.js @@ -0,0 +1,18 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/security/workspace_user_membership' +], function (Backbone, WorkspaceUserMembership) { + 'use strict'; + var WorkspaceUserMemberships = Backbone.Collection.extend({ + + model: WorkspaceUserMembership, + + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/memberships/users'; + } + + }); + + return WorkspaceUserMemberships; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/collections/substitute_part_collection.js b/docdoku-web-front/app/js/common-objects/collections/substitute_part_collection.js new file mode 100644 index 0000000000..d574eb628f --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/substitute_part_collection.js @@ -0,0 +1,21 @@ +/*global define*/ +define([ + 'backbone', + 'common-objects/models/part' +], function (Backbone, Part) { + 'use strict'; + var SubstitutePartList = Backbone.Collection.extend({ + model: Part, + + className: 'SubstitutePartList', + + setMainPart: function (part) { + this.part = part; + }, + initialize: function () { + + } + }); + + return SubstitutePartList; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/tag.js b/docdoku-web-front/app/js/common-objects/collections/tag.js new file mode 100644 index 0000000000..ed2b77b3b0 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/tag.js @@ -0,0 +1,46 @@ +/*global define,App,$*/ +define([ + 'backbone', + 'common-objects/models/tag' +], function (Backbone, Tag) { + 'use strict'; + var TagList = Backbone.Collection.extend({ + model: Tag, + + className: 'TagList', + + comparator: function (tagA, tagB) { + // sort tags by label + var labelA = tagA.get('label'); + var labelB = tagB.get('label'); + + if (labelA === labelB) { + return 0; + } + return (labelA < labelB) ? -1 : 1; + }, + + + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/tags'; + }, + + createTags: function (tags, callbackSuccess, callbackError) { + $.ajax({ + context: this, + type: 'POST', + url: this.url() + '/multiple', + data: JSON.stringify({tags:tags}), + contentType: 'application/json; charset=utf-8', + success: function () { + this.fetch({reset: true}); + callbackSuccess(); + }, + error:callbackError + }); + + } + }); + + return TagList; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/task_models.js b/docdoku-web-front/app/js/common-objects/collections/task_models.js new file mode 100644 index 0000000000..7ac6d05ac8 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/task_models.js @@ -0,0 +1,12 @@ +/*global define*/ +define([ + 'backbone', + 'common-objects/models/task_model' +], function (Backbone, TaskModel) { + 'use strict'; + var TaskModels = Backbone.Collection.extend({ + model: TaskModel + }); + + return TaskModels; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/collections/used_by/used_by_document.js b/docdoku-web-front/app/js/common-objects/collections/used_by/used_by_document.js new file mode 100644 index 0000000000..f4e33c3587 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/used_by/used_by_document.js @@ -0,0 +1,28 @@ +/*global define*/ +define([ + 'backbone', + 'common-objects/models/document/document_revision' +], function (Backbone, DocumentRevision) { + 'use strict'; + var UsedByDocumentList = Backbone.Collection.extend({ + + model: DocumentRevision, + + setLinkedDocumentIterationId: function (linkedDocumentIterationId) { + this.linkedDocumentIterationId = linkedDocumentIterationId; + }, + + setLinkedDocument: function (linkedDocument) { + this.linkedDocument = linkedDocument; + }, + + url: function () { + return this.linkedDocument.baseUrl() + '/' + this.linkedDocumentIterationId + '/inverse-document-link'; + } + + }); + + UsedByDocumentList.className = 'UsedByDocumentList'; + + return UsedByDocumentList; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/user_groups.js b/docdoku-web-front/app/js/common-objects/collections/user_groups.js new file mode 100644 index 0000000000..ccf2bcbd56 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/user_groups.js @@ -0,0 +1,15 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/user_group' +], function (Backbone, UserGroup) { + 'use strict'; + var UserGroups = Backbone.Collection.extend({ + model: UserGroup, + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/user-group'; + } + }); + + return UserGroups; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/users.js b/docdoku-web-front/app/js/common-objects/collections/users.js new file mode 100644 index 0000000000..7cbc174c2c --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/users.js @@ -0,0 +1,15 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/user' +], function (Backbone, User) { + 'use strict'; + var Users = Backbone.Collection.extend({ + model: User, + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/users'; + } + }); + + return Users; +}); diff --git a/docdoku-web-front/app/js/common-objects/collections/workflow_models.js b/docdoku-web-front/app/js/common-objects/collections/workflow_models.js new file mode 100644 index 0000000000..4e26e00020 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/collections/workflow_models.js @@ -0,0 +1,15 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/workflow/workflow_model' +], function (Backbone, WorkflowModel) { + 'use strict'; + var WorkflowModels = Backbone.Collection.extend({ + model: WorkflowModel, + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/workflow-models'; + } + }); + + return WorkflowModels; +}); diff --git a/docdoku-web-front/app/js/common-objects/common/singleton_decorator.js b/docdoku-web-front/app/js/common-objects/common/singleton_decorator.js new file mode 100644 index 0000000000..77dd52d074 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/common/singleton_decorator.js @@ -0,0 +1,19 @@ +/*global _,define*/ +define(function () { + // A Singleton decorator. + // requires underscrore.js + // var Foo = function () {}; + // Foo = singletonDecorator(Foo); + 'use strict'; + var singletonDecorator = function (constructor) { + constructor.getInstance = function () { + if (!constructor._instance) { + constructor._instance = _.extend(constructor.prototype, {}); + constructor.apply(constructor._instance, arguments); + } + return constructor._instance; + }; + return constructor; + }; + return singletonDecorator; +}); diff --git a/docdoku-web-front/app/js/common-objects/contextResolver.js b/docdoku-web-front/app/js/common-objects/contextResolver.js new file mode 100644 index 0000000000..c5bf1040f8 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/contextResolver.js @@ -0,0 +1,87 @@ +/*global _,$,define,App*/ +define([ + 'common-objects/models/user', + 'common-objects/models/workspace' + +], function (User, Workspace) { + + 'use strict'; + + + var ContextResolver = function () { + }; + + + $.ajaxSetup({ + beforeSend: function(xhr) { + if(localStorage.jwt){ + xhr.setRequestHeader('Authorization', 'Bearer '+ localStorage.jwt); + } + }, + statusCode: { + 401: function(){ + delete localStorage.jwt; + if(App.config.needAuthentication){ + window.location.href = App.config.contextPath + '/?denied=true&originURL=' + encodeURIComponent(window.location.pathname + window.location.hash); + } + } + } + }); + + function onError(res) { + return res; + } + + ContextResolver.prototype.resolveServerProperties = function(){ + return $.getJSON('../webapp.properties.json').then(function (properties) { + App.config.contextPath = properties.contextRoot; + },onError); + }; + + ContextResolver.prototype.resolveAccount = function(){ + return User.getAccount().then(function(account){ + App.config.connected = true; + App.config.account = account; + App.config.login = account.login; + App.config.userName = account.name; + App.config.timeZone = account.timeZone; + App.config.admin = account.admin; + + if(window.localStorage.locale === 'unset'){ + window.localStorage.locale = account.language || 'en'; + window.location.reload(); + }else{ + window.localStorage.locale = account.language || 'en'; + } + + return account; + },onError); + }; + + ContextResolver.prototype.resolveWorkspaces = function () { + return Workspace.getWorkspaces().then(function (workspaces) { + App.config.workspaces = workspaces; + App.config.workspaceAdmin = _.findWhere(App.config.workspaces.administratedWorkspaces,{id:App.config.workspaceId}) !== undefined; + App.config.workspaces.nonAdministratedWorkspaces = _.reject(App.config.workspaces.allWorkspaces,function(workspace){ + return _.contains(_.pluck(App.config.workspaces.administratedWorkspaces,'id'),workspace.id); + }); + return workspaces; + },onError); + }; + + ContextResolver.prototype.resolveGroups = function () { + return User.getGroups(App.config.workspaceId) + .then(function(groups){ + App.config.groups = groups; + },onError); + }; + + ContextResolver.prototype.resolveUser = function () { + return User.whoami(App.config.workspaceId) + .then(function(user){ + App.config.user = user; + },onError); + }; + + return new ContextResolver(); +}); diff --git a/docdoku-web-front/app/js/common-objects/models/admin.js b/docdoku-web-front/app/js/common-objects/models/admin.js new file mode 100644 index 0000000000..e5992dd2a1 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/admin.js @@ -0,0 +1,41 @@ +/*global $,define,App*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var Admin = Backbone.Model.extend({ + initialize: function () { + this.className = 'Admin'; + } + }); + + Admin.getDiskSpaceUsageStats = function () { + return $.getJSON(App.config.contextPath + '/api/admin/disk-usage-stats'); + }; + Admin.getUsersStats = function () { + return $.getJSON(App.config.contextPath + '/api/admin/users-stats'); + }; + Admin.getDocumentsStats = function () { + return $.getJSON(App.config.contextPath + '/api/admin/documents-stats'); + }; + Admin.getProductsStats = function () { + return $.getJSON(App.config.contextPath + '/api/admin/products-stats'); + }; + Admin.getPartsStats = function () { + return $.getJSON(App.config.contextPath + '/api/admin/parts-stats'); + }; + + Admin.indexWorkspace = function (workspaceId) { + return $.ajax({ + type: 'PUT', + url: App.config.contextPath + '/api/admin/index/'+workspaceId + }); + }; + + Admin.indexAllWorkspaces = function () { + return $.ajax({ + type: 'PUT', + url: App.config.contextPath + '/api/admin/index-all' + }); + }; + + return Admin; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/attribute.js b/docdoku-web-front/app/js/common-objects/models/attribute.js new file mode 100644 index 0000000000..050ecb09ad --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/attribute.js @@ -0,0 +1,57 @@ +/*global define*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var Attribute = Backbone.Model.extend({ + + getType: function () { + if (this.get('type')) { + return this.get('type'); + } else if (this.get('attributeType')) { + return this.get('attributeType'); + } else { + return null; + } + }, + + isMandatory: function () { + return this.get('mandatory'); + }, + + getName: function () { + return this.get('name'); + }, + + getLOVName: function () { + return this.get('lovName'); + }, + + getValue: function () { + return this.get('value'); + }, + + getItems: function () { + return this.get('items'); + }, + + getLocked: function () { + return this.get('locked'); + }, + + toString: function () { + return this.getName() + ':' + this.getValue() + '(' + this.getType() + ') '; + } + + }); + + Attribute.types = { + NUMBER: 'NUMBER', + DATE: 'DATE', + BOOLEAN: 'BOOLEAN', + TEXT: 'TEXT', + LONG_TEXT: 'LONG_TEXT', + URL: 'URL', + LOV: 'LOV' + }; + + return Attribute; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/baseline.js b/docdoku-web-front/app/js/common-objects/models/baseline.js new file mode 100644 index 0000000000..ab37cf8e0a --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/baseline.js @@ -0,0 +1,44 @@ +/*global _,define,App*/ +define(['backbone', 'common-objects/utils/date'], function (Backbone, Date) { + 'use strict'; + var Baseline = Backbone.Model.extend({ + + initialize: function () { + _.bindAll(this); + }, + + getId: function () { + return this.get('id'); + }, + + getType:function(){ + return this.get('type'); + }, + + isReleased:function(){ + return this.get('type')==='RELEASED'; + }, + + getName: function () { + return this.get('name'); + }, + + getDescription:function(){ + return this.get('description'); + }, + + getCreationDate: function () { + return this.get('creationDate'); + }, + + getFormattedCreationDate: function () { + return Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getCreationDate() + ); + } + + }); + + return Baseline; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/document/document_iteration.js b/docdoku-web-front/app/js/common-objects/models/document/document_iteration.js new file mode 100644 index 0000000000..1a6fdadf63 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/document/document_iteration.js @@ -0,0 +1,119 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'common-objects/utils/date', + 'common-objects/collections/attribute_collection', + 'common-objects/collections/file/attached_file_collection' +], function (Backbone, date, AttributeCollection, AttachedFileCollection) { + 'use strict'; + var DocumentIteration = Backbone.Model.extend({ + + idAttribute: 'iteration', + + url: function () { + if (this.getIteration()) { + if (App.config.documentConfigSpec) { + return this.baseUrl()+ '?configSpec='+App.config.documentConfigSpec; + } else { + return this.baseUrl(); + } + } + return this.collection.baseUrl(); + }, + + baseUrl: function () { + return this.collection.baseUrl() + '/' + this.getIteration(); + }, + + initialize: function () { + + this.className = 'DocumentIteration'; + + var attributes = new AttributeCollection(this.get('instanceAttributes')); + + var filesMapping = _.map(this.get('attachedFiles'), function (fullName) { + return { + 'fullName': fullName, + shortName: _.last(fullName.split('/')), + created: true + }; + }); + var attachedFiles = new AttachedFileCollection(filesMapping); + + //'attributes' is a special name for Backbone + this.set('instanceAttributes', attributes); + this.set('attachedFiles', attachedFiles); + }, + + defaults: { + attachedFiles: [], + instanceAttributes: [] + }, + + getAttachedFiles: function () { + return this.get('attachedFiles'); + }, + + getAttributes: function () { + return this.get('instanceAttributes'); + }, + + getWorkspace: function () { + return this.get('workspaceId'); + }, + + getReference: function () { + console.warn('Usage of getReference() is deprecated use getId()'); + return this.getId(); + }, + getId: function () { + return this.get('id'); + }, + + getIteration: function () { + return this.get('iteration'); + }, + + getDocumentMasterId: function () { + return this.get('documentMasterId'); + }, + + getVersion: function () { + return this.get('version'); + }, + + getTitle: function () { + return this.get('title'); + }, + + getDocKey: function () { + return this.getDocumentMasterId() + '-' + this.getVersion(); + }, + + getLinkedDocuments: function () { + return this.get('linkedDocuments'); + }, + + /** + * file Upload uses the old servlet, not the JAXRS Api * + * return /files/{workspace}/documents/{docId}/{version}/{iteration}/ + * @returns string + */ + getUploadBaseUrl: function () { + return App.config.contextPath + '/api/files/' + this.getBaseName() + '/'; + }, + + getBaseName: function () { + return this.getWorkspace() + '/documents/' + this.getDocumentMasterId() + '/' + this.getVersion() + '/' + this.getIteration(); + }, + + getUsedByDocuments: function () { + return []; + }, + + getUsedByParts: function () { + return []; + } + }); + return DocumentIteration; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/document/document_revision.js b/docdoku-web-front/app/js/common-objects/models/document/document_revision.js new file mode 100644 index 0000000000..7b7e9f5d9a --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/document/document_revision.js @@ -0,0 +1,465 @@ +/*global $,_,define,App,window*/ +define([ + 'backbone', + 'common-objects/utils/date', + 'common-objects/collections/document_iteration', + 'common-objects/utils/acl-checker' +], function (Backbone, Date, DocumentIterationList, ACLChecker) { + 'use strict'; + var DocumentRevision = Backbone.Model.extend({ + + urlRoot: function () { + if (this.isNew()) { + return this.collection.url(); + } + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/documents'; + }, + + initialize: function () { + _.bindAll(this); + }, + + url: function () { + if (this.getId()) { + if (App.config.documentConfigSpec) { + return this.baseUrl() + '?configSpec=' + App.config.documentConfigSpec; + } else { + return this.baseUrl(); + } + } + return this.urlRoot(); + }, + + baseUrl: function () { + return this.urlRoot() + '/' + encodeURIComponent(this.getId()); + }, + + getAbortedWorkflowsUrl: function () { + return this.urlRoot() + '/' + encodeURIComponent(this.getId()) + '/aborted-workflows'; + }, + + parse: function (data) { + this.iterations = new DocumentIterationList(data.documentIterations); + this.iterations.setDocument(this); + delete data.documentIterations; + delete data.lastIteration; + return data; + }, + + getId: function () { + return this.get('id'); + }, + + getDescription: function() { + return this.get('description'); + }, + + getReference: function () { + var id = this.get('id'); + return id.substr(0, id.lastIndexOf('-')); + }, + + getVersion: function () { + return this.get('version'); + }, + + getTitle: function () { + return this.get('title'); + }, + + getWorkspace: function () { + return this.get('workspaceId'); + }, + + getCheckoutUser: function () { + return this.get('checkOutUser'); + }, + + isReleased: function () { + return this.get('status') === 'RELEASED'; + }, + + isObsolete : function(){ + return this.get('status') === 'OBSOLETE'; + }, + + getObsoleteDate: function() { + return Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.get('obsoleteDate') + ); + }, + + getObsoleteAuthor: function() { + return this.get('obsoleteAuthor'); + }, + + getObsoleteAuthorLogin: function () { + if (this.isObsolete()) { + return this.getObsoleteAuthor().login; + } + return null; + }, + + getObsoleteAuthorName: function () { + if (this.isObsolete()) { + return this.getObsoleteAuthor().name; + } + return null; + }, + + getReleaseDate: function() { + return Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.get('releaseDate') + ); + }, + + getReleaseAuthor: function() { + return this.get('releaseAuthor'); + }, + + getReleaseAuthorLogin: function () { + if (this.getReleaseAuthor()) { + return this.getReleaseAuthor().login; + } + return null; + }, + + getReleaseAuthorName: function () { + if (this.getReleaseAuthor()) { + return this.getReleaseAuthor().name; + } + return null; + }, + + isCheckoutByConnectedUser: function () { + return this.isCheckout() ? this.getCheckoutUser().login === App.config.login : false; + }, + + isLocked:function(){ + return this.isCheckout() && !this.isCheckoutByConnectedUser(); + }, + + getUrl: function () { + return this.url(); + }, + + hasIterations: function () { + return !this.getIterations().isEmpty(); + }, + + getLastIteration: function () { + return this.getIterations().last(); + }, + + getIterations: function () { + return this.iterations; + }, + + isIterationChangedSubscribed: function () { + return this.get('iterationSubscription'); + }, + + isStateChangedSubscribed: function () { + return this.get('stateSubscription'); + }, + + getTags: function () { + return this.get('tags'); + }, + + getPath: function () { + return this.get('path'); + }, + + getDisplayKey: function () { + if (this.getTitle()) { + return this.getTitle() + ' < ' + this.getId() + ' >'; + } + return '< ' + this.getId() + ' >'; + }, + + checkout: function () { + return $.ajax({ + context: this, + type: 'PUT', + url: this.baseUrl() + '/checkout', + error: function (xhr) { + window.alert(xhr.responseText); + } + }); + }, + + + undocheckout: function () { + return $.ajax({ + context: this, + type: 'PUT', + url: this.baseUrl() + '/undocheckout', + error: function (xhr) { + window.alert(xhr.responseText); + } + }); + }, + + checkin: function () { + return $.ajax({ + context: this, + type: 'PUT', + url: this.baseUrl() + '/checkin', + error: function (xhr) { + window.alert(xhr.responseText); + } + }); + }, + + release: function () { + return $.ajax({ + context: this, + type: 'PUT', + url: this.baseUrl() + '/release', + success: function () { + this.fetch(); + } + }); + }, + + markAsObsolete: function () { + return $.ajax({ + context: this, + type: 'PUT', + url: this.baseUrl() + '/obsolete', + success: function () { + this.fetch(); + } + }); + }, + + toggleStateSubscribe: function (oldState) { + + var action = oldState ? 'unsubscribe' : 'subscribe'; + + $.ajax({ + context: this, + type: 'PUT', + url: this.baseUrl() + '/notification/stateChange/' + action, + success: function () { + this.fetch(); + } + }); + }, + + toggleIterationSubscribe: function (oldState) { + + var action = oldState ? 'unsubscribe' : 'subscribe'; + + $.ajax({ + context: this, + type: 'PUT', + url: this.baseUrl() + '/notification/iterationChange/' + action, + success: function () { + this.fetch(); + } + }); + + }, + + isCheckout: function () { + return this.attributes.checkOutDate; + }, + + getPermalink: function () { + return encodeURI( + window.location.origin + + App.config.contextPath + + '/documents/#' + + this.getWorkspace() + + '/' + + this.getReference() + + '/' + + this.getVersion() + ); + }, + + addTags: function (tags) { + + return $.ajax({ + context: this, + type: 'POST', + url: this.baseUrl() + '/tags', + data: JSON.stringify({tags:tags}), + contentType: 'application/json; charset=utf-8', + success: function () { + this.fetch(); + } + }); + + }, + + removeTag: function (tag, callback) { + $.ajax({ + type: 'DELETE', + url: this.baseUrl() + '/tags/' + tag, + success: function () { + callback(); + } + }); + }, + + removeTags: function (tags, callback) { + var baseUrl = this.baseUrl() + '/tags/'; + var count = 0; + var total = _(tags).length; + _(tags).each(function (tag) { + $.ajax({ + type: 'DELETE', + url: baseUrl + tag, + success: function () { + count++; + if (count >= total) { + callback(); + } + } + }); + }); + + }, + + createNewVersion: function (title, description, workflow, roleMappingList, aclList) { + + var data = { + title: title, + description: description, + workflowModelId: workflow ? workflow.get('id') : null, + roleMapping: workflow ? roleMappingList : null, + acl: aclList + }; + + $.ajax({ + context: this, + type: 'PUT', + url: this.baseUrl() + '/newVersion', + data: JSON.stringify(data), + contentType: 'application/json; charset=utf-8', + success: function () { + this.collection.fetch({reset: true}); + } + }); + }, + + moveInto: function (path, callback, error) { + + var data = { + path: path + }; + + $.ajax({ + context: this, + type: 'PUT', + url: this.baseUrl() + '/move', + data: JSON.stringify(data), + contentType: 'application/json; charset=utf-8', + success: function () { + if (callback) { + callback(); + } + }, + error: function (xhr) { + window.alert(xhr.responseText); + error(); + } + }); + }, + + createShare: function (args) { + $.ajax({ + type: 'POST', + url: this.baseUrl() + '/share', + data: JSON.stringify(args.data), + contentType: 'application/json; charset=utf-8', + success: args.success + }); + }, + + publish: function (args) { + $.ajax({ + type: 'PUT', + url: this.baseUrl() + '/publish', + success: args.success + }); + }, + + unpublish: function (args) { + $.ajax({ + type: 'PUT', + url: this.baseUrl() + '/unpublish', + success: args.success + }); + }, + + updateACL: function (args) { + $.ajax({ + type: 'PUT', + url: this.baseUrl() + '/acl', + data: JSON.stringify(args.acl), + contentType: 'application/json; charset=utf-8', + success: args.success, + error: args.error + }); + }, + + hasACLForCurrentUser: function () { + return this.getACLPermissionForCurrentUser() !== false; + }, + + isForbidden: function () { + return this.getACLPermissionForCurrentUser() === 'FORBIDDEN'; + }, + + isReadOnly: function () { + return this.getACLPermissionForCurrentUser() === 'READ_ONLY'; + }, + + isFullAccess: function () { + return this.getACLPermissionForCurrentUser() === 'FULL_ACCESS'; + }, + + getACLPermissionForCurrentUser: function () { + return ACLChecker.getPermission(this.get('acl')); + }, + + isAttributesLocked: function () { + return this.get('attributesLocked'); + }, + + getUsedByPartList: function (iteration, args) { + $.ajax({ + type: 'GET', + url: this.baseUrl() + '/' + iteration + '/inverse-part-link', + success: args.success, + error: args.error + }); + }, + getUsedByProductInstances: function (iteration, args) { + $.ajax({ + type: 'GET', + url: this.baseUrl() + '/' + iteration + '/inverse-product-instances-link', + success: args.success, + error: args.error + }); + }, + getInversePathDataLinks: function (iteration, args) { + $.ajax({ + type: 'GET', + url: this.baseUrl() + '/' + iteration + '/inverse-path-data-link', + success: args.success, + error: args.error + }); + } + + }); + + return DocumentRevision; + +}); diff --git a/docdoku-web-front/app/js/common-objects/models/document_baseline.js b/docdoku-web-front/app/js/common-objects/models/document_baseline.js new file mode 100644 index 0000000000..68589da7d5 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/document_baseline.js @@ -0,0 +1,52 @@ +/*global $,define,App*/ +define(['common-objects/models/baseline'], function (Baseline) { + 'use strict'; + var DocumentBaseline = Baseline.extend({ + getWorkspaceId: function () { + return this.get('workspaceId'); + }, + setWorkspaceId: function (workspaceId) { + this.set('workspaceId', workspaceId); + }, + + getBaselineDocuments: function (ref, callback) { + var baselinedDocuments = null; + $.getJSON(this.url() + '/documents?q=' + ref) + .success(function (data) { + baselinedDocuments = data; + if (callback && callback.success) { + callback.success(data); + } + }) + .error(function (data) { + if (callback && callback.error) { + callback.error(data); + } + }); + return baselinedDocuments; + }, + + getBaselineFolders: function (ref, callback) { + var baselinedFolders = null; + $.getJSON(this.url() + '/folders?q=' + ref) + .success(function (data) { + baselinedFolders = data; + if (callback && callback.success) { + callback.success(data); + } + }) + .error(function (data) { + if (callback && callback.error) { + callback.error(data); + } + }); + return baselinedFolders; + }, + + getZipUrl: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/document-baselines/export-files?configSpecType=' + encodeURIComponent(this.getId()); + } + }); + + return DocumentBaseline; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/file/attached_file.js b/docdoku-web-front/app/js/common-objects/models/file/attached_file.js new file mode 100644 index 0000000000..c88c9c5fea --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/file/attached_file.js @@ -0,0 +1,48 @@ +/*global define*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var AttachedFile = Backbone.Model.extend({ + idAttribute: 'fullName', + + + + getFullName:function(){ + return this.get('fullName'); + }, + + setFullName:function(name){ + this.set('fullName',name); + }, + + getShortName:function(){ + return this.get('shortName'); + }, + + setShortName:function(name){ + this.set('shortName',name); + }, + + getSubType: function () { + if (this.getFullName().indexOf('nativecad') > 0) { + return 'nativecad'; + } else if (this.getFullName().indexOf('attachedfiles') > 0) { + return 'attachedfiles'; + } else { + return ''; + } + }, + + rewriteUrl:function(){ + var name = this.getShortName(); + var url = this.url; + var index = url.lastIndexOf('/'); + this.url = url.substr(0,index)+'/'+name; + } + + }); + + + + return AttachedFile; + +}); diff --git a/docdoku-web-front/app/js/common-objects/models/language.js b/docdoku-web-front/app/js/common-objects/models/language.js new file mode 100644 index 0000000000..038b0385a5 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/language.js @@ -0,0 +1,15 @@ +/*global $,define,App*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var Language = Backbone.Model.extend({ + initialize: function () { + this.className = 'Language'; + } + }); + + Language.getLanguages = function () { + return $.getJSON(App.config.contextPath + '/api/languages'); + }; + + return Language; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/linked/linked_change_item.js b/docdoku-web-front/app/js/common-objects/models/linked/linked_change_item.js new file mode 100644 index 0000000000..fc2396beec --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/linked/linked_change_item.js @@ -0,0 +1,35 @@ +/*global _,define*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var linkedChangeItems = Backbone.Model.extend({ + + initialize: function () { + _.bindAll(this); + }, + + getId: function () { + return this.get('id'); + }, + + getName: function () { + return this.get('name'); + }, + + getPriority: function () { + return this.get('priority'); + }, + + getCategory: function () { + return this.get('category'); + }, + + getAffectedDocuments: function () { + return this.get('affectedDocuments'); + }, + + getAffectedParts: function () { + return this.get('affectedParts'); + } + }); + return linkedChangeItems; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/linked/linked_document.js b/docdoku-web-front/app/js/common-objects/models/linked/linked_document.js new file mode 100644 index 0000000000..dda3cf9898 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/linked/linked_document.js @@ -0,0 +1,53 @@ +/*global _,define*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var linkedDocument = Backbone.Model.extend({ + initialize: function () { + _.bindAll(this); + }, + + getWorkspace: function () { + return this.get('workspaceId'); + }, + + getReference: function () { + return this.getDocKey() + '-' + this.getIteration(); + }, + + getId: function () { + return this.get('id'); + }, + + getDocumentMasterId: function () { + return this.get('documentMasterId'); + }, + + getTitle: function () { + return this.get('title'); + }, + + getVersion: function () { + return this.get('version'); + }, + + getDocKey: function () { + return this.getDocumentMasterId() + '-' + this.getVersion(); + }, + + getDisplayDocKey: function () { + if (this.getTitle()) { + return this.getTitle() + ' < ' + this.getDocumentMasterId() + '-' + this.getVersion() + ' >'; + } + return '< ' + this.getDocumentMasterId() + '-' + this.getVersion() + ' >'; + }, + + setDocumentLinkComment:function(comment){ + this.set('commentLink', comment); + }, + + getDocumentLinkComment:function(){ + return this.get('commentLink'); + } + }); + return linkedDocument; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/linked/linked_document_iteration.js b/docdoku-web-front/app/js/common-objects/models/linked/linked_document_iteration.js new file mode 100644 index 0000000000..120af853dc --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/linked/linked_document_iteration.js @@ -0,0 +1,50 @@ +/*global _,define*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var linkedDocumentIteration = Backbone.Model.extend({ + initialize: function () { + _.bindAll(this); + }, + + getWorkspace: function () { + return this.get('workspaceId'); + }, + + getId: function () { + return this.get('id'); + }, + + getDocumentMasterId: function () { + return this.get('documentMasterId'); + }, + + getTitle: function () { + return this.get('title'); + }, + + getVersion: function () { + return this.get('version'); + }, + + getIteration: function () { + return this.get('iteration'); + }, + + getDocumentLinkComment:function(){ + return this.get('commentLink'); + }, + + getDocKey: function () { + return this.getDocumentMasterId() + '-' + this.getVersion() + '-' + this.getIteration(); + }, + + getDisplayDocKey: function () { + if (this.getTitle()) { + return this.getTitle() + ' < ' + this.getDocumentMasterId() + '-' + this.getVersion() + '-' + this.getIteration() + ' >'; + } + return '< ' + this.getDocumentMasterId() + '-' + this.getVersion() + '-' + this.getIteration() + ' >'; + } + + }); + return linkedDocumentIteration; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/linked/linked_part.js b/docdoku-web-front/app/js/common-objects/models/linked/linked_part.js new file mode 100644 index 0000000000..0c1a4857ec --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/linked/linked_part.js @@ -0,0 +1,63 @@ +/*global _,define,App*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var linkedPart = Backbone.Model.extend({ + + initialize: function () { + _.bindAll(this); + }, + + getWorkspace: function () { + return this.get('workspaceId'); + }, + + getReference: function () { + return this.getPartKey() + '-' + this.getIteration(); + }, + + getId: function () { + return this.get('id'); + }, + + getIteration: function () { + return this.get('iteration'); + }, + + getNumber: function () { + return this.get('number'); + }, + + getName: function () { + return this.get('name'); + }, + + getVersion: function () { + return this.get('version'); + }, + + getPartKey: function () { + return this.getNumber() + '-' + this.getVersion(); + }, + + getDisplayPartKey: function () { + if (this.getName()) { + return this.getName() + ' < ' + this.getNumber() + '-' + this.getVersion() + ' >'; + } + return '< ' + this.getNumber() + '-' + this.getVersion() + ' >'; + }, + + getPartMasterPermalink: function () { + return encodeURI( + window.location.origin + + App.config.contextPath + + '/parts/#' + + this.getWorkspace() + + '/' + + this.getNumber() + + '/' + + this.getVersion() + ); + } + }); + return linkedPart; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/lov/lov.js b/docdoku-web-front/app/js/common-objects/models/lov/lov.js new file mode 100644 index 0000000000..054c4f8c0a --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/lov/lov.js @@ -0,0 +1,63 @@ +/*global _,define,App*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var LOVModel = Backbone.Model.extend({ + + idAttribute:'id', + + initialize: function () { + _.bindAll(this); + }, + + getLOVId:function(){ + return this.get('id'); + }, + + setLOVId:function(){ + this.set('id', this.getLOVName()); + }, + + getLOVName:function(){ + return this.get('name'); + }, + + setLOVName:function(newName){ + this.set('name', newName); + }, + + getLOVValues:function(){ + return this.get('values'); + }, + + getNumberOfValue:function(){ + return this.get('values').length; + }, + + getWorkspaceId:function(){ + return this.get('workspaceId'); + }, + + isDeletable:function(){ + return this.get('deletable'); + }, + /* + Override the isNew function of backbone to send the good request POST for new and PUT for update + */ + /*setNew:function(isNew){ + this.isNew = function(){ + return isNew; + }; + },*/ + + url:function(){ + var endUrl = ''; + if(this.getLOVName()){ + endUrl = this.isNew()?'':'/'+ encodeURIComponent(this.getLOVName()); + } + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/lov'+ endUrl; + } + + }); + + return LOVModel; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/modification_notification.js b/docdoku-web-front/app/js/common-objects/models/modification_notification.js new file mode 100644 index 0000000000..13642d7cdd --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/modification_notification.js @@ -0,0 +1,131 @@ +/*global _,define,App,$*/ +define([ + 'backbone', + 'common-objects/utils/date' +], function (Backbone, date) { + 'use strict'; + var ModificationNotification = Backbone.Model.extend({ + + idAttribute: 'id', + + initialize: function () { + _.bindAll(this); + this.className = 'ModificationNotification'; + }, + + getId: function () { + return this.get('id'); + }, + + getImpactedPartNumber: function () { + return this.get('impactedPartNumber'); + }, + + getImpactedPartVersion: function () { + return this.get('impactedPartVersion'); + }, + + getModifiedPartNumber: function () { + return this.get('modifiedPartNumber'); + }, + + getModifiedPartVersion: function () { + return this.get('modifiedPartVersion'); + }, + + getModifiedPartIteration: function () { + return this.get('modifiedPartIteration'); + }, + + getModifiedPartName: function () { + return this.get('modifiedPartName'); + }, + + hasCheckInDate: function () { + return !_.isNull(this.get('checkInDate')); + }, + + getCheckInDate: function () { + return this.get('checkInDate'); + }, + + getFormattedCheckInDate: function () { + return date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getCheckInDate() + ); + }, + + hasAuthor: function () { + return !_.isNull(this.get('author')); + }, + + getAuthor: function () { + return this.get('author'); + }, + + getAuthorName: function () { + return this.getAuthor().name; + }, + + getIterationNote: function () { + return this.get('iterationNote'); + }, + + isAcknowledged: function () { + return this.get('acknowledged'); + }, + + getAckComment: function () { + return this.get('ackComment'); + }, + + getAckAuthor: function () { + return this.get('ackAuthor'); + }, + + getAckAuthorName: function () { + if (this.getAckAuthor()) { + return this.getAckAuthor().name; + } else { + return App.config.userName; + } + }, + + getAckDate: function () { + return this.get('ackDate'); + }, + + getFormattedAckDate: function () { + return date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getAckDate() + ); + }, + + setAcknowledged: function (data) { + return $.ajax({ + context: this, + type: 'PUT', + url: this.collection.url() + this.getId(), + data: JSON.stringify(data), + contentType: 'application/json; charset=utf-8', + error: function (xhr) { + window.alert(xhr.responseText); + this.set('acknowledged', false); + } + }).success(function () { + this.set('ackComment', data.ackComment); + var now = new Date(); + var nowUtc = new Date(now.getUTCFullYear(),now.getUTCMonth(), now.getUTCDate() , + now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds()); + this.set('ackDate', (nowUtc).toString()); + this.set('acknowledged', true); + }); + } + + }); + + return ModificationNotification; + +}); diff --git a/docdoku-web-front/app/js/common-objects/models/part.js b/docdoku-web-front/app/js/common-objects/models/part.js new file mode 100644 index 0000000000..1c27ced25f --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/part.js @@ -0,0 +1,544 @@ +/*global _,$,define,App,window*/ +define([ + 'backbone', + 'common-objects/utils/date', + 'common-objects/collections/part_iteration_collection', + 'common-objects/collections/modification_notification_collection', + 'common-objects/utils/acl-checker', + 'common-objects/views/alert' + ], + function (Backbone, Date, PartIterationList, ModificationNotificationCollection, ACLChecker, AlertView) { + 'use strict'; + + var Part = Backbone.Model.extend({ + idAttribute: 'partKey', + + initialize: function () { + _.bindAll(this); + }, + + parse: function (data) { + this.iterations = new PartIterationList(data.partIterations); + this.iterations.setPart(this); + delete data.partIterations; + delete data.partList; + this.modificationNotifications = new ModificationNotificationCollection(data.notifications); + delete data.notifications; + return data; + }, + + init: function (number, version) { + this.set('number', number); + this.set('version', version); + return this; + }, + + getNumber: function () { + return this.get('number'); + }, + + getType: function () { + return this.get('type'); + }, + + getTags: function () { + return this.get('tags'); + }, + getName: function () { + return this.get('name'); + }, + + getVersion: function () { + return this.get('version'); + }, + + getDescription: function () { + return this.get('description'); + }, + + getPartKey: function () { + return this.get('partKey'); + }, + + getWorkspace: function () { + return this.get('workspaceId'); + }, + + getCheckoutUser: function () { + return this.get('checkOutUser'); + }, + + isReleased: function () { + return this.get('status') === 'RELEASED'; + }, + + isObsolete : function(){ + return this.get('status') === 'OBSOLETE'; + }, + + getObsoleteDate: function() { + return Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.get('obsoleteDate') + ); + }, + + getObsoleteAuthor: function() { + return this.get('obsoleteAuthor'); + }, + + getObsoleteAuthorLogin: function () { + if (this.isObsolete()) { + return this.getObsoleteAuthor().login; + } + return null; + }, + + getObsoleteAuthorName: function () { + if (this.isObsolete()) { + return this.getObsoleteAuthor().name; + } + return null; + }, + + getReleaseDate: function() { + return Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.get('releaseDate') + ); + }, + + getReleaseAuthor: function() { + return this.get('releaseAuthor'); + }, + + getReleaseAuthorLogin: function () { + if (this.getReleaseAuthor()) { + return this.getReleaseAuthor().login; + } + return null; + }, + + getReleaseAuthorName: function () { + if (this.getReleaseAuthor()) { + return this.getReleaseAuthor().name; + } + return null; + }, + + getFormattedCheckoutDate: function () { + return Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getCheckoutDate() + ); + }, + + getFormattedCreationDate: function () { + return Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getCreationDate() + ); + }, + + getFormattedModificationDate: function () { + return Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getModificationDate() + ); + }, + + getCheckoutDate: function () { + return this.get('checkOutDate'); + }, + + getCreationDate: function () { + return this.get('creationDate'); + }, + + getModificationDate: function () { + var lastIteration = this.getLastIteration(); + if (lastIteration) { + return lastIteration.get('modificationDate'); + } + return null; + }, + + getRevisionDate: function () { + var lastIteration = this.getLastIteration(); + if (this.isCheckout()) { + return lastIteration.get('creationDate'); + } else { + return lastIteration.get('checkInDate'); + } + }, + + + isCheckoutByConnectedUser: function () { + return this.isCheckout() ? this.getCheckOutUserLogin() === App.config.login : false; + }, + + isLocked:function(){ + return this.isCheckout() && !this.isCheckoutByConnectedUser(); + }, + getUrl: function () { + return this.url(); + }, + + hasIterations: function () { + return !this.getIterations().isEmpty(); + }, + + getLastIteration: function () { + return this.getIterations().last(); + }, + + isLastIteration: function (iterationNumber) { + // return TRUE if the iteration is the very last (check or uncheck) + return this.get('lastIterationNumber') === iterationNumber; + }, + + isLastIterationAssembly: function () { + if (this.hasIterations()) { + return this.getLastIteration().isAssembly(); + } + return false; + }, + + hasLastIterationAttachedFiles: function () { + if (this.hasIterations()) { + return this.getLastIteration().getAttachedFiles().length > 0 || this.getLastIteration().get('nativeCADFile'); + } + return false; + }, + + getIterations: function () { + return this.iterations; + }, + + hasModificationNotifications: function () { + return this.modificationNotifications && this.modificationNotifications.models.length; + }, + + hasUnreadModificationNotifications: function () { + return _.select(this.modificationNotifications.models || [], function(notif) { + return !notif.isAcknowledged(); + }).length; + }, + + getModificationNotifications: function () { + return this.modificationNotifications; + }, + + getAuthorLogin: function () { + return this.get('author').login; + }, + + getAuthorName: function () { + return this.get('author').name; + }, + + getAuthor: function () { + return this.get('author').name; + }, + + getCheckOutUserName: function () { + if (this.isCheckout()) { + return this.getCheckoutUser().name; + } + return null; + }, + + getCheckOutUserLogin: function () { + if (this.isCheckout()) { + return this.getCheckoutUser().login; + } + return null; + }, + + isStandardPart: function () { + return this.get('standardPart') ? 1 : 0; + }, + + isStandardPartReadable: function () { + return this.get('standardPart') ? App.config.i18n.TRUE : App.config.i18n.FALSE; + }, + + getLifeCycleState: function () { + return this.get('lifeCycleState'); + }, + + isAttributesLocked: function () { + return this.get('attributesLocked'); + }, + + getDisplayKey: function () { + if (this.getName()) { + return this.getName() + ' < ' + this.getNumber() + '-' + this.getVersion() + ' >'; + } + return '< ' + this.getNumber() + '-' + this.getVersion() + ' >'; + }, + + getUsedByProductInstances: function (args) { + $.ajax({ + type: 'GET', + url: this.getUrl() + '/used-by-product-instance-masters', + success: args.success, + error: args.error + }); + }, + + getUsedByPartsAsComponent: function (args) { + $.ajax({ + type: 'GET', + url: this.getUrl() + '/used-by-as-component', + success: args.success, + error: args.error + }); + }, + + getUsedByPartsAsSubstitute: function (args) { + $.ajax({ + type: 'GET', + url: this.getUrl() + '/used-by-as-substitute', + success: args.success, + error: args.error + }); + }, + + addTags: function (tags) { + + return $.ajax({ + context: this, + type: 'POST', + url: this.url() + '/tags', + data: JSON.stringify({tags:tags}), + contentType: 'application/json; charset=utf-8', + success: function () { + this.fetch(); + }, + error: function () { + this.onError(); + } + }); + + }, + + removeTag: function (tag, callback) { + $.ajax({ + type: 'DELETE', + url: this.url() + '/tags/' + tag, + success: function () { + callback(); + }, + error: function () { + this.onError(); + } + }); + }, + + removeTags: function (tags, callback) { + var baseUrl = this.url() + '/tags/'; + var count = 0; + var total = _(tags).length; + _(tags).each(function (tag) { + $.ajax({ + type: 'DELETE', + url: baseUrl + tag, + success: function () { + count++; + if (count >= total) { + callback(); + } + }, + error: function () { + this.onError(); + } + + }); + }); + + }, + + + checkout: function () { + return $.ajax({ + context: this, + type: 'PUT', + url: this.url() + '/checkout', + success:function(){ + this.fetch(); + Backbone.Events.trigger('part:iterationChange'); + }, + error: function (xhr) { + window.alert(xhr.responseText); + } + }); + }, + + undocheckout: function () { + return $.ajax({ + context: this, + type: 'PUT', + url: this.url() + '/undocheckout', + success:function(){ + this.fetch(); + Backbone.Events.trigger('part:iterationChange'); + }, + error: function (xhr) { + window.alert(xhr.responseText); + } + }); + }, + + checkin: function () { + return $.ajax({ + context: this, + type: 'PUT', + url: this.url() + '/checkin', + error: function (xhr) { + window.alert(xhr.responseText); + } + }); + }, + + isCheckout: function () { + return !_.isNull(this.get('checkOutDate')); + }, + + getPermalink: function () { + return encodeURI( + window.location.origin + + App.config.contextPath + + '/parts/#' + + this.getWorkspace() + + '/' + + this.getNumber() + + '/' + + this.getVersion() + ); + }, + + createShare: function (args) { + $.ajax({ + type: 'POST', + url: this.url() + '/share', + data: JSON.stringify(args.data), + contentType: 'application/json; charset=utf-8', + success: args.success + }); + }, + + publish: function (args) { + $.ajax({ + type: 'PUT', + url: this.url() + '/publish', + success: args.success + }); + }, + + unpublish: function (args) { + $.ajax({ + type: 'PUT', + url: this.url() + '/unpublish', + success: args.success + }); + }, + + updateACL: function (args) { + $.ajax({ + type: 'PUT', + url: this.url() + '/acl', + data: JSON.stringify(args.acl), + contentType: 'application/json; charset=utf-8', + success: args.success, + error: args.error + }); + }, + + hasACLForCurrentUser: function () { + return this.getACLPermissionForCurrentUser() !== false; + }, + + isForbidden: function () { + return this.getACLPermissionForCurrentUser() === 'FORBIDDEN'; + }, + + isReadOnly: function () { + return this.getACLPermissionForCurrentUser() === 'READ_ONLY'; + }, + + isFullAccess: function () { + return this.getACLPermissionForCurrentUser() === 'FULL_ACCESS'; + }, + + getACLPermissionForCurrentUser: function () { + return ACLChecker.getPermission(this.get('acl')); + }, + + createNewVersion: function (description, workflow, roleMappingList, aclList, onSuccess, onError) { + + var data = { + description: description, + workflowModelId: workflow ? workflow.get('id') : null, + roleMapping: workflow ? roleMappingList : null, + acl: aclList + }; + + $.ajax({ + context: this, + type: 'PUT', + url: this.url() + '/newVersion', + data: JSON.stringify(data), + contentType: 'application/json; charset=utf-8', + success: function () { + this.collection.fetch({reset: true, sucess: onSuccess}); + }, + error: onError + }); + }, + release: function () { + return $.ajax({ + context: this, + type: 'PUT', + url: this.url() + '/release', + success: function () { + this.fetch(); + } + }); + }, + markAsObsolete: function () { + return $.ajax({ + context: this, + type: 'PUT', + url: this.url() + '/obsolete', + success: function () { + this.fetch(); + } + }); + }, + + getVisualizationUrl:function(){ + return App.config.contextPath + '/visualization/#assembly/' + App.config.workspaceId + '/' + this.getPartKey() +'/0/0/0'; + }, + + url: function () { + if (this.getPartKey()) { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/' + this.getPartKey(); + } + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/'; + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + + this.$el.find('.notifications').first().append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + } + + }); + + return Part; + + }); diff --git a/docdoku-web-front/app/js/common-objects/models/part_iteration.js b/docdoku-web-front/app/js/common-objects/models/part_iteration.js new file mode 100644 index 0000000000..c03dc8a245 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/part_iteration.js @@ -0,0 +1,144 @@ +/*global _,define,App,$*/ +define([ + 'backbone', + 'common-objects/utils/date', + 'common-objects/collections/attribute_collection', + 'common-objects/collections/file/attached_file_collection' +], function (Backbone, date, AttributeCollection, AttachedFileCollection) { + 'use strict'; + var PartIteration = Backbone.Model.extend({ + + idAttribute: 'iteration', + + initialize: function () { + + this.className = 'PartIteration'; + + var attributes = new AttributeCollection(this.get('instanceAttributes')); + this.set('instanceAttributes', attributes); + this.resetNativeCADFile(); + + var filesMapping = _.map(this.get('attachedFiles'), function (fullName) { + return { + 'fullName': fullName, + shortName: _.last(fullName.split('/')), + created: true + }; + }); + + var attachedFiles = new AttachedFileCollection(filesMapping); + + this.set('attachedFiles', attachedFiles); + + }, + + resetNativeCADFile: function () { + var nativeCADFullName = this.get('nativeCADFile'); + if (nativeCADFullName) { + var nativeCad = { + fullName: nativeCADFullName, + shortName: _.last(nativeCADFullName.split('/')), + created: true + }; + this._nativeCADFile = new AttachedFileCollection(nativeCad); + } else { + this._nativeCADFile = new AttachedFileCollection(); + } + }, + + + defaults: { + instanceAttributes: [] + }, + + getAttributes: function () { + return this.get('instanceAttributes'); + }, + + getAttributeTemplates: function () { + return this.get('instanceAttributeTemplates'); + }, + + getWorkspace: function () { + return this.get('workspaceId'); + }, + + getReference: function () { + return this.getPartKey() + '-' + this.getIteration(); + }, + + getIteration: function () { + return this.get('iteration'); + }, + + getPartKey: function () { + return this.get('number') + '-' + this.get('version'); + }, + + getAttachedFiles: function () { + return this.get('attachedFiles'); + }, + + getBaseName: function (subType) { + return this.getWorkspace() + '/parts/' + this.getNumber() + '/' + this.getVersion() + '/' + this.get('iteration') + '/' + subType; + }, + + getNumber: function () { + return this.collection.part.getNumber(); + }, + + getVersion: function () { + return this.collection.part.getVersion(); + }, + + getComponents: function () { + return this.get('components'); + }, + + isAssembly: function () { + var components = this.getComponents(); + return components && components.length > 0; + }, + + + getLinkedDocuments: function () { + return this.get('linkedDocuments'); + }, + + setLinkedDocuments: function (linkedDocuments) { + this.set('linkedDocuments', linkedDocuments); + }, + + getLifeCycleState: function () { + return this.get('lifeCycleState'); + }, + + getConversionUrl:function(){ + return App.config.contextPath + + '/api/workspaces/' + this.getWorkspace() + + '/parts/' + this.getNumber() + '-' + this.getVersion() + + '/iterations/' + this.get('iteration') + + '/conversion'; + }, + + getConversionStatus:function(){ + return $.get(this.getConversionUrl()); + }, + + launchConversion:function(){ + return $.ajax({method:'PUT',url:this.getConversionUrl()}); + }, + + getAttachedFilesUploadBaseUrl: function () { + return App.config.contextPath + '/api/files/' + this.getWorkspace() + '/parts/' + this.getNumber() + '/' + this.getVersion() + '/' + this.get('iteration') + '/attachedfiles/'; + }, + + getNativeCadFileUploadBaseUrl: function () { + return App.config.contextPath + '/api/files/' + this.getWorkspace() + '/parts/' + this.getNumber() + '/' + this.getVersion() + '/' + this.get('iteration') + '/nativecad/'; + } + + }); + + return PartIteration; + +}); diff --git a/docdoku-web-front/app/js/common-objects/models/path_data_iteration.js b/docdoku-web-front/app/js/common-objects/models/path_data_iteration.js new file mode 100644 index 0000000000..1cf9edd0c9 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/path_data_iteration.js @@ -0,0 +1,104 @@ +/*global define,_,App*/ +define(['backbone'], function (Backbone) { + + 'use strict'; + + var PathDataIteration = Backbone.Model.extend({ + + + initialize: function () { + _.bindAll(this); + }, + + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + App.config.productId + '/product-instances/' + this.getSerialNumber() + '/pathdata/' + this.getId() + '/' + this.getIteration(); + }, + + getId: function () { + return this.get('id'); + }, + + setId: function (id) { + this.set('id', id); + }, + + getDocumentLinked: function () { + return this.get('linkedDocuments'); + }, + + setDocumentLinked: function (linkedDocuments) { + this.set('linkedDocuments', linkedDocuments); + }, + + getInstanceAttributes: function () { + return this.get('instanceAttributes'); + }, + + setInstanceAttributes: function (attributes) { + this.set('instanceAttributes', attributes); + }, + + getPath: function () { + return this.get('path'); + }, + + setPath: function (path) { + this.set('path', path); + }, + + setSerialNumber: function (serialNumber) { + this.set('serialNumber', serialNumber); + }, + + getSerialNumber: function () { + return this.get('serialNumber'); + }, + + getPartLinks: function () { + return this.get('partLinksList').partLinks; + }, + + getIterationNote: function () { + return this.get('iterationNote'); + }, + + setIterationNote: function (iterationNote) { + this.set('iterationNote', iterationNote); + }, + + setIteration: function (iteration) { + this.set('iteration', iteration); + }, + + getIteration: function () { + return this.get('iteration'); + }, + + getAttachedFiles: function () { + return this.get('attachedFiles'); + }, + + setAttachedFiles: function (attachedFiles) { + this.set('attachedFiles', attachedFiles); + }, + + isLastIteration: function (iterationNumber) { + // return TRUE if the iteration is the very last (check or uncheck) + return this.get('lastIterationNumber') === iterationNumber; + }, + + getUploadBaseUrl: function () { + return App.config.contextPath + '/api/files/' + App.config.workspaceId + '/product-instances/' + this.getSerialNumber() + '/' + App.config.productId + '/pathdata/' + this.getId() + '/iterations/' + this.getIteration() + '/'; + }, + + getDeleteBaseUrl: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + App.config.productId + '/product-instances/' + this.getSerialNumber() + '/pathdata/' + this.getId() + '/iterations/' + this.getIteration(); + } + + }); + + return PathDataIteration; + +}); + + diff --git a/docdoku-web-front/app/js/common-objects/models/product_baseline.js b/docdoku-web-front/app/js/common-objects/models/product_baseline.js new file mode 100644 index 0000000000..5c8bb82bce --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/product_baseline.js @@ -0,0 +1,75 @@ +/*global define,App*/ + +define(['common-objects/models/baseline'], +function(Baseline){ + 'use strict'; + var ProductBaseline = Baseline.extend({ + getBaselinedParts:function(){ + return this.get('baselinedParts'); + }, + setBaselinedParts:function(baselinedParts){ + return this.set('baselinedParts',baselinedParts); + }, + getConfigurationItemId: function(){ + return this.get('configurationItemId'); + }, + setConfigurationItemId: function(configurationItemId){ + this.set('configurationItemId',configurationItemId); + }, + getSubstituteLinks:function(){ + return this.get('substituteLinks'); + }, + setSubstituteLinks:function(substituteLinks){ + this.set('substituteLinks',substituteLinks); + }, + getOptionalUsageLinks:function(){ + return this.get('optionalUsageLinks'); + }, + setOptionalUsageLinks:function(optionalUsageLinks){ + this.set('optionalUsageLinks',optionalUsageLinks); + }, + + getBomUrl: function () { + return App.config.contextPath + '/product-structure/#' + App.config.workspaceId + '/' + encodeURIComponent(this.getConfigurationItemId()) + '/config-spec/'+this.getId()+'/bom' ; + }, + + getSceneUrl: function () { + return App.config.contextPath + '/product-structure/#' + App.config.workspaceId + '/' + encodeURIComponent(this.getConfigurationItemId()) + '/config-spec/'+this.getId()+'/scene' ; + }, + + getZipUrl: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + encodeURIComponent(this.getConfigurationItemId()) + '/export-files?configSpecType=' + encodeURIComponent(this.getId()); + }, + + getSubstitutesParts:function(){ + //can be null, and used as an array. + return this.get('substitutesParts'); + }, + + getOptionalsParts:function(){ + //can be null, and used as an array. + return this.get('optionalsParts'); + }, + + hasObsoletePartRevisions:function(){ + return this.get('hasObsoletePartRevisions'); + }, + + hasPathToPathLink: function() { + return this.getPathToPathLinks().length; + }, + getPathToPathLinks: function () { + return this.get('pathToPathLinks'); + }, + + getAuthor:function(){ + return this.get('author').name; + }, + + getAuthorLogin: function () { + return this.get('author').login; + } + }); + + return ProductBaseline; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/product_instance.js b/docdoku-web-front/app/js/common-objects/models/product_instance.js new file mode 100644 index 0000000000..adc153b325 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/product_instance.js @@ -0,0 +1,150 @@ +/*global $,_,define,App*/ +define(['backbone', + 'common-objects/collections/product_instance_iterations', + 'common-objects/utils/acl-checker', + 'common-objects/utils/date' +], function (Backbone, ProductInstanceList,ACLChecker,date) { + 'use strict'; + var ProductInstance = Backbone.Model.extend({ + + initialize: function () { + _.bindAll(this); + }, + + idAttribute: 'identifier', + + url:function(){ + if(this.get('identifier')){ + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.getConfigurationItemId() + '/product-instances/' + this.getSerialNumber(); + } + return this.urlRoot(); + }, + + urlRoot: function () { + if (this.getConfigurationItemId()) { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.getConfigurationItemId() + '/product-instances'; + } else { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/product-instances'; + } + }, + + parse: function (data) { + if (data) { + this.iterations = new ProductInstanceList(data.productInstanceIterations); + this.iterations.setProductInstanceMaster(this); + delete data.productInstanceIterations; + return data; + } + }, + getACL:function(){ + return this.get('acl'); + }, + getSerialNumber: function () { + return this.get('serialNumber'); + }, + getConfigurationItemId: function () { + return this.get('configurationItemId'); + }, + setConfigurationItemId: function (configurationItemId) { + this.set('configurationItemId', configurationItemId); + }, + getIterations: function () { + return this.iterations; + }, + getNbIterations: function () { + return this.getIterations().length; + }, + getLastIteration: function () { + return this.getIterations().last(); + }, + hasIterations: function () { + return !this.getIterations().isEmpty(); + }, + getUpdateAuthor: function () { + return this.getLastIteration().getUpdateAuthor(); + }, + getUpdateAuthorName: function () { + return this.getLastIteration().getUpdateAuthorName(); + }, + getCreationDate: function () { + return this.getIterations().at(0).getCreationDate(); + }, + getFormattedCreationDate: function () { + return date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getCreationDate() + ); + }, + getInstanceAttributes: function(){ + return this.get('instanceAttributes'); + }, + getModificationDate: function () { + return date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getLastIteration().getModificationDate() + ); + }, + updateACL: function (args) { + $.ajax({ + type: 'PUT', + url: this.url()+'/acl', + data: JSON.stringify(args.acl), + contentType: 'application/json; charset=utf-8', + success: args.success, + error: args.error + }); + }, + + getBomUrl: function () { + return App.config.contextPath + '/product-structure/#' + App.config.workspaceId + '/' + encodeURIComponent(this.getConfigurationItemId()) + '/config-spec/pi-'+encodeURIComponent(this.getSerialNumber())+'/bom' ; + }, + + getSceneUrl: function () { + return App.config.contextPath + '/product-structure/#' + App.config.workspaceId + '/' + encodeURIComponent(this.getConfigurationItemId()) + '/config-spec/pi-'+encodeURIComponent(this.getSerialNumber())+'/scene' ; + }, + + getZipUrl:function (){ + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + encodeURIComponent(this.getConfigurationItemId()) + '/export-files?configSpecType=pi-' + encodeURIComponent(this.getSerialNumber()); + }, + + hasPathToPathLink: function () { + return this.getPathToPathLinks().length; + }, + + getPathToPathLinks: function () { + //PathToPathLinks of a product_instance reference the pathToPathLinks of the last iteration + return this.getLastIteration().getPathToPathLinks(); + }, + + hasPathDataInLastIteration: function () { + return this.getLastIteration().getPathData().length; + }, + + hasAttachedFilesInLastIteration: function () { + return this.getLastIteration().getAttachedFiles().length; + }, + + hasACLForCurrentUser: function () { + return this.getACLPermissionForCurrentUser() !== false; + }, + + isForbidden: function () { + return this.getACLPermissionForCurrentUser() === 'FORBIDDEN'; + }, + + isReadOnly: function () { + return this.getACLPermissionForCurrentUser() === 'READ_ONLY'; + }, + + isFullAccess: function () { + return this.getACLPermissionForCurrentUser() === 'FULL_ACCESS'; + }, + + getACLPermissionForCurrentUser: function () { + return ACLChecker.getPermission(this.getACL()); + } + + }); + + return ProductInstance; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/product_instance_iteration.js b/docdoku-web-front/app/js/common-objects/models/product_instance_iteration.js new file mode 100644 index 0000000000..37360fe07a --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/product_instance_iteration.js @@ -0,0 +1,124 @@ +/*global _,define,App*/ +define(['backbone' +], function (Backbone) { + 'use strict'; + var ProductInstanceIteration = Backbone.Model.extend({ + idAttribute: 'iteration', + + initialize: function () { + this.className = 'ProductInstanceIteration'; + _.bindAll(this); + }, + defaults: { + attachedFiles: [], + instanceAttributes: [] + }, + + urlRoot: function () { + if (this.getConfigurationItemId) { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.getConfigurationItemId() + + '/product-instances/' + this.getSerialNumber() + '/iterations/'; + } else { + return this.prototype.urlRoot(); + } + }, + getUploadBaseUrl: function () { + return App.config.contextPath + '/api/files/' + this.getBaseName(); + + }, + getBaseName: function () { + return App.config.workspaceId + '/product-instances/' + this.getSerialNumber() + '/' + this.getConfigurationItemId() + '/iterations/' + this.getIteration() + '/'; + }, + getSerialNumber: function () { + return this.get('serialNumber'); + }, + getIteration: function () { + return this.get('iteration'); + }, + setIteration: function (iteration) { + return this.set('iteration', iteration); + }, + getIterationNote: function () { + return this.get('iterationNote'); + }, + setIterationNote: function (iterationNote) { + this.set('iterationNote', iterationNote); + }, + getConfigurationItemId: function () { + return this.get('configurationItemId'); + }, + getBasedOnName: function () { + return this.get('basedOn').name; + }, + getBasedOnId: function () { + return this.get('basedOn').id; + }, + getUpdateAuthor: function () { + return this.get('updateAuthor'); + }, + getUpdateAuthorName: function () { + return this.get('updateAuthorName'); + }, + getCreationDate: function() { + return this.get('creationDate'); + }, + getModificationDate: function () { + return this.get('modificationDate'); + }, + getBaselinedParts: function () { + return this.get('baselinedParts'); + }, + getACL: function () { + return this.get('acl'); + }, + getInstanceAttributes: function () { + return this.get('instanceAttributes'); + }, + getlinkedDocuments: function () { + return this.get('linkedDocuments'); + }, + getPathToPathLinks: function () { + return this.get('pathToPathLinks'); + }, + + hasPathData: function () { + return this.getPathData().length; + }, + + getPathData: function () { + return this.get('pathDataMasterList'); + }, + + getPathDataPaths: function () { + return this.get('pathDataPaths'); + }, + + getAttachedFiles: function () { + return this.get('attachedFiles'); + }, + + setInstanceAttributes: function (instanceAttributes) { + return this.set('instanceAttributes', instanceAttributes); + }, + + setBaselinedParts: function (baselinedParts) { + this.set('baselinedParts', baselinedParts); + }, + setConfigurationItemId: function (configurationItemId) { + this.set('configurationItemId', configurationItemId); + }, + + getSubstitutesParts: function () { + return this.get('substitutesParts'); + }, + getOptionalsParts: function () { + return this.get('optionalsParts'); + }, + + setLinkedDocuments: function (linkedDocuments) { + this.set('linkedDocuments', linkedDocuments); + } + }); + + return ProductInstanceIteration; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/role.js b/docdoku-web-front/app/js/common-objects/models/role.js new file mode 100644 index 0000000000..74e9d67261 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/role.js @@ -0,0 +1,32 @@ +/*global _,define*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var Role = Backbone.Model.extend({ + + initialize: function () { + this.className = 'Role'; + _.bindAll(this); + }, + + getName: function () { + return this.get('name'); + }, + + getDefaultAssignedUsers: function () { + return this.get('defaultAssignedUsers'); + }, + + setDefaultAssignedUsers: function (users) { + return this.set('defaultAssignedUsers',users); + }, + + getDefaultAssignedGroups: function () { + return this.get('defaultAssignedGroups'); + }, + + setDefaultAssignedGroups: function (groups) { + return this.set('defaultAssignedGroups',groups); + } + }); + return Role; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/security/acl_user_entry.js b/docdoku-web-front/app/js/common-objects/models/security/acl_user_entry.js new file mode 100644 index 0000000000..1d3a4850be --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/security/acl_user_entry.js @@ -0,0 +1,35 @@ +/*global _,define*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var UserAclEntry = Backbone.Model.extend({ + + initialize: function () { + _.bindAll(this); + }, + + key: function () { + return this.get('userLogin'); + }, + + isForbidden: function () { + return this.getPermission() === 'FORBIDDEN'; + }, + isReadOnly: function () { + return this.getPermission() === 'READ_ONLY'; + }, + isFullAccess: function () { + return this.getPermission() === 'FULL_ACCESS'; + }, + + setPermission: function (permission) { + this.set('permission', permission); + }, + + getPermission: function () { + return this.get('permission'); + } + + }); + + return UserAclEntry; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/models/security/acl_user_group_entry.js b/docdoku-web-front/app/js/common-objects/models/security/acl_user_group_entry.js new file mode 100644 index 0000000000..3d7ce7e18c --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/security/acl_user_group_entry.js @@ -0,0 +1,35 @@ +/*global _,define*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var UserAclEntry = Backbone.Model.extend({ + + initialize: function () { + _.bindAll(this); + }, + + key: function () { + return this.get('groupId'); + }, + + isForbidden: function () { + return this.getPermission() === 'FORBIDDEN'; + }, + isReadOnly: function () { + return this.getPermission() === 'READ_ONLY'; + }, + isFullAccess: function () { + return this.getPermission() === 'FULL_ACCESS'; + }, + + setPermission: function (permission) { + this.set('permission', permission); + }, + + getPermission: function () { + return this.get('permission'); + } + + }); + + return UserAclEntry; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/models/security/admin.js b/docdoku-web-front/app/js/common-objects/models/security/admin.js new file mode 100644 index 0000000000..d5f95f74b1 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/security/admin.js @@ -0,0 +1,17 @@ +/*global define,App*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var Admin = Backbone.Model.extend({ + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/users/admin'; + }, + + getLogin: function () { + return this.get('login'); + } + + }); + + return Admin; + +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/models/security/workspace_user_group_membership.js b/docdoku-web-front/app/js/common-objects/models/security/workspace_user_group_membership.js new file mode 100644 index 0000000000..181cda6d53 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/security/workspace_user_group_membership.js @@ -0,0 +1,43 @@ +/*global _,define*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var WorkspaceUserGroupMembership = Backbone.Model.extend({ + + initialize: function () { + var permission = this.isReadOnly() ? 'READ_ONLY' : 'FULL_ACCESS'; + this.setPermission(permission); + _.bindAll(this); + }, + + key: function () { + return this.getGroupId(); + }, + + name: function () { + return this.key(); + }, + + getWorkspaceId: function () { + return this.get('workspaceId'); + }, + + getGroupId: function () { + return this.get('memberId'); + }, + + isReadOnly: function () { + return this.get('readOnly'); + }, + + setPermission: function (permission) { + this.set('permission', permission); + }, + + getPermission: function () { + return this.get('permission'); + } + + }); + + return WorkspaceUserGroupMembership; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/models/security/workspace_user_membership.js b/docdoku-web-front/app/js/common-objects/models/security/workspace_user_membership.js new file mode 100644 index 0000000000..0e7f011450 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/security/workspace_user_membership.js @@ -0,0 +1,50 @@ +/*global _,define*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var WorkspaceUserMembership = Backbone.Model.extend({ + + initialize: function () { + var permission = this.isReadOnly() ? 'READ_ONLY' : 'FULL_ACCESS'; + this.setPermission(permission); + _.bindAll(this); + }, + + key: function () { + return this.getUserLogin(); + }, + + name: function () { + return this.getUserName(); + }, + + getUserLogin: function () { + return this.getUser().login; + }, + + getUserName: function () { + return this.getUser().name; + }, + + getWorkspaceId: function () { + return this.get('workspaceId'); + }, + + getUser: function () { + return this.get('member'); + }, + + isReadOnly: function () { + return this.get('readOnly'); + }, + + setPermission: function (permission) { + this.set('permission', permission); + }, + + getPermission: function () { + return this.get('permission'); + } + }); + + return WorkspaceUserMembership; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/models/tag.js b/docdoku-web-front/app/js/common-objects/models/tag.js new file mode 100644 index 0000000000..c61a756add --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/tag.js @@ -0,0 +1,10 @@ +/*global define*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var Tag = Backbone.Model.extend({ + initialize: function () { + this.className = 'Tag'; + } + }); + return Tag; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/task_model.js b/docdoku-web-front/app/js/common-objects/models/task_model.js new file mode 100644 index 0000000000..fc9d074f69 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/task_model.js @@ -0,0 +1,30 @@ +/*global _,define*/ +define([ + 'backbone', + 'common-objects/models/role' +], function (Backbone, Role) { + 'use strict'; + var TaskModel = Backbone.Model.extend({ + + defaults: { + duration: 25 + }, + + initialize: function () { + if (!_.isUndefined(this.attributes.role)) { + this.attributes.role = new Role(this.attributes.role); + } + }, + + toJSON: function () { + var index = this.collection.indexOf(this); + var clonedAttributes = _.clone(this.attributes); + clonedAttributes.role = clonedAttributes.role.toJSON(); + _.extend(clonedAttributes, {num: index}); + return clonedAttributes; + } + + }); + + return TaskModel; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/timezone.js b/docdoku-web-front/app/js/common-objects/models/timezone.js new file mode 100644 index 0000000000..a6e608b28f --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/timezone.js @@ -0,0 +1,15 @@ +/*global $,define,App*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var TimeZone = Backbone.Model.extend({ + initialize: function () { + this.className = 'TimeZone'; + } + }); + + TimeZone.getTimeZones = function () { + return $.getJSON(App.config.contextPath + '/api/timezones'); + }; + + return TimeZone; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/user.js b/docdoku-web-front/app/js/common-objects/models/user.js new file mode 100644 index 0000000000..0ecb02b0c1 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/user.js @@ -0,0 +1,36 @@ +/*global $,define,App*/ +define(['backbone'], +function (Backbone) { + 'use strict'; + var UserModel = Backbone.Model.extend({ + getLogin: function () { + return this.get('login'); + }, + getName: function () { + return this.get('name'); + } + }); + + UserModel.whoami = function (workspaceId) { + return $.getJSON(App.config.contextPath + '/api/workspaces/' + workspaceId + '/users/me'); + }; + + UserModel.getGroups = function (workspaceId) { + return $.getJSON(App.config.contextPath + '/api/workspaces/' + workspaceId + '/memberships/usergroups/me'); + }; + + UserModel.getAccount = function () { + return $.getJSON(App.config.contextPath + '/api/accounts/me'); + }; + + UserModel.updateAccount = function (account) { + return $.ajax({ + type: 'PUT', + url: App.config.contextPath + '/api/accounts/me', + data: JSON.stringify(account), + contentType: 'application/json; charset=utf-8' + }); + }; + + return UserModel; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/user_group.js b/docdoku-web-front/app/js/common-objects/models/user_group.js new file mode 100644 index 0000000000..a1f786d394 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/user_group.js @@ -0,0 +1,12 @@ +/*global define*/ +define(['backbone'], +function (Backbone) { + 'use strict'; + var UserGroupModel = Backbone.Model.extend({ + getId: function () { + return this.get('id'); + } + }); + + return UserGroupModel; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/workflow/activity_model.js b/docdoku-web-front/app/js/common-objects/models/workflow/activity_model.js new file mode 100644 index 0000000000..c81d3acb29 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/workflow/activity_model.js @@ -0,0 +1,35 @@ +/*global _,define*/ +define([ + 'backbone', + 'common-objects/collections/task_models' +], function (Backbone, TaskModels) { + 'use strict'; + var ActivityModel = Backbone.Model.extend({ + + defaults: function () { + return { + type: 'SEQUENTIAL', + tasksToComplete: 0, + taskModels: new TaskModels() + }; + }, + + initialize: function () { + if (_.isUndefined(this.get('taskModels').models)) { + this.set({ + taskModels: new TaskModels(this.get('taskModels')) + }); + } + }, + + toJSON: function () { + var index = this.collection.indexOf(this); + _.extend(this.attributes, {step: index}); + + return _.clone(this.attributes); + } + + }); + + return ActivityModel; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/workflow/workflow_model.js b/docdoku-web-front/app/js/common-objects/models/workflow/workflow_model.js new file mode 100644 index 0000000000..25d5cc00e8 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/workflow/workflow_model.js @@ -0,0 +1,64 @@ +/*global _,define,App,$*/ +define([ + 'backbone', + 'common-objects/collections/activity_models','common-objects/utils/acl-checker' +], function (Backbone, ActivityModels,ACLChecker) { + 'use strict'; + var WorkflowModel = Backbone.Model.extend({ + + initialize: function () { + _.bindAll(this); + }, + + urlRoot: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/workflow-models'; + }, + + defaults: function () { + return { + activityModels: new ActivityModels() + }; + }, + + parse: function (response) { + response.activityModels = new ActivityModels(response.activityModels); + return response; + }, + + getId: function () { + return this.get('id'); + }, + hasACLForCurrentUser: function () { + return this.getACLPermissionForCurrentUser() !== false; + }, + + isForbidden: function () { + return this.getACLPermissionForCurrentUser() === 'FORBIDDEN'; + }, + + isReadOnly: function () { + return this.getACLPermissionForCurrentUser() === 'READ_ONLY'; + }, + + isFullAccess: function () { + return this.getACLPermissionForCurrentUser() === 'FULL_ACCESS'; + }, + + getACLPermissionForCurrentUser: function () { + return ACLChecker.getPermission(this.get('acl')); + }, + updateWorkflowACL: function (args) { + return $.ajax({ + type: 'PUT', + url: this.url()+'/acl', + data: JSON.stringify(args.acl), + contentType: 'application/json; charset=utf-8', + success: args.success, + error: args.error + }); + } + + + }); + return WorkflowModel; +}); diff --git a/docdoku-web-front/app/js/common-objects/models/workspace.js b/docdoku-web-front/app/js/common-objects/models/workspace.js new file mode 100644 index 0000000000..255bdcca0d --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/models/workspace.js @@ -0,0 +1,199 @@ +/*global $,define,App*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var Workspace = Backbone.Model.extend({ + initialize: function () { + this.className = 'Workspace'; + } + }); + + Workspace.getWorkspaces = function () { + return $.getJSON(App.config.contextPath + '/api/workspaces'); + }; + + Workspace.createWorkspace = function (workspace) { + return $.ajax({ + type: 'POST', + url: App.config.contextPath + '/api/workspaces', + data: JSON.stringify(workspace), + contentType: 'application/json; charset=utf-8' + }); + }; + + Workspace.updateWorkspace = function (workspace) { + return $.ajax({ + type: 'PUT', + url: App.config.contextPath + '/api/workspaces/' + workspace.id, + data: JSON.stringify(workspace), + contentType: 'application/json; charset=utf-8' + }); + }; + + Workspace.deleteWorkspace = function (workspaceId) { + return $.ajax({ + type: 'DELETE', + url: App.config.contextPath + '/api/workspaces/' + workspaceId + }); + }; + + Workspace.removeUsersFromWorkspace = function (workspaceId, userLogins) { + var promiseArray = []; + _.each(userLogins, function (login) { + promiseArray.push(Workspace.removeUserFromWorkspace(workspaceId, {login: login})); + }); + return $.when.apply(undefined, promiseArray); + }; + + Workspace.removeUserFromWorkspace = function (workspaceId, user) { + return $.ajax({ + type: 'PUT', + url: App.config.contextPath + '/api/workspaces/' + workspaceId + '/remove-from-workspace', + data: JSON.stringify(user), + contentType: 'application/json; charset=utf-8' + }); + }; + + Workspace.enableUser = function (workspaceId, user) { + return $.ajax({ + type: 'PUT', + url: App.config.contextPath + '/api/workspaces/' + workspaceId + '/enable-user', + data: JSON.stringify(user), + contentType: 'application/json; charset=utf-8' + }); + }; + Workspace.enableUsers = function (workspaceId, users) { + var promiseArray = []; + _.each(users, function (user) { + promiseArray.push(Workspace.enableUser(workspaceId,user)); + }); + return $.when.apply(undefined, promiseArray); + }; + + Workspace.disableUsers = function (workspaceId, users) { + var promiseArray = []; + _.each(users, function (user) { + promiseArray.push(Workspace.disableUser(workspaceId,user)); + }); + return $.when.apply(undefined, promiseArray); + }; + + Workspace.disableUser = function (workspaceId, user) { + return $.ajax({ + type: 'PUT', + url: App.config.contextPath + '/api/workspaces/' + workspaceId + '/disable-user', + data: JSON.stringify(user), + contentType: 'application/json; charset=utf-8' + }); + }; + + Workspace.addUser = function (workspaceId, user, group) { + return $.ajax({ + type: 'PUT', + url: App.config.contextPath + '/api/workspaces/' + workspaceId + '/add-user' + (group ? '?group=' + group : ''), + data: JSON.stringify(user), + contentType: 'application/json; charset=utf-8' + }); + }; + + Workspace.addGroup = function (workspaceId, group) { + return $.ajax({ + type: 'POST', + url: App.config.contextPath + '/api/workspaces/' + workspaceId + '/user-group', + data: JSON.stringify(group), + contentType: 'application/json; charset=utf-8' + }); + }; + Workspace.removeUserFromGroup = function (workspaceId, groupId, login) { + return $.ajax({ + type: 'PUT', + url: App.config.contextPath + '/api/workspaces/' + workspaceId + '/remove-from-group/' + groupId, + data: JSON.stringify({login: login}), + contentType: 'application/json; charset=utf-8' + }); + }; + Workspace.removeGroup = function (workspaceId, groupId) { + return $.ajax({ + type: 'DELETE', + url: App.config.contextPath + '/api/workspaces/' + workspaceId + '/user-group/' + groupId + }); + }; + + Workspace.getUsersMemberships = function (workspaceId) { + return $.getJSON(App.config.contextPath + '/api/workspaces/' + workspaceId + '/memberships/users'); + }; + + Workspace.getUsers = function (workspaceId) { + return $.getJSON(App.config.contextPath + '/api/workspaces/' + workspaceId + '/users'); + }; + + Workspace.getUserGroupsMemberships = function (workspaceId) { + return $.getJSON(App.config.contextPath + '/api/workspaces/' + workspaceId + '/memberships/usergroups'); + }; + + Workspace.setUsersMembership = function (workspaceId, membership) { + return $.ajax({ + type: 'PUT', + url: App.config.contextPath + '/api/workspaces/' + workspaceId + '/user-access', + data: JSON.stringify(membership), + contentType: 'application/json; charset=utf-8' + }); + }; + + Workspace.setGroupAccess = function (workspaceId, membership) { + return $.ajax({ + type: 'PUT', + url: App.config.contextPath + '/api/workspaces/' + workspaceId + '/group-access', + data: JSON.stringify(membership), + contentType: 'application/json; charset=utf-8' + }); + }; + + Workspace.getStatsOverView = function (workspaceId) { + return $.getJSON(App.config.contextPath + '/api/workspaces/' + workspaceId + '/stats-overview'); + }; + + Workspace.getDiskUsageStats = function (workspaceId) { + return $.getJSON(App.config.contextPath + '/api/workspaces/' + workspaceId + '/disk-usage-stats'); + }; + + Workspace.getCheckedOutDocumentsStats = function (workspaceId) { + return $.getJSON(App.config.contextPath + '/api/workspaces/' + workspaceId + '/checked-out-documents-stats'); + }; + + Workspace.getCheckedOutPartsStats = function (workspaceId) { + return $.getJSON(App.config.contextPath + '/api/workspaces/' + workspaceId + '/checked-out-parts-stats'); + }; + + Workspace.getUsersStats = function (workspaceId) { + return $.when.apply(undefined, [ + $.getJSON(App.config.contextPath + '/api/workspaces/' + workspaceId + '/users').then(function (data) { + return data; + }), + $.getJSON(App.config.contextPath + '/api/workspaces/' + workspaceId + '/users-stats').then(function (data) { + return data; + }) + ]); + }; + + Workspace.getUsersInGroups = function (args) { + var groups = args.groups; + var next = args.next; + var promiseArray = []; + _.each(groups, function (group) { + promiseArray.push($.getJSON(App.config.contextPath + '/api/workspaces/' + group.workspaceId + '/groups/' + group.memberId + '/users').then(function (users) { + return next(group, users); + })); + }); + return $.when.apply(undefined, promiseArray); + }; + + Workspace.moveUsers = function (workspaceId, groupId, userLogins) { + var promiseArray = []; + _.each(userLogins, function (login) { + promiseArray.push(Workspace.addUser(workspaceId, {login: login}, groupId)); + }); + return $.when.apply(undefined, promiseArray); + }; + + return Workspace; +}); diff --git a/docdoku-web-front/app/js/common-objects/templates/alert.html b/docdoku-web-front/app/js/common-objects/templates/alert.html new file mode 100644 index 0000000000..ae0a48af28 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/alert.html @@ -0,0 +1,6 @@ +
        + × +

        {{model.title}}

        + +

        {{model.message}}

        +
        diff --git a/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item.html b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item.html new file mode 100644 index 0000000000..685ce21a3d --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item.html @@ -0,0 +1,31 @@ + +
        + +
        +
        + +
        + +{{#availability.type}} +
        + +
        +{{/availability.type}} +{{^availability.type}} +
        +
        +{{/availability.type}} + diff --git a/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_boolean.html b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_boolean.html new file mode 100644 index 0000000000..467b088610 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_boolean.html @@ -0,0 +1,9 @@ +{{> attributeListItem}} +
        + +
        diff --git a/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_date.html b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_date.html new file mode 100644 index 0000000000..1b72374bc3 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_date.html @@ -0,0 +1,14 @@ +{{> attributeListItem}} +
        + {{#availability.value}} + + {{#availability.displayRequired}} + {{#model.mandatory}}*{{/model.mandatory}} + {{/availability.displayRequired}} + {{/availability.value}} + {{^availability.value}} + {{model.value}} + {{/availability.value}} + +
        diff --git a/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_long_text.html b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_long_text.html new file mode 100644 index 0000000000..3190915614 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_long_text.html @@ -0,0 +1,12 @@ +{{> attributeListItem}} +
        + {{#availability.value}} + + {{#availability.displayRequired}} + {{#model.mandatory}}*{{/model.mandatory}} + {{/availability.displayRequired}} + {{/availability.value}} + {{^availability.value}} + {{/availability.value}} +
        diff --git a/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_lov.html b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_lov.html new file mode 100644 index 0000000000..a0dc575eaa --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_lov.html @@ -0,0 +1,5 @@ +{{> attributeListItem}} +
        + +
        diff --git a/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_number.html b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_number.html new file mode 100644 index 0000000000..c5f6b08341 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_number.html @@ -0,0 +1,14 @@ +{{> attributeListItem}} +
        + {{#availability.value}} + + {{#availability.displayRequired}} + {{#model.mandatory}}*{{/model.mandatory}} + {{/availability.displayRequired}} + {{/availability.value}} + {{^availability.value}} + {{model.value}} + {{/availability.value}} +
        diff --git a/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_text.html b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_text.html new file mode 100644 index 0000000000..031ae2b35f --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_text.html @@ -0,0 +1,11 @@ +{{> attributeListItem}} +
        + {{#availability.value}} + + {{#availability.displayRequired}} + {{#model.mandatory}}*{{/model.mandatory}} + {{/availability.displayRequired}} + {{/availability.value}} + {{^availability.value}}{{model.value}}{{/availability.value}} +
        diff --git a/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_url.html b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_url.html new file mode 100644 index 0000000000..5eb87dff24 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/attributes/attribute_list_item_url.html @@ -0,0 +1,13 @@ +{{> attributeListItem}} +
        + {{#availability.value}} + + {{#availability.displayRequired}} + {{#model.mandatory}}*{{/model.mandatory}} + {{/availability.displayRequired}} + {{/availability.value}} + {{^availability.value}} + {{model.value}} + {{/availability.value}} +
        diff --git a/docdoku-web-front/app/js/common-objects/templates/attributes/attributes.html b/docdoku-web-front/app/js/common-objects/templates/attributes/attributes.html new file mode 100644 index 0000000000..d25a5606b4 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/attributes/attributes.html @@ -0,0 +1,6 @@ +
        +{{^frozenMode}} + + {{i18n.APPEND}} + +{{/frozenMode}} diff --git a/docdoku-web-front/app/js/common-objects/templates/attributes/template_new_attribute_list_item.html b/docdoku-web-front/app/js/common-objects/templates/attributes/template_new_attribute_list_item.html new file mode 100644 index 0000000000..aeb6c357a2 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/attributes/template_new_attribute_list_item.html @@ -0,0 +1,31 @@ + +
        + +
        +
        + +
        +
        + +
        + +{{^attributesLocked}} + +{{/attributesLocked}} diff --git a/docdoku-web-front/app/js/common-objects/templates/attributes/template_new_attributes.html b/docdoku-web-front/app/js/common-objects/templates/attributes/template_new_attributes.html new file mode 100644 index 0000000000..715a08c6e7 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/attributes/template_new_attributes.html @@ -0,0 +1,15 @@ +{{#editMode}} +{{^unfreezable}} + +{{/unfreezable}} +{{/editMode}} + +
        + +{{#editMode}} + + {{i18n.APPEND}} + +{{/editMode}} diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/ACL_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/ACL_button.html new file mode 100644 index 0000000000..adfc3c3132 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/ACL_button.html @@ -0,0 +1,3 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/checkout_button_group.html b/docdoku-web-front/app/js/common-objects/templates/buttons/checkout_button_group.html new file mode 100644 index 0000000000..43a096cc7a --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/checkout_button_group.html @@ -0,0 +1,11 @@ + + + + + diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/delete_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/delete_button.html new file mode 100644 index 0000000000..7f9b01408e --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/delete_button.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/duplicate_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/duplicate_button.html new file mode 100644 index 0000000000..fda9c4431f --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/duplicate_button.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/import_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/import_button.html new file mode 100644 index 0000000000..3aef0cd1cf --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/import_button.html @@ -0,0 +1,3 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/new_configuration_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/new_configuration_button.html new file mode 100644 index 0000000000..171a4f2d85 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/new_configuration_button.html @@ -0,0 +1,3 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/new_product_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/new_product_button.html new file mode 100644 index 0000000000..8a68529cf0 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/new_product_button.html @@ -0,0 +1,3 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/new_product_instance_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/new_product_instance_button.html new file mode 100644 index 0000000000..42e3d83f98 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/new_product_instance_button.html @@ -0,0 +1,3 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/new_version_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/new_version_button.html new file mode 100644 index 0000000000..3fee028191 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/new_version_button.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/obsolete_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/obsolete_button.html new file mode 100644 index 0000000000..ead089baf7 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/obsolete_button.html @@ -0,0 +1,3 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/release_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/release_button.html new file mode 100644 index 0000000000..77de2fe537 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/release_button.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/snap_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/snap_button.html new file mode 100644 index 0000000000..78ef85b64e --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/snap_button.html @@ -0,0 +1,3 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/snap_latest_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/snap_latest_button.html new file mode 100644 index 0000000000..9ed8a44db5 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/snap_latest_button.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/snap_released_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/snap_released_button.html new file mode 100644 index 0000000000..6390e77df7 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/snap_released_button.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/tags_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/tags_button.html new file mode 100644 index 0000000000..324eeb568d --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/tags_button.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/templates/buttons/udf_button.html b/docdoku-web-front/app/js/common-objects/templates/buttons/udf_button.html new file mode 100644 index 0000000000..5c8f7d789b --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/buttons/udf_button.html @@ -0,0 +1,3 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/document/document_iteration.html b/docdoku-web-front/app/js/common-objects/templates/document/document_iteration.html new file mode 100644 index 0000000000..1f45957047 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/document/document_iteration.html @@ -0,0 +1,267 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/document/used_by_list.html b/docdoku-web-front/app/js/common-objects/templates/document/used_by_list.html new file mode 100644 index 0000000000..9435a9a386 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/document/used_by_list.html @@ -0,0 +1,9 @@ +{{i18n.DOCUMENTS}} : +
          +{{i18n.PARTS}} : +
            +{{i18n.PRODUCT_INSTANCES}} : +
              +{{i18n.PRODUCT_INSTANCE_DATA}} : +
                +
                diff --git a/docdoku-web-front/app/js/common-objects/templates/document/used_by_list_item.html b/docdoku-web-front/app/js/common-objects/templates/document/used_by_list_item.html new file mode 100644 index 0000000000..fd083cd455 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/document/used_by_list_item.html @@ -0,0 +1 @@ +{{model.getDisplayKey}} diff --git a/docdoku-web-front/app/js/common-objects/templates/file/file.html b/docdoku-web-front/app/js/common-objects/templates/file/file.html new file mode 100644 index 0000000000..a973bfda15 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/file/file.html @@ -0,0 +1,10 @@ +{{#editMode}}{{/editMode}} +{{shortName}} +{{#editMode}} + +
                + + + +
                +{{/editMode}} diff --git a/docdoku-web-front/app/js/common-objects/templates/file/file_list.html b/docdoku-web-front/app/js/common-objects/templates/file/file_list.html new file mode 100644 index 0000000000..cbdaac9bf5 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/file/file_list.html @@ -0,0 +1,27 @@ +
                + +{{#title}} +

                {{title}}

                +{{/title}} +{{#multiple}} {{#editMode}} +{{i18n.CHECK_ALL}} +{{/editMode}} +{{/multiple}} +
                  + +
                  + +{{#editMode}} +
                  +
                  + + + {{#multiple}}{{i18n.DROP_FILES_HERE}}{{/multiple}}{{^multiple}}{{i18n.DROP_FILE_HERE}}{{/multiple}} +

                  +
                  +
                  +
                  +
                  + +
                  +{{/editMode}} diff --git a/docdoku-web-front/app/js/common-objects/templates/forbidden.html b/docdoku-web-front/app/js/common-objects/templates/forbidden.html new file mode 100644 index 0000000000..cd6316db63 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/forbidden.html @@ -0,0 +1,14 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/header.html b/docdoku-web-front/app/js/common-objects/templates/header.html new file mode 100644 index 0000000000..3278f5def7 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/header.html @@ -0,0 +1,254 @@ + + + + + + + + diff --git a/docdoku-web-front/app/js/common-objects/templates/linked/linked_change_item.html b/docdoku-web-front/app/js/common-objects/templates/linked/linked_change_item.html new file mode 100644 index 0000000000..333093edef --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/linked/linked_change_item.html @@ -0,0 +1,4 @@ +{{#editMode}} + +{{/editMode}} +{{linkedItem.getName}} diff --git a/docdoku-web-front/app/js/common-objects/templates/linked/linked_change_items.html b/docdoku-web-front/app/js/common-objects/templates/linked/linked_change_items.html new file mode 100644 index 0000000000..e9e332d45f --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/linked/linked_change_items.html @@ -0,0 +1,12 @@ +{{#editMode}} + + +{{/editMode}} +
                  + Low + Medium + High + Emergency +
                  +
                    \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/templates/linked/linked_document.html b/docdoku-web-front/app/js/common-objects/templates/linked/linked_document.html new file mode 100644 index 0000000000..cd56be2723 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/linked/linked_document.html @@ -0,0 +1,19 @@ +{{#editMode}} + +{{/editMode}} +{{linkedDocument.getDisplayDocKey}} +{{#commentEditable}} +{{#editMode}} + + +{{linkedDocument.getDocumentLinkComment}} +
                    + + + +
                    +{{/editMode}} +{{^editMode}} +{{linkedDocument.getDocumentLinkComment}} +{{/editMode}} +{{/commentEditable}} diff --git a/docdoku-web-front/app/js/common-objects/templates/linked/linked_items.html b/docdoku-web-front/app/js/common-objects/templates/linked/linked_items.html new file mode 100644 index 0000000000..ae2a976339 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/linked/linked_items.html @@ -0,0 +1,6 @@ +{{#editMode}} + + +{{/editMode}} +
                      \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/templates/linked/linked_part.html b/docdoku-web-front/app/js/common-objects/templates/linked/linked_part.html new file mode 100644 index 0000000000..fe62aacca0 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/linked/linked_part.html @@ -0,0 +1,4 @@ +{{#editMode}} + +{{/editMode}} +{{linkedPart.getDisplayPartKey}} diff --git a/docdoku-web-front/app/js/common-objects/templates/lov/lov_item.html b/docdoku-web-front/app/js/common-objects/templates/lov/lov_item.html new file mode 100644 index 0000000000..0c69389a41 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/lov/lov_item.html @@ -0,0 +1,19 @@ +{{#model.isDeletable}} +
                      + +
                      +{{/model.isDeletable}} + + {{i18n.NAME}}: + {{model.getLOVName}} + {{^model.getLOVName}} + + {{/model.getLOVName}} +
                      + {{model.getNumberOfValue}} {{i18n.POSSIBLE_VALUE}} +
                      +
                      + +
                      + + diff --git a/docdoku-web-front/app/js/common-objects/templates/lov/lov_modal.html b/docdoku-web-front/app/js/common-objects/templates/lov/lov_modal.html new file mode 100644 index 0000000000..d4a3918b13 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/lov/lov_modal.html @@ -0,0 +1,24 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/lov/lov_possible_value.html b/docdoku-web-front/app/js/common-objects/templates/lov/lov_possible_value.html new file mode 100644 index 0000000000..d4f1ae7491 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/lov/lov_possible_value.html @@ -0,0 +1,6 @@ + + + +
                      + +
                      diff --git a/docdoku-web-front/app/js/common-objects/templates/not-found.html b/docdoku-web-front/app/js/common-objects/templates/not-found.html new file mode 100644 index 0000000000..73322f13b7 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/not-found.html @@ -0,0 +1,7 @@ +
                      +

                      404

                      +

                      {{i18n.ERROR_404_SORRY}}

                      +

                      {{i18n.ERROR_404}}

                      +

                      {{reason}}

                      + {{i18n.HOME_PAGE}} +
                      diff --git a/docdoku-web-front/app/js/common-objects/templates/part/cad_instance.html b/docdoku-web-front/app/js/common-objects/templates/part/cad_instance.html new file mode 100644 index 0000000000..30051984ac --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/cad_instance.html @@ -0,0 +1,17 @@ +
                      +
                      Translation (x y z)
                      + + + +
                      +
                      +
                      Rotation (x y z) (rad)
                      + + + +
                      +{{#canRemove}} +
                      + +
                      +{{/canRemove}} diff --git a/docdoku-web-front/app/js/common-objects/templates/part/conversion_status.html b/docdoku-web-front/app/js/common-objects/templates/part/conversion_status.html new file mode 100644 index 0000000000..0963a33ea4 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/conversion_status.html @@ -0,0 +1,18 @@ +{{#hasCadFile}} +{{#status}} + {{#pending}} + {{i18n.CONVERSION_PENDING}} ... + {{/pending}} + {{^pending}} + {{#succeed}} + {{i18n.CONVERSION_SUCCEED}} + {{/succeed}} + {{^succeed}} + {{i18n.CONVERSION_FAILED}} + + {{i18n.RETRY}} + + {{/succeed}} + {{/pending}} +{{/status}} +{{/hasCadFile}} diff --git a/docdoku-web-front/app/js/common-objects/templates/part/import_status.html b/docdoku-web-front/app/js/common-objects/templates/part/import_status.html new file mode 100644 index 0000000000..e9df15b4fa --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/import_status.html @@ -0,0 +1,36 @@ +{{#status}} + {{#pending}} + {{i18n.IMPORT_PENDING}} ... + {{/pending}} + {{^pending}} + {{#succeed}} + {{i18n.IMPORT_SUCCEED}} + {{/succeed}} + {{^succeed}} + {{i18n.IMPORT_FAILED}} + {{/succeed}} + +

                      + {{i18n.FILE}}: {{fileName}} +

                      +

                      + {{i18n.DATE}}: {{startDate}} +

                      + + + {{#errors.length}} +

                      {{i18n.ERRORS}}

                      + {{#errors}} +

                      {{.}}

                      + {{/errors}} + {{/errors.length}} + + {{#warnings.length}} +

                      {{i18n.WARNINGS}}

                      + {{#warnings}} +

                      {{.}}

                      + {{/warnings}} + {{/warnings.length}} + + {{/pending}} +{{/status}} diff --git a/docdoku-web-front/app/js/common-objects/templates/part/modification_notification_group_list.html b/docdoku-web-front/app/js/common-objects/templates/part/modification_notification_group_list.html new file mode 100644 index 0000000000..37ddd3230a --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/modification_notification_group_list.html @@ -0,0 +1 @@ +{{i18n.MODIFICATION_NOTIFICATION_LIST_DESCRIPTION}} diff --git a/docdoku-web-front/app/js/common-objects/templates/part/modification_notification_list.html b/docdoku-web-front/app/js/common-objects/templates/part/modification_notification_list.html new file mode 100644 index 0000000000..d70fc3723a --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/modification_notification_list.html @@ -0,0 +1,17 @@ +
                      + +
                      + {{#modificationNotification.getModifiedPartName}}{{modificationNotification.getModifiedPartName}}{{/modificationNotification.getModifiedPartName}} + < {{modificationNotification.getModifiedPartNumber}} > +
                      + + {{#hasUnreadModificationNotifications}} + + {{/hasUnreadModificationNotifications}} + +
                      + +
                      diff --git a/docdoku-web-front/app/js/common-objects/templates/part/modification_notification_list_item.html b/docdoku-web-front/app/js/common-objects/templates/part/modification_notification_list_item.html new file mode 100644 index 0000000000..5ad109f523 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/modification_notification_list_item.html @@ -0,0 +1,40 @@ +
                      + + {{^modificationNotification.isAcknowledged}} + + {{/modificationNotification.isAcknowledged}} + +
                      + + {{modificationNotification.getModifiedPartVersion}}-{{modificationNotification.getModifiedPartIteration}} + {{i18n.CHECKED_IN}} + {{#modificationNotification.hasCheckInDate}} {{i18n.ON}} {{modificationNotification.getFormattedCheckInDate}}{{/modificationNotification.hasCheckInDate}} + {{#modificationNotification.hasAuthor}} {{i18n.BY}} {{modificationNotification.getAuthorName}}{{/modificationNotification.hasAuthor}} + + {{#modificationNotification.getIterationNote}} +
                      + "{{modificationNotification.getIterationNote}}" +
                      + {{/modificationNotification.getIterationNote}} + + {{#modificationNotification.isAcknowledged}} + + {{#modificationNotification.getAckComment}} +
                      + "{{modificationNotification.getAckComment}}" +
                      + {{/modificationNotification.getAckComment}} + {{/modificationNotification.isAcknowledged}} + + {{^modificationNotification.isAcknowledged}} + + {{/modificationNotification.isAcknowledged}} +
                      + +
                      diff --git a/docdoku-web-front/app/js/common-objects/templates/part/part_assembly.html b/docdoku-web-front/app/js/common-objects/templates/part/part_assembly.html new file mode 100644 index 0000000000..b900842919 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/part_assembly.html @@ -0,0 +1,13 @@ +{{#editMode}} + + +{{/editMode}} +
                      diff --git a/docdoku-web-front/app/js/common-objects/templates/part/part_link.html b/docdoku-web-front/app/js/common-objects/templates/part/part_link.html new file mode 100644 index 0000000000..899baac6c6 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/part_link.html @@ -0,0 +1,29 @@ +{{#editMode}}×{{/editMode}} + + +{{#handleSubstitutes}} + +{{/handleSubstitutes}} + + + + + +{{#editMode}} + + +{{/editMode}} +
                      +{{#handleSubstitutes}} + + +{{/handleSubstitutes}} + diff --git a/docdoku-web-front/app/js/common-objects/templates/part/part_modal.html b/docdoku-web-front/app/js/common-objects/templates/part/part_modal.html new file mode 100644 index 0000000000..94e3ee55b3 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/part_modal.html @@ -0,0 +1,295 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/part/used_by_list.html b/docdoku-web-front/app/js/common-objects/templates/part/used_by_list.html new file mode 100644 index 0000000000..542260dee9 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/used_by_list.html @@ -0,0 +1,8 @@ +
                      +
                      + {{i18n.PRODUCT}} < {{configurationItemId}} > +
                      + + {{i18n.PRODUCT_INSTANCES}} :
                        + +
                        diff --git a/docdoku-web-front/app/js/common-objects/templates/part/used_by_list_item.html b/docdoku-web-front/app/js/common-objects/templates/part/used_by_list_item.html new file mode 100644 index 0000000000..ecd625f87f --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/used_by_list_item.html @@ -0,0 +1 @@ +
                        {{model.getSerialNumber}}
                        diff --git a/docdoku-web-front/app/js/common-objects/templates/part/used_by_list_item_part.html b/docdoku-web-front/app/js/common-objects/templates/part/used_by_list_item_part.html new file mode 100644 index 0000000000..b7b6fe1de9 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/used_by_list_item_part.html @@ -0,0 +1 @@ +{{model.getName}} < {{model.getNumber}}-{{model.getVersion}} > diff --git a/docdoku-web-front/app/js/common-objects/templates/part/used_by_path_data_item.html b/docdoku-web-front/app/js/common-objects/templates/part/used_by_path_data_item.html new file mode 100644 index 0000000000..cbc3a8c071 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/used_by_path_data_item.html @@ -0,0 +1,3 @@ +
                        +
                        +
                        diff --git a/docdoku-web-front/app/js/common-objects/templates/part/used_by_pd_instance_list.html b/docdoku-web-front/app/js/common-objects/templates/part/used_by_pd_instance_list.html new file mode 100644 index 0000000000..894142a3bd --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/used_by_pd_instance_list.html @@ -0,0 +1,8 @@ +
                        +
                        + {{i18n.PRODUCT_INSTANCE}} < {{serialNumber}} > +
                        + +
                          + +
                          diff --git a/docdoku-web-front/app/js/common-objects/templates/part/used_by_view.html b/docdoku-web-front/app/js/common-objects/templates/part/used_by_view.html new file mode 100644 index 0000000000..db790b7c85 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/part/used_by_view.html @@ -0,0 +1,7 @@ +{{i18n.IS_COMPONENT_OF}} +
                            + +{{i18n.IS_SUBSTITUTE_IN}} +
                              + +
                              diff --git a/docdoku-web-front/app/js/common-objects/templates/path/path.html b/docdoku-web-front/app/js/common-objects/templates/path/path.html new file mode 100644 index 0000000000..fe603cf189 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/path/path.html @@ -0,0 +1,8 @@ +
                              + {{#editMode}} + + {{/editMode}} + {{#partLinks}} + {{name}} < {{number}} > {{#referenceDescription}}({{.}}){{/referenceDescription}} + {{/partLinks}} +
                              diff --git a/docdoku-web-front/app/js/common-objects/templates/pathToPathLink/path_to_path_link_item.html b/docdoku-web-front/app/js/common-objects/templates/pathToPathLink/path_to_path_link_item.html new file mode 100644 index 0000000000..3921957e22 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/pathToPathLink/path_to_path_link_item.html @@ -0,0 +1,21 @@ +
                              + + diff --git a/docdoku-web-front/app/js/common-objects/templates/prompt.html b/docdoku-web-front/app/js/common-objects/templates/prompt.html new file mode 100644 index 0000000000..aa6edd1d9d --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/prompt.html @@ -0,0 +1,14 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/security/acl_edit.html b/docdoku-web-front/app/js/common-objects/templates/security/acl_edit.html new file mode 100644 index 0000000000..0a508dc40e --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/security/acl_edit.html @@ -0,0 +1,37 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/security/acl_entries.html b/docdoku-web-front/app/js/common-objects/templates/security/acl_entries.html new file mode 100644 index 0000000000..c5c58f5c50 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/security/acl_entries.html @@ -0,0 +1,17 @@ +
                              +

                              {{i18n.USE_ACL}}

                              + +
                              + +
                              +
                              + +
                              +

                              {{i18n.USERS}}

                              + +
                              +
                              +

                              {{i18n.GROUPS}}

                              + +
                              +
                              \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/templates/security/acl_entry_item.html b/docdoku-web-front/app/js/common-objects/templates/security/acl_entry_item.html new file mode 100644 index 0000000000..2020e73944 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/security/acl_entry_item.html @@ -0,0 +1,27 @@ +
                              + + + +
                              + + + + + + + + + +
                              + + +
                              diff --git a/docdoku-web-front/app/js/common-objects/templates/security/membership_item.html b/docdoku-web-front/app/js/common-objects/templates/security/membership_item.html new file mode 100644 index 0000000000..505e8338cd --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/security/membership_item.html @@ -0,0 +1,25 @@ +
                              + + + +
                              + + + + + + + + + +
                              + +
                              diff --git a/docdoku-web-front/app/js/common-objects/templates/share/share_entity.html b/docdoku-web-front/app/js/common-objects/templates/share/share_entity.html new file mode 100644 index 0000000000..bf49793324 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/share/share_entity.html @@ -0,0 +1,79 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/share/shared_entity.html b/docdoku-web-front/app/js/common-objects/templates/share/shared_entity.html new file mode 100644 index 0000000000..59ea255c8d --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/share/shared_entity.html @@ -0,0 +1,7 @@ +
                              + + + +
                              diff --git a/docdoku-web-front/app/js/common-objects/templates/tags/tag.html b/docdoku-web-front/app/js/common-objects/templates/tags/tag.html new file mode 100644 index 0000000000..bdf709545c --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/tags/tag.html @@ -0,0 +1 @@ +{{#isRemovable}}{{/isRemovable}}{{tag.id}} \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/templates/tags/tags_management.html b/docdoku-web-front/app/js/common-objects/templates/tags/tags_management.html new file mode 100644 index 0000000000..97bf025b9a --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/tags/tags_management.html @@ -0,0 +1,47 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/time_zone.html b/docdoku-web-front/app/js/common-objects/templates/time_zone.html new file mode 100644 index 0000000000..55215c78e5 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/time_zone.html @@ -0,0 +1,12 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/udf/calculation.html b/docdoku-web-front/app/js/common-objects/templates/udf/calculation.html new file mode 100644 index 0000000000..2a784ba086 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/udf/calculation.html @@ -0,0 +1,24 @@ + + + +
                              +
                              + {{i18n.RESULT}} : + +
                              +
                              + {{i18n.NODE_ASSEMBLIES_VISITED}} : + +
                              +
                              + {{i18n.NODE_INSTANCES_VISITED}} : + +
                              +
                              diff --git a/docdoku-web-front/app/js/common-objects/templates/udf/user_defined_function.html b/docdoku-web-front/app/js/common-objects/templates/udf/user_defined_function.html new file mode 100644 index 0000000000..8ba3f71438 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/udf/user_defined_function.html @@ -0,0 +1,51 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/workflow/lifecycle.html b/docdoku-web-front/app/js/common-objects/templates/workflow/lifecycle.html new file mode 100644 index 0000000000..4db368e84e --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/workflow/lifecycle.html @@ -0,0 +1,25 @@ +
                              + +
                              + + {{#abortedWorkflows.length}} + {{i18n.WORKFLOW_HISTORY}} +
                              + +
                              + {{/abortedWorkflows.length}} +
                              + {{i18n.WORKFLOW_LEGEND_INCOMPLETE}} + {{i18n.WORKFLOW_LEGEND_COMPLETE}} + {{i18n.WORKFLOW_LEGEND_REJECTED}} + {{i18n.WORKFLOW_LEGEND_INPROGRESS}} +
                              +
                              +
                              +
                              +
                              diff --git a/docdoku-web-front/app/js/common-objects/templates/workflow/lifecycle_activity.html b/docdoku-web-front/app/js/common-objects/templates/workflow/lifecycle_activity.html new file mode 100644 index 0000000000..c87dd67b90 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/workflow/lifecycle_activity.html @@ -0,0 +1,3 @@ +
                              {{activity.lifeCycleState}} ({{activityType}})
                              +{{#activity.relaunchActivityState}}

                              {{.}}

                              {{/activity.relaunchActivityState}} +
                              diff --git a/docdoku-web-front/app/js/common-objects/templates/workflow/lifecycle_task.html b/docdoku-web-front/app/js/common-objects/templates/workflow/lifecycle_task.html new file mode 100644 index 0000000000..c5363becff --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/workflow/lifecycle_task.html @@ -0,0 +1,59 @@ +
                              {{task.title}} + {{#task.isAcceptableOrRejectable}} + + + {{/task.isAcceptableOrRejectable}} + {{#task.closureDate}} + + {{/task.closureDate}} +
                              + +

                              {{task.instructions}}

                              + +{{#task.worker}} +

                              + {{i18n.TASK_WORKER}} {{task.worker.name}} +

                              +{{/task.worker}} + +{{i18n.ASSIGNMENTS}} + +{{#task.assignedUsers}} + {{name}} +{{/task.assignedUsers}} + +{{#task.assignedGroups}} + {{id}} +{{/task.assignedGroups}} + + +{{#task.closureDate}} +
                              +

                              + {{task.ClosureStatus}}
                              {{task.formattedClosureDate}}
                              + {{i18n.ITERATION}} : {{task.targetIteration}}
                              + {{i18n.COMMENT}}
                              {{task.closureComment}}
                              + {{#task.signature}} + {{i18n.SIGN_TASK}}
                              + {{/task.signature}} +

                              +
                              +{{/task.closureDate}} +{{#task.isAcceptableOrRejectable}} +
                              +
                              + +
                              +
                              + + +
                              +
                              +
                              + + +
                              +
                              +
                              +{{/task.isAcceptableOrRejectable}} diff --git a/docdoku-web-front/app/js/common-objects/templates/workflow/lifecycle_task_signing.html b/docdoku-web-front/app/js/common-objects/templates/workflow/lifecycle_task_signing.html new file mode 100644 index 0000000000..3750361ecc --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/workflow/lifecycle_task_signing.html @@ -0,0 +1,12 @@ + + + diff --git a/docdoku-web-front/app/js/common-objects/templates/workflow/role_item.html b/docdoku-web-front/app/js/common-objects/templates/workflow/role_item.html new file mode 100644 index 0000000000..c70d5fbfdb --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/workflow/role_item.html @@ -0,0 +1,5 @@ +

                              {{model.getName}}

                              +{{^required}}

                              {{i18n.ASSIGNED_ROLE_PRE_FILL}}

                              {{/required}} + + +{{#required}}

                              {{i18n.ASSIGNED_ROLE_REQUIRED}}

                              {{/required}} diff --git a/docdoku-web-front/app/js/common-objects/templates/workflow/workflow_mapping.html b/docdoku-web-front/app/js/common-objects/templates/workflow/workflow_mapping.html new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/workflow/workflow_mapping.html @@ -0,0 +1 @@ + diff --git a/docdoku-web-front/app/js/common-objects/templates/workflow/workflow_select.html b/docdoku-web-front/app/js/common-objects/templates/workflow/workflow_select.html new file mode 100644 index 0000000000..f9d4025c5e --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/templates/workflow/workflow_select.html @@ -0,0 +1,11 @@ +
                              + + +
                              + +
                              +
                              diff --git a/docdoku-web-front/app/js/common-objects/utils/acl-checker.js b/docdoku-web-front/app/js/common-objects/utils/acl-checker.js new file mode 100644 index 0000000000..2c71657505 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/utils/acl-checker.js @@ -0,0 +1,63 @@ +/*global _,define,App*/ +define([], function () { + 'use strict'; + var ACLChecker = { + + getPermission: function (acl) { + + if (!acl) { + return false; + } + + var permission = false; + var userEntries = acl.userEntries.entry; + var groupEntries = acl.groupEntries.entry; + + var userLogin = App.config.login; + var userGroups = App.config.groups; + + var userAccess = _(userEntries).filter(function (a) { + return a.key === userLogin && a.value; + })[0]; + + if (userAccess) { + return userAccess.value; + } + + var self = this; + var groupAccess; + + _.each(userGroups, function (group) { + + groupAccess = _(groupEntries).filter(function (a) { + return a.key === group.memberId && a.value; + }); + }); + + if (groupAccess.length) { + permission = _.sortBy(groupAccess,self.accessPriority)[0].value; + } + + return permission; + }, + + accessPriority: function(access) { + switch(access) { + case 'FULL_ACCESS': + return 0; + case 'READ_ONLY': + return 1; + case 'FORBIDDEN': + return 2; + default: + //should log this, it's strange, or exception ? + return 3; + } + } + }; + + + + return ACLChecker; + +}); diff --git a/docdoku-web-front/app/js/common-objects/utils/attribute_accessibility.js b/docdoku-web-front/app/js/common-objects/utils/attribute_accessibility.js new file mode 100644 index 0000000000..a0bdcab3d2 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/utils/attribute_accessibility.js @@ -0,0 +1,92 @@ +/*global define*/ + +define(function () { + + 'use strict'; + + return { + + STATE: { + OPEN: 0, + LOCKED: 1, + DISPLAY_ONLY: 2, + FROZEN: 3, + NO_EDIT: 4 + }, + + /** + * Create an object which define the availables action for each STATE. + */ + getAvailabilityByState: function (state) { + var proto = { + remove: true, + sortable: true, + type: true, + name: true, + value: true, + displayRequired: true + }; + switch (state) { + + case this.STATE.NO_EDIT: + proto.value = false; + proto.name = false; + proto.type = false; + proto.remove = false; + proto.sortable = false; + break; + + case this.STATE.FROZEN: + proto.sortable = false; + proto.remove = false; + proto.name = false; + proto.type = false; + break; + + case this.STATE.LOCKED: + proto.remove = false; + proto.name = false; + proto.type = false; + break; + + case this.STATE.DISPLAY_ONLY: + proto.sortable = false; + proto.type = true; + // won't print the display label nor specify the input value as required. + proto.displayRequired = false; + break; + + //Prototype is already in open mode + //no need to redefine. + default: + } + + return proto; + }, + /** + * Get the availability state in function of the attribute data. + */ + getState: function (editMode, attributesLocked, locked, displayOnly) { + + if (!editMode) { + return this.STATE.NO_EDIT; + } else if (displayOnly) { + return this.STATE.DISPLAY_ONLY; + } + else if (attributesLocked) { + return this.STATE.FROZEN; + } else if (locked) { + return this.STATE.LOCKED; + } else { + return this.STATE.OPEN; + } + }, + + /** + * Get the object which define the available actions in function of the attribute data. + */ + getAvailability: function (editMode, attributesLocked, locked, displayOnly) { + return this.getAvailabilityByState(this.getState(editMode, attributesLocked, locked, displayOnly)); + } + }; +}); diff --git a/docdoku-web-front/app/js/common-objects/utils/date.js b/docdoku-web-front/app/js/common-objects/utils/date.js new file mode 100644 index 0000000000..5a8fab7e4f --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/utils/date.js @@ -0,0 +1,164 @@ +/*global _,$,define,App*/ +define([ + 'moment', + 'momentTimeZone' +], function (moment) { + 'use strict'; + + console.log('Using timezone ' + App.config.timeZone + ' and locale ' + App.config.locale); + // calculating the offset so that it's not calculating again by momentjs + moment.suppressDeprecationWarnings = true; + moment.locale(App.config.locale); + + var offset = moment().tz(App.config.timeZone).zone(); + var moffset = offset * 60 * 1000; + + return { + // This function should be removed as it does not use the correct timezone offset + formatTimestamp: function (format, timestamp) { + if (!timestamp) { + return ''; + } + try { + // set the timezone to be the current one (problem with daylight saving time) + // and return the string with the format specified + return moment.utc(timestamp).zone(offset).format(format); + } catch (error) { + console.error('Date.formatTimestamp(' + format + ', ' + timestamp + ')', error); + return timestamp; + } + }, + + // This function gets the real offset, that is the offset of the timestamp not the current offset + correctFormatTimestamp: function (format, timestamp) { + if (!timestamp) { + return ''; + } + try { + // set the timezone to be the current one (problem with daylight saving time) + // and return the string with the format specified + var timestampOffset = moment(timestamp).tz(App.config.timeZone).zone(); + return moment.utc(timestamp).zone(timestampOffset).format(format); + } catch (error) { + console.error('Date.formatTimestamp(' + format + ', ' + timestamp + ')', error); + return timestamp; + } + }, + + formatLocalTime: function(format, timestamp) { + if(!timestamp) { + return ''; + } + try { + return moment(timestamp).format(format); + } catch(error) { + console.error('Date.formatTimestamp(' + format + ', ' + timestamp + ')', error); + return timestamp; + } + }, + + toUTCWithTimeZoneOffset: function (dateString) { + // get the right timestamp from the offset calculated previously + var dateUTCWithOffset = moment.utc(dateString).toDate().getTime() + moffset; + return moment(dateUTCWithOffset).utc().format('YYYY-MM-DDTHH:mm:ss'); + }, + + getMainZonesDates: function (timestamp) { + var mainZones = ['America/Los_Angeles', 'America/New_York', 'Europe/London', 'Europe/Paris', 'Europe/Moscow', 'Asia/Tokyo']; + var mainZonesDates = []; + _(mainZones).each(function (zone) { + mainZonesDates.push({ + name: zone, + date: moment.utc(timestamp).tz(zone).format(App.config.i18n._DATE_FORMAT) + }); + }); + + mainZonesDates.push({ + name: App.config.timeZone + ' (yours)', + date: moment.utc(timestamp).tz(App.config.timeZone).format(App.config.i18n._DATE_FORMAT) + }); + + mainZonesDates.push({ + name: 'locale', + date: moment(timestamp).format(App.config.i18n._DATE_FORMAT) + }); + mainZonesDates.push({ + name: 'utc', + date: moment.utc(timestamp).format(App.config.i18n._DATE_FORMAT) + }); + return mainZonesDates; + }, + + dateHelper: function ($querySelector) { + this.dateHelperWithPlacement($querySelector, 'top'); + }, + + dateHelperWithPlacement: function ($querySelector, placement) { + + $querySelector.each(function () { + var _date = $(this).text(); + var dateUTCWithOffset = moment.utc(_date, App.config.i18n._DATE_FORMAT).toDate().getTime() + moffset; + moment(dateUTCWithOffset).utc(); + + var fromNow = moment(dateUTCWithOffset).utc().fromNow(); + $(this).popover({ + title: '' + App.config.timeZone + '
                              ' + _date + '
                              ' + fromNow, + html: true, + content: 'UTC
                              ' + moment.utc(_date, App.config.i18n._DATE_FORMAT).zone(-offset).format(App.config.i18n._DATE_FORMAT), + trigger: 'manual', + placement: placement + }).click(function (e) { + $(this).popover('show'); + e.stopPropagation(); + e.preventDefault(); + return false; + }); + }); + }, + + lastTimeOfDayDate: function (anyDate) { + return moment(anyDate).endOf('day'); + }, + + + lastDayOfMonthDate: function (anyDate) { + return moment.utc(anyDate).zone(offset).endOf('month'); + }, + + lastDayOfMonthDatestring: function (anyDate, format) { + return this.lastDayOfMonthDate(anyDate).format(format); + }, + + monthDifference: function (d1, d2) { + var date1 = moment(d1); + var date2 = moment(d2); + var months = (date2.year() - date1.year()) * 12; + months -= date1.month(); + months += date2.month(); + return months <= 0 ? 0 : months; + }, + + changeDateWithMonthDifference: function (date, difference) { + return moment(date).add(difference, 'months').format(); + }, + + nextDayDate: function (anyDate, format) { + var duration = moment.duration({'days' : 1}); + var datestring = moment.utc(anyDate).zone(offset).add(duration).format(format); + return new Date(datestring); + }, + + stringAsDate: function (anyDate, format) { + var datestring = moment.utc(anyDate).zone(offset).format(format); + return new Date(datestring); + }, + + dateAsString: function (date, format) { + return moment(date).format(format); + }, + + addSeconds: function (anyDate, seconds) { + moment(anyDate).add(seconds, 'seconds'); + } + }; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/alert.js b/docdoku-web-front/app/js/common-objects/views/alert.js new file mode 100644 index 0000000000..252dccc657 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/alert.js @@ -0,0 +1,30 @@ +/*global define*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/alert.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var AlertView = Backbone.View.extend({ + event:{ + 'click .close': 'onClose' + }, + + render: function(){ + this.$el.html(Mustache.render(template,{ + model : { + type : this.options.type, + title : this.options.title, + message: this.options.message + } + })); + return this; + }, + + onClose : function(){ + this.remove(); + } + + }); + return AlertView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list.js b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list.js new file mode 100644 index 0000000000..79f375da39 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list.js @@ -0,0 +1,71 @@ +/*global define*/ +define([ + 'common-objects/views/components/list', + 'common-objects/views/attributes/attribute_list_item_boolean', + 'common-objects/views/attributes/attribute_list_item_date', + 'common-objects/views/attributes/attribute_list_item_number', + 'common-objects/views/attributes/attribute_list_item_text', + 'common-objects/views/attributes/attribute_list_item_long_text', + 'common-objects/views/attributes/attribute_list_item_url', + 'common-objects/views/attributes/attribute_list_item_lov' +], function (ListView, AttributeListItemBooleanView, AttributeListItemDateView, AttributeListItemNumberView, AttributeListItemTextView, AttributeListItemLongTextView, AttributeListItemUrlView, AttributeListItemLOVView) { + 'use strict'; + var AttributeListView = ListView.extend({ + + typeViewMapping: { + 'BOOLEAN': AttributeListItemBooleanView, + 'DATE': AttributeListItemDateView, + 'NUMBER': AttributeListItemNumberView, + 'TEXT': AttributeListItemTextView, + 'LONG_TEXT': AttributeListItemLongTextView, + 'URL': AttributeListItemUrlView, + 'LOV': AttributeListItemLOVView + }, + + editMode: true, + + attributesLocked: false, + + + initialize: function () { + ListView.prototype.initialize.apply(this, arguments); + this.lovs = this.options.lovs; + this.displayOnly = this.options.displayOnly ? this.options.displayOnly : false; + }, + + itemViewFactory: function (model) { + var type = model.get('type'); + if (!type) { + type = model.get('attributeType'); + } + + if(type !== 'TEXT' && type !== 'LONG_TEXT' && type !== 'BOOLEAN' && type !== 'NUMBER' && type !== 'URL' && type !== 'DATE'){ + type = 'LOV'; + } + var Constructor = this.typeViewMapping[type]; + var view = new Constructor({ + model: model, + lovs:this.lovs.models, + displayOnly: this.displayOnly + }); + view.setEditMode(this.editMode); + view.setAttributesLocked(this.attributesLocked); + return view; + }, + + collectionAdd: function (model) { + this.createItemView(model); + }, + + + setEditMode: function (editMode) { + this.editMode = editMode; + }, + + setAttributesLocked: function (attributesLocked) { + this.attributesLocked = attributesLocked; + } + + }); + return AttributeListView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item.js b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item.js new file mode 100644 index 0000000000..3984c87879 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item.js @@ -0,0 +1,117 @@ +/*global define,App*/ +define([ + 'mustache', + 'common-objects/views/components/list_item', + 'common-objects/utils/attribute_accessibility' +], function (Mustache, ListItemView,AttributeAccessibility) { + 'use strict'; + var AttributeListItemView = ListItemView.extend({ + + tagName: 'div', + + editMode: true, + + attributesLocked: false, + // for attributes to be displayed but not saved + // actually used in the search form. + displayOnly: false, + + lovs: null, + + setEditMode: function (editMode) { + this.editMode = editMode; + }, + + initialize: function () { + ListItemView.prototype.initialize.apply(this, arguments); + this.events['change .type'] = 'typeChanged'; + this.events['change .name'] = 'updateName'; + this.events['change .value'] = 'updateValue'; + this.events['click .fa-times'] = 'removeAction'; + this.events.drop = 'drop'; + this.lovs = this.options.lovs; + this.displayOnly = this.options.displayOnly; + }, + + drop: function (event, index) { + this.$el.trigger('update-sort', [this.model, index]); + }, + + rendered: function () { + var type = this.model.get('type') || this.model.get('attributeType'); + if (this.editMode && !this.attributesLocked) { + this.$el.find('select.type').val(type); + } + else { + this.$el.find('div.type').html(App.config.i18n[type]); + } + + if (this.model.get('locked') === true) { + this.$el.find('div.type').html(App.config.i18n[type]); + } + + this.$el.addClass('well'); + this.$('input[required]').customValidity(App.config.i18n.REQUIRED_FIELD); + }, + + removeAction: function () { + this.model.destroy({ + dataType: 'text' // server doesn't send a json hash in the response body + }); + }, + + typeChanged: function (evt) { + var type = evt.target.value; + + if (type !== 'TEXT' && type !== 'LONG_TEXT' && type !== 'NUMBER' && type !== 'BOOLEAN' && type !== 'DATE' && type !== 'URL') { + this.model.set({ + type: 'LOV', + lovName: type, + value: '' // TODO: Validate and convert if possible between types + }); + } else { + this.model.set({ + type: type, + value: '' // TODO: Validate and convert if possible between types + }); + } + this.model.collection.trigger('reset'); + }, + + updateName: function () { + this.model.set({ + name: this.$el.find('input.name:first').val() + }); + }, + + updateValue: function () { + var el = this.$el.find('input.value:first'); + this.model.set({ + value: this.getValue(el) + }); + }, + + getValue: function (el) { + return el.val(); + }, + + render: function () { + this.deleteSubViews(); + var partials = this.partials ? this.partials : null; + var data = this.renderData(); + data.availability = AttributeAccessibility. + getAvailability(this.editMode,this.attributesLocked,this.model.get('locked'), this.displayOnly); + data.lovs = this.lovs; + data.items = this.model.get('items'); + this.$el.html(Mustache.render(this.template, data, partials)); + this.rendered(); + return this; + }, + + setAttributesLocked: function (attributesLocked) { + this.attributesLocked = attributesLocked; + } + }); + + return AttributeListItemView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_boolean.js b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_boolean.js new file mode 100644 index 0000000000..4d40cf94ac --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_boolean.js @@ -0,0 +1,32 @@ +/*global define*/ +define([ + 'common-objects/views/attributes/attribute_list_item', + 'text!common-objects/templates/attributes/attribute_list_item.html', + 'text!common-objects/templates/attributes/attribute_list_item_boolean.html' +], function (AttributeListItemView, attributeListItem, template) { + 'use strict'; + var AttributeListItemBooleanView = AttributeListItemView.extend({ + + template: template, + + partials: { + attributeListItem: attributeListItem + }, + initialize: function () { + AttributeListItemView.prototype.initialize.apply(this, arguments); + this.events['change .value'] = 'updateValue'; + }, + getValue: function (el) { + return el.is(':checked'); + }, + modelToJSON: function () { + return { + name: this.model.get('name'), + type: this.model.get('type'), + value: this.model.get('value') === true || this.model.get('value') === 'true', + locked: this.model.get('locked') + }; + } + }); + return AttributeListItemBooleanView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_date.js b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_date.js new file mode 100644 index 0000000000..62316ad752 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_date.js @@ -0,0 +1,63 @@ +/*global _,define,App*/ +define([ + 'common-objects/views/attributes/attribute_list_item', + 'text!common-objects/templates/attributes/attribute_list_item.html', + 'text!common-objects/templates/attributes/attribute_list_item_date.html', + 'common-objects/utils/date' +], function (AttributeListItemView, attributeListItem, template, date) { + 'use strict'; + var AttributeListItemDateView = AttributeListItemView.extend({ + + template: template, + + partials: { + attributeListItem: attributeListItem + }, + + initialize: function () { + AttributeListItemView.prototype.initialize.apply(this, arguments); + this.templateExtraData = { + timeZone : App.config.timeZone, + language : App.config.locale + }; + }, + + /** + * format date from attribute model (timestamp string) to html5 input date ('yyyy-mm-dd') + */ + modelToJSON: function () { + var format = this.editMode ? App.config.i18n._DATE_PICKER_DATE_FORMAT + : App.config.i18n._DATE_SHORT_FORMAT; + var data = this.model.toJSON(); + if (!_.isEmpty(data.value)) { + data.value = date.formatLocalTime( + format, + new Date(data.value) + ); + } + return data; + }, + + /** + * format date from html5 input to timestamp string + */ + getValue: function (el) { + return date.formatLocalTime('YYYY-MM-DDTHH:mm:ss',el.val()); + }, + + /** + * called on input change + */ + updateValue: function () { + var el = this.$('input.value'); + this.model.set({ + value: this.getValue(el) + }, { + silent: true + }); + } + + }); + + return AttributeListItemDateView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_long_text.js b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_long_text.js new file mode 100644 index 0000000000..a49c61a45e --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_long_text.js @@ -0,0 +1,25 @@ +/*global define*/ +define([ + 'common-objects/views/attributes/attribute_list_item', + 'text!common-objects/templates/attributes/attribute_list_item.html', + 'text!common-objects/templates/attributes/attribute_list_item_long_text.html' +], function (AttributeListItemView, attributeListItem, template) { + 'use strict'; + var AttributeListItemLongTextView = AttributeListItemView.extend({ + + template: template, + partials: { + attributeListItem: attributeListItem + }, + initialize: function () { + AttributeListItemView.prototype.initialize.apply(this, arguments); + }, + updateValue: function () { + var el = this.$el.find('textarea.value:first'); + this.model.set({ + value: this.getValue(el) + }); + } + }); + return AttributeListItemLongTextView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_lov.js b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_lov.js new file mode 100644 index 0000000000..13ee8cb84d --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_lov.js @@ -0,0 +1,114 @@ +/*global $,_,define,App*/ +define([ + 'common-objects/views/attributes/attribute_list_item', + 'text!common-objects/templates/attributes/attribute_list_item.html', + 'text!common-objects/templates/attributes/attribute_list_item_lov.html' +], function (AttributeListItemView, attributeListItem, template) { + 'use strict'; + var AttributeListItemLOVView = AttributeListItemView.extend({ + + template: template, + partials: { + attributeListItem: attributeListItem + }, + + possibleValues : null, + + initialize: function () { + AttributeListItemView.prototype.initialize.apply(this, arguments); + }, + + rendered:function(){ + + this.modelChange = function(){}; + + var type = this.model.get('type') || this.model.get('attributeType'); + var items = this.model.get('items'); + var typeCopy = type; + if (type === 'LOV') { + type = this.model.get('lovName'); + } + if ((this.editMode && !this.attributesLocked && !this.model.get('locked') && !this.model.get('mandatory')) || this.displayOnly) { + this.$el.find('select.type').val(type); + } else { + this.$('div.type').html(type); + } + + if (this.model.get('locked') === true && !type) { + this.$el.find('div.type').html(App.config.i18n.LOV); + } + + this.$el.addClass('well'); + this.$('input[required]').customValidity(App.config.i18n.REQUIRED_FIELD); + + var that = this; + if (!items) { + if (typeCopy === 'LOV') { + $.ajax({ + type: 'GET', + url: App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/lov/' + type, + contentType: 'application/json; charset=utf-8', + success: function (data) { + var possibleValues = data.values; + that.model.set('items', possibleValues); + that.addOptions(possibleValues); + }, + error: function () { + } + }); + } + } else { + if (typeCopy === 'LOV') { + var lovName = this.model.get('lovName'); + if (!lovName) { + if (this.editMode && !this.attributesLocked) { + this.$el.find('select.type').parent().addClass('listOfValues'); + this.$el.find('select.type').parent().html(App.config.i18n.LOV); + } else { + this.$('div.type').html(App.config.i18n.LOV); + } + + if (this.model.get('locked') === true) { + this.$el.find('div.type').html(App.config.i18n.LOV); + } + } + this.addOptions(items); + } + } + + }, + + addOptions: function(items){ + var select = this.$('.value'); + var defaultValue = 0; + select.html(''); + _.each(items, function(item, index){ + if(index === 0){ + defaultValue = item.name; + } + select.append(''); + }); + var selectedValue = this.model.get('value'); + if(selectedValue){ + select.val(selectedValue); + }else{ + select.val(defaultValue); + } + + this.updateValue(); + }, + + typeChanged: function(){ + this.model.set('items', null); + AttributeListItemView.prototype.typeChanged.apply(this, arguments); + }, + + updateValue: function () { + var el = this.$el.find('.value'); + this.model.set({ + value: el.val() + }); + } + }); + return AttributeListItemLOVView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_number.js b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_number.js new file mode 100644 index 0000000000..b83738f128 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_number.js @@ -0,0 +1,20 @@ +/*global define*/ +define([ + 'common-objects/views/attributes/attribute_list_item', + 'text!common-objects/templates/attributes/attribute_list_item.html', + 'text!common-objects/templates/attributes/attribute_list_item_number.html' +], function (AttributeListItemView, attributeListItem, template) { + 'use strict'; + var AttributeListItemNumberView = AttributeListItemView.extend({ + + template: template, + + partials: { + attributeListItem: attributeListItem + }, + initialize: function () { + AttributeListItemView.prototype.initialize.apply(this, arguments); + } + }); + return AttributeListItemNumberView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_text.js b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_text.js new file mode 100644 index 0000000000..97710df216 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_text.js @@ -0,0 +1,19 @@ +/*global define*/ +define([ + 'common-objects/views/attributes/attribute_list_item', + 'text!common-objects/templates/attributes/attribute_list_item.html', + 'text!common-objects/templates/attributes/attribute_list_item_text.html' +], function (AttributeListItemView, attributeListItem, template) { + 'use strict'; + var AttributeListItemTextView = AttributeListItemView.extend({ + + template: template, + partials: { + attributeListItem: attributeListItem + }, + initialize: function () { + AttributeListItemView.prototype.initialize.apply(this, arguments); + } + }); + return AttributeListItemTextView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_url.js b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_url.js new file mode 100644 index 0000000000..0918d70416 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/attributes/attribute_list_item_url.js @@ -0,0 +1,19 @@ +/*global define*/ +define([ + 'common-objects/views/attributes/attribute_list_item', + 'text!common-objects/templates/attributes/attribute_list_item.html', + 'text!common-objects/templates/attributes/attribute_list_item_url.html' +], function (AttributeListItemView, attributeListItem, template) { + 'use strict'; + var AttributeListItemUrlView = AttributeListItemView.extend({ + + template: template, + partials: { + attributeListItem: attributeListItem + }, + initialize: function () { + AttributeListItemView.prototype.initialize.apply(this, arguments); + } + }); + return AttributeListItemUrlView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/attributes/attributes.js b/docdoku-web-front/app/js/common-objects/views/attributes/attributes.js new file mode 100644 index 0000000000..cacd8f9c44 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/attributes/attributes.js @@ -0,0 +1,110 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/views/base', + 'common-objects/views/attributes/attribute_list', + 'text!common-objects/templates/attributes/attributes.html', + 'common-objects/collections/lovs' +], function (Backbone,Mustache, BaseView, AttributeListView, template, LOVCollection) { + 'use strict'; + var AttributesView = BaseView.extend({ + + template: template, + + editMode: true, + + attributesLocked: false, + + lovs:new LOVCollection(), + + collection: function () { + return new Backbone.Collection(); + }, + + setEditMode: function (editMode) { + this.editMode = editMode; + }, + + setAttributesLocked: function (attributesLocked) { + this.attributesLocked = attributesLocked; + }, + + initialize: function () { + _.bindAll(this); + BaseView.prototype.initialize.apply(this, arguments); + this.events['click .add'] = this.addAttribute; + this.events['update-sort'] = this.updateSort; + }, + + updateSort: function(event,model,index){ + this.collection.remove(model,{silent:true}); + this.collection.add(model, {at: index,silent:true}); + }, + + render: function () { + var data = { + view: this.viewToJSON(), + frozenMode: !this.editMode || this.attributesLocked, + i18n: App.config.i18n + }; + this.$el.html(Mustache.render(template, data)); + var that = this; + this.lovs.fetch().success(function(){ + that.rendered(); + }); + return this; + }, + + rendered: function () { + var attributesViewList = new AttributeListView({ + el: this.$('#items-' + this.cid), + collection: this.collection, + lovs:this.lovs + }); + this.attributesView = this.addSubView( + attributesViewList + ); + + this.attributesView.setEditMode(this.editMode); + this.attributesView.setAttributesLocked(this.attributesLocked); + if(this.editMode){ + this.attributesView.$el.sortable({ + handle: '.sortable-handler', + placeholder: 'list-item well highlight', + stop: function(event, ui) { + ui.item.trigger('drop', ui.item.index()); + } + }); + } + + attributesViewList.collectionReset(); + }, + + addAttribute: function () { + this.collection.add({ + mandatory: false, + name: '', + type: 'TEXT', + value: '', + lovName:null, + locked:false + }); + }, + + addAndFillAttribute: function (attribute) { + this.collection.add({ + mandatory: attribute.isMandatory(), + name: attribute.getName(), + type: attribute.getType(), + value: attribute.getValue(), + lovName: attribute.getLOVName(), + items:attribute.getItems(), + locked:attribute.getLocked() + }); + } + + }); + + return AttributesView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/attributes/template_new_attribute_list.js b/docdoku-web-front/app/js/common-objects/views/attributes/template_new_attribute_list.js new file mode 100644 index 0000000000..7e4c306dda --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/attributes/template_new_attribute_list.js @@ -0,0 +1,31 @@ +/*global define,_*/ +define([ + 'common-objects/views/components/list', + 'common-objects/views/attributes/template_new_attribute_list_item' +], function (ListView, TemplateNewAttributeListItemView) { + 'use strict'; + var TemplateNewAttributeListView = ListView.extend({ + + initialize: function () { + ListView.prototype.initialize.apply(this, arguments); + this.lovs = this.options.lovs; + this.editMode = this.options.editMode; + }, + + itemViewFactory: function (model) { + return new TemplateNewAttributeListItemView({ + model: model, + lovs:this.lovs, + editMode:this.editMode, + attributesLocked: this.attributesLocked + }); + }, + setAttributesLocked: function(attributesLocked) { + this.attributesLocked = attributesLocked; + _.each(this.subViews,function(view) { + view.setAttributesLocked(attributesLocked); + }); + } + }); + return TemplateNewAttributeListView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/attributes/template_new_attribute_list_item.js b/docdoku-web-front/app/js/common-objects/views/attributes/template_new_attribute_list_item.js new file mode 100644 index 0000000000..2b01d22ca2 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/attributes/template_new_attribute_list_item.js @@ -0,0 +1,122 @@ +/*global define,App*/ +define([ + 'common-objects/views/components/list_item', + 'text!common-objects/templates/attributes/template_new_attribute_list_item.html' +], function (ListItemView, template) { + 'use strict'; + var TemplateNewAttributeListItemView = ListItemView.extend({ + + template: template, + + tagName: 'div', + + lovList:null, + + initialize: function () { + ListItemView.prototype.initialize.apply(this, arguments); + this.lovList = this.options.lovs; + this.editMode = this.options.editMode; + this.events['change .type'] = 'typeChanged'; + this.events['change .name'] = 'updateName'; + this.events['click .fa-times'] = 'removeAction'; + this.events['change .attribute-mandatory input'] = 'mandatoryChanged'; + this.events['change .attribute-locked input'] = 'lockedChanged'; + this.events.drop = 'drop'; + this.templateExtraData = { + lovs : this.lovList.models, + editMode : this.editMode, + attributesLocked: this.options.attributesLocked + }; + }, + rendered: function () { + this.$el.addClass('well'); + this.$('input[required]').customValidity(App.config.i18n.REQUIRED_FIELD); + + var type = this.model.get('attributeType'); + if (type !== 'LOV'){ + this.$('select.type:first').val(type); + }else{ + var lovNameSelected = this.model.get('lovName'); + this.$('select.type:first').val(lovNameSelected); + } + + this.setVisibility(); + }, + removeAction: function () { + this.model.destroy({ + dataType: 'text' // server doesn't send a json hash in the response body + }); + }, + typeChanged: function (evt) { + var typeValue = evt.target.value; + if (typeValue === 'TEXT' || typeValue === 'LONG_TEXT' || typeValue === 'BOOLEAN' || typeValue === 'DATE' || typeValue === 'NUMBER' || typeValue === 'URL' ){ + this.model.set({ + attributeType: typeValue + }); + this.setChoice(null); + }else{ + this.model.set({ + attributeType: 'LOV' + }); + this.setChoice(typeValue); + } + }, + updateName: function () { + this.model.set({ + name: this.$el.find('input.name:first').val() + }); + }, + mandatoryChanged: function () { + + var mandatory = this.$el.find('.attribute-mandatory input')[0].checked; + if(this.attributesLocked) { + this.model.set({ + mandatory: mandatory + }); + } else { + this.model.set({ + mandatory: mandatory, + locked: this.model.get('locked') || mandatory + }); + } + + }, + + lockedChanged: function(){ + var locked = this.$el.find('.attribute-locked input')[0].checked; + this.model.set({ + mandatory : !locked ? false : this.model.get('mandatory'), + locked: locked + }); + + }, + + setAttributesLocked: function(attributesLocked) { + this.attributesLocked = attributesLocked; + this.attributesLocked = attributesLocked; + this.templateExtraData = { + lovs : this.lovList.models, + editMode : this.editMode, + attributesLocked: this.attributesLocked + }; + this.render(); + }, + + setVisibility: function () { + if (!this.editMode) { + this.$el.find('i.fa-bars:first').addClass('invisible'); + this.$el.find('a.fa-times:first').addClass('invisible'); + } + }, + + drop: function(event, index) { + this.$el.trigger('update-sort', [this.model, index]); + }, + setChoice:function(value){ + this.model.set({ + lovName:value + }); + } + }); + return TemplateNewAttributeListItemView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/attributes/template_new_attributes.js b/docdoku-web-front/app/js/common-objects/views/attributes/template_new_attributes.js new file mode 100644 index 0000000000..e41397e696 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/attributes/template_new_attributes.js @@ -0,0 +1,113 @@ +/*global _,define*/ +define([ + 'backbone', + 'common-objects/views/base', + 'common-objects/views/attributes/template_new_attribute_list', + 'text!common-objects/templates/attributes/template_new_attributes.html', + 'common-objects/collections/lovs' +], function (Backbone,BaseView, TemplateNewAttributeListView, template, LOVCollection) { + 'use strict'; + var TemplateNewAttributesView = BaseView.extend({ + + template: template, + + attributesLocked: false, + //boolean to check if the list of attributes can be frozen + unfreezable: false, + + collection: function () { + return new Backbone.Collection(); + }, + + initialize: function () { + BaseView.prototype.initialize.apply(this, arguments); + this.lovs = new LOVCollection(); + this.events['click .add'] = this.addAttribute; + this.events['change .lock input'] = this.attributesLockedChange; + this.events['update-sort'] = this.updateSort; + if (this.options.attributesLocked) { + this.attributesLocked = this.options.attributesLocked; + } + + if(this.options.unfreezable) { + this.unfreezable = this.options.unfreezable; + } + + if(this.options.editMode){ + this.editMode = this.options.editMode; + } + + this.templateExtraData = { + editMode : this.editMode, + unfreezable: this.unfreezable, + attributesLocked: this.attributesLocked + }; + }, + + updateSort: function(event,model,index){ + this.collection.remove(model,{silent:true}); + this.collection.add(model, {at: index,silent:true}); + }, + + rendered: function () { + this.lovs.fetch().success(this.displayAttribute); + }, + + displayAttribute: function(){ + var listViewAttributes = new TemplateNewAttributeListView({ + el: '#items-' + this.cid, + collection: this.collection, + lovs:this.lovs, + editMode: this.editMode + }); + this.attributesView = this.addSubView( + listViewAttributes + ); + listViewAttributes.collectionReset(); + + var lockInput = this.$el.find('.lock input')[0]; + if(lockInput){ + lockInput.checked = this.attributesLocked; + } + this.attributesView.setAttributesLocked(this.attributesLocked); + + this.attributesView.$el.sortable({ + handle: '.sortable-handler', + placeholder: 'list-item well highlight', + stop: function(event, ui) { + ui.item.trigger('drop', ui.item.index()); + } + }); + + }, + + addAttribute: function () { + this.collection.add({ + name: '', + attributeType: 'TEXT' + }); + }, + + attributesLockedChange: function (e) { + this.attributesLocked = e.target.checked; + this.attributesView.setAttributesLocked(this.attributesLocked); + if (!this.attributesLocked) { + _.map(this.collection.models, function (attribute) { + attribute.set('locked', false); + return attribute.set('mandatory', false); + }); + this.$el.find('.attribute-locked input').prop('checked', false); + } else { + _.map(this.collection.models, function (attribute) { + attribute.set('locked', true); + }); + } + + }, + + isAttributesLocked: function () { + return this.attributesLocked; + } + }); + return TemplateNewAttributesView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/base.js b/docdoku-web-front/app/js/common-objects/views/base.js new file mode 100644 index 0000000000..09844156cb --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/base.js @@ -0,0 +1,183 @@ +/*global _,define,App,$*/ +define([ + 'backbone', + 'mustache', + 'require', + 'text!common-objects/templates/alert.html' +], function (Backbone, Mustache, require, alertTemplate) { + 'use strict'; + var BaseView = Backbone.View.extend({ + + modelEvents: { + 'change': 'modelChange', + 'sync': 'modelSync', + 'destroy': 'modelDestroy' + }, + collectionEvents: { + 'reset': 'collectionReset', + 'add': 'collectionAdd', + 'remove': 'collectionRemove' + }, + initialize: function () { + + // Owned events + this.events = {}; + + // Owned child views + this.subViews = {}; + + // Collection creation from factory + if (_.isFunction(this.collection)) { + this.collection = this.collection(); + } + + // Bindings + _.bindAll(this); + this.bindModel(); + this.bindCollection(); + }, + destroy: function () { + this.deleteSubViews(); + this.undelegateEvents(); + this.unbindCollection(); + this.unbindModel(); + this.unbind(); + if (this.parentView) { + delete this.parentView.subViews[this.cid]; + } + if (_.isFunction(this.destroyed)) { + this.destroyed(); + } + }, + destroyed: function () { + this.remove(); + }, + clear: function () { + this.$el.html(''); + }, + addSubView: function (view) { + view.parentView = this; + this.subViews[view.cid] = view; + if (_.isFunction(this.viewAdded)) { + this.viewAdded(view); + } + return view; + }, + deleteSubViews: function () { + _.each(_.values(this.subViews), function (view) { + if (_.isFunction(view.destroy)) { + view.destroy(); + }else{ + view.remove(); + } + }); + }, + _eventsBindings: function (options) { + var target = options.target; + var events = options.events; + var action = options.action; + var that = this; + _.each(events, function(key,evt){ + if (key in that && that[key]) { + target[action](evt, that[key]); + } + }); + }, + bindModel: function () { + if (this.model) { + this._eventsBindings({ + target: this.model, + events: this.modelEvents, + action: 'bind' + }); + } + }, + bindCollection: function () { + if (this.collection) { + this._eventsBindings({ + target: this.collection, + events: this.collectionEvents, + action: 'bind' + }); + } + }, + unbindModel: function () { + if (this.model) { + this._eventsBindings({ + target: this.model, + events: this.modelEvents, + action: 'unbind' + }); + } + }, + unbindCollection: function () { + if (this.collection) { + this._eventsBindings({ + target: this.collection, + events: this.collectionEvents, + action: 'unbind' + }); + } + }, + render: function () { + this.deleteSubViews(); + var html = ''; + if (this.template) { + var partials = this.partials ? this.partials : null; + html = Mustache.render(this.template, this.renderData(), partials); + } + this.$el.html(html); + if (_.isFunction(this.rendered)) { + this.rendered(); + } + return this; + }, + renderData: function () { + var data = {}; + data.i18n = App.config.i18n; + data.workspaceId = App.config.workspaceId; + data.view = this.viewToJSON(); + if (this.model) { + data.model = this.modelToJSON(); + } + if (this.collection) { + data.collection = this.collectionToJSON(); + } + if (this.templateExtraData) { + _.extend(data, this.templateExtraData); + } + return data; + }, + viewToJSON: function () { + return { + cid: this.cid + }; + }, + modelToJSON: function () { + return this.model.toJSON ? + this.model.toJSON() : + this.model; + }, + collectionToJSON: function () { + return this.collection.toJSON ? + this.collection.toJSON() : + this.collection; + }, + alert: function (options) { + // AlertView not used to resolve circular dependency + var titles = { + 'error': App.config.i18n.ERROR + }; + options.title = options.title ? options.title : titles[options.type]; + var html = Mustache.render(alertTemplate, { + model: { + type: options.type, + title: options.title, + message: options.message + } + }); + $('#alerts-' + this.cid).html(html); + } + }); + return BaseView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/components/collapsible_list.js b/docdoku-web-front/app/js/common-objects/views/components/collapsible_list.js new file mode 100644 index 0000000000..da465cee19 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/components/collapsible_list.js @@ -0,0 +1,27 @@ +/*global _,define*/ +define([ + 'common-objects/views/components/list' +], function (ListView) { + 'use strict'; + var CollapsibleListView = ListView.extend({ + show: function () { + this.$el.show(); + this.$el.addClass('in'); + var that = this; + this.collection.fetch({ + reset: true, + success: function () { + if (_.isFunction(that.shown)) { + that.shown(); + } + } + }); + }, + hide: function () { + this.$el.hide(); + this.$el.removeClass('in'); + this.clear(); + } + }); + return CollapsibleListView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/components/list.js b/docdoku-web-front/app/js/common-objects/views/components/list.js new file mode 100644 index 0000000000..23e688e17f --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/components/list.js @@ -0,0 +1,28 @@ +/*global define*/ +define([ + 'common-objects/views/base' +], function (BaseView) { + 'use strict'; + var ListView = BaseView.extend({ + collectionReset: function () { + this.clear(); + this.render(); + this.collection.each(this.createItemView); + this.trigger('_ready'); + }, + collectionAdd: function () { + this.collectionReset(); + }, + collectionRemove: function () { + this.collectionReset(); + }, + createItemView: function (model) { + var view = this.addSubView( + this.itemViewFactory(model) + ); + this.$el.append(view.el); + view.render(); + } + }); + return ListView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/components/list_item.js b/docdoku-web-front/app/js/common-objects/views/components/list_item.js new file mode 100644 index 0000000000..ceb36884fe --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/components/list_item.js @@ -0,0 +1,19 @@ +/*global define*/ +define([ + 'common-objects/views/base' +], function (BaseView) { + 'use strict'; + var ListItemView = BaseView.extend({ + className: 'list-item', + modelDestroy: function () { + this.destroy(); + }, + modelChange: function () { + this.render(); + }, + modelSync: function () { + this.render(); + } + }); + return ListItemView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/components/modal.js b/docdoku-web-front/app/js/common-objects/views/components/modal.js new file mode 100644 index 0000000000..9add25f0fb --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/components/modal.js @@ -0,0 +1,44 @@ +/*global define,$*/ +define([ + 'common-objects/views/base' +], function (BaseView) { + 'use strict'; + /** + * A Modal window may have a primaryAction function bind on a '.btn-primary' button + */ + var ModalView = BaseView.extend({ + initialize: function () { + BaseView.prototype.initialize.apply(this, arguments); + if (this.cancelAction) { + this.events['click .modal-footer .cancel'] = 'cancelAction'; + } + if (this.primaryAction) { + this.events['click .modal-footer .btn-primary'] = 'primaryAction'; + } + this.$el.one('shown', this.shown); + this.$el.one('hidden', this.hidden); + this.show(); + }, + show: function () { + this.$el.modal('show'); + }, + shown: function () { + this.render(); + }, + hide: function () { + this.$el.modal('hide'); + // Sometimes the destroy is too fast. + // Bootstrap should have thrown hidden only when all is finished + // see: bootstrap hideModal + $('.modal-backdrop').remove(); // TODO: Fin a way to remove the hack. + }, + hidden: function () { + this.destroy(); + }, + cancelAction: function () { + this.hide(); + return false; + } + }); + return ModalView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/document/document_iteration.js b/docdoku-web-front/app/js/common-objects/views/document/document_iteration.js new file mode 100644 index 0000000000..649db555ad --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/document/document_iteration.js @@ -0,0 +1,476 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/views/components/modal', + 'common-objects/views/file/file_list', + 'common-objects/views/attributes/attributes', + 'common-objects/views/workflow/lifecycle', + 'common-objects/views/linked/linked_documents', + 'common-objects/views/used_by/used_by_list_view', + 'common-objects/models/tag', + 'common-objects/views/tags/tag', + 'common-objects/collections/linked/linked_document_collection', + 'text!common-objects/templates/document/document_iteration.html', + 'common-objects/views/prompt', + 'common-objects/utils/date' +], function (Backbone, Mustache, ModalView, FileListView, DocumentAttributesView, LifecycleView, LinkedDocumentsView, UsedByListView, Tag, TagView, LinkedDocumentCollection, template, PromptView, date) { + 'use strict'; + + var IterationView = ModalView.extend({ + + initialize: function () { + + this.iterations = this.model.getIterations(); + this.iteration = this.options.iteration ? this.iterations.get(this.options.iteration) : this.model.getLastIteration(); + + ModalView.prototype.initialize.apply(this, arguments); + + this.events['click a#previous-iteration'] = 'onPreviousIteration'; + this.events['click a#next-iteration'] = 'onNextIteration'; + this.events['click .modal-footer button.btn-primary'] = 'interceptSubmit'; + this.events['submit form'] = 'onSubmitForm'; + this.events['click a.document-path'] = 'onFolderPathClicked'; + this.events['click .action-checkin'] = 'actionCheckin'; + this.events['click .action-checkout'] = 'actionCheckout'; + this.events['click .action-undocheckout'] = 'actionUndoCheckout'; + this.events['close-modal-request'] = 'closeModal'; + + this.tagsToRemove = []; + + this.bindDom(); + + }, + + bindDom: function () { + this.revisionNote = this.$('#inputRevisionNote'); + this.$modal = this.$('#product_details_modal'); + }, + onPreviousIteration: function () { + if (this.iterations.hasPreviousIteration(this.iteration)) { + this.switchIteration(this.iterations.previous(this.iteration)); + } + return false; + }, + + onNextIteration: function () { + if (this.iterations.hasNextIteration(this.iteration)) { + this.switchIteration(this.iterations.next(this.iteration)); + } + return false; + }, + + switchIteration: function (iteration) { + this.iteration = iteration; + var activeTabIndex = this.getActiveTabIndex(); + this.render(); + this.activateTab(activeTabIndex); + }, + + getActiveTabIndex: function () { + return this.tabs.filter('.active').index(); + }, + + activateTab: function (index) { + this.tabs.eq(index).children().tab('show'); + }, + activateFileTab: function () { + this.activateTab(3); + }, + validation: function () { + + /*checking attributes*/ + var ok = true; + var attributes = this.attributesView.model; + attributes.each(function (item) { + if (!item.isValid()) { + ok = false; + } + }); + if (!ok) { + this.getPrimaryButton().attr('disabled', 'disabled'); + } else { + this.getPrimaryButton().removeAttr('disabled'); + } + }, + + render: function () { + this.deleteSubViews(); + + var editMode = this.model.isCheckoutByConnectedUser() && this.iterations.isLast(this.iteration); + + var data = { + isCheckoutByConnectedUser: this.model.isCheckoutByConnectedUser(), + isLocked: this.model.isLocked(), + isCheckout: this.model.isCheckout(), + isReleased: this.model.isReleased(), + isObsolete: this.model.isObsolete(), + editMode: editMode, + docRevision: this.model.toJSON(), + i18n: App.config.i18n, + permalink: this.model.getPermalink(), + hasIterations: this.iterations.length + }; + + data.isLocked = this.model.isCheckout() && !this.model.isCheckoutByConnectedUser(); + data.docRevision.reference = this.model.getReference(); + data.docRevision.creationDate = date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + data.docRevision.creationDate + ); + data.docRevision.releaseDate = this.model.getReleaseDate(); + data.docRevision.obsoleteDate = this.model.getObsoleteDate(); + + + var fullPath = data.docRevision.path; + var re = new RegExp(App.config.workspaceId, ''); + fullPath = fullPath.replace(re, ''); + this.folderPath = fullPath.replace(/\//g, ':'); + if (this.folderPath[0] === ':') { + this.folderPath = this.folderPath.substr(1, this.folderPath.length); + } + + + if (this.model.hasIterations()) { + var hasNextIteration = this.iterations.hasNextIteration(this.iteration); + var hasPreviousIteration = this.iterations.hasPreviousIteration(this.iteration); + data.iteration = this.iteration.toJSON(); + data.iteration.hasNextIteration = hasNextIteration; + data.iteration.hasPreviousIteration = hasPreviousIteration; + data.reference = this.iteration.getId(); + + data.iteration.creationDate = date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + data.iteration.creationDate + ); + + data.iteration.modificationDate = date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + data.iteration.modificationDate + ); + + if (editMode) { + data.iteration.revisionDate = data.iteration.creationDate; + } else { + data.iteration.revisionDate = date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + data.iteration.checkInDate + ); + } + } + + if (this.model.isCheckout()) { + data.docRevision.checkOutDate = date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + data.docRevision.checkOutDate + ); + } + + if (App.config.documentConfigSpec !== 'latest') { + data.isForBaseline = true; + } + + /*Main window*/ + var html = Mustache.render(template, data); + this.$el.html(html); + + this.tabs = this.$('.nav-tabs li'); + + this.attributesView = this.addSubView( + new DocumentAttributesView({ + el: '#iteration-additional-attributes-container' + }) + ); + + this.attributesView.setAttributesLocked(this.model.isAttributesLocked()); + + this.attributesView.setEditMode(editMode); + this.attributesView.render(); + + var that = this; + + if (this.model.hasIterations()) { + if (this.iteration.getAttributes().length) { + this.iteration.getAttributes().each(function (item) { + that.attributesView.addAndFillAttribute(item); + }); + } + + this.initFileListView(editMode); + this.initLinkedDocumentsView(editMode); + this.initUsedByListView(); + } + + if (this.model.get('workflow')) { + + this.lifecycleView = new LifecycleView({ + el: '#tab-iteration-lifecycle' + }).setWorkflow(this.model.get('workflow')).setEntityType('documents').render(); + + this.lifecycleView.on('lifecycle:change', function () { + that.model.fetch({ + success: function () { + that.lifecycleView.setWorkflow(that.model.get('workflow')).setEntityType('documents').render(); + } + }); + }); + + } else { + this.$('a[href=#tab-iteration-lifecycle]').hide(); + } + + this.$authorLink = this.$('.author-popover'); + this.$checkoutUserLink = this.$('.checkout-user-popover'); + this.$releaseUserLink = this.$('.release-user-popover'); + this.$obsoleteUserLink = this.$('.obsolete-user-popover'); + this.bindUserPopover(); + + date.dateHelper(this.$('.date-popover')); + + this.tagsManagement(editMode); + + return this; + }, + + bindUserPopover: function () { + this.$authorLink.userPopover(this.model.attributes.author.login, this.model.id, 'right'); + if (this.model.isCheckout()) { + this.$checkoutUserLink.userPopover(this.model.getCheckoutUser().login, this.model.id, 'right'); + } + if (this.model.getReleaseAuthor()) { + this.$releaseUserLink.userPopover(this.model.getReleaseAuthorLogin(), this.model.id, 'right'); + } + if (this.model.isObsolete()) { + this.$obsoleteUserLink.userPopover(this.model.getObsoleteAuthorLogin(), this.model.id, 'right'); + } + }, + + interceptSubmit: function () { + this.isValid = !this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + + if (this.isValid) { + + /*saving iteration*/ + var _this = this; + + this.iteration.save({ + revisionNote: this.$('#inputRevisionNote').val(), + instanceAttributes: this.attributesView.collection.toJSON(), + linkedDocuments: this.linkedDocumentsView.collection.toJSON() + }, { + success: function () { + _this.closeModal(); + _this.model.fetch(); + } + }); + + /*There is a parsing problem at saving time*/ + var files = this.iteration.get('attachedFiles'); + + /*tracking back files*/ + this.iteration.set({ + attachedFiles: files + }); + + /* + *saving new files : nothing to do : it's already saved + *deleting unwanted files + */ + this.fileListView.deleteFilesToDelete(); + + /* + * Delete tags if needed + * */ + + this.deleteClickedTags(); + + } + + // prevent page reload when pressing enter + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + cancelAction: function () { + + if (this.model.hasIterations()) { + //Abort file upload and delete new files + this.fileListView.deleteNewFiles(); + } + ModalView.prototype.cancelAction.call(this); + }, + + getPrimaryButton: function () { + return this.$('div.modal-footer button.btn-primary'); + }, + + initFileListView: function (editMode) { + this.fileListView = new FileListView({ + baseName: this.iteration.getBaseName(), + deleteBaseUrl: this.iteration.baseUrl(), + uploadBaseUrl: this.iteration.getUploadBaseUrl(), + collection: this.iteration.getAttachedFiles(), + editMode: editMode + }).render(); + + /* Add the fileListView to the tab */ + this.$('#iteration-files').html(this.fileListView.el); + }, + + initLinkedDocumentsView: function (editMode) { + this.linkedDocumentsView = new LinkedDocumentsView({ + editMode: editMode, + commentEditable: true, + documentIteration: this.iteration, + collection: new LinkedDocumentCollection(this.iteration.getLinkedDocuments()) + }).render(); + + /* Add the documentLinksView to the tab */ + this.$('#iteration-links').html(this.linkedDocumentsView.el); + }, + + initUsedByListView: function () { + this.usedByListView = new UsedByListView({ + linkedDocumentIterationId: this.iteration.getIteration(), + linkedDocument: this.model + }).render(); + + /* Add the usedByListView to the tab */ + this.$('#iteration-used-by').html(this.usedByListView.el); + }, + + tagsManagement: function (editMode) { + + var $tagsZone = this.$('.master-tags-list'); + var that = this; + + _.each(this.model.attributes.tags, function (tagLabel) { + + var tagView; + + var tagViewParams = editMode ? + { + model: new Tag({id: tagLabel, label: tagLabel}), + isAdded: true, + clicked: function () { + that.tagsToRemove.push(tagLabel); + tagView.$el.remove(); + } + } : + { + model: new Tag({id: tagLabel, label: tagLabel}), + isAdded: true, + clicked: function () { + that.tagsToRemove.push(tagLabel); + tagView.$el.remove(); + that.model.removeTag(tagLabel, function () { + if (that.model.collection.parent) { + if (_.contains(that.tagsToRemove, that.model.collection.parent.id)) { + that.model.collection.remove(that.model); + } + } + }); + tagView.$el.remove(); + } + }; + + tagView = new TagView(tagViewParams).render(); + + $tagsZone.append(tagView.el); + + }); + + }, + + deleteClickedTags: function () { + if (this.tagsToRemove.length) { + var that = this; + this.model.removeTags(this.tagsToRemove, function () { + if (that.model.collection.parent) { + if (_.contains(that.tagsToRemove, that.model.collection.parent.id)) { + that.model.collection.remove(that.model); + } + } + }); + } + }, + + actionCheckin: function () { + this.interceptSubmit(); + if (this.isValid) { + + /*saving iteration*/ + var that = this; + + this.iteration.save({ + revisionNote: this.$('#inputRevisionNote').val() || null, + instanceAttributes: this.attributesView.collection.toJSON(), + linkedDocuments: this.linkedDocumentsView.collection.toJSON() + }, { + success: function () { + that.model.checkin().success(function () { + that.onSuccess(); + }); + } + }); + + /*There is a parsing problem at saving time*/ + var files = this.iteration.get('attachedFiles'); + + /*tracking back files*/ + this.iteration.set({ + attachedFiles: files + }); + + /* + *saving new files : nothing to do : it's already saved + *deleting unwanted files + */ + this.fileListView.deleteFilesToDelete(); + + } + }, + + actionCheckout: function () { + var self = this; + self.model.checkout().success(function () { + self.onSuccess(); + }); + + }, + actionUndoCheckout: function () { + var self = this; + self.model.undocheckout().success(function () { + self.onSuccess(); + }); + + }, + + onSuccess: function () { + this.model.fetch().success(function () { + this.iteration = this.model.getLastIteration(); + this.iterations = this.model.getIterations(); + this.render(); + this.activateTab(1); + Backbone.Events.trigger('document:iterationChange'); + }.bind(this)); + }, + + closeModal: function () { + this.hide(); + }, + + + onFolderPathClicked: function () { + var redirectPath = this.folderPath ? 'folders/' + encodeURIComponent(this.folderPath) : 'folders'; + App.router.navigate(App.config.workspaceId + '/' + redirectPath, {trigger: true}); + ModalView.prototype.cancelAction.call(this); + } + + }); + return IterationView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/documents/checkbox_list.js b/docdoku-web-front/app/js/common-objects/views/documents/checkbox_list.js new file mode 100644 index 0000000000..1e9957946b --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/documents/checkbox_list.js @@ -0,0 +1,41 @@ +/*global _,define*/ +define([ + 'common-objects/views/components/list' +], function (ListView) { + 'use strict'; + var CheckboxListView = ListView.extend({ + initialize: function () { + ListView.prototype.initialize.apply(this, arguments); + this.checkToggle = '#check-toggle-' + this.cid; + this.events['click ' + this.checkToggle] = 'toggle'; + }, + toggle: function () { + + if (this.$(this.checkToggle).is(':checked')) { + _.each(_.values(this.subViews), function (view) { + view.check(); + }); + } else { + _.each(_.values(this.subViews), function (view) { + view.uncheck(); + }); + } + }, + viewAdded: function (view) { + var that = this; + view.on('checked unchecked', function () { + that.trigger('selectionChange'); + }); + }, + checkedViews: function () { + return _.filter(_.values(this.subViews), + function (view) { + return view.isChecked; + }); + }, + eachChecked: function (callback) { + _.each(this.checkedViews(), callback); + } + }); + return CheckboxListView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/documents/checkbox_list_item.js b/docdoku-web-front/app/js/common-objects/views/documents/checkbox_list_item.js new file mode 100644 index 0000000000..651174ddc2 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/documents/checkbox_list_item.js @@ -0,0 +1,40 @@ +/*global define*/ +define([ + 'common-objects/views/components/list_item' +], function (ListItemView) { + + 'use strict'; + + var CheckboxListItemView = ListItemView.extend({ + initialize: function () { + ListItemView.prototype.initialize.apply(this, arguments); + this.checkToggle = '#check-toggle-' + this.cid; + this.events['click ' + this.checkToggle] = 'toggle'; + this.isChecked = false; + }, + rendered: function () { + // Restore check state + this.$(this.checkToggle).prop('checked', this.isChecked); + }, + stateChanged: function () { + this.trigger(this.isChecked ? 'checked' : 'unchecked'); + }, + toggle: function () { + // Save check state to restore it after render + this.isChecked = this.$(this.checkToggle).prop('checked'); + this.stateChanged(); + }, + setCheckState: function (value) { + this.isChecked = value; + this.$(this.checkToggle).prop('checked', value); + this.stateChanged(); + }, + check: function () { + this.setCheckState(true); + }, + uncheck: function () { + this.setCheckState(false); + } + }); + return CheckboxListItemView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/file/file.js b/docdoku-web-front/app/js/common-objects/views/file/file.js new file mode 100644 index 0000000000..996d01ca0c --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/file/file.js @@ -0,0 +1,116 @@ +/*global define*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/file/file.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var FileView = Backbone.View.extend({ + + tagName: 'li', + className: 'file', + + editMode: true, + + events: { + 'change input.file-check': 'fileCheckChanged', + 'dragstart a.fileName': 'dragStart', + 'click .edit-name ':'editName', + 'click .validate-name ':'validateName', + 'click .cancel-name ':'cancelName' + }, + + initialize: function () { + this.editMode = this.options.editMode; + this.model.url = this.options.deleteBaseUrl + '/files/'; + if(this.model.getSubType()) { + this.model.url += this.model.getSubType() + '/'; + } + this.model.url += this.model.get('shortName'); + this.fileUrl = this.options.uploadBaseUrl + this.model.get('shortName'); + }, + + onModelChanged:function(){ + this.fileUrl = this.options.uploadBaseUrl + this.model.get('shortName'); + }, + + fileCheckChanged: function () { + if (this.checkbox.is(':checked')) { + this.fileNameEl.addClass('stroke'); + this.options.filesToDelete.add(this.model); + } else { + this.fileNameEl.removeClass('stroke'); + this.options.filesToDelete.remove(this.model); + } + }, + + dragStart: function (evt) { + evt.dataTransfer.setData('DownloadURL', 'application/octet-stream:' + this.model.get('shortName') + ':' + window.location.origin + '/' + this.fileUrl); + }, + + render: function () { + this.$el.html(Mustache.render(template,{ + url: this.fileUrl, + shortName: this.model.get('shortName'), + editMode: this.editMode + })); + + this.bindDomElements(); + + return this; + }, + + bindDomElements: function () { + this.checkbox = this.$('input.file-check'); + this.fileNameEl = this.$('.fileName'); + this.fileNameInput = this.$('input[name=filename]'); + }, + + editName:function(){ + this.$el.toggleClass('edition'); + this.fileNameInput.focus(); + }, + + validateName:function(){ + var newName = this.fileNameInput.val().trim(); + if (!newName.length) { + return; + } + + var _this = this; + var oldName = _this.model.getShortName(); + + if (this.model.getShortName() !== newName ) { + this.model.setShortName(newName); + + this.model.save().success(function() { + _this.model.rewriteUrl(); + _this.onModelChanged(); + _this.trigger('clear'); + _this.render(); + _this.$el.toggleClass('edition'); + _this.checkbox.change(); + + }).error(function(error) { + + _this.model.setShortName(oldName); + _this.render(); + _this.$el.toggleClass('edition'); + + var errorMessage = error ? error.responseText : _this.model; + _this.trigger('notification','error', errorMessage); + }); + + } else { + this.$el.toggleClass('edition'); + } + + }, + + cancelName:function(){ + this.$el.toggleClass('edition'); + } + }); + + return FileView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/file/file_list.js b/docdoku-web-front/app/js/common-objects/views/file/file_list.js new file mode 100644 index 0000000000..7decba574b --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/file/file_list.js @@ -0,0 +1,291 @@ +/*global _,$,define,App,window*/ +define([ + 'backbone', + 'mustache', + 'unorm', + 'text!common-objects/templates/file/file_list.html', + 'common-objects/models/file/attached_file', + 'common-objects/views/file/file', + 'common-objects/views/alert' + +], function (Backbone, Mustache, unorm, template, AttachedFile, FileView, AlertView) { + 'use strict'; + var FileListView = Backbone.View.extend({ + + tagName: 'div', + className: 'attachedFiles idle', + + editMode: true, + + events: { + 'click form button.cancel-upload-btn': 'cancelButtonClicked', + 'change form input.upload-btn': 'fileSelectHandler', + 'dragover .droppable': 'fileDragHover', + 'dragleave .droppable': 'fileDragHover', + 'drop .droppable': 'fileDropHandler', + 'submit form':'formSubmit', + 'click a.toggle-checkAll': 'toggleCheckAll' + }, + + initialize: function () { + this.editMode = this.options.editMode; + this.title = this.options.title; + + this.xhrs = []; + + // jQuery creates it's own event object, and it doesn't have a + // dataTransfer property yet. This adds dataTransfer to the event object. + $.event.props.push('dataTransfer'); + + // Prevent browser behavior on file drop + window.addEventListener('drop', function (e) { + e.preventDefault(); + return false; + }, false); + + window.addEventListener('ondragenter', function (e) { + e.preventDefault(); + return false; + }, false); + + this.filesToDelete = new Backbone.Collection(); + this.newItems = new Backbone.Collection(); + + if (this.options.singleFile) { + this.listenTo(this.collection, 'add', this.addSingleFile); + } else { + this.listenTo(this.collection, 'add', this.addOneFile); + } + this.checkAll = true; + + }, + + // cancel event and hover styling + fileDragHover: function (e) { + e.stopPropagation(); + e.preventDefault(); + if (e.type === 'dragover') { + this.filedroparea.addClass('hover'); + } + else { + this.filedroparea.removeClass('hover'); + } + }, + + fileDropHandler: function (e) { + this.fileDragHover(e); + if (this.options.singleFile && e.dataTransfer.files.length > 1) { + this.printNotifications('error', App.config.i18n.SINGLE_FILE_RESTRICTION); + return; + } + + _.each(e.dataTransfer.files, this.uploadNewFile.bind(this)); + }, + + fileSelectHandler: function (e) { + _.each(e.target.files, this.uploadNewFile.bind(this)); + }, + + addAllFiles: function () { + this.collection.each(this.addOneFile, this); + }, + + addOneFile: function (attachedFile) { + var self = this; + this.$toggleCheckAll.show(); + var fileView = new FileView({ + model: attachedFile, + filesToDelete: self.filesToDelete, + deleteBaseUrl: self.options.deleteBaseUrl, + uploadBaseUrl: self.options.uploadBaseUrl, + editMode: self.editMode + }); + this.listenTo(fileView,'notification',this.printNotifications); + this.listenTo(fileView,'clear', this.clearNotifications); + fileView.render(); + self.filesUL.append(fileView.el); + }, + + printNotifications: function(type,message) { + this.notifications.append(new AlertView({ + type: type, + message: message + }).render().$el); + }, + + clearNotifications: function() { + this.notifications.text(''); + }, + + addSingleFile: function (attachedFile) { + this.filesUL.empty(); + this.filesToDelete.reset(); + this.addOneFile(attachedFile); + this.$el.trigger('file:uploaded'); + }, + + toggleCheckAll: function() { + this.$('input.file-check').prop('checked',this.checkAll).change(); + this.checkAll = ! this.checkAll; + var text = this.checkAll ? App.config.i18n.CHECK_ALL : App.config.i18n.UNCHECK_ALL; + this.$toggleCheckAll.text(text); + }, + + uploadNewFile: function (file) { + + var self = this; + + var fileName = unorm.nfc(file.name); + var progressBar = $('
                              '+fileName+'
                              '); + var bar = progressBar.find('.bar'); + this.progressBars.append(progressBar); + + this.gotoUploadingState(); + + var newFile = new AttachedFile({ + fullName: this.options.baseName + '/' + fileName, + shortName: fileName + }); + + var xhr = new XMLHttpRequest(); + + xhr.upload.addEventListener('progress', function (evt) { + if (evt.lengthComputable) { + var percentComplete = Math.round(evt.loaded * 100 / evt.total); + bar.width(percentComplete + '%'); + } + }, false); + + xhr.addEventListener('load', function (e) { + + if (e.currentTarget.status !== 200 && e.currentTarget.status !== 201) { + self.xhrFinishedWithError(xhr, App.config.i18n.FILE + ' <' + fileName + '> : ' + e.currentTarget.statusText); + progressBar.remove(); + return false; + } + + self.xhrFinishedWithSuccess(xhr); + progressBar.remove(); + newFile.isNew = function () { + return false; + }; + var existingFile = self.filesToDelete.findWhere({fullName:newFile.getFullName()}); + if(existingFile){ + self.filesToDelete.remove(existingFile); + var checkbox = self.$('[data-fullname="'+existingFile.getShortName()+'"]'); + if(checkbox && checkbox.is(':checked')){ + checkbox.click(); + } + }else{ + self.collection.add(newFile); + self.newItems.add(newFile); + } + }, false); + + xhr.open('POST', this.options.uploadBaseUrl); + + var fd = new window.FormData(); + fd.append('upload', file); + console.log(fd); + xhr.send(fd); + + this.xhrs.push(xhr); + }, + + xhrFinishedWithSuccess: function(xhr) { + this.xhrs.splice(this.xhrs.indexOf(xhr), 1); + if (!this.xhrs.length) { + this.gotoIdleState(); + var message = this.options.singleFile ? App.config.i18n.FILE_UPLOADED : App.config.i18n.FILES_UPLOADED; + this.printNotifications('info',message); + } + + }, + + xhrFinishedWithError: function(xhr, error) { + this.printNotifications('error',error); + + this.xhrs.splice(this.xhrs.indexOf(xhr), 1); + if (!this.xhrs.length) { + this.gotoIdleState(); + } + }, + + finished: function () { + this.$el.find('.progress.progress-striped').remove(); + this.gotoIdleState(); + }, + + cancelButtonClicked: function () { + _.invoke(this.xhrs,'abort'); + //empty the array + this.xhrs.length = 0; + this.finished(); + }, + + deleteFilesToDelete: function () { + /*we need to reverse read because model.destroy() remove elements from collection*/ + while (this.filesToDelete.length !== 0) { + var file = this.filesToDelete.pop(); + this.deleteAFile(file); + } + }, + + deleteNewFiles: function () { + //Abort file upload if there is one + _.invoke(this.xhrs,'abort'); + + //deleting unwanted files that have been added by upload + //we need to reverse read because model.destroy() remove elements from collection + while (this.newItems.length !== 0) { + var file = this.newItems.pop(); + this.deleteAFile(file); + } + }, + + deleteAFile: function(file){ + file.destroy({ + dataType: 'text', // server doesn't send a json hash in the response body + error: function () { + window.alert(App.config.i18n.FILE_DELETION_ERROR.replace('%{id}',file.id)); + } + }); + }, + + gotoIdleState: function () { + this.$el.removeClass('uploading'); + this.$el.addClass('idle'); + this.uploadInput.val(''); + }, + + gotoUploadingState: function () { + this.$el.removeClass('idle'); + this.$el.addClass('uploading'); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, title:this.title, editMode: this.editMode, multiple:!this.options.singleFile})); + + this.bindDomElements(); + + this.addAllFiles(); + + return this; + }, + + formSubmit:function(){ + return false; + }, + + bindDomElements: function () { + this.filedroparea = this.$('.filedroparea'); + this.filesUL = this.$('ul.file-list'); + this.uploadInput = this.$('input.upload-btn'); + this.progressBars = this.$('div.progress-bars'); + this.notifications = this.$('div.notifications'); + //Hide the toggleCheckAll link which will be shown on add new File + this.$toggleCheckAll = this.$('a.toggle-checkAll').hide(); + } + }); + return FileListView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/forbidden.js b/docdoku-web-front/app/js/common-objects/views/forbidden.js new file mode 100644 index 0000000000..eb9bc0c9f7 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/forbidden.js @@ -0,0 +1,63 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/forbidden.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var ForbiddenView = Backbone.View.extend({ + + events: { + 'click .disconnect': 'disconnect', + 'click .back': 'back' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, { + title: App.config.i18n.FORBIDDEN, + content:App.config.i18n.FORBIDDEN_MESSAGE, + backButton: history.length > 2 ? App.config.i18n.HOME_PAGE:null, + disconnectButton: App.config.i18n.LOGOUT + })); + this.bindDomElements(); + return this; + }, + + bindDomElements: function () { + this.$modal = this.$('#forbidden_modal'); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onShown: function () { + }, + + onHidden: function () { + location.reload(); + }, + + back:function(){ + window.location.href = App.config.contextPath + '/workspace-management/'; + }, + + disconnect:function(){ + delete localStorage.jwt; + $.get(App.config.contextPath + '/api/auth/logout').complete(function () { + location.reload(); + }); + } + + }); + + return ForbiddenView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/header.js b/docdoku-web-front/app/js/common-objects/views/header.js new file mode 100644 index 0000000000..0edbf48281 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/header.js @@ -0,0 +1,80 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/header.html' +], function (Backbone, Mustache, template) { + 'use strict'; + + + + var HeaderView = Backbone.View.extend({ + el: '#header', + + events:{ + 'click #logout_link a':'logout' + }, + + render: function () { + + var $el = this.$el; + + var workspaces = App.config.workspaces; + + function isCurrent(workspace){ + workspace.isCurrent = workspace.id === App.config.workspaceId; + } + + if(workspaces){ + _.each(workspaces.administratedWorkspaces, isCurrent); + _.each(workspaces.nonAdministratedWorkspaces, isCurrent); + } + + $el.html(Mustache.render(template, { + connected:App.config.connected, + currentWorkspace: App.config.workspaceId, + contextPath: App.config.contextPath, + administratedWorkspaces: workspaces ? workspaces.administratedWorkspaces: null, + nonAdministratedWorkspaces: workspaces ? workspaces.nonAdministratedWorkspaces: null, + i18n: App.config.i18n, + userName: App.config.userName, + isDocumentManagement: window.location.pathname.match('/document-management/'), + isProductManagement: window.location.pathname.match('/product-management/'), + isProductStructure: window.location.pathname.match('/product-structure/'), + isChangeManagement: window.location.pathname.match('/change-management/'), + isWorkspaceManagement: window.location.pathname.match('/workspace-management/'), + isAdmin:App.config.admin + })); + + $el.show().addClass('loaded'); + + if(this.CoWorkersView) { + var CoWorkersView = this.CoWorkersView; + new CoWorkersView().render(); + } + + return this; + }, + + removeActionDisabled: function () { + this.$('#coworkers_access_module_entries').find('.fa-globe').removeClass('corworker-action-disable').addClass('corworker-action'); + }, + addActionDisabled: function () { + this.$('#coworkers_access_module_entries').find('.fa-globe').removeClass('corworker-action').addClass('corworker-action-disable'); + }, + + setCoWorkersView:function(View){ + this.CoWorkersView = View; + }, + + logout:function(){ + delete localStorage.jwt; + $.get(App.config.contextPath + '/api/auth/logout').complete(function () { + window.location.href = App.config.contextPath + '/?logout=true'; + }); + } + + }); + + return HeaderView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/linked/linked_change_item.js b/docdoku-web-front/app/js/common-objects/views/linked/linked_change_item.js new file mode 100644 index 0000000000..379152b740 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/linked/linked_change_item.js @@ -0,0 +1,45 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/linked/linked_change_item.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var LinkedChangeItemView = Backbone.View.extend({ + + tagName: 'li', + className: 'linked-item well', + + events: { + 'click .delete-linked-item': 'deleteButtonClicked' + }, + + initialize: function () { + }, + + render: function () { + this.$el.html(Mustache.render(template, + { + i18n: App.config.i18n, + linkedItem: this.model, + editMode: this.options.editMode + } + )); + + if (this.model.getPriority) { + this.$el.addClass('priorityColor-' + this.model.getPriority()); + } + + return this; + }, + + deleteButtonClicked: function () { + this.model.collection.remove(this.model); + this.remove(); + return false; + } + + }); + return LinkedChangeItemView; +}); + diff --git a/docdoku-web-front/app/js/common-objects/views/linked/linked_change_items.js b/docdoku-web-front/app/js/common-objects/views/linked/linked_change_items.js new file mode 100644 index 0000000000..0bac0d29c1 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/linked/linked_change_items.js @@ -0,0 +1,96 @@ +/*global define,App,_*/ +define([ + 'backbone', + 'mustache', + 'common-objects/collections/linked/linked_change_item_collection', + 'common-objects/views/linked/linked_change_item', + 'text!common-objects/templates/linked/linked_change_items.html', + 'common-objects/collections/linked/linked_document_collection', + 'common-objects/collections/linked/linked_part_collection' +], function (Backbone, Mustache, LinkedChangeItemCollection, LinkedChangeItemView, template, LinkedDocumentCollection, LinkedPartCollection) { + + 'use strict'; + + var LinkedRequestsView = Backbone.View.extend({ + + tagName: 'div', + className: 'linked-items-view', + + initialize: function () { + this.searchResults = []; + this._subViews = []; + this.options.linkedPartsView = (this.options.linkedPartsView) ? this.options.linkedPartsView : null; + this.options.linkedDocumentsView = (this.options.linkedDocumentsView) ? this.options.linkedDocumentsView : null; + var self = this; + this.$el.on('remove', function () { + _(self._subViews).invoke('remove'); + }); + }, + + render: function () { + var self = this; + + this.$el.html(Mustache.render(template,{ + i18n: App.config.i18n, + editMode: this.options.editMode, + label: this.options.label, + view: this + })); + + this.bindDomElements(); + this.bindTypeahead(); + + this.collection.each(function (linkedChangeItem) { + self.initialAddLinkView(linkedChangeItem); + }); + + return this; + }, + + bindDomElements: function () { + this.referenceInput = this.$('.linked-items-reference-typehead'); + this.linksUL = this.$('#linked-items-' + this.cid); + }, + + initialAddLinkView: function (linkedChangeItem) { + var linkView = new LinkedChangeItemView({ + editMode: this.options.editMode, + model: linkedChangeItem + }).render(); + this._subViews.push(linkView); + this.linksUL.append(linkView.el); + }, + + addLinkView: function (linkedChangeItem) { + var alreadyExist = false; + _.each(this._subViews, function (view) { + if (view.model.getId() === linkedChangeItem.getId()) { + alreadyExist = true; + } + }); + if (!alreadyExist) { + this.initialAddLinkView(linkedChangeItem); + + var self = this; + var affectedDocuments = linkedChangeItem.getAffectedDocuments(); + + var affectedDocumentsCollection = new LinkedDocumentCollection(affectedDocuments); + affectedDocumentsCollection.each(function (linkedDocument) { + self.options.linkedDocumentsView.collection.add(linkedDocument); + self.options.linkedDocumentsView.addLinkView(linkedDocument); + }); + + + var affectedParts = linkedChangeItem.getAffectedParts(); + + var affectedPartsCollection = new LinkedPartCollection(affectedParts); + affectedPartsCollection.each(function (linkedPart) { + self.options.linkedPartsView.collection.add(linkedPart); + self.options.linkedPartsView.addLinkView(linkedPart); + }); + + } + } + }); + return LinkedRequestsView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/linked/linked_document.js b/docdoku-web-front/app/js/common-objects/views/linked/linked_document.js new file mode 100644 index 0000000000..ed85bef14b --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/linked/linked_document.js @@ -0,0 +1,84 @@ +/*global define,require,App,_*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/linked/linked_document.html', + 'common-objects/models/document/document_revision' +], function (Backbone, Mustache, template, DocumentRevision) { + 'use strict'; + var LinkedDocumentView = Backbone.View.extend({ + + tagName: 'li', + className: 'linked-item well', + + events: { + 'click .delete-linked-item': 'deleteButtonClicked', + 'click .edit-linked-item-comment' : 'showEditCommentField', + 'click .delete-comment' : 'deleteComment', + 'click .validate-comment' : 'validateComment', + 'click a.reference': 'toDocumentDetailView' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template,{ + i18n: App.config.i18n, + linkedDocument: this.model, + commentEditable: this.options.commentEditable, + editMode: this.options.editMode + })); + + return this; + }, + + deleteButtonClicked: function () { + this.model.collection.remove(this.model); + this.remove(); + return false; + }, + + showEditCommentField: function(){ + this.$el.toggleClass('edition'); + }, + + deleteComment: function(){ + this.$('input.commentInput')[0].value = ''; + }, + + validateComment:function(){ + var commentValue = this.$el.find('input.commentInput')[0].value; + this.model.setDocumentLinkComment(commentValue); + this.$('span.comment').html(commentValue); + this.$el.toggleClass('edition'); + }, + + toDocumentDetailView: function () { + setTimeout(this.openDocumentDetailView, 500); + this.$el.trigger('close-modal-request'); + }, + + openDocumentDetailView: function () { + var documentRevision = new DocumentRevision({ + id: this.model.get('documentMasterId') + '-' + this.model.get('version') + }); + + var self = this; + + documentRevision.fetch().success(function () { + require(['common-objects/views/document/document_iteration'], function (IterationView) { + var view = new IterationView({ + model: documentRevision, + iteration: self.model.getIteration ? self.model.getIteration() : undefined + }); + view.show(); + }); + + }); + } + + }); + return LinkedDocumentView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/linked/linked_documents.js b/docdoku-web-front/app/js/common-objects/views/linked/linked_documents.js new file mode 100644 index 0000000000..fd64d3f0c2 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/linked/linked_documents.js @@ -0,0 +1,119 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/collections/linked/linked_document_collection', + 'common-objects/views/linked/linked_document', + 'text!common-objects/templates/linked/linked_items.html' +], function (Backbone, Mustache, LinkedDocumentCollection, LinkedDocumentView, template) { + 'use strict'; + var LinkedDocumentsView = Backbone.View.extend({ + + tagName: 'div', + className: 'linked-items-view', + + initialize: function () { + this.searchResults = []; + this._subViews = []; + var self = this; + this.$el.on('remove', function () { + _(self._subViews).invoke('remove'); + }); + }, + + render: function () { + var self = this; + + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + editMode: this.options.editMode, + commentEditable: this.options.commentEditable, + label: App.config.i18n.ADD_DOCUMENT, + view: this + })); + + this.bindDomElements(); + this.bindTypeahead(); + + this.collection.each(function (linkedDocument) { + self.addLinkView(linkedDocument); + }); + + return this; + }, + + bindDomElements: function () { + this.documentReferenceInput = this.$('.linked-items-reference-typehead'); + this.linksUL = this.$('#linked-items-' + this.cid); + }, + + addLinkView: function (linkedDocument) { + var linkView = new LinkedDocumentView({ + editMode: this.options.editMode, + commentEditable: this.options.commentEditable, + model: linkedDocument + }).render(); + + this._subViews.push(linkView); + this.linksUL.append(linkView.el); + }, + + bindTypeahead: function () { + var self = this; + var itemsLimit = 15; + + this.documentReferenceInput.typeahead({ + items: itemsLimit, + + source: function (query, process) { + $.getJSON(App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/documents/doc_revs?q=' + query + '&l=' + itemsLimit, function (data) { + + self.searchResults = new LinkedDocumentCollection(data); + + if (self.options.documentIteration && self.options.documentIteration.className === 'DocumentIteration') { + data = _.reject(self.searchResults.models, function (linkedDocument) { + return linkedDocument.getDocKey() === self.options.documentIteration.getDocKey(); + }, self); + self.searchResults = new LinkedDocumentCollection(data); + } + + // Remove documents that are already linked + var docsToRemove = []; + self.searchResults.each( + function (searchLinkedDocument) { + var linkedDoc = self.collection.find( + function (addedLinkedDocument) { + return addedLinkedDocument.getDocKey() === searchLinkedDocument.getDocKey(); + }); + if (!_.isUndefined(linkedDoc)) { + docsToRemove.push(searchLinkedDocument); + } + } + ); + self.searchResults.remove(docsToRemove); + + process(self.searchResults.map(function (docLastIter) { + return docLastIter.getDisplayDocKey(); + })); + }); + }, + + sorter: function (docsLastIterDocKey) { + return docsLastIterDocKey.sort(); + }, + + updater: function (docLastIterDocKey) { + var linkedDocument = self.searchResults.find(function (docLastIter) { + return docLastIter.getDisplayDocKey() === docLastIterDocKey; + }); + linkedDocument.collection.remove(linkedDocument); + self.collection.add(linkedDocument); + + self.addLinkView(linkedDocument); + } + }); + } + + }); + return LinkedDocumentsView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/linked/linked_issues.js b/docdoku-web-front/app/js/common-objects/views/linked/linked_issues.js new file mode 100644 index 0000000000..e89ba61ed8 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/linked/linked_issues.js @@ -0,0 +1,46 @@ +/*global $,define,App*/ +define([ + 'common-objects/collections/linked/linked_change_item_collection', + 'common-objects/views/linked/linked_change_items' +], function (LinkedChangeItemCollection, LinkedItemsView) { + 'use strict'; + var LinkedIssuesView = LinkedItemsView.extend({ + + initialize: function () { + LinkedItemsView.prototype.initialize.apply(this, arguments); + this.options.label = App.config.i18n.ADD_ISSUE; + }, + + bindTypeahead: function () { + var self = this; + + this.referenceInput.typeahead({ + source: function (query, process) { + $.getJSON(App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/changes/issues/link/?q=' + query, function (data) { + + self.searchResults = new LinkedChangeItemCollection(data); + process(self.searchResults.map(function (issue) { + return issue.getName(); + })); + }); + }, + + sorter: function (issuesNames) { + return issuesNames.sort(); + }, + + updater: function (issueName) { + var linkedIssue = self.searchResults.find(function (issue) { + return issue.getName() === issueName; + }); + linkedIssue.collection.remove(linkedIssue); + self.collection.add(linkedIssue); + + self.addLinkView(linkedIssue); + } + }); + } + + }); + return LinkedIssuesView; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/views/linked/linked_orders.js b/docdoku-web-front/app/js/common-objects/views/linked/linked_orders.js new file mode 100644 index 0000000000..b945cb62e0 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/linked/linked_orders.js @@ -0,0 +1,46 @@ +/*global $,define,App*/ +define([ + 'common-objects/collections/linked/linked_change_item_collection', + 'common-objects/views/linked/linked_change_items' +], function (LinkedChangeItemCollection, LinkedItemsView) { + 'use strict'; + var LinkedOrdersView = LinkedItemsView.extend({ + + initialize: function () { + LinkedItemsView.prototype.initialize.apply(this, arguments); + this.options.label = App.config.i18n.ADD_REQUEST; + }, + + bindTypeahead: function () { + var self = this; + + this.referenceInput.typeahead({ + source: function (query, process) { + $.getJSON(App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/changes/orders/link/?q=' + query, function (data) { + + self.searchResults = new LinkedChangeItemCollection(data); + process(self.searchResults.map(function (order) { + return order.getName(); + })); + }); + }, + + sorter: function (ordersNames) { + return ordersNames.sort(); + }, + + updater: function (orderName) { + var linkedOrder = self.searchResults.find(function (order) { + return order.getName() === orderName; + }); + linkedOrder.collection.remove(linkedOrder); + self.collection.add(linkedOrder); + + self.addLinkView(linkedOrder); + } + }); + } + + }); + return LinkedOrdersView; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/views/linked/linked_part.js b/docdoku-web-front/app/js/common-objects/views/linked/linked_part.js new file mode 100644 index 0000000000..dc1675caeb --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/linked/linked_part.js @@ -0,0 +1,38 @@ +/*global define, App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/linked/linked_part.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var LinkedPartView = Backbone.View.extend({ + + tagName: 'li', + className: 'linked-item well', + + events: { + 'click .delete-linked-item': 'deleteButtonClicked' + }, + + initialize: function () { + }, + + render: function () { + this.$el.html(Mustache.render(template,{ + i18n: App.config.i18n, + linkedPart: this.model, + editMode: this.options.editMode + })); + + return this; + }, + + deleteButtonClicked: function () { + this.model.collection.remove(this.model); + this.remove(); + return false; + } + + }); + return LinkedPartView; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/views/linked/linked_parts.js b/docdoku-web-front/app/js/common-objects/views/linked/linked_parts.js new file mode 100644 index 0000000000..9156398955 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/linked/linked_parts.js @@ -0,0 +1,112 @@ +/*global $,_,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/collections/linked/linked_part_collection', + 'common-objects/views/linked/linked_part', + 'text!common-objects/templates/linked/linked_items.html' +], function (Backbone, Mustache, LinkedPartCollection, LinkedPartView, template) { + 'use strict'; + var LinkedPartsView = Backbone.View.extend({ + + tagName: 'div', + className: 'linked-items-view', + + initialize: function () { + this.searchResults = []; + this._subViews = []; + var self = this; + this.$el.on('remove', function () { + _(self._subViews).invoke('remove'); + }); + }, + + render: function () { + var self = this; + + this.$el.html(Mustache.render(template, + { + i18n: App.config.i18n, + editMode: this.options.editMode, + label: App.config.i18n.ADD_PART, + view: this + } + )); + + this.bindDomElements(); + this.bindTypeahead(); + + this.collection.each(function (linkedPart) { + self.addLinkView(linkedPart); + }); + + return this; + }, + + bindDomElements: function () { + this.partReferenceInput = this.$('.linked-items-reference-typehead'); + this.linksUL = this.$('#linked-items-' + this.cid); + }, + + addLinkView: function (linkedPart) { + var linkView = new LinkedPartView({ + editMode: this.options.editMode, + model: linkedPart + }).render(); + + this._subViews.push(linkView); + this.linksUL.append(linkView.el); + }, + + bindTypeahead: function () { + var self = this; + var itemsLimit = 15; + + this.partReferenceInput.typeahead({ + items: itemsLimit, + + source: function (query, process) { + $.getJSON(App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/parts_last_iter?q=' + query + '&l=' + itemsLimit, function (data) { + + self.searchResults = new LinkedPartCollection(data); + + // Remove parts that are already linked + var partsToRemove = []; + self.searchResults.each( + function (partIteration) { + var linkedPart = self.collection.find( + function (linkedPart) { + return linkedPart.getPartKey() === partIteration.getPartKey(); + }); + if (!_.isUndefined(linkedPart)) { + partsToRemove.push(partIteration); + } + } + ); + self.searchResults.remove(partsToRemove); + + process(self.searchResults.map(function (partLastIter) { + return partLastIter.getDisplayPartKey(); + })); + }); + }, + + sorter: function (partsLastIterPartKey) { + return partsLastIterPartKey.sort(); + }, + + updater: function (partLastIterPartKey) { + var linkedPart = self.searchResults.find(function (partLastIter) { + return partLastIter.getDisplayPartKey() === partLastIterPartKey; + }); + linkedPart.collection.remove(linkedPart); + self.collection.add(linkedPart); + + self.addLinkView(linkedPart); + } + }); + } + + }); + return LinkedPartsView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/linked/linked_requests.js b/docdoku-web-front/app/js/common-objects/views/linked/linked_requests.js new file mode 100644 index 0000000000..c1a6a8e4a3 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/linked/linked_requests.js @@ -0,0 +1,46 @@ +/*global $,define,App*/ +define([ + 'common-objects/collections/linked/linked_change_item_collection', + 'common-objects/views/linked/linked_change_items' +], function (LinkedChangeItemCollection, LinkedItemsView) { + 'use strict'; + var LinkedRequestsView = LinkedItemsView.extend({ + + initialize: function () { + LinkedItemsView.prototype.initialize.apply(this, arguments); + this.options.label = App.config.i18n.ADD_REQUEST; + }, + + bindTypeahead: function () { + var self = this; + + this.referenceInput.typeahead({ + source: function (query, process) { + $.getJSON(App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/changes/requests/link/?q=' + query, function (data) { + + self.searchResults = new LinkedChangeItemCollection(data); + process(self.searchResults.map(function (request) { + return request.getName(); + })); + }); + }, + + sorter: function (requestsNames) { + return requestsNames.sort(); + }, + + updater: function (requestName) { + var linkedRequest = self.searchResults.find(function (request) { + return request.getName() === requestName; + }); + linkedRequest.collection.remove(linkedRequest); + self.collection.add(linkedRequest); + + self.addLinkView(linkedRequest); + } + }); + } + + }); + return LinkedRequestsView; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/views/lov/lov_item.js b/docdoku-web-front/app/js/common-objects/views/lov/lov_item.js new file mode 100644 index 0000000000..9c9fc3d159 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/lov/lov_item.js @@ -0,0 +1,115 @@ +/*global define,App,_*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/lov/lov_item.html', + 'common-objects/views/lov/lov_possible_value' +], function (Backbone, Mustache, template, LOVPossibleValueView) { + 'use strict'; + var LOVItemView = Backbone.View.extend({ + + events:{ + 'click .deleteLovItem': 'removeItem', + 'click .addLOVValue': 'addValueInList', + 'click .expandIcon': 'showEditMode', + 'blur .lovItemNameInput': 'onItemNameChanged' + }, + + className: 'lovItem ui-sortable well', + + isExpand:false, + + $lovListDiv : '', + + initialize: function () { + this.isExpand = this.options.isExpand; + }, + + render: function () { + this.$el.html(Mustache.render(template,{ + i18n: App.config.i18n, + model: this.model, + isExpand: this.isExpand + })); + if(this.isExpand){ + this.$el.addClass('edition'); + } + if(this.model.getLOVName()){ + this.$el.addClass('isOldItem'); + } + this.$lovListDiv = this.$('.lovValues'); + + _.each(this.model.getLOVValues(), this.addPossibleValueView.bind(this)); + + //Reorganise possible value order + var oldIndex = null; + var that = this; + this.$lovListDiv.sortable({ + handle: '.sortable-handler', + placeholder: 'list-item well highlight', + start: function(event, ui){ + oldIndex = ui.item.index(); + }, + stop: function(event, ui) { + var newIndex = ui.item.index(); + that.arrayMove(that.model.getLOVValues(),oldIndex, newIndex); + oldIndex = null; + } + }); + + this.$('input[required]').customValidity(App.config.i18n.REQUIRED_FIELD); + + return this; + }, + + removeItem:function(){ + this.trigger('remove'); + this.remove(); + }, + + deletePossibleValue:function(possibleValueView){ + var indexInModel = _.indexOf(this.model.getLOVValues(),possibleValueView.model); + this.model.getLOVValues().splice(indexInModel, 1); + this.onNumberOfPossibleValueChanged(); + }, + + addPossibleValueView:function(possibleValue){ + var possibleValueView = new LOVPossibleValueView({ + model:possibleValue + }); + + possibleValueView.on('remove', this.deletePossibleValue.bind(this, possibleValueView)); + possibleValueView.render(); + this.$lovListDiv.append(possibleValueView.$el); + this.onNumberOfPossibleValueChanged(); + }, + + addValueInList:function(){ + var newPossibleValue = {name:'', value:''}; + this.model.getLOVValues().push(newPossibleValue); + this.addPossibleValueView(newPossibleValue); + }, + + showEditMode:function(){ + this.isExpand = !this.isExpand; + this.$el.toggleClass('edition'); + }, + + onNumberOfPossibleValueChanged:function(){ + this.$('.lovNumberOfValue').html(this.model.getNumberOfValue()); + }, + + onItemNameChanged: function(){ + var newName = this.$('.lovItemNameInput').val(); + this.$('.lovItemName').html(newName); + this.model.setLOVName(newName); + }, + + arrayMove: function (array, oldIndex, newIndex) { + array.splice(newIndex, 0, array.splice(oldIndex, 1)[0]); + } + + }); + + return LOVItemView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/lov/lov_modal.js b/docdoku-web-front/app/js/common-objects/views/lov/lov_modal.js new file mode 100644 index 0000000000..43344a9a2e --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/lov/lov_modal.js @@ -0,0 +1,160 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/lov/lov_modal.html', + 'common-objects/collections/lovs', + 'common-objects/views/lov/lov_item', + 'common-objects/models/lov/lov', + 'async', + 'common-objects/views/alert' +], function (Backbone, Mustache, template, LOVCollection, LOVItemView, LOVModel, async, AlertView) { + 'use strict'; + var LOVModalView = Backbone.View.extend({ + + template: template, + + events: { + 'hidden .modal.list_lov': 'onHidden', + 'click .addLOVButton': 'addLov', + 'submit form': 'onSaveLovs', + 'click .btn-saveLovs': 'interceptSubmit' + }, + + collection: new LOVCollection(), + + initialize: function () { + _.bindAll(this); + this.deletedLovModel = []; + this.lovViews = []; + this.listenTo(this.collection, 'reset', this.onCollectionReset); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.$modal = this.$('.modal.list_lov'); + this.$lovListDiv = this.$('.modal .modal-body .list_of_lov'); + this.$notifications = this.$('.notifications'); + this.collection.fetch({reset: true}); + return this; + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + onCollectionReset: function() { + this.collection.each(this.addLovView.bind(this, false)); + }, + + addLovView: function(isExpand, lov){ + var lovView = new LOVItemView({ + model: lov, + isExpand: isExpand + }); + this.collection.push(lov); + lovView.on('remove', this.removeLovView.bind(this, lovView)); + this.lovViews.push(lovView); + lovView.render(); + this.$lovListDiv.append(lovView.$el); + }, + + addLov: function(){ + var newModel = new LOVModel( + { + name:'', + values:[{name:'', value:''}], + workspaceId:App.config.workspaceId, + deletable: true + } + ); + //newModel.setNew(true); + + this.addLovView(true, newModel); + }, + + removeLovView:function(lovView){ + if(!lovView.model.isNew()){ + this.deletedLovModel.push(lovView.model); + } + this.collection.remove(lovView.model); + this.lovViews = _.without(this.lovViews, lovView); + }, + + interceptSubmit:function(){ + this.isValid = this.checkEmptyHiddenFields(); + }, + + checkEmptyHiddenFields:function(){ + var invalidItems = this.$('input:invalid'); + invalidItems.closest('.lovItem').addClass('edition'); + return !invalidItems.length; + }, + + onSaveLovs: function(event){ + if(this.isValid){ + var nbError = 0; + + var that = this; + + var errorFunction = function(response, callback){ + that.$notifications.append(new AlertView({ + type: 'error', + message: response.responseText + }).render().$el); + nbError++; + callback(); + }; + + var queueDelete = async.queue(function(model, callback){ + model.destroy({dataType : 'text'}).success(function(){ + that.deletedLovModel = _.without(that.deletedLovModel,model); + callback(); + }).error(function(response){ + errorFunction(response, callback); + }); + }); + + var queueSave = async.queue(function(model,callback){ + model.save().success(function(){ + callback(); + }).error(function(response){ + errorFunction(response, callback); + }); + }); + + queueSave.drain = function(){ + if(!nbError){ + that.closeModal(); + } + }; + + queueDelete.drain = function(){ + if(!nbError && that.collection.models.length === 0){ + that.closeModal(); + }else{ + queueSave.push(that.collection.models); + } + }; + + if(this.deletedLovModel.length === 0){ + queueSave.push(this.collection.models); + }else{ + queueDelete.push(this.deletedLovModel); + } + } + + event.preventDefault(); + return false; + } + }); + return LOVModalView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/lov/lov_possible_value.js b/docdoku-web-front/app/js/common-objects/views/lov/lov_possible_value.js new file mode 100644 index 0000000000..da9bb54404 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/lov/lov_possible_value.js @@ -0,0 +1,54 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/lov/lov_possible_value.html' +], function (Backbone, Mustache, template){ + 'use strict'; + var LOVPossibleValueView = Backbone.View.extend({ + + events:{ + 'blur .lovItemNameValueNameInput' : 'onNameChanged', + 'blur .lovItemNameValueValueInput' : 'onValueChanged', + 'click .deleteLovItemPossibleValue': 'onDeleteView' + }, + + className : 'lovPossibleValue', + + nameInput: null, + + valueInput: null, + + initialize: function () { + + }, + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + possibleValue: this.model + })); + + this.nameInput = this.$('.lovItemNameValueNameInput'); + this.valueInput = this.$('.lovItemNameValueValueInput'); + this.$('input[required]').customValidity(App.config.i18n.REQUIRED_FIELD); + return this; + }, + + onNameChanged: function(){ + this.model.name = this.nameInput.val(); + }, + + onValueChanged: function(){ + this.model.value = this.valueInput.val(); + }, + + onDeleteView: function(){ + this.trigger('remove'); + this.remove(); + } + + }); + + return LOVPossibleValueView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/not-found.js b/docdoku-web-front/app/js/common-objects/views/not-found.js new file mode 100644 index 0000000000..e22828e85e --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/not-found.js @@ -0,0 +1,29 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/not-found.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var NotFoundView = Backbone.View.extend({ + + render: function (error, url) { + + var tmpContainer = document.createElement('div'); + var text = document.createTextNode(error.responseText); + tmpContainer.appendChild(text); + text = tmpContainer.innerHTML; + + this.$el.html(Mustache.render(template, { + contextPath:App.config.contextPath, + i18n: App.config.i18n, + url:url, + reason:text + })); + + return this; + } + }); + + return NotFoundView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/cad_instance_view.js b/docdoku-web-front/app/js/common-objects/views/part/cad_instance_view.js new file mode 100644 index 0000000000..c5ae3bb698 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/cad_instance_view.js @@ -0,0 +1,69 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/part/cad_instance.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var CadInstanceView = Backbone.View.extend({ + + className: 'cad-instance', + + events: { + 'change input[name=tx]': 'changeTX', + 'change input[name=ty]': 'changeTY', + 'change input[name=tz]': 'changeTZ', + 'change input[name=rx]': 'changeRX', + 'change input[name=ry]': 'changeRY', + 'change input[name=rz]': 'changeRZ', + 'change input':'onChange', + 'click .delete-cad-instance': 'removeCadInstance' + }, + + initialize: function () { + }, + + render: function () { + var disabled = this.options.editMode ? '':'disabled'; + this.$el.html(Mustache.render(template, { + canRemove:this.options.editMode, + disabled:disabled, + instance: this.model.attributes, + i18n: App.config.i18n + })); + return this; + }, + + changeTX: function (e) { + this.model.set('tx', e.target.value); + }, + + changeTY: function (e) { + this.model.set('ty',e.target.value); + }, + + changeTZ: function (e) { + this.model.set('tz', e.target.value); + }, + + changeRX: function (e) { + this.model.set('rx', e.target.value); + }, + + changeRY: function (e) { + this.model.set('ry', e.target.value); + }, + + changeRZ: function (e) { + this.model.set('rz', e.target.value); + }, + + removeCadInstance: function () { + this.model.collection.remove(this.model); + this.remove(); + } + + }); + + return CadInstanceView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/conversion_status_view.js b/docdoku-web-front/app/js/common-objects/views/part/conversion_status_view.js new file mode 100644 index 0000000000..fe95be40da --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/conversion_status_view.js @@ -0,0 +1,44 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/part/conversion_status.html' +], function (Backbone,Mustache,template) { + 'use strict'; + var ConversionStatusView = Backbone.View.extend({ + className:'conversion-status', + hasNewFiles: false, + events:{ + 'click .reload':'render', + 'click .launch':'launch' + }, + render:function(){ + var _this = this; + this.model.getConversionStatus().success(function(status){ + _this.$el.html(Mustache.render(template, {status:status,hasCadFile:_this.model.get('nativeCADFile') || _this.hasNewFiles,i18n:App.config.i18n})); + if(status && status.pending){ + setTimeout(function() { + _this.render(); + }, 3000); + } + }).error(function(){ + _this.$el.html(Mustache.render(template, {status:null,hasCadFile:_this.model.get('nativeCADFile'),i18n:App.config.i18n})); + }); + return this; + }, + launch:function(){ + this.hasNewFiles = true; + var self = this; + this.$el.html(Mustache.render(template, {status:{pending:true},hasCadFile:this.model.get('nativeCADFile'),i18n:App.config.i18n})); + this.model.launchConversion().success(function(){ + self.render(); + }).error(function(){ + self.render(); + }); + setTimeout(function() { + self.render(); + }, 3000); + } + }); + return ConversionStatusView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/import_status_view.js b/docdoku-web-front/app/js/common-objects/views/part/import_status_view.js new file mode 100644 index 0000000000..05b13a2b58 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/import_status_view.js @@ -0,0 +1,51 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/part/import_status.html' +], function (Backbone,Mustache,template) { + 'use strict'; + var ImportStatusView = Backbone.View.extend({ + className:'import-status', + + initialize:function(){ + _.bindAll(this); + this.$el.on('remove', this.stopRefresh); + this.timeout = null; + }, + + render:function(){ + this.$el.html(Mustache.render(template, {status:this.model,i18n:App.config.i18n})); + if(this.model.pending){ + this.timeout = setTimeout(this.refresh.bind(this),1000); + } + return this; + }, + + deleteImport:function(){ + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/import/'+this.model.id; + $.ajax({ + url:url, + method:'DELETE', + success:this.remove.bind(this) + }); + }, + + refresh:function(){ + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/import/'+this.model.id; + var _this = this; + $.get(url).then(function(pImport){ + _this.model = pImport; + _this.render(); + }); + }, + + stopRefresh : function(){ + if(this.timeout){ + clearTimeout(this.timeout); + } + } + + }); + return ImportStatusView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/modification_notification_group_list_view.js b/docdoku-web-front/app/js/common-objects/views/part/modification_notification_group_list_view.js new file mode 100644 index 0000000000..79709b3c46 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/modification_notification_group_list_view.js @@ -0,0 +1,47 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/views/part/modification_notification_list_view', + 'common-objects/models/modification_notification', + 'common-objects/collections/modification_notification_collection', + 'text!common-objects/templates/part/modification_notification_group_list.html' +], function (Backbone, Mustache, ModificationNotificationListView, ModificationNotification, ModificationNotificationCollection, template) { + 'use strict'; + var ModificationNotificationGroupListView = Backbone.View.extend({ + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + + this.modificationNotificationViews = []; + + this.groupedMap = _.groupBy( + this.collection.models, + function (notif) { + return notif.getModifiedPartNumber(); + }, + this + ); + + _.each(_.keys(this.groupedMap), this.addListView, this); + + return this; + }, + + addListView: function (key) { + var modificationNotificationView = new ModificationNotificationListView({ + collection: new ModificationNotificationCollection(this.groupedMap[key]) + }).render(); + + this.modificationNotificationViews.push(modificationNotificationView); + this.$el.append(modificationNotificationView.$el); + } + + }); + + return ModificationNotificationGroupListView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/modification_notification_list_item_view.js b/docdoku-web-front/app/js/common-objects/views/part/modification_notification_list_item_view.js new file mode 100644 index 0000000000..0ac5c9dd37 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/modification_notification_list_item_view.js @@ -0,0 +1,52 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/part/modification_notification_list_item.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var ModificationNotificationListItemView = Backbone.View.extend({ + + events: { + 'click .action-acknowledge': 'acknowledge' + }, + + initialize: function () { + _.bindAll(this); + this.listenTo(this.model, 'change', this.render); + }, + + render: function () { + var data = { + modificationNotification: this.model, + i18n: App.config.i18n + }; + + this.$el.html(Mustache.render(template, data)); + this.bindUserPopover(); + return this; + }, + + bindUserPopover: function () { + this.$('.author-popover').userPopover(this.model.getAuthor().login, this.model.getImpactedPartNumber(), 'right'); + if (this.model.isAcknowledged() && this.model.getAckAuthor()) { + this.$('.ack-author-popover').userPopover(this.model.getAckAuthor().login, this.model.getImpactedPartNumber(), 'right'); + } + }, + + acknowledge: function () { + var _this = this; + var data = {ackComment: this.getAcknowledgementComment()}; + this.model.setAcknowledged(data).success(function () { + _this.$el.trigger('notification:acknowledged'); + }); + }, + + getAcknowledgementComment: function () { + return this.$('#acknowledgement-comment').val() || null; + } + + }); + + return ModificationNotificationListItemView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/modification_notification_list_view.js b/docdoku-web-front/app/js/common-objects/views/part/modification_notification_list_view.js new file mode 100644 index 0000000000..582c0c67ce --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/modification_notification_list_view.js @@ -0,0 +1,78 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'async', + 'common-objects/views/part/modification_notification_list_item_view', + 'common-objects/models/modification_notification', + 'common-objects/collections/modification_notification_collection', + 'text!common-objects/templates/part/modification_notification_list.html' +], function (Backbone, Mustache, Async, ModificationNotificationListItemView, ModificationNotification, ModificationNotificationCollection, template) { + 'use strict'; + var ModificationNotificationListView = Backbone.View.extend({ + + events: { + 'click .action-group-acknowledge': 'acknowledgeAll' + }, + + initialize: function () { + _.bindAll(this); + this.events['notification:acknowledged'] = 'render'; + }, + + render: function () { + var data = { + modificationNotification: this.collection.at(0), + hasUnreadModificationNotifications: this.collection.hasUnreadModificationNotifications() > 1, + i18n: App.config.i18n + }; + + this.$el.html(Mustache.render(template, data)); + this.bindDomElements(); + + this.modificationNotificationViews = []; + + this.collection.each(this.addView.bind(this)); + + return this; + }, + + bindDomElements: function () { + this.$items = this.$('.items'); + }, + + addView: function (model) { + var modificationNotificationView = new ModificationNotificationListItemView({ + model: model + }).render(); + + this.modificationNotificationViews.push(modificationNotificationView); + this.$items.append(modificationNotificationView.$el); + }, + + acknowledgeAll: function () { + var _this = this; + var data = {ackComment: this.geGroupAcknowledgementComment()}; + + Async.each(this.collection.models, function(notif, callback) { + if (notif.isAcknowledged()) { + callback(); + } else { + notif.setAcknowledged(data).success(callback); + } + + }, function(err) { + if (!err) { + _this.$el.trigger('notification:acknowledged'); + } + }); + }, + + geGroupAcknowledgementComment: function () { + return this.$('#group-acknowledgement-comment').val() || null; + } + + }); + + return ModificationNotificationListView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/part_assembly_view.js b/docdoku-web-front/app/js/common-objects/views/part/part_assembly_view.js new file mode 100644 index 0000000000..94e6b288ae --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/part_assembly_view.js @@ -0,0 +1,169 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/part/part_assembly.html', + 'common-objects/views/part/part_usage_link_view' +], function (Backbone, Mustache, template, PartUsageLinkView) { + + 'use strict'; + + var PartAssemblyView = Backbone.View.extend({ + + events: { + 'click #create-part-revision-as-part-usage-link': 'createNewPartRevisionAsPartUsageLink', + 'click #create-part-revision-as-part-substitute-link': 'createNewPartRevisionAsPartSubstitutesLink', + 'selectstart': 'preventSelect' + }, + + initialize: function () { + this.collection.bind('add', this.addPartUsageLink, this); + this.collection.bind('remove', this.removePartUsageLink, this); + this.selectedPartUsageLinkView = null; + }, + + preventSelect: function () { + return false; + }, + + render: function () { + + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, editMode: this.options.editMode})); + this.$components = this.$('.components'); + + this.collection.each(this.addPartUsageLinkView.bind(this)); + + if (this.options.editMode) { + this.bindPartUsageLinkTypeAhead(); + this.bindPartSubstituteLinkTypeAhead(); + } + + return this; + }, + + addPartUsageLinkView: function (partUsageLink) { + + var partUsageLinkView = new PartUsageLinkView({ + model: partUsageLink, + editMode: this.options.editMode + }).render(); + + this.$components.append(partUsageLinkView.$el); + this.listenTo(partUsageLinkView, 'part-link:toggle-selected', this.togglePartUsageLinkSelected.bind(this)); + + }, + + unSelectView: function () { + this.$('.component.selected').removeClass('selected'); + this.selectedPartUsageLinkView = null; + this.$el.removeClass('component-selected'); + }, + + togglePartUsageLinkSelected: function (view) { + if (view === this.selectedPartUsageLinkView) { + this.unSelectView(); + } else { + this.$('.component.selected').removeClass('selected'); + view.$el.addClass('selected'); + this.selectedPartUsageLinkView = view; + this.$el.addClass('component-selected'); + } + }, + + addPartUsageLink: function (model) { + this.addPartUsageLinkView(model); + }, + + removePartUsageLink: function (model) { + if (this.selectedPartUsageLinkView && this.selectedPartUsageLinkView.model === model) { + this.unSelectView(); + } + }, + + createNewPartRevisionAsPartUsageLink: function () { + this.collection.push({ + amount: 1, + component: { + standardPart: false + }, + cadInstances: [ + {tx: 0, ty: 0, tz: 0, rx: 0, ry: 0, rz: 0} + ], + unit: this.unit, + optional: false, + substitutes: [] + }); + }, + + createNewPartRevisionAsPartSubstitutesLink: function () { + this.selectedPartUsageLinkView.substitutes.add({ + amount: this.selectedPartUsageLinkView.model.get('amount'), + unit: this.selectedPartUsageLinkView.model.get('unit'), + substitute: { + standardPart: false + }, + cadInstances: [ + {tx: 0, ty: 0, tz: 0, rx: 0, ry: 0, rz: 0} + ] + }); + }, + + bindPartUsageLinkTypeAhead: function () { + + var collection = this.collection; + + this.$('#part-usage-link-type-ahead').typeahead({ + source: this.source.bind(this), + updater: function (part) { + collection.push({ + amount: 1, + component: { + number: part.split('<')[1].replace('>', '').trim(), + name: part.split('<')[0].trim() + }, + substitutes: [], + cadInstances: [ + {tx: 0, ty: 0, tz: 0, rx: 0, ry: 0, rz: 0} + ] + }); + } + }); + }, + + bindPartSubstituteLinkTypeAhead: function () { + var that = this; + this.$('#part-substitute-link-type-ahead').typeahead({ + source: this.source.bind(this), + updater: function (part) { + that.selectedPartUsageLinkView.substitutes.add({ + amount: 1, + unit: '', + substitute: { + number: part.split('<')[1].replace('>', '').trim(), + name: part.split('<')[0].trim() + }, + cadInstances: [ + {tx: 0, ty: 0, tz: 0, rx: 0, ry: 0, rz: 0} + ] + }); + } + }); + }, + + source: function (query, process) { + var model = this.model; + $.getJSON(App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/numbers?q=' + query, function (data) { + var partNumbers = []; + _(data).each(function (d) { + if ((!model.getNumber()) || (model.getNumber() !== d.partNumber)) { + partNumbers.push(d.partName + ' < ' + d.partNumber + ' >'); + } + }); + process(partNumbers); + }); + } + + }); + + return PartAssemblyView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/part_link_view.js b/docdoku-web-front/app/js/common-objects/views/part/part_link_view.js new file mode 100644 index 0000000000..61638cb568 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/part_link_view.js @@ -0,0 +1,194 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/part/part_link.html', + 'common-objects/views/part/cad_instance_view' +], function (Backbone, Mustache, template, CadInstanceView) { + + 'use strict'; + + var PartLinkView = Backbone.View.extend({ + + events: { + 'change >input[name=amount]': 'changeAmount', + 'change >input[name=reference-description]': 'changeReferenceDescription', + 'change >input[name=number]': 'changeNumber', + 'change >input[name=name]': 'changeName', + 'change >label>input[name=optional]': 'changeIsOptional', + 'change >input[name=unit]': 'changeUnit', + 'click >.add-cad-instance': 'onAddCadInstance', + 'click >.remove-cad-instance': 'onRemoveCadInstance', + 'click >.toggle-cad-instances':'toggleCadInstances', + 'click >.remove':'onRemove', + 'click':'toggleSelected' + }, + + initialize: function () { + this.cadInstances = new Backbone.Collection(this.model.get('cadInstances')); + this.cadInstances.bind('add',this.addCadInstance.bind(this)); + this.cadInstances.bind('remove',this.removeCadInstance.bind(this)); + this.cadInstances.bind('reset',this.removeAllCadInstances.bind(this)); + this.model.on('change',this.onModelChanged.bind(this)); + }, + + render:function(){ + + this.component = this.getComponent(); + + var disabled = this.options.editMode ? '':'disabled'; + var optionalChecked = this.model.get('optional') ? 'checked':''; + var componentDisabled = this.component.number ? 'disabled':''; + + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + editMode: this.options.editMode, + disabled:disabled, + optionalChecked:optionalChecked, + handleSubstitutes:this.handleSubstitutes, + model:this.model.attributes, + component:this.component, + componentDisabled:componentDisabled + })); + + this.$cadInstances = this.$('.cad-instances'); + this.cadInstances.each(this.addCadInstanceView.bind(this)); + + this.$('>[name=unit]').selectize({ + create: true, + onChange:this.changeUnit.bind(this) + })[0].selectize.setValue(this.model.get('unit'), true); + + this.updateCadInstancesCount(); + this.updateAmountField(); + + return this; + }, + + hasUnit:function(){ + return this.model.get('unit'); + }, + + onModelChanged:function(){ + this.model.set('id',0,{silent:true}); + this.model.set('ROTATIONTYPE','ANGLE',{silent:true}); + }, + + changeAmount: function (e) { + this.model.set('amount', parseFloat(e.target.value)); + }, + + changeReferenceDescription: function (e) { + this.model.set('referenceDescription', e.target.value); + }, + + changeNumber: function (e) { + this.component.number = e.target.value; + }, + + changeName: function (e) { + this.component.name = e.target.value; + }, + + changeIsOptional: function (e) { + this.model.set('optional', e.target.checked); + }, + + changeUnit: function (unit) { + this.model.set('unit', unit); + this.model.trigger('change'); + this.updateAmountField(); + if(this.hasUnit()){ + this.cadInstances.reset(); + }else{ + this.onAddCadInstance(); + } + }, + + updateAmountField:function(){ + if(this.hasUnit()){ + this.$('>[name=amount]').removeProp('disabled'); + this.$('>.change-cad-instances-amount').hide(); + }else{ + this.$('>[name=amount]').prop('disabled','disabled'); + this.updateAmountChangeIcons(); + } + }, + + onAddCadInstance:function(){ + this.cadInstances.add({tx: 0, ty: 0, tz: 0, rx: 0, ry: 0, rz: 0}); + }, + + addCadInstance: function (cadInstance) { + this.addCadInstanceView(cadInstance); + this.updateCadInstances(); + this.onModelChanged(); + }, + + updateCadInstances:function(){ + this.model.set('cadInstances',this.cadInstances.models); + this.updateCadInstancesCount(); + }, + + updateCadInstancesCount:function(){ + if(!this.hasUnit()){ + this.model.set('amount', this.cadInstances.size()); + this.$('>[name=amount]').val(this.cadInstances.size()); + this.updateAmountChangeIcons(); + } + }, + + updateAmountChangeIcons:function(){ + this.$('.add-cad-instance').show(); + if (this.cadInstances.size() <= 1) { + this.$('.remove-cad-instance').hide(); + }else{ + this.$('.remove-cad-instance').show(); + } + }, + + addCadInstanceView: function (cadInstance) { + var instanceView = new CadInstanceView({ + editMode: this.options.editMode, + model:cadInstance + }).render(); + this.$cadInstances.append(instanceView.$el); + this.listenTo(cadInstance,'change',this.updateCadInstances.bind(this)); + this.listenTo(cadInstance,'change',this.onModelChanged.bind(this)); + }, + + removeCadInstance: function () { + this.updateCadInstances(); + }, + + removeAllCadInstances:function(){ + this.model.set('cadInstances',[]); + this.$('>.cad-instances .cad-instance').remove(); + }, + + onRemoveCadInstance: function () { + this.cadInstances.remove(this.cadInstances.at(this.cadInstances.size()-1)); + this.$('>.cad-instances .cad-instance:last').remove(); + this.updateCadInstances(); + }, + + toggleCadInstances:function(){ + this.$el.toggleClass('cad-instances-opened'); + }, + + onRemove:function(){ + // Remove by cid, instead of default "remove by id" (our ids are set to 0 on model changed) + this.model.collection.remove({cid:this.model.cid}); + this.remove(); + }, + + toggleSelected:function(e){ + if (e.target.className.indexOf('component') !== -1 || e.target.parentNode.className === 'cad-instance' || e.target.parentNode.className === 'cad-instances') { + this.trigger('part-link:toggle-selected', this); + } + } + + }); + + return PartLinkView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/part_modal_view.js b/docdoku-web-front/app/js/common-objects/views/part/part_modal_view.js new file mode 100644 index 0000000000..afc14b33c5 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/part_modal_view.js @@ -0,0 +1,531 @@ +/*global _,define,App,$*/ +define([ + 'backbone', + 'mustache', + 'common-objects/views/components/modal', + 'common-objects/views/file/file_list', + 'text!common-objects/templates/part/part_modal.html', + 'common-objects/views/attributes/attributes', + 'common-objects/views/attributes/template_new_attributes', + 'common-objects/views/part/part_assembly_view', + 'common-objects/views/part/modification_notification_group_list_view', + 'common-objects/views/linked/linked_documents', + 'common-objects/views/part/used_by_view', + 'common-objects/views/alert', + 'common-objects/collections/linked/linked_document_collection', + 'common-objects/collections/linked/linked_document_iteration_collection', + 'common-objects/views/workflow/lifecycle', + 'common-objects/views/part/conversion_status_view', + 'common-objects/utils/date', + 'common-objects/views/tags/tag', + 'common-objects/models/tag' +], function (Backbone, Mustache, ModalView, FileListView, template, AttributesView, TemplateNewAttributesView, PartAssemblyView, ModificationNotificationGroupListView, LinkedDocumentsView, UsedByView, AlertView, LinkedDocumentCollection, LinkedDocumentIterationCollection, LifecycleView, ConversionStatusView, date,TagView,Tag) { + + 'use strict'; + + var PartModalView = ModalView.extend({ + + initialize: function () { + + this.iterations = this.model.getIterations(); + + this.iteration = this.options.iteration && this.options.iteration < this.iterations.size() ? + this.iterations.get(this.options.iteration) : this.model.getLastIteration(); + + this.productId = this.options.productId; + this.productConfigSpec = this.options.productConfigSpec; + + ModalView.prototype.initialize.apply(this, arguments); + + this.events['click a#previous-iteration'] = 'onPreviousIteration'; + this.events['click a#next-iteration'] = 'onNextIteration'; + this.events['click .modal-footer button.btn-primary'] = 'interceptSubmit'; + this.events['submit #form-part'] = 'onSubmitForm'; + this.events['click .action-checkin'] = 'actionCheckin'; + this.events['click .action-checkout'] = 'actionCheckout'; + this.events['click .action-undocheckout'] = 'actionUndoCheckout'; + this.events['notification:acknowledged'] = 'updateModificationNotifications'; + this.events['file:uploaded'] = 'updateConversionStatusView'; + this.events['close-modal-request'] = 'closeModal'; + + this.tagsToRemove = []; + }, + + onPreviousIteration: function () { + if (this.iterations.hasPreviousIteration(this.iteration)) { + this.switchIteration(this.iterations.previous(this.iteration)); + } + return false; + }, + + onNextIteration: function () { + if (this.iterations.hasNextIteration(this.iteration)) { + this.switchIteration(this.iterations.next(this.iteration)); + } + return false; + }, + + switchIteration: function (iteration) { + this.iteration = iteration; + var activeTabIndex = this.getActiveTabIndex(); + this.render(); + this.activateTab(activeTabIndex); + }, + + getActiveTabIndex: function () { + return this.$tabs.filter('.active').index(); + }, + + activateTab: function (index) { + this.$tabs.eq(index).children().tab('show'); + }, + + activateFileTab: function(){ + this.activateTab(3); + }, + + activateNotificationsTab: function(){ + this.activateTab(this.$tabs.length - 1); + }, + + render: function () { + var data = { + part: this.model, + i18n: App.config.i18n, + permalink: this.model.getPermalink(), + hasIterations: this.model.hasIterations() + }; + + this.editMode = this.model.isCheckoutByConnectedUser() && this.iterations.isLast(this.iteration); + data.editMode = this.editMode; + data.isCheckout = this.model.isCheckout(); + this.isCheckout = data.isCheckout ; + this.isReleased = this.model.attributes.status === 'RELEASED'; + data.isReleased = this.isReleased; + this.isObsolete = this.model.attributes.status === 'OBSOLETE'; + data.isObsolete = this.isObsolete; + data.isShowingLast = this.iterations.isLast(this.iteration); + data.isLocked = this.model.isCheckout() && !this.model.isCheckoutByConnectedUser(); + + if (this.model.hasIterations()) { + var hasNextIteration = this.iterations.hasNextIteration(this.iteration); + var hasPreviousIteration = this.iterations.hasPreviousIteration(this.iteration); + data.iterations = this.model.getIterations().length; + data.iteration = this.iteration.toJSON(); + data.iteration.hasNextIteration = hasNextIteration; + data.iteration.hasPreviousIteration = hasPreviousIteration; + data.reference = this.iteration.getReference(); + data.iteration.creationDate = date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + data.iteration.creationDate + ); + data.iteration.modificationDate = date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + data.iteration.modificationDate + ); + + if (this.editMode) { + data.iteration.revisionDate = data.iteration.creationDate; + } else { + data.iteration.revisionDate = date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + data.iteration.checkInDate + ); + } + } + data.hasOneIteration= (this.iterations.length < 1); + this.$el.html(Mustache.render(template, data)); + + this.$authorLink = this.$('.author-popover'); + this.$checkoutUserLink = this.$('.checkout-user-popover'); + this.$releaseUserLink = this.$('.release-user-popover'); + this.$obsoleteUserLink = this.$('.obsolete-user-popover'); + + this.$inputIterationNote = this.$('#inputRevisionNote'); + this.$tabs = this.$('.nav-tabs li'); + + this.bindUserPopover(); + if (this.iteration) { + this.initCadFileUploadView(); + this.initAttachedFilesUploadView(); + this.initAttributesView(); + this.initPartAssemblyView(); + this.initLinkedDocumentsView(); + this.initUsedByView(); + this.initLifeCycleView(); + + if (!data.iteration.hasNextIteration) { + this.initModificationNotificationGroupListView(); + } + } + + date.dateHelper(this.$('.date-popover')); + this.tagsManagement(this.editMode); + return this; + }, + + bindUserPopover: function () { + this.$authorLink.userPopover(this.model.getAuthorLogin(), this.model.getNumber(), 'right'); + if (this.model.isCheckout()) { + this.$checkoutUserLink.userPopover(this.model.getCheckOutUserLogin(), this.model.getNumber(), 'right'); + } + if (this.model.getReleaseAuthor()) { + this.$releaseUserLink.userPopover(this.model.getReleaseAuthorLogin(), this.model.getNumber(), 'right'); + } + if (this.model.isObsolete()) { + this.$obsoleteUserLink.userPopover(this.model.getObsoleteAuthorLogin(), this.model.getNumber(), 'right'); + } + }, + + initAttributesView: function () { + + var that = this; + + this.attributes = new Backbone.Collection(); + + this.attributesView = new AttributesView({ + el: this.$('#attributes-list') + }); + + this.attributesView.setAttributesLocked(this.model.isAttributesLocked()); + this.attributesView.setEditMode(this.editMode); + this.attributesView.render(); + + _.each(this.iteration.getAttributes().models, function (item) { + that.attributesView.addAndFillAttribute(item); + }); + + this.attributeTemplatesView = new TemplateNewAttributesView({ + el: this.$('#attribute-templates-list'), + attributesLocked: false, + editMode : this.editMode + }); + this.attributeTemplatesView.render(); + this.attributeTemplatesView.collection.reset(this.iteration.getAttributeTemplates()); + + }, + + interceptSubmit: function () { + this.isValid = !this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + + // cannot pass a collection of cad file to server. + var cadFile = this.cadFileView.collection.first(); + if (cadFile) { + this.iteration.set('nativeCADFile', cadFile.get('fullName')); + } else { + this.iteration.set('nativeCADFile', ''); + } + + var that = this; + + this.iteration.save({ + iterationNote: this.$inputIterationNote.val(), + components: this.partAssemblyView.collection.toJSON(), + instanceAttributes: this.attributesView.collection.toJSON(), + instanceAttributeTemplates: this.attributeTemplatesView.collection.toJSON(), + linkedDocuments: this.linkedDocumentsView.collection.toJSON() + }, { + success: function () { + if (that.model.collection){ + that.model.collection.fetch(); + } + that.model.fetch(); + that.hide(); + that.model.trigger('change'); + Backbone.Events.trigger('part:saved'); + Backbone.Events.trigger('part:iterationChange'); + }, + error: this.onError + }); + + + that.deleteClickedTags(); + this.cadFileView.deleteFilesToDelete(); + this.attachedFilesView.deleteFilesToDelete(); + + e.preventDefault(); + e.stopPropagation(); + + return false; + }, + + initCadFileUploadView: function () { + this.cadFileView = new FileListView({ + title: App.config.i18n.CAD_FILE, + baseName: this.iteration.getBaseName('nativecad'), + deleteBaseUrl: this.iteration.url(), + uploadBaseUrl: this.iteration.getNativeCadFileUploadBaseUrl(), + collection: this.iteration._nativeCADFile, + editMode: this.editMode, + singleFile: true + }).render(); + + this.$('#iteration-files').html(this.cadFileView.el); + if(this.editMode){ + this.conversionStatusView = new ConversionStatusView({ + model:this.iteration + }).render(); + this.$('.file-list').first().after(this.conversionStatusView.el); + } + + }, + + initAttachedFilesUploadView: function () { + this.attachedFilesView = new FileListView({ + title: App.config.i18n.ATTACHED_FILES, + baseName: this.iteration.getBaseName('attachedfiles'), + deleteBaseUrl: this.iteration.url(), + uploadBaseUrl: this.iteration.getAttachedFilesUploadBaseUrl(), + collection: this.iteration.getAttachedFiles(), + editMode: this.editMode + }).render(); + + this.$('#iteration-files').append(this.attachedFilesView.el); + + }, + + updateConversionStatusView:function(){ + this.conversionStatusView.launch(); + }, + + initPartAssemblyView: function () { + this.partAssemblyView = new PartAssemblyView({ + el: '#iteration-components', + collection: new Backbone.Collection(this.iteration.getComponents()), + editMode: this.editMode, + model: this.model + }).render(); + }, + + initLinkedDocumentsView: function () { + if (this.productConfigSpec) { + var self = this; + $.ajax({ + type:'GET', + url: App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.productId + '/document-links/' + this.iteration.getReference() + '/' + this.productConfigSpec, + contentType:'application/json', + + success:function(linkedDocuments) { + self.iteration.setLinkedDocuments(linkedDocuments); + self.displayLinkedDocumentIterationsView(); + }, + + error: function() { + self.displayLinkedDocumentsView(); + } + }); + + } else { + this.displayLinkedDocumentsView(); + } + }, + + displayLinkedDocumentsView: function () { + this.linkedDocumentsView = new LinkedDocumentsView({ + editMode: this.editMode, + commentEditable:true, + documentIteration: this.iteration, + collection: new LinkedDocumentCollection(this.iteration.getLinkedDocuments()) + }).render(); + + /* Add the documentLinksView to the tab */ + this.$('#iteration-links').html(this.linkedDocumentsView.el); + }, + + displayLinkedDocumentIterationsView: function () { + this.linkedDocumentsView = new LinkedDocumentsView({ + editMode: this.editMode, + commentEditable:true, + documentIteration: this.iteration, + collection: new LinkedDocumentIterationCollection(this.iteration.getLinkedDocuments()) + }).render(); + + /* Add the documentLinksView to the tab */ + this.$('#iteration-links').html(this.linkedDocumentsView.el); + }, + + initUsedByView: function () { + this.usedByView = new UsedByView({ + linkedPart: this.model + }).render(); + + /* Add the usedByView to the tab */ + this.$('#iteration-used-by').html(this.usedByView.el); + }, + + initLifeCycleView: function () { + var that = this; + if (this.model.get('workflow')) { + + this.lifecycleView = new LifecycleView({ + el: '#tab-iteration-lifecycle' + }).setWorkflow(this.model.get('workflow')).setEntityType('parts').render(); + + this.lifecycleView.on('lifecycle:change', function () { + that.model.fetch({success: function () { + that.lifecycleView.setWorkflow(that.model.get('workflow')).setEntityType('parts').render(); + }}); + }); + + } else { + this.$('a[href=#tab-iteration-lifecycle]').hide(); + } + }, + + initModificationNotificationGroupListView: function () { + new ModificationNotificationGroupListView({ + el: '#iteration-modification-notifications', + collection: this.model.getModificationNotifications() + }).render(); + }, + + updateModificationNotifications: function () { + var unread = 0; + this.model.getModificationNotifications().each(function(notif) { + if (!notif.isAcknowledged()) { + unread++; + } + }); + if (unread === 0) { + this.model.fetch(); + Backbone.Events.trigger('part:saved'); + } + }, + + tagsManagement: function (editMode) { + + var $tagsZone = this.$('.master-tags-list'); + var that = this; + + _.each(this.model.attributes.tags, function (tagLabel) { + + var tagView; + + var tagViewParams = editMode ? + { + model: new Tag({id: tagLabel, label: tagLabel}), + isAdded: true, + clicked: function () { + that.tagsToRemove.push(tagLabel); + tagView.$el.remove(); + } + } + : + { + model: new Tag({id: tagLabel, label: tagLabel}), + isAdded: true, + clicked: function () { + that.tagsToRemove.push(tagLabel); + tagView.$el.remove(); + that.model.removeTag(tagLabel, function () { + if (that.model.collection.parent) { + if (_.contains(that.tagsToRemove, that.model.collection.parent.id)) { + that.model.collection.remove(that.model); + } + } + }); + tagView.$el.remove(); + } + }; + + tagView = new TagView(tagViewParams).render(); + + $tagsZone.append(tagView.el); + + }); + }, + + deleteClickedTags: function () { + if (this.tagsToRemove.length) { + var that = this; + this.model.removeTags(this.tagsToRemove, function () { + if (that.model.collection.parent) { + if (_.contains(that.tagsToRemove, that.model.collection.parent.id)) { + that.model.collection.remove(that.model); + } + } + }); + } + }, + + actionCheckin: function () { + + // cannot pass a collection of cad file to server. + var cadFile = this.cadFileView.collection.first(); + if (cadFile) { + this.iteration.set('nativeCADFile', cadFile.get('fullName')); + } else { + this.iteration.set('nativeCADFile', ''); + } + + var that = this; + this.iteration.save({ + iterationNote: this.$inputIterationNote.val() || null, + components: this.partAssemblyView.collection.toJSON(), + instanceAttributes: this.attributesView.collection.toJSON(), + instanceAttributeTemplates: this.attributeTemplatesView.collection.toJSON(), + linkedDocuments: this.linkedDocumentsView.collection.toJSON() + }, { + success: function () { + that.model.checkin().success(function () { + that.onSuccess(); + }); + }, + error: this.onError + } + ); + + this.deleteClickedTags(); + this.cadFileView.deleteFilesToDelete(); + this.attachedFilesView.deleteFilesToDelete(); + }, + + actionCheckout: function () { + var self = this; + self.model.checkout().success(function () { + self.onSuccess(); + }); + + }, + + actionUndoCheckout: function () { + var self = this; + self.model.undocheckout().success(function () { + self.onSuccess(); + }); + + }, + + onSuccess: function () { + this.model.fetch().success(function () { + this.iteration = this.model.getLastIteration(); + this.iterations = this.model.getIterations(); + this.render(); + this.activateTab(1); + Backbone.Events.trigger('part:saved'); + Backbone.Events.trigger('part:iterationChange'); + }.bind(this)); + + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + + this.$el.find('.notifications').first().append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + closeModal: function () { + this.hide(); + } + + }); + + return PartModalView; + +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/part_substitute_link_view.js b/docdoku-web-front/app/js/common-objects/views/part/part_substitute_link_view.js new file mode 100644 index 0000000000..b86f548993 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/part_substitute_link_view.js @@ -0,0 +1,19 @@ +/*global define*/ +define([ + 'backbone', + 'common-objects/views/part/part_link_view' +], function (Backbone, PartLinkView) { + + 'use strict'; + + var PartSubstituteLinkView = PartLinkView.extend({ + className:'component part-substitute-link', + handleSubstitutes:false, + getComponent:function(){ + return this.model.get('substitute'); + } + }); + + return PartSubstituteLinkView; +}); + diff --git a/docdoku-web-front/app/js/common-objects/views/part/part_usage_link_view.js b/docdoku-web-front/app/js/common-objects/views/part/part_usage_link_view.js new file mode 100644 index 0000000000..4467b1016b --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/part_usage_link_view.js @@ -0,0 +1,78 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/views/part/part_link_view', + 'common-objects/views/part/part_substitute_link_view' +], function (Backbone, PartLinkView, PartSubstituteLinkView) { + + 'use strict'; + + var PartUsageLinkView = PartLinkView.extend({ + + className:'component part-usage-link', + + handleSubstitutes:true, + + initialize:function(){ + PartLinkView.prototype.initialize.apply(this, arguments); + this.substitutes = new Backbone.Collection(this.model.get('substitutes')); + this.substitutes.bind('add',this.addPartSubstituteLink.bind(this)); + this.substitutes.bind('remove',this.removePartSubstituteLink.bind(this)); + this.events['click >.part-substitute-links-count']='toggleSubstitutes'; + }, + + render:function(){ + PartLinkView.prototype.render.apply(this, arguments); + this.initSubstituteViews(); + this.updateSubstitutesCount(); + return this; + }, + + getComponent:function(){ + return this.model.get('component'); + }, + + initSubstituteViews:function(){ + this.substitutes.each(this.addPartSubstituteLinkView.bind(this)); + }, + + addPartSubstituteLink:function(partSubstituteLink){ + this.addPartSubstituteLinkView(partSubstituteLink); + this.updateSubstitutes(); + this.onModelChanged(); + this.$el.addClass('substitutes-opened'); + }, + + addPartSubstituteLinkView:function(partSubstituteLink){ + var partSubstituteLinkView = new PartSubstituteLinkView({ + model:partSubstituteLink, + editMode:this.options.editMode + }).render(); + + this.$('.part-substitute-links').append(partSubstituteLinkView.$el); + this.listenTo(partSubstituteLink,'change',this.updateSubstitutes.bind(this)); + }, + + removePartSubstituteLink : function(){ + this.updateSubstitutes(); + this.onModelChanged(); + }, + + updateSubstitutes:function(){ + this.model.set('substitutes',this.substitutes.models); + this.updateSubstitutesCount(); + }, + + updateSubstitutesCount:function(){ + var substitutesCount = this.substitutes.size(); + this.$('.part-substitute-links-count').text(substitutesCount + ' ' + (substitutesCount <= 1 ? App.config.i18n.PART_SUBSTITUTE : App.config.i18n.PARTS_SUBSTITUTES)); + }, + + toggleSubstitutes:function(){ + this.$el.toggleClass('substitutes-opened'); + } + + }); + + return PartUsageLinkView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/used_by_group_list_view.js b/docdoku-web-front/app/js/common-objects/views/part/used_by_group_list_view.js new file mode 100644 index 0000000000..833eb0a5de --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/used_by_group_list_view.js @@ -0,0 +1,54 @@ +/*global _,define*/ +define([ + 'backbone', + 'mustache', + 'common-objects/views/part/used_by_list_view', + 'common-objects/models/product_instance', + 'common-objects/collections/product_instances' +], function (Backbone, Mustache, UsedByListView, ProductInstance, ProductInstancesCollection) { + 'use strict'; + var UsedByGroupListView = Backbone.View.extend({ + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.usedByViews = []; + + var that = this; + + this.options.linkedPart.getUsedByProductInstances({ + success: function (productInstancesArray) { + that.groupCollection(productInstancesArray); + } + }); + + return this; + }, + + groupCollection: function (productInstancesArray) { + this.groupedMap = _.groupBy( + productInstancesArray, + function (productInstance) { + return productInstance.configurationItemId; + }, + this + ); + + _.each(_.keys(this.groupedMap), this.addListView, this); + }, + + addListView: function (key) { + var usedByView = new UsedByListView({ + collection: new ProductInstancesCollection(this.groupedMap[key]) + }).render(); + + this.usedByViews.push(usedByView); + this.$el.append(usedByView.$el); + } + + }); + + return UsedByGroupListView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/used_by_list_item_part_view.js b/docdoku-web-front/app/js/common-objects/views/part/used_by_list_item_part_view.js new file mode 100644 index 0000000000..f8b86edb03 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/used_by_list_item_part_view.js @@ -0,0 +1,47 @@ +/*global _,define,App,require*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/part/used_by_list_item_part.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var UsedByListItemPartView = Backbone.View.extend({ + + tagName: 'li', + className: 'used-by-item well', + + events:{ + 'click a.reference': 'toPartDetailView' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + var data = { + i18n: App.config.i18n, + model: this.model + }; + + this.$el.html(Mustache.render(template, data)); + return this; + }, + + toPartDetailView:function(){ + this.$el.trigger('close-modal-request'); + var part = this.model; + part.fetch().success(function () { + require(['common-objects/views/part/part_modal_view'],function(PartModalView){ + var partModalView = new PartModalView({ + model: part + }); + partModalView.show(); + }); + }); + } + + }); + + return UsedByListItemPartView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/used_by_list_item_view.js b/docdoku-web-front/app/js/common-objects/views/part/used_by_list_item_view.js new file mode 100644 index 0000000000..3e1889fcbb --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/used_by_list_item_view.js @@ -0,0 +1,31 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/part/used_by_list_item.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var UsedByListItemView = Backbone.View.extend({ + + tagName: 'li', + className: 'used-by-item well', + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + var data = { + i18n: App.config.i18n, + model: this.model + }; + + this.$el.html(Mustache.render(template, data)); + return this; + } + + }); + + return UsedByListItemView; +}); + diff --git a/docdoku-web-front/app/js/common-objects/views/part/used_by_list_view.js b/docdoku-web-front/app/js/common-objects/views/part/used_by_list_view.js new file mode 100644 index 0000000000..7ae5045323 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/used_by_list_view.js @@ -0,0 +1,48 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/views/part/used_by_list_item_view', + 'text!common-objects/templates/part/used_by_list.html' +], function (Backbone, Mustache, UsedByListItemView, template) { + 'use strict'; + var UsedByListView = Backbone.View.extend({ + + className: 'used-by-items-view', + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + var data = { + configurationItemId: this.options.collection.at(0).getConfigurationItemId(), + i18n: App.config.i18n + }; + + this.$el.html(Mustache.render(template, data)); + this.bindDomElements(); + + this.usedByProductInstanceViews = []; + this.options.collection.each(this.addProductInstanceView.bind(this)); + + return this; + }, + + bindDomElements: function () { + this.productInstancesUL = this.$('#used-by-product-instances'); + }, + + addProductInstanceView: function (model) { + var usedByView = new UsedByListItemView({ + model: model + }).render(); + + this.usedByProductInstanceViews.push(usedByView); + this.productInstancesUL.append(usedByView.$el); + } + + }); + + return UsedByListView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/used_by_path_data_item_view.js b/docdoku-web-front/app/js/common-objects/views/part/used_by_path_data_item_view.js new file mode 100644 index 0000000000..2f20ef1a74 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/used_by_path_data_item_view.js @@ -0,0 +1,45 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/part/used_by_path_data_item.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var UsedByPathDataItemView = Backbone.View.extend({ + + tagName: 'li', + className: 'used-by-item well', + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + + var data = { + i18n: App.config.i18n, + model: this.model + }; + + this.$el.html(Mustache.render(template, data)); + + var partLinks = this.model.getPartLinks(); + var $pathDescription = this.$('.path-description'); + _.each(partLinks, function (partLink) { + var text = partLink.name + ' < ' + partLink.number + ' >'; + if(partLink.referenceDescription){ + text+=' ('+partLink.referenceDescription+')'; + } + $pathDescription.append(text + ' '); + }); + + this.$('.fa.fa-long-arrow-right').last().remove(); + + return this; + } + + }); + + return UsedByPathDataItemView; +}); + diff --git a/docdoku-web-front/app/js/common-objects/views/part/used_by_pd_instance_group_list_view.js b/docdoku-web-front/app/js/common-objects/views/part/used_by_pd_instance_group_list_view.js new file mode 100644 index 0000000000..832f03abee --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/used_by_pd_instance_group_list_view.js @@ -0,0 +1,53 @@ +/*global _,define*/ +define([ + 'backbone', + 'mustache', + 'common-objects/views/part/used_by_pd_instance_list_view', + 'common-objects/collections/path_data_iterations' +], function (Backbone, Mustache, UsedByListView, PathDataIterations) { + 'use strict'; + var UsedByGroupListView = Backbone.View.extend({ + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.usedByViews = []; + var docId = this.options.linkedDocumentId; + var that = this; + + this.options.linkedDocument.getInversePathDataLinks(docId,{ + success: function (productInstancesArray) { + that.groupCollection(productInstancesArray); + } + }); + + return this; + }, + + groupCollection: function (productInstancesArray) { + this.groupedMap = _.groupBy( + productInstancesArray, + function (productInstance) { + return productInstance.serialNumber; + }, + this + ); + + _.each(_.keys(this.groupedMap), this.addListView, this); + }, + + addListView: function (key) { + var usedByView = new UsedByListView({ + collection: new PathDataIterations(this.groupedMap[key]) + }).render(); + + this.usedByViews.push(usedByView); + this.$el.append(usedByView.$el); + } + + }); + + return UsedByGroupListView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/used_by_pd_instance_list_view.js b/docdoku-web-front/app/js/common-objects/views/part/used_by_pd_instance_list_view.js new file mode 100644 index 0000000000..fa18d4d550 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/used_by_pd_instance_list_view.js @@ -0,0 +1,48 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/views/part/used_by_path_data_item_view', + 'text!common-objects/templates/part/used_by_pd_instance_list.html' +], function (Backbone, Mustache, UsedByListItemView, template) { + 'use strict'; + var UsedByListView = Backbone.View.extend({ + + className: 'used-by-items-view', + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + var data = { + serialNumber: this.options.collection.at(0).getSerialNumber(), + i18n: App.config.i18n + }; + + this.$el.html(Mustache.render(template, data)); + this.bindDomElements(); + + this.usedByProductInstanceViews = []; + this.options.collection.each(this.addProductInstanceView.bind(this)); + + return this; + }, + + bindDomElements: function () { + this.productInstancesUL = this.$('#used-by-product-instances'); + }, + + addProductInstanceView: function (model) { + var usedByView = new UsedByListItemView({ + model: model + }).render(); + + this.usedByProductInstanceViews.push(usedByView); + this.productInstancesUL.append(usedByView.$el); + } + + }); + + return UsedByListView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/part/used_by_view.js b/docdoku-web-front/app/js/common-objects/views/part/used_by_view.js new file mode 100644 index 0000000000..5330aac577 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/part/used_by_view.js @@ -0,0 +1,99 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/part/used_by_view.html', + 'common-objects/views/part/used_by_group_list_view', + 'common-objects/views/part/used_by_list_item_part_view', + 'common-objects/models/part' +], function (Backbone, Mustache, template, UsedByGroupListView, UsedByListItemPartView, Part) { + 'use strict'; + var UsedByView = Backbone.View.extend({ + + className: 'used-by-items-view', + + initialize:function(){ + this.linkedPart = this.options.linkedPart; + }, + + render:function(){ + + var data = { + i18n: App.config.i18n + }; + + this.$el.html(Mustache.render(template, data)); + + this.bindDomElements(); + if(this.linkedPart){ + var self = this; + $.ajax({ + type:'GET', + url: App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/' + this.linkedPart.getPartKey() + '/used-by-as-component', + contentType:'application/json', + success:function(parts){ + _.each(parts, function(part){ + var newPartModel = new Part(part); + var usedByView = new UsedByListItemPartView({ + model: newPartModel + }).render(); + + self.$componenetOfUL.append(usedByView.$el); + }); + + if(parts.length === 0){ + self.$('#title-used-by-is-component-of').hide(); + } + + }, + error: function(){ + + } + }); + + $.ajax({ + type:'GET', + url: App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/' + this.linkedPart.getPartKey() + '/used-by-as-substitute', + contentType:'application/json', + success:function(parts){ + _.each(parts, function(part){ + var newPartModel = new Part(part); + var usedByView = new UsedByListItemPartView({ + model: newPartModel + }).render(); + + self.$substituteOfUL.append(usedByView.$el); + }); + + if(parts.length === 0){ + self.$('#title-used-by-is-substitute-of').hide(); + } + }, + error: function(){ + + } + }); + } + + this.initUsedByGroup(); + + return this; + }, + + bindDomElements: function () { + this.$componenetOfUL = this.$('#used-by-is-component-of'); + this.$substituteOfUL = this.$('#used-by-is-substitute-of'); + }, + + initUsedByGroup:function(){ + this.usedByGroupListView = new UsedByGroupListView({ + linkedPart: this.linkedPart + }).render(); + + /* Add the usedByGroupListView to the tab */ + this.$('#used-by-group-list-view').html(this.usedByGroupListView.el); + } + }); + + return UsedByView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/pathToPathLink/path_to_path_link_item.js b/docdoku-web-front/app/js/common-objects/views/pathToPathLink/path_to_path_link_item.js new file mode 100644 index 0000000000..b2082e0f20 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/pathToPathLink/path_to_path_link_item.js @@ -0,0 +1,69 @@ +/*global define,App,$*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/pathToPathLink/path_to_path_link_item.html', + 'common-objects/views/alert' +], function (Backbone, Mustache, template,AlertView){ + 'use strict'; + + var PathToPathLinkItemView = Backbone.View.extend({ + + className:'well', + + events:{ + 'click .delete-item' : 'onDeleteItem' + }, + + initialize: function(){ + this.model = this.options.model; + }, + + + render: function () { + + var data = { + i18n: App.config.i18n, + creationMode : this.model.creationMode, + sourceComponents : this.model.pathToPath.sourceComponents, + targetComponents : this.model.pathToPath.targetComponents, + availableType: this.model.availableType, + description : this.model.pathToPath.description, + type : this.model.pathToPath.type, + canSuppress: this.model.canSuppress + }; + + this.$el.html(Mustache.render(template, data)); + this.$('.link-source .fa-long-arrow-right').last().remove(); + this.$('.link-target .fa-long-arrow-right').last().remove(); + + return this; + }, + onDeleteItem: function () { + if (this.model.canSuppress) { + var self = this; + var urlToDelete = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.model.serialNumber + '/path-to-path-links/' + this.model.pathToPath.id; + $.ajax({ + type: 'DELETE', + url: urlToDelete, + contentType: 'application/json', + success: function () { + self.trigger('pathToPathLink:remove'); + self.remove(); + }, + error: function (errorMessage) { + self.$('.error-div').append(new AlertView({ + type: 'error', + message: errorMessage.responseText + }).render().$el); + } + }); + } + } + + + + }); + + return PathToPathLinkItemView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/prompt.js b/docdoku-web-front/app/js/common-objects/views/prompt.js new file mode 100644 index 0000000000..2bdd26df92 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/prompt.js @@ -0,0 +1,88 @@ +/*global _,define*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/prompt.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var PromptView = Backbone.View.extend({ + + events: { + 'click #cancelPrompt': 'cancelAction', + 'click #submitPrompt': 'primaryAction', + 'hidden #prompt_modal': 'onHidden', + 'shown #prompt_modal': 'onShown' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, { + title: this.title, + question: this.question, + primaryButton: this.primaryButton, + cancelButton: this.cancelButton, + inputSpecified: this.inputSpecified, + defaultValue: this.defaultValue, + label: this.label, + type:this.type || 'text' + })); + this.bindDomElements(); + return this; + }, + + bindDomElements: function () { + this.$modal = this.$('#prompt_modal'); + this.$promptInput = this.$('#prompt_input'); + }, + + primaryAction: function (e) { + this.trigger('prompt-ok', [this.$promptInput.val()]); + this.closeModal(); + e.preventDefault(); + return false; + }, + + cancelAction: function (e) { + this.trigger('prompt-cancel'); + this.closeModal(); + e.preventDefault(); + return false; + }, + + setPromptOptions: function (title, question, primaryButton, cancelButton, defaultValue, label, type) { + this.title = title; + this.question = question; + this.primaryButton = primaryButton; + this.cancelButton = cancelButton; + this.defaultValue = defaultValue; + this.label = label; + this.type = type; + }, + + specifyInput: function(inputSpecified) { + this.inputSpecified = inputSpecified; + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onShown: function () { + this.$promptInput.focus(); + this.$promptInput.addClass('ready'); + }, + + onHidden: function () { + this.remove(); + } + }); + + return PromptView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/security/acl.js b/docdoku-web-front/app/js/common-objects/views/security/acl.js new file mode 100644 index 0000000000..1b129a959f --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/security/acl.js @@ -0,0 +1,100 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/collections/security/workspace_user_memberships', + 'common-objects/collections/security/workspace_user_group_memberships', + 'common-objects/views/security/membership_item', + 'common-objects/models/security/admin', + 'text!common-objects/templates/security/acl_entries.html' +], function (Backbone, Mustache, WorkspaceUserMemberships, WorkspaceUserGroupMemberships, MembershipItemView, Admin, template) { + 'use strict'; + var ACLView = Backbone.View.extend({ + + initialize: function () { + _.bindAll(this); + this.useACL = false; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + + var that = this; + + this.admin = new Admin(); + this.userMemberships = new WorkspaceUserMemberships(); + this.userGroupMemberships = new WorkspaceUserGroupMemberships(); + + this.$aclSwitch.bootstrapSwitch(); + this.$aclSwitch.bootstrapSwitch('setState', this.useACL); + + this.$aclSwitch.on('switch-change', function () { + that.useACL = !that.useACL; + that.$usingAcl.toggleClass('hide'); + }); + + + this.admin.fetch({reset: true, success: function () { + + that.listenToOnce(that.userMemberships, 'reset', that.onUserMembershipsReset); + that.listenToOnce(that.userGroupMemberships, 'reset', that.onUserGroupMembershipsReset); + + that.userMemberships.fetch({reset: true}); + that.userGroupMemberships.fetch({reset: true}); + }}); + + return this; + }, + + bindDomElements: function () { + this.$usersAcls = this.$('#users-acl-entries'); + this.$userGroupsAcls = this.$('#groups-acl-entries'); + this.$usingAcl = this.$('.using-acl'); + this.$aclSwitch = this.$('.acl-switch'); + }, + + onUserMembershipsReset: function () { + var that = this; + this.userMemberships.each(function (userMembership) { + var view = new MembershipItemView({model: userMembership, editMode: that.options.editMode && userMembership.getUserLogin() !== that.admin.getLogin() && userMembership.getUserLogin() !== App.config.login}).render(); + that.$usersAcls.append(view.$el); + }); + }, + + onUserGroupMembershipsReset: function () { + var that = this; + this.userGroupMemberships.each(function (userGroupMembership) { + var view = new MembershipItemView({model: userGroupMembership, editMode: that.options.editMode}).render(); + that.$userGroupsAcls.append(view.$el); + }); + }, + + toList: function () { + + if (this.useACL) { + + var data = {}; + data.userEntries = {}; + data.groupEntries = {}; + + data.userEntries.entry = []; + data.groupEntries.entry = []; + this.userMemberships.each(function (userMembership) { + data.userEntries.entry.push({key: userMembership.key(), value: userMembership.getPermission()}); + }); + this.userGroupMemberships.each(function (userGroupMembership) { + data.groupEntries.entry.push({key: userGroupMembership.key(), value: userGroupMembership.getPermission()}); + }); + return data; + + } + + return null; + + } + + }); + + return ACLView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/security/acl_clone_edit.js b/docdoku-web-front/app/js/common-objects/views/security/acl_clone_edit.js new file mode 100644 index 0000000000..723bf32b84 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/security/acl_clone_edit.js @@ -0,0 +1,169 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/collections/security/workspace_user_memberships', + 'common-objects/collections/security/workspace_user_group_memberships', + 'common-objects/views/security/membership_item', + 'common-objects/models/security/acl_user_entry', + 'common-objects/models/security/acl_user_group_entry', + 'common-objects/views/security/acl_item', + 'common-objects/models/security/admin', + 'text!common-objects/templates/security/acl_entries.html', + 'common-objects/views/alert' +], function (Backbone, Mustache, WorkspaceUserMemberships, WorkspaceUserGroupMemberships, MembershipItemView, ACLUserEntry, ACLUserGroupEntry, ACLItemView, Admin, template,AlertView) { + 'use strict'; + var ACLEditView = Backbone.View.extend({ + + + initialize: function () { + _.bindAll(this); + this.useACL = false; + this.acl = this.options.acl; + this.aclUserEntries = []; + this.aclUserGroupEntries = []; + }, + + + bindDomElements: function () { + this.$usersAcls = this.$('#users-acl-entries'); + this.$userGroupsAcls = this.$('#groups-acl-entries'); + this.$usingAcl = this.$('.using-acl'); + this.$aclSwitch = this.$('.acl-switch'); + + }, + + render: function () { + + var that = this; + + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, title: this.title})); + + this.bindDomElements(); + + this.admin = new Admin(); + this.admin.fetch({reset: true, success: function () { + + that.useACL = false; + + if (that.acl) { + if (that.acl.userEntries.entry.length > 0 || that.acl.groupEntries.entry.length > 0) { + that.useACL = true; + that.$usingAcl.removeClass('hide'); + } + }else{ + that.$usingAcl.addClass('hide'); + } + + if (!that.acl) { + that.onNoAclGiven(); + } else { + + _.each(that.acl.userEntries.entry, function (entry) { + var userLogin = entry.key; + var permission = entry.value; + var editMode = that.options.editMode && userLogin !== that.admin.getLogin() && userLogin !== App.config.login; + var userAclView = new ACLItemView({model: new ACLUserEntry({userLogin: userLogin, permission: permission}), editMode: editMode}).render(); + that.$usersAcls.append(userAclView.$el); + that.aclUserEntries.push(userAclView.model); + }); + + + _.each(that.acl.groupEntries.entry, function (entry) { + var groupId = entry.key; + var permission = entry.value; + var editMode = that.options.editMode; + var groupAclView = new ACLItemView({model: new ACLUserGroupEntry({groupId: groupId, permission: permission}), editMode: editMode}).render(); + that.$userGroupsAcls.append(groupAclView.$el); + that.aclUserGroupEntries.push(groupAclView.model); + }); + + } + + that.$aclSwitch.bootstrapSwitch(); + that.$aclSwitch.bootstrapSwitch('setState', that.useACL); + that.$aclSwitch.on('switch-change', function () { + that.useACL = !that.useACL; + that.$usingAcl.toggleClass('hide'); + }); + + }}); + + return this; + }, + + + onNoAclGiven: function () { + this.loadWorkspaceMembership(); + }, + + loadWorkspaceMembership: function () { + this.userMemberships = new WorkspaceUserMemberships(); + this.userGroupMemberships = new WorkspaceUserGroupMemberships(); + this.listenToOnce(this.userMemberships, 'reset', this.onUserMembershipsReset); + this.listenToOnce(this.userGroupMemberships, 'reset', this.onUserGroupMembershipsReset); + this.userMemberships.fetch({reset: true}); + this.userGroupMemberships.fetch({reset: true}); + }, + + onUserMembershipsReset: function () { + var that = this; + this.userMemberships.each(function (userMembership) { + var view = new ACLItemView({model: new ACLUserEntry({userLogin: userMembership.key(), permission: userMembership.getPermission()}), editMode: that.options.editMode && userMembership.key() !== that.admin.getLogin() && userMembership.key() !== App.config.login}).render(); + that.$usersAcls.append(view.$el); + that.aclUserEntries.push(view.model); + }); + }, + + onUserGroupMembershipsReset: function () { + var that = this; + this.userGroupMemberships.each(function (userGroupMembership) { + var view = new ACLItemView({model: new ACLUserGroupEntry({groupId: userGroupMembership.key(), permission: userGroupMembership.getPermission()}), editMode: that.options.editMode}).render(); + that.$userGroupsAcls.append(view.$el); + that.aclUserGroupEntries.push(view.model); + }); + }, + + toList: function () { + + var dto = {}; + dto.userEntries = {}; + dto.groupEntries = {}; + + dto.userEntries.entry = []; + dto.groupEntries.entry = []; + + if (this.useACL) { + _(this.aclUserEntries).each(function (aclEntry) { + dto.userEntries.entry.push({key: aclEntry.key(), value: aclEntry.getPermission()}); + }); + _(this.aclUserGroupEntries).each(function (aclEntry) { + dto.groupEntries.entry.push({key: aclEntry.key(), value: aclEntry.getPermission()}); + }); + return dto; + } + + return null; + + }, + + onSubmit: function (e) { + this.trigger('acl:update'); + e.preventDefault(); + e.stopPropagation(); + return false; + }, + onError:function(model, error){ + var errorMessage = error ? model.responseText : error; + var alertView =new AlertView({type: 'error',message: errorMessage}).render(); + this.$notifications.append(alertView.$el); + }, + onRemove: function () { + this.remove(); + } + + + }); + + return ACLEditView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/security/acl_edit.js b/docdoku-web-front/app/js/common-objects/views/security/acl_edit.js new file mode 100644 index 0000000000..816c498c72 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/security/acl_edit.js @@ -0,0 +1,188 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/collections/security/workspace_user_memberships', + 'common-objects/collections/security/workspace_user_group_memberships', + 'common-objects/views/security/membership_item', + 'common-objects/models/security/acl_user_entry', + 'common-objects/models/security/acl_user_group_entry', + 'common-objects/views/security/acl_item', + 'common-objects/models/security/admin', + 'text!common-objects/templates/security/acl_edit.html', + 'common-objects/views/alert' +], function (Backbone, Mustache, WorkspaceUserMemberships, WorkspaceUserGroupMemberships, MembershipItemView, ACLUserEntry, ACLUserGroupEntry, ACLItemView, Admin, template,AlertView) { + 'use strict'; + var ACLEditView = Backbone.View.extend({ + + events: { + 'hidden #acl_edit_modal': 'onRemove', + 'submit form': 'onSubmit' + }, + + initialize: function () { + _.bindAll(this); + this.useACL = false; + this.acl = this.options.acl; + this.aclUserEntries = []; + this.aclUserGroupEntries = []; + }, + + setTitle: function (title) { + this.title = title; + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + bindDomElements: function () { + this.$modal = this.$('#acl_edit_modal'); + this.$usersAcls = this.$('#users-acl-entries'); + this.$userGroupsAcls = this.$('#groups-acl-entries'); + this.$usingAcl = this.$('.using-acl'); + this.$aclSwitch = this.$('.acl-switch'); + this.$notifications = this.$('.notifications'); + }, + + render: function () { + + var that = this; + + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, title: this.title})); + + this.bindDomElements(); + + this.admin = new Admin(); + this.admin.fetch({reset: true, success: function () { + + that.useACL = false; + + if (that.acl) { + if (that.acl.userEntries.entry.length > 0 || that.acl.groupEntries.entry.length > 0) { + that.useACL = true; + } + } + + if (!that.useACL) { + that.$usingAcl.addClass('hide'); + } + + if (!that.acl) { + that.onNoAclGiven(); + } else { + + _.each(that.acl.userEntries.entry, function (entry) { + var userLogin = entry.key; + var permission = entry.value; + var editMode = that.options.editMode && userLogin !== that.admin.getLogin() && userLogin !== App.config.login; + var userAclView = new ACLItemView({model: new ACLUserEntry({userLogin: userLogin, permission: permission}), editMode: editMode}).render(); + that.$usersAcls.append(userAclView.$el); + that.aclUserEntries.push(userAclView.model); + }); + + + _.each(that.acl.groupEntries.entry, function (entry) { + var groupId = entry.key; + var permission = entry.value; + var editMode = that.options.editMode; + var groupAclView = new ACLItemView({model: new ACLUserGroupEntry({groupId: groupId, permission: permission}), editMode: editMode}).render(); + that.$userGroupsAcls.append(groupAclView.$el); + that.aclUserGroupEntries.push(groupAclView.model); + }); + + } + + that.$aclSwitch.bootstrapSwitch(); + that.$aclSwitch.bootstrapSwitch('setState', that.useACL); + that.$aclSwitch.on('switch-change', function () { + that.useACL = !that.useACL; + that.$usingAcl.toggleClass('hide'); + }); + + }}); + + return this; + }, + + + onNoAclGiven: function () { + this.loadWorkspaceMembership(); + }, + + loadWorkspaceMembership: function () { + this.userMemberships = new WorkspaceUserMemberships(); + this.userGroupMemberships = new WorkspaceUserGroupMemberships(); + this.listenToOnce(this.userMemberships, 'reset', this.onUserMembershipsReset); + this.listenToOnce(this.userGroupMemberships, 'reset', this.onUserGroupMembershipsReset); + this.userMemberships.fetch({reset: true}); + this.userGroupMemberships.fetch({reset: true}); + }, + + onUserMembershipsReset: function () { + var that = this; + this.userMemberships.each(function (userMembership) { + var view = new ACLItemView({model: new ACLUserEntry({userLogin: userMembership.key(), permission: userMembership.getPermission()}), editMode: that.options.editMode && userMembership.key() !== that.admin.getLogin() && userMembership.key() !== App.config.login}).render(); + that.$usersAcls.append(view.$el); + that.aclUserEntries.push(view.model); + }); + }, + + onUserGroupMembershipsReset: function () { + var that = this; + this.userGroupMemberships.each(function (userGroupMembership) { + var view = new ACLItemView({model: new ACLUserGroupEntry({groupId: userGroupMembership.key(), permission: userGroupMembership.getPermission()}), editMode: that.options.editMode}).render(); + that.$userGroupsAcls.append(view.$el); + that.aclUserGroupEntries.push(view.model); + }); + }, + + toList: function () { + + var dto = {}; + dto.userEntries = {}; + dto.groupEntries = {}; + + dto.userEntries.entry = []; + dto.groupEntries.entry = []; + + if (this.useACL) { + _(this.aclUserEntries).each(function (aclEntry) { + dto.userEntries.entry.push({key: aclEntry.key(), value: aclEntry.getPermission()}); + }); + _(this.aclUserGroupEntries).each(function (aclEntry) { + dto.groupEntries.entry.push({key: aclEntry.key(), value: aclEntry.getPermission()}); + }); + return dto; + } + + return null; + + }, + + onSubmit: function (e) { + this.trigger('acl:update'); + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onError:function(error){ + var errorMessage = error ? error.responseText : error; + var alertView = new AlertView({type: 'error',message: errorMessage}).render(); + this.$notifications.append(alertView.$el); + }, + + onRemove: function () { + this.remove(); + } + + + }); + + return ACLEditView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/security/acl_item.js b/docdoku-web-front/app/js/common-objects/views/security/acl_item.js new file mode 100644 index 0000000000..2dab9a056b --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/security/acl_item.js @@ -0,0 +1,33 @@ +/*global define,App,_*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/security/acl_entry_item.html' +], function (Backbone, Mustache, template) { + + 'use strict'; + + var ACLItemView = Backbone.View.extend({ + + events: { + 'change input[type=radio]': 'change' + }, + + initialize: function () { + _.bindAll(this); + }, + + change: function (e) { + this.model.set('permission', e.target.value); + }, + + render: function () { + var permission = App.config.i18n[this.model.getPermission()]; + this.$el.html(Mustache.render(template, {acl: this.model, i18n: App.config.i18n, editMode: this.options.editMode, permission: permission, radioName: this.model.key() + '-radio-' + this.cid})); + return this; + } + + }); + + return ACLItemView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/security/membership_item.js b/docdoku-web-front/app/js/common-objects/views/security/membership_item.js new file mode 100644 index 0000000000..eb28e7d79c --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/security/membership_item.js @@ -0,0 +1,33 @@ +/*global define,App,_*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/security/membership_item.html' +], function (Backbone, Mustache, template) { + + 'use strict'; + + var MembershipItemView = Backbone.View.extend({ + + events: { + 'change input[type=radio]': 'change' + }, + + initialize: function () { + _.bindAll(this); + }, + + change: function (e) { + this.model.set('permission', e.target.value); + }, + + render: function () { + var permission = App.config.i18n[this.model.getPermission()]; + this.$el.html(Mustache.render(template, {membership: this.model, i18n: App.config.i18n, editMode: this.options.editMode, permission: permission, radioName: this.model.key() + '-radio-' + this.cid})); + return this; + } + + }); + + return MembershipItemView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/share/share_entity.js b/docdoku-web-front/app/js/common-objects/views/share/share_entity.js new file mode 100644 index 0000000000..7aa11c01f1 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/share/share_entity.js @@ -0,0 +1,124 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/share/share_entity.html', + 'text!common-objects/templates/share/shared_entity.html', + 'common-objects/utils/date' +], function (Backbone, Mustache, template, templateShared, date) { + 'use strict'; + var ShareView = Backbone.View.extend({ + tagName: 'div', + + events: { + 'click #generate-private-share': 'createShare', + 'hidden #share-modal': 'closeModal' + }, + + initialize: function () { + }, + + render: function () { + var that = this; + var title = ''; + + switch (this.options.entityType) { + case 'documents' : + title = App.config.i18n.SHARED_DOCUMENTS_TITLE + ' : ' + this.model.getReference() + '-' + this.model.getVersion(); + break; + case 'parts' : + title = App.config.i18n.SHARED_PARTS_TITLE + ' : ' + this.model.getNumber() + '-' + this.model.getVersion(); + break; + default : + break; + } + + this.$el.html(Mustache.render(template, { + timeZone:App.config.timeZone, + language : App.config.locale, + i18n: App.config.i18n, + title: title, + permalink: this.model.getPermalink() + })); + this.bindDomElements(); + + this.$badPasswordLabel.hide(); + + this.$publicSharedSwitch.bootstrapSwitch(); + this.$publicSharedSwitch.bootstrapSwitch('setState', this.model.get('publicShared')); + + this.$publicSharedSwitch.on('switch-change', function () { + if (that.model.get('publicShared')) { + that.model.unpublish({success: function () { + that.model.fetch(); + }}); + } + else { + that.model.publish({success: function () { + that.model.fetch(); + }}); + } + }); + + + return this; + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.remove(); + }, + + bindDomElements: function () { + this.$modal = this.$('#share-modal'); + this.$password = this.$('.password'); + this.$confirmPassword = this.$('.confirm-password'); + this.$badPasswordLabel = this.$('.bad-password'); + this.$passwordControl = this.$('#password-control'); + this.$expireDate = this.$('.expire-date'); + this.$publicSharedSwitch = this.$('.public-shared-switch'); + this.$privateShare = this.$('#private-share'); + }, + + setEntityType: function (entityType) { + this.entityType = entityType; + }, + + createShare: function () { + var that = this; + var data = {}; + switch (this.options.entityType) { + case 'documents' : + data = {documentMasterId: this.model.getReference(), documentMasterVersion: this.model.getVersion()}; + break; + case 'parts' : + data = {partMasterNumber: this.model.getNumber(), partMasterVersion: this.model.getVersion()}; + break; + default : + break; + } + + if (this.$password.val() !== this.$confirmPassword.val()) { + this.$passwordControl.addClass('error'); + this.$badPasswordLabel.show(); + } else { + data.password = this.$password.val() ? this.$password.val() : null; + data.expireDate = this.$expireDate.val() ? date.toUTCWithTimeZoneOffset(this.$expireDate.val()) : null; + this.model.createShare({data: data, success: function (pData) { + that.$privateShare.empty(); + that.$privateShare.html(Mustache.render(templateShared,{i18n: App.config.i18n, generatedUrl: that.generateUrlFromUUID(pData.uuid)})); + }}); + } + + }, + + generateUrlFromUUID: function (uuid) { + return window.location.origin + App.config.contextPath + '/'+this.options.entityType+'/#' + uuid; + } + + }); + return ShareView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/tags/tag.js b/docdoku-web-front/app/js/common-objects/views/tags/tag.js new file mode 100644 index 0000000000..31bcca8eb8 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/tags/tag.js @@ -0,0 +1,49 @@ +/*global define*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/tags/tag.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var TagView = Backbone.View.extend({ + tagName: 'li', + className: 'pull-left well', + + initialize: function () { + if (this.options.isAdded) { + this.events = { 'click a': 'clicked' }; + } + else if (this.options.isAvailable) { + this.events = { 'click a': 'crossClicked', + 'click': 'clicked' }; + } else { + this.events = { 'click': 'clicked' }; + } + + this.isRemovable = this.options.isAdded || this.options.isAvailable; + + return this; + }, + + render: function () { + this.$el.html(Mustache.render(template, {tag: this.model, isRemovable: this.isRemovable})); + return this; + }, + + clicked: function () { + if (this.options.clicked) { + this.options.clicked(); + } + }, + + crossClicked: function (e) { + if (this.options.crossClicked) { + this.options.crossClicked(); + } + e.stopPropagation(); + } + + }); + + return TagView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/tags/tags_management.js b/docdoku-web-front/app/js/common-objects/views/tags/tags_management.js new file mode 100644 index 0000000000..3f92082b30 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/tags/tags_management.js @@ -0,0 +1,360 @@ +/*global $,_,define,App*/ +define([ + 'backbone', + 'common-objects/models/tag', + 'common-objects/collections/tag', + 'common-objects/views/components/modal', + 'common-objects/views/tags/tag', + 'text!common-objects/templates/tags/tags_management.html' +], function (Backbone,Tag, TagList, ModalView, TagView, template) { + 'use strict'; + var TagsManagementView = ModalView.extend({ + + template: template, + + templateExtraData: {}, + + initialize: function () { + this._collectionSize = this.collection.size(); // Get number of item check + this.templateExtraData = { // Set template variable to know the number of item check + isSingleChecked: this._collectionSize === 1, + isEmptyCollection: this._collectionSize === 0 + }; + + ModalView.prototype.initialize.apply(this, arguments); + this.events['submit #form-' + this.cid] = 'onSubmitForm'; + this.events['click .newTag-button'] = 'onNewTagButton'; + this.events['input .newTag'] = 'onInputChange'; + this.$newTagButton = this.$('.newTag-button').hide(); + this.buttonEnabled = false; + + this._existingTagsCollection = new TagList(); + this._existingTagsCollection.fetch({ // Get all Tag of the Workspace + reset: true, + success: this.onExistingTagsFetched + }); + this._existingTagsViews = []; + + this._currentTagsCollection = new TagList(); + this._currentTagsViews = []; + + this._tagsToCreate = []; + this._tagsToDeleteCollection = []; + this._tagsToAddCollection = []; + + _.bindAll(this); + this.$el.on('remove', this.removeSubviews); // Remove cascade + }, + + onInputChange: function(e) { + //Show or hide only if it's not already hidden/shown + if(!this.buttonEnabled && e.target.value) { + this.$newTagButton.show(); + this.buttonEnabled = true; + } + if(this.buttonEnabled && !e.target.value) { + this.$newTagButton.hide(); + this.buttonEnabled = false; + } + }, + + removeSubviews: function () { // Remove all tagViews + _(this._existingTagsViews).invoke('remove'); + _(this._currentTagsViews).invoke('remove'); + delete this._existingTagsViews; + delete this._currentTagsViews; + }, + + onExistingTagsFetched: function () { // Action to do after All tags fetching + this.renderExistingTags(); + if (this._collectionSize === 1) { + this.activeSingleElementChecked(); + } else { + this.launchEventBind(); + } + }, + + launchEventBind: function () { // Initialize event binding + this._currentTagsCollection.on('add', this.onTagAdded, this); + this._currentTagsCollection.on('remove', this.onTagRemoved, this); + this._existingTagsCollection.on('add', this.onTagCreated, this); + this._existingTagsCollection.on('remove', this.onTagDelete, this); + }, + + activeSingleElementChecked: function () { // Initialize context for single document tags management + this._oldTagsCollection = []; + this._tagsToRemoveCollection = []; + + var that = this; + var singleModel = this.collection.first(); + singleModel.fetch({ + reset: false, + success: function () { + var oldTags = singleModel.getTags(); + if (!_.isEmpty(oldTags)) { + that.createPreviousTags(oldTags); + } + that.launchEventBind(); + } + }); + }, + + renderExistingTags: function () { + var $existingTabs = this.$('.existing-tags-list'); + $existingTabs.empty(); + var that = this; + _.each(this._existingTagsCollection.models, function (model) { + that.addTagViewToExistingTagsList(model); + }); + }, + + createPreviousTags: function (tags) { + var that = this; + _.each(tags, function (tagLabel) { + _.each(that._existingTagsCollection.models, function (model) { + if (model.id === tagLabel) { + that._currentTagsCollection.push(model); + that._oldTagsCollection.push(model); + that.addTagViewToResourceTagsList(model); + that.removeTagViewFromExistingTagsList(model); + } + }); + }); + }, + + onNewTagButton: function () { + var $newTagInput = this.$('.newTag'); + var tagId = $newTagInput.val(); + if (tagId) { + var newModel = new Tag({label: tagId, id: tagId, workspaceId: App.config.workspaceId}); + var modelAlreadyExists = false; + + _.each(this._currentTagsCollection.models, function (model) { + if (model.id === newModel.id) { + modelAlreadyExists = true; + } + }); + + _.each(this._existingTagsCollection.models, function (model) { + if (model.id === newModel.id) { + modelAlreadyExists = true; + } + }); + + if (!modelAlreadyExists) { + this._existingTagsCollection.push(newModel); + if (this._collectionSize !== 0) { + this._currentTagsCollection.push(newModel); + } + $newTagInput.val('').trigger('input'); + } + } + }, + + onTagCreated: function (model) { + this._tagsToCreate.push(model); + this.addTagViewToExistingTagsList(model); + }, + onTagDelete: function (model) { + var isInCreateList = _.contains(this._tagsToCreate, model); + if (isInCreateList) { + this._tagsToCreate = _(this._tagsToCreate).without(model); + } else { + this._tagsToDeleteCollection.push(model); + } + this.removeTagViewFromExistingTagsList(model); + }, + onTagAdded: function (model) { + var isInTagsToRemove = false; + var isInOldTag = false; + if (this._tagsToRemoveCollection) { + isInTagsToRemove = _.contains(this._tagsToRemoveCollection, model); + } + if (this._oldTagsCollection) { + isInOldTag = _.contains(this._oldTagsCollection, model); + } + if (isInTagsToRemove) { + this._tagsToRemoveCollection = this._tagsToRemoveCollection.without(model); + } + if (!isInOldTag) { + this._tagsToAddCollection.push(model); + } + this.addTagViewToResourceTagsList(model); + this.removeTagViewFromExistingTagsList(model); + }, + onTagRemoved: function (model) { + var isInOldTag = false; + var isInTagsToAdd = _.contains(this._tagsToAddCollection, model); + var isInTagsToCreate = _.contains(this._tagsToCreate, model); + if (this._oldTagsCollection) { + isInOldTag = _.contains(this._oldTagsCollection, model); + } + if (isInTagsToAdd) { + this._tagsToAddCollection = _.without(this._tagsToAddCollection, model); + } + if (isInOldTag) { + this._tagsToRemoveCollection.push(model); + } + if (isInTagsToCreate) { // It's a new tag that you remove from resource + this.addTagViewToExistingTagsList(model, true); + } else { + this.addTagViewToExistingTagsList(model); + } + this.removeTagViewFromResourceTagsList(model); + }, + + onSubmitForm: function () { + var that = this; + + this.createNewTags(function () { + that.clearDeleteTags(function () { + if (that._collectionSize !== 0) { + that.addTagsToResources(); + that.removeTagsToResource(); + } + that.hide(); + }); + }); + return false; + }, + + + addTagViewToExistingTagsList: function (model, editmode) { + var $existingTags = this.$('.existing-tags-list'); + var that = this; + var tagView = null; + + if (this._collectionSize === 0){ + tagView = new TagView({ + model: model, + isAdded: false, + isAvailable: true, + clicked: function () {}, + crossClicked: function () { + that._existingTagsCollection.remove(model); + } + }).render(); + } else if(editmode === true) { + tagView = new TagView({ + model: model, + isAdded: false, + isAvailable: true, + clicked: function () { + that._currentTagsCollection.push(model); + }, + crossClicked: function () { + that._existingTagsCollection.remove(model); + } + }).render(); + } else { + tagView = new TagView({ + model: model, + isAdded: false, + isAvailable: false, + clicked: function () { + that._currentTagsCollection.push(model); + } + }).render(); + } + + $existingTags.append($(tagView.el)); + this._existingTagsViews.push(tagView); + }, + removeTagViewFromExistingTagsList: function (model) { + var viewToRemove = _(this._existingTagsViews).select(function (view) { + return view.model === model; + })[0]; + if (viewToRemove) { + $(viewToRemove.el).remove(); + this._existingTagsViews = _(this._existingTagsViews).without(viewToRemove); + viewToRemove.remove(); + } + }, + addTagViewToResourceTagsList: function (model) { + var $tagsToAdd = this.$('.tags-to-add-list'); + var that = this; + var tagView = new TagView({model: model, isAdded: true, clicked: function () { + that._currentTagsCollection.remove(model); + }}).render(); + var $tag = $(tagView.el); + $tagsToAdd.append($tag); + this._currentTagsViews.push(tagView); + }, + removeTagViewFromResourceTagsList: function (model) { + var viewToRemove = _(this._currentTagsViews).select(function (view) { + return view.model === model; + })[0]; + if (viewToRemove) { + $(viewToRemove.el).remove(); + this._currentTagsViews = _(this._currentTagsViews).without(viewToRemove); + viewToRemove.remove(); + } + }, + + + createNewTags: function (callbackSuccess) { + if (this._tagsToCreate.length) { + var that = this; + this._existingTagsCollection.createTags( + that._tagsToCreate, + function () { + callbackSuccess(); + Backbone.Events.trigger('refreshTagNavViewCollection'); + }, + function (error) { + that.alert({message: error.responseText, type: 'error'}); + } + ); + } else { + callbackSuccess(); + } + }, + clearDeleteTags: function (callbackSuccess) { + if (this._tagsToDeleteCollection && this._tagsToDeleteCollection.length) { + var baseUrl = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/tags/'; + var total = this._tagsToDeleteCollection.length; + var count = 0; + _(this._tagsToDeleteCollection).each(function (tag) { + $.ajax({ + type: 'DELETE', + url: baseUrl + tag.id, + success: function () { + count++; + if (count >= total) { + callbackSuccess(); + Backbone.Events.trigger('refreshTagNavViewCollection'); + } + } + }); + }); + } else { + callbackSuccess(); + } + }, + addTagsToResources: function () { + var that = this; + if (this.collection.length) { + _.each(this.collection.models, function (model) { + model.addTags(that._tagsToAddCollection); + }); + } + }, + removeTagsToResource: function () { + var that = this; + + if (this.collection.length) { + _.each(this.collection.models, function (model) { + if (that._tagsToRemoveCollection && that._tagsToRemoveCollection.length) { + _.each(that._tagsToRemoveCollection, function (tag) { + model.removeTag(tag.id, function () { + }); + }); + } + }); + } + } + + }); + + return TagsManagementView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/time_zone.js b/docdoku-web-front/app/js/common-objects/views/time_zone.js new file mode 100644 index 0000000000..c2a9e8aeae --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/time_zone.js @@ -0,0 +1,49 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/time_zone.html', + 'moment', + 'common-objects/utils/date' +], function (Backbone, Mustache, template, moment, date) { + 'use strict'; + var TimeZoneView = Backbone.View.extend({ + events: { + 'hidden #exportSceneModal': 'onHidden' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + dates:this.dates + })); + this.$modal = this.$('#timezone_modal'); + return this; + }, + + setTimestamp:function(timestamp){ + this.dates = date.getMainZonesDates(timestamp); + return this; + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + } + + + }); + + return TimeZoneView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/udf/calculation.js b/docdoku-web-front/app/js/common-objects/views/udf/calculation.js new file mode 100644 index 0000000000..f3e79edd8a --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/udf/calculation.js @@ -0,0 +1,97 @@ +/*global define,App,_*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/udf/calculation.html' +],function(Backbone, Mustache, template){ + + 'use strict'; + + var CalculationView = Backbone.View.extend({ + + className:'calculation', + + events:{ + 'click .remove':'onRemove' + }, + + initialise:function(){ + _.bindAll(this); + }, + + resetCalculation:function(){ + this.memo = 0; + this.visitedAssemblies = 0; + this.visitedInstances = 0; + this.$memo.text(''); + this.$assembliesVisited.text(''); + this.$instancesVisited.text(''); + this.$result.hide(); + }, + + render:function(){ + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, attributeNames:this.options.attributeNames})); + this.binDOMElements(); + this.resetCalculation(); + return this; + }, + + binDOMElements:function(){ + this.$operator = this.$('[name="operator"]'); + this.$attributeName = this.$('[name="attributeName"]'); + this.$result = this.$('.result'); + this.$memo = this.$('.memo'); + this.$instancesVisited = this.$('.instances-visited'); + this.$assembliesVisited = this.$('.assemblies-visited'); + }, + + getOperator:function(){ + return this.$operator.val(); + }, + + getAttributeName:function(){ + return this.$attributeName.val(); + }, + + getMemo:function(){ + return this.memo; + }, + + setMemo:function(memo){ + this.memo = memo; + }, + + incVisitedAssemblies:function(){ + this.visitedAssemblies++; + }, + + incVisitedInstances:function(){ + this.visitedInstances++; + }, + + onEnd:function(){ + + var visitedNodes = this.visitedAssemblies + this.visitedInstances; + + if(visitedNodes){ + if(this.getOperator() === 'AVG'){ + this.memo = this.memo / visitedNodes; + } + } + + this.$memo.text(this.memo); + this.$assembliesVisited.text(this.visitedAssemblies); + this.$instancesVisited.text(this.visitedInstances); + this.$result.show(); + }, + + onRemove:function(){ + this.trigger('removed'); + this.remove(); + } + + }); + + return CalculationView; + +}); diff --git a/docdoku-web-front/app/js/common-objects/views/udf/user_defined_function.js b/docdoku-web-front/app/js/common-objects/views/udf/user_defined_function.js new file mode 100644 index 0000000000..420ae21a4a --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/udf/user_defined_function.js @@ -0,0 +1,231 @@ +/*global _,define,App,$*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/udf/user_defined_function.html', + 'collections/configuration_items', + 'common-objects/collections/baselines', + 'common-objects/views/udf/calculation' +], function (Backbone, Mustache, template,ConfigurationItemCollection,Baselines, CalculationView) { + + 'use strict'; + + var UserDefinedFunctionView = Backbone.View.extend({ + + events: { + 'hidden #user_defined_function_modal': 'onHidden', + 'submit #user_defined_function_form':'run', + 'change .user-defined-product-select':'fetchValues', + 'change .user-defined-type-select':'fetchValues', + 'click .add-calculation':'addCalculation' + }, + + initialize: function () { + _.bindAll(this); + this.calculationViews = []; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.$modal= this.$('#user_defined_function_modal'); + this.$productList = this.$('.user-defined-product-select'); + this.$typeList = this.$('.user-defined-type-select'); + this.$valueList = this.$('.user-defined-value-select'); + this.$runButton = this.$('.run-udf'); + this.$calculations = this.$('.calculations'); + this.fetchProducts(); + return this; + }, + + setBaselineMode:function(){ + this.$typeList.val('baseline'); + }, + + fetchProducts: function () { + var productList = this.$productList; + var typeList = this.$typeList; + var _this = this; + + typeList.append(''); + typeList.append(''); + + new ConfigurationItemCollection().fetch({success:function(products){ + products.each(function(product){ + productList.append(''); + }); + _this.fetchValues(); + _this.fetchAttributes(); + }}); + + }, + + fetchValues: function () { + var productId = this.$productList.val(); + var typeId = this.$typeList.val(); + var valueList = this.$valueList; + valueList.empty(); + + if (productId) { + if (typeId === 'latest') { + valueList.append(''); + valueList.append(''); + valueList.append(''); + + } else if (typeId === 'baseline') { + new Baselines({},{type:'product',productId:productId}).fetch({success:function(baselines) { + baselines.each(function(baseline){ + valueList.append(''); + }); + }}); + + } + } + }, + + fetchAttributes:function(){ + this.availableAttributes = []; + var self = this; + $.ajax({ + url: App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/attributes/part-iterations', + success: function (attributes) { + _.each(attributes,function(attribute){ + if(attribute.type === 'NUMBER'){ + self.availableAttributes.push(attribute.name); + } + }); + } + }); + }, + + addCalculation:function(){ + var _this = this; + var calculationView = new CalculationView({attributeNames:this.availableAttributes}).render(); + this.$calculations.append(calculationView.$el); + this.calculationViews.push(calculationView); + + calculationView.on('removed',function(){ + + _this.calculationViews.splice(_this.calculationViews.indexOf(calculationView),1); + + if(!_this.calculationViews.length){ + _this.$runButton.hide(); + } + + }); + + this.$runButton.show(); + + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + run: function(e){ + + _.each(this.calculationViews,function(view){ + view.resetCalculation(); + }); + + var productId = this.$productList.val(); + var valueId = this.$valueList.val(); + var runButton = this.$runButton; + + runButton.html(App.config.i18n.LOADING +' ...').prop('disabled',true); + + var PartCollection = Backbone.Collection.extend({ + url: function () { + return this.urlBase() + '/filter?configSpec=' + valueId + '&depth=10&path=-1'; + }, + + urlBase: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + productId; + } + }); + + new PartCollection().fetch({ + success:this.doUDF.bind(this) + }); + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + doUDF:function(pRootComponent){ + + var calculationViews = this.calculationViews; + + var onNodeVisited = function(node){ + + _.each(calculationViews,function(view){ + + var operator = view.getOperator(); + var attributeName = view.getAttributeName(); + var memo = view.getMemo(); + + var attribute = node.attrs[attributeName]; + + if(attribute !== undefined){ + + switch(operator){ + case 'SUM': + case 'AVG': + memo += attribute; + break; + + default: + } + + if(node.components.length){ + view.incVisitedAssemblies(); + }else{ + view.incVisitedInstances(); + } + + view.setMemo(memo); + } + + }); + + }; + + var visit = function(rootComponent){ + + rootComponent.attrs = {}; + + _.each(rootComponent.attributes,function(attr){ + if(attr.type === 'NUMBER'){ + rootComponent.attrs[attr.name] = parseFloat(attr.value); + } + }); + + for(var i = 0 ; i < rootComponent.amount ; i++) { + onNodeVisited(rootComponent); + _.each(rootComponent.components,visit); + } + + }; + + visit(pRootComponent.first().attributes); + + _.each(calculationViews,function(view){ + view.onEnd(); + }); + + this.$runButton.html(App.config.i18n.RUN).prop('disabled',false); + } + + }); + + return UserDefinedFunctionView; + +}); diff --git a/docdoku-web-front/app/js/common-objects/views/used_by/used_by_list_item_view.js b/docdoku-web-front/app/js/common-objects/views/used_by/used_by_list_item_view.js new file mode 100644 index 0000000000..22ef6d3bcc --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/used_by/used_by_list_item_view.js @@ -0,0 +1,31 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/document/used_by_list_item.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var UsedByListItemView = Backbone.View.extend({ + + tagName: 'li', + className: 'used-by-item well', + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + var data = { + i18n: App.config.i18n, + model: this.model + }; + + this.$el.html(Mustache.render(template, data)); + return this; + } + + }); + + return UsedByListItemView; +}); + diff --git a/docdoku-web-front/app/js/common-objects/views/used_by/used_by_list_view.js b/docdoku-web-front/app/js/common-objects/views/used_by/used_by_list_view.js new file mode 100644 index 0000000000..986507516d --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/used_by/used_by_list_view.js @@ -0,0 +1,120 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/collections/part_collection', + 'common-objects/collections/product_instances', + 'common-objects/collections/used_by/used_by_document', + 'common-objects/views/used_by/used_by_list_item_view', + 'common-objects/views/part/used_by_list_item_view', + 'common-objects/views/part/used_by_pd_instance_group_list_view', + 'text!common-objects/templates/document/used_by_list.html' +], function (Backbone, Mustache, PartList, ProductInstanceList, UsedByDocumentList, UsedByListItemView,UsedByProductInstanceListItemView, UsedByGroupListView,template) { + 'use strict'; + var UsedByListView = Backbone.View.extend({ + + className: 'used-by-items-view', + + initialize: function () { + _.bindAll(this); + + this.linkedDocumentIterationId = this.options.linkedDocumentIterationId; + this.linkedDocument = this.options.linkedDocument; + + this.documentsCollection = new UsedByDocumentList(); + this.documentsCollection.setLinkedDocumentIterationId(this.linkedDocumentIterationId); + this.documentsCollection.setLinkedDocument(this.linkedDocument); + + this.listenTo(this.documentsCollection, 'reset', this.addDocumentViews); + + var that = this; + this.linkedDocument.getUsedByPartList(this.linkedDocumentIterationId, { + success: function (parts) { + that.partsCollection = new PartList(parts); + that.addPartViews(); + } + }); + this.linkedDocument.getUsedByProductInstances(this.linkedDocumentIterationId, { + success: function (productInstanceIterations) { + that.productInstancesCollection = new ProductInstanceList(productInstanceIterations); + that.addProductInstanceViews(); + } + }); + + }, + + render: function () { + var data = { + i18n: App.config.i18n + }; + + this.$el.html(Mustache.render(template, data)); + this.bindDomElements(); + + this.usedByPartViews = []; + this.usedByDocumentViews = []; + this.usedByProductInstanceViews = []; + this.documentsCollection.fetch({reset: true}); + + this.initUsedByGroup(); + return this; + }, + + bindDomElements: function () { + this.documentsUL = this.$('#used-by-documents'); + this.partsUL = this.$('#used-by-parts'); + this.productInstancesUL = this.$('#used-by-product-instances'); + }, + + addPartViews: function () { + this.partsCollection.each(this.addPartView.bind(this)); + }, + + addProductInstanceViews: function () { + this.productInstancesCollection.each(this.addProductInstanceView.bind(this)); + }, + + addDocumentViews: function () { + this.documentsCollection.each(this.addDocumentView.bind(this)); + }, + + addPartView: function (model) { + var usedByView = new UsedByListItemView({ + model: model + }).render(); + + this.usedByPartViews.push(usedByView); + this.partsUL.append(usedByView.$el); + }, + addProductInstanceView: function (model) { + var usedByView = new UsedByProductInstanceListItemView({ + model: model + }).render(); + + this.usedByProductInstanceViews.push(usedByView); + this.productInstancesUL.append(usedByView.$el); + }, + + + addDocumentView: function (model) { + var usedByView = new UsedByListItemView({ + model: model + }).render(); + + this.usedByDocumentViews.push(usedByView); + this.documentsUL.append(usedByView.$el); + }, + initUsedByGroup:function(){ + this.usedByGroupListView = new UsedByGroupListView({ + linkedDocument: this.linkedDocument, + linkedDocumentId: this.linkedDocumentIterationId + }).render(); + + /* Add the usedByGroupListView to the tab */ + this.$('#used-by-group-list-view').html(this.usedByGroupListView.el); + } + + }); + + return UsedByListView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/workflow/lifecycle.js b/docdoku-web-front/app/js/common-objects/views/workflow/lifecycle.js new file mode 100644 index 0000000000..0a8868eb3b --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/workflow/lifecycle.js @@ -0,0 +1,134 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/views/workflow/lifecycle_activity', + 'common-objects/utils/date', + 'common-objects/views/alert', + 'text!common-objects/templates/workflow/lifecycle.html' + +], function (Backbone, Mustache, LifecycleActivityView, Date, AlertView, template) { + 'use strict'; + var LifecycleView = Backbone.View.extend({ + + tagName: 'div', + + events: { + 'click a.LifecycleModalTab-historyLink': 'history', + 'click a.abortedWorkflow': 'abortedWorkflow', + 'click a.currentWorkflow': 'currentWorkflow' + }, + + initialize: function () { + Backbone.Events.on('task:errorDisplay', this.onError.bind(this)); + }, + setWorkflow: function (workflow) { + this.workflow = workflow; + this.abortedWorkflowsUrl = App.config.contextPath+'/api/workspaces/'+App.config.workspaceId+'/workflow-instances/'+workflow.id+'/aborted'; + return this; + }, + + setEntityType: function (entityType) { + this.entityType = entityType; + return this; + }, + + render: function () { + + var that = this; + $.ajax({ + url: this.abortedWorkflowsUrl, + success: function (abortedWorkflows) { + _.each(abortedWorkflows, function (workflow) { + + workflow.abortedFormattedDate = Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + workflow.abortedDate + ); + }); + + // Find last rejected tasks in aborted workflows... + // Last aborted task will replace corresponding task in current workflow + if (abortedWorkflows.length) { + for (var i = 0; i < that.workflow.activities.length; i++) { + if (i >= that.workflow.currentStep) { + for (var j = 0; j < that.workflow.activities[i].tasks.length; j++) { + if (that.workflow.activities[i].tasks[j].status === 'NOT_STARTED') { + for (var k = abortedWorkflows.length - 1; k--; k >= 0) { + if (abortedWorkflows[k].activities[i].tasks[j].status === 'REJECTED') { + that.workflow.activities[i].tasks[j] = abortedWorkflows[k].activities[i].tasks[j]; + break; + } + } + } + } + } + } + } + + that.abortedWorkflows = abortedWorkflows; + that.$el.html(Mustache.render(template, {i18n: App.config.i18n, workflow: that.workflow, abortedWorkflows: abortedWorkflows})); + that.bindDomElements(); + that.displayWorkflow(that.workflow); + } + }); + return this; + }, + + bindDomElements: function () { + this.$historyContent = this.$('.LifecycleModalTab-historyContent'); + this.$lifecycleActivities = this.$('#lifecycle-activities'); + }, + + history: function (e) { + this.$historyContent.toggleClass('hide'); + this.currentWorkflow(e); + }, + + currentWorkflow: function (e) { + this.displayWorkflow(this.workflow); + this.$historyContent.find('a.active').removeClass('active'); + e.target.classList.add('active'); + }, + + abortedWorkflow: function (e) { + var that = this; + var workflowId = parseInt(e.target.dataset.id,10); + var workflow = _.select(that.abortedWorkflows, function (workflow) { + return workflow.id === workflowId; + })[0]; + if (workflow) { + this.$historyContent.find('a.active').removeClass('active'); + e.target.classList.add('active'); + this.displayWorkflow(workflow); + } + }, + + displayWorkflow: function (workflow) { + var that = this; + this.$lifecycleActivities.empty(); + _.each(workflow.activities, function (activity) { + if (activity && activity.toDo) { + activity.parentWorkflowId = that.workflow.id; + activity.relaunchActivityState = activity.relaunchStep >= 0 ? workflow.activities[activity.relaunchStep].lifeCycleState:null; + var lifecycleActivityView = new LifecycleActivityView().setActivity(activity).setEntityType(that.entityType).render(); + that.$lifecycleActivities.append(lifecycleActivityView.$el); + lifecycleActivityView.on('activity:change', function () { + that.trigger('lifecycle:change'); + }); + } + }); + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + + this.$el.find('.notifications').first().append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + } + + }); + return LifecycleView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/workflow/lifecycle_activity.js b/docdoku-web-front/app/js/common-objects/views/workflow/lifecycle_activity.js new file mode 100644 index 0000000000..4ff9fe5ccc --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/workflow/lifecycle_activity.js @@ -0,0 +1,90 @@ +/*global define,App,_*/ +define([ + 'backbone', + 'mustache', + 'common-objects/views/workflow/lifecycle_task', + 'text!common-objects/templates/workflow/lifecycle_activity.html' + +], function (Backbone, Mustache, LifecycleTaskView, template) { + + 'use strict'; + + var LifecycleActivityView = Backbone.View.extend({ + + tagName: 'div', + className: 'activity well', + + events: { + }, + + initialize: function () { + }, + + setActivity: function (activity) { + this.activity = activity; + return this; + }, + + setEntityType: function (entityType) { + this.entityType = entityType; + return this; + }, + + render: function () { + + var that = this; + + switch (this.activity.type) { + case 'SEQUENTIAL': + this.activityType = App.config.i18n.SEQUENTIAL_ACTIVITY; + break; + case 'PARALLEL': + this.activityType = App.config.i18n.PARALLEL_ACTIVITY + ' ' + this.activity.tasksToComplete; + break; + } + + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, activity: this.activity, activityType: this.activityType})); + + var completeClass = 'incomplete'; + + if (this.activity.complete) { + completeClass = 'complete'; + } + if (this.activity.stopped) { + completeClass = 'rejected'; + } + if (this.activity.inProgress) { + completeClass = 'in_progress'; + } + + this.$el.addClass(this.activity.type.toLowerCase()).addClass(completeClass); + + var $tasks = this.$('.tasks'); + + _.each(this.activity.tasks, function (task, index) { + + task.parentWorkflowId = that.activity.parentWorkflowId; + task.parentActivityStep = that.activity.step; + task.index = index; + + if ((that.activity.stopped || that.activity.complete) && task.status.toLowerCase() === 'in_progress') { + task.status = 'NOT_STARTED'; // Disable task if activity is close + } + + var lifecycleTaskView = new LifecycleTaskView().setTask(task).setEntityType(that.entityType).render(); + + $tasks.append(lifecycleTaskView.$el); + lifecycleTaskView.on('task:change', function () { + that.trigger('activity:change'); + }); + + }); + + return this; + + } + + }); + return LifecycleActivityView; + +}); diff --git a/docdoku-web-front/app/js/common-objects/views/workflow/lifecycle_task.js b/docdoku-web-front/app/js/common-objects/views/workflow/lifecycle_task.js new file mode 100644 index 0000000000..fcec380390 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/workflow/lifecycle_task.js @@ -0,0 +1,184 @@ +/*global define,App,$*/ +define([ + 'backbone', + 'mustache', + 'common-objects/views/workflow/lifecycle_task_signing', + 'common-objects/utils/date', + 'text!common-objects/templates/workflow/lifecycle_task.html' +], function (Backbone, Mustache, LifecycleTaskSigningView, date, template) { + 'use strict'; + var LifecycleTaskView = Backbone.View.extend({ + + tagName: 'div', + className: 'task well', + + events: { + 'click i.toggle-comment': 'toggleComment', + 'click i.approve-task': 'approveTaskButtonClicked', + 'click i.reject-task': 'rejectTaskButtonClicked', + 'submit .closure-comment-form': 'submitClosure', + 'click .closure-comment .cancel': 'cancelClosure' + }, + + initialize: function () { + this.APPROVE_MODE = '1'; + this.REJECT_MODE = '2'; + }, + + setTask: function (task) { + this.task = task; + + if (this.task.closureDate) { + this.task.formattedClosureDate = date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.task.closureDate + ); + this.task.ClosureStatus = (this.task.status.toLowerCase() === 'approved') ? App.config.i18n.TASK_APPROVED_ON : App.config.i18n.TASK_REJECT_ON; + } + + this.task.isAcceptableOrRejectable = ( + this.isPotentialWorker() && + this.task.status.toLowerCase() === 'in_progress' + ); + + return this; + }, + + isPotentialWorker: function(){ + var isAssignedFromGroup = _.intersection(App.config.groups.map(function(membership){ + return membership.memberId; + }),this.task.assignedGroups.map(function(group){ + return group.id; + })).length > 0; + + var isAssignedAsUser = _.where(this.task.assignedUsers,{login:App.config.login}).length === 1; + + return isAssignedAsUser || isAssignedFromGroup; + }, + + setEntityType: function (entityType) { + this.entityType = entityType; + return this; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, task: this.task})); + this.$el.addClass(this.task.status.toLowerCase()); + if(this.task.worker){ + this.$('.worker-popover').userPopover(this.task.worker.login, this.task.title, 'left'); + } + var title = this.task.title; + this.$('.assigned-users-popover').each(function(){ + $(this).userPopover($(this).attr('data-login'), title, 'left'); + }); + this.bindDomElements(); + this.lifecycleTaskSigningView = new LifecycleTaskSigningView().render(); + this.$tasksigning.append(this.lifecycleTaskSigningView.$el); + return this; + }, + + bindDomElements: function () { + this.$comment = this.$('.task-comment'); + this.$closureComment = this.$('.closure-comment'); + this.$closureCommentTitle = this.$closureComment.find('h5'); + this.$closureTypeInput = this.$closureComment.find('input[name=closure-type]'); + this.$commentInput = this.$closureComment.find('input[name=closure-comment-input]'); + this.$tasksigning = this.$('.task-signing'); + }, + + toggleComment: function () { + this.$comment.toggleClass('toggled'); + }, + + approveTaskButtonClicked: function () { + this.$closureComment.addClass('toggled'); + this.$closureTypeInput.val(this.APPROVE_MODE); + this.$closureCommentTitle.text(App.config.i18n.APPROVE_TASK); + }, + + rejectTaskButtonClicked: function () { + this.$closureComment.addClass('toggled'); + this.$closureTypeInput.val(this.REJECT_MODE); + this.$closureCommentTitle.text(App.config.i18n.REJECT_TASK); + }, + + submitClosure: function (e) { + + var processUrl = App.config.contextPath + + '/api/workspaces/' + + App.config.workspaceId + '/tasks/' + + this.task.parentWorkflowId + '-' + this.task.parentActivityStep + '-' + this.task.index + + '/process'; + + var closureComment = this.$commentInput.val(); + var closureType = this.$closureTypeInput.val(); + var signature = this.lifecycleTaskSigningView.signature; + + var data = JSON.stringify({ + comment: closureComment, + signature: signature, + action : closureType === this.APPROVE_MODE ? 'APPROVE' : 'REJECT' + }); + + if (closureType === this.APPROVE_MODE) { + $.ajax({ + context: this, + type: 'PUT', + url: processUrl, + data: data, + contentType: 'application/json;charset=UTF-8', + success: function () { + this.task.closureDate = Date.now(); + this.task.closureComment = closureComment; + this.task.signature = signature; + this.task.status = 'approved'; + this.refreshTask(); + }, + error: function (error) { + Backbone.Events.trigger('task:errorDisplay', this.task, error); + } + }); + + } else if (closureType === this.REJECT_MODE) { + $.ajax({ + context: this, + type: 'PUT', + url: processUrl, + data: data, + contentType: 'application/json;charset=UTF-8', + success: function () { + this.task.closureDate = Date.now(); + this.task.closureComment = closureComment; + this.task.signature = signature; + this.task.status = 'rejected'; + this.refreshTask(); + }, + error: function (error) { + Backbone.Events.trigger('task:errorDisplay', this.task, error); + } + }); + + } + + e.stopPropagation(); + e.preventDefault(); + return false; + }, + + cancelClosure: function (e) { + this.$closureComment.removeClass('toggled'); + e.stopPropagation(); + e.preventDefault(); + return false; + }, + + refreshTask: function () { + this.trigger('task:change'); + // this.setTask(this.task); + // this.render(); + } + + }); + return LifecycleTaskView; + +}); diff --git a/docdoku-web-front/app/js/common-objects/views/workflow/lifecycle_task_signing.js b/docdoku-web-front/app/js/common-objects/views/workflow/lifecycle_task_signing.js new file mode 100644 index 0000000000..ee013d2298 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/workflow/lifecycle_task_signing.js @@ -0,0 +1,167 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/workflow/lifecycle_task_signing.html' + +], function (Backbone, Mustache, template) { + + 'use strict'; + + var LifecycleTaskSigningView = Backbone.View.extend({ + + tagName: 'div', + + events: { + 'click .lifecycle-task-signing-link-a': 'toggleSigningCanvas', + 'click .lifecycle-task-signing-delete-a': 'deleteSignature', + 'mousedown .lifecycle-activities-canvas': 'canvasMouseDown', + 'mousemove .lifecycle-activities-canvas': 'canvasMouseMove', + 'mouseup .lifecycle-activities-canvas': 'canvasMouseUp', + 'mouseleave .lifecycle-activities-canvas': 'canvasMouseLeave', + 'click i.save-signing': 'saveSigning', + 'click i.clear-signing': 'clearClicked' + }, + + initialize: function () { + this.moves = []; + this.pressed = false; + this.oppened = false; + this.signature = null; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + this.initCanvas(); + return this; + }, + + bindDomElements: function () { + this.$signingPopover = this.$('.lifecycle-task-signing-popover'); + this.$signingImg = this.$('.lifecycle-task-signing-img'); + this.$saveSigning = this.$('i.save-signing'); + this.$clearSigning = this.$('i.clear-signing'); + }, + + initCanvas: function () { + + }, + + toggleSigningCanvas: function () { + if (this.oppened) { + this.closeSigningCanvas(); + } else { + this.openSigningCanvas(); + } + this.oppened = !this.oppened; + }, + + openSigningCanvas: function () { + this.$saveSigning.show(); + this.$clearSigning.show(); + this.$signingPopover.show(); + this.$signingImg.hide(); + this.canvas = this.$('.lifecycle-activities-canvas').get(0); + this.context = this.canvas.getContext('2d'); + this.canvas.width = 200; + this.canvas.height = 150; + }, + + closeSigningCanvas: function () { + this.$saveSigning.hide(); + this.$clearSigning.hide(); + this.$signingPopover.hide(); + this.$signingImg.show(); + }, + + canvasMouseDown: function (e) { + e.originalEvent.preventDefault(); + this.pressed = true; + this.moves.push([e.pageX - this.canvas.getBoundingClientRect().left, + e.pageY - this.canvas.getBoundingClientRect().top, + false]); + this.redraw(); + }, + + canvasMouseMove: function (e) { + if (this.pressed) { + this.moves.push([e.pageX - this.canvas.getBoundingClientRect().left, + e.pageY - this.canvas.getBoundingClientRect().top, + true]); + this.redraw(); + } + }, + + canvasMouseUp: function () { + this.pressed = false; + }, + + canvasMouseLeave: function () { + this.pressed = false; + }, + + redraw: function () { + this.context.strokeStyle = '#333333'; + this.context.lineJoin = 'round'; + this.context.lineWidth = 3; + + for (var i = 0; i < this.moves.length; i++) { + this.context.beginPath(); + if (this.moves[i][2] && i) { + this.context.moveTo(this.moves[i - 1][0], this.moves[i - 1][1]); + } else { + this.context.moveTo(this.moves[i][0], this.moves[i][1]); + } + this.context.lineTo(this.moves[i][0], this.moves[i][1]); + this.context.closePath(); + this.context.stroke(); + } + }, + + clearClicked: function (e) { + e.stopPropagation(); + e.preventDefault(); + this.clearSigning(); + }, + + deleteSignature: function (e) { + e.stopPropagation(); + e.preventDefault(); + + this.signature = null; + this.$('.lifecycle-task-signing-img img').attr('src', this.signature); + this.$('.lifecycle-task-signing-img').addClass('hidden'); + this.$('.lifecycle-task-signing-delete-a').addClass('hidden'); + }, + + clearSigning: function () { + // Store the current transformation matrix + this.context.save(); + + // Use the identity matrix while clearing the canvas + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + // Restore the transform + this.context.restore(); + + this.moves.length = 0; + }, + + saveSigning: function (e) { + e.stopPropagation(); + e.preventDefault(); + + this.signature = this.canvas.toDataURL(); + this.$('.lifecycle-task-signing-img img').attr('src', this.signature); + this.$('.lifecycle-task-signing-img').removeClass('hidden'); + this.$('.lifecycle-task-signing-delete-a').removeClass('hidden'); + + this.toggleSigningCanvas(); + this.clearSigning(); + } + + }); + return LifecycleTaskSigningView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/workflow/role_item_view.js b/docdoku-web-front/app/js/common-objects/views/workflow/role_item_view.js new file mode 100644 index 0000000000..27df1a2a4f --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/workflow/role_item_view.js @@ -0,0 +1,141 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!common-objects/templates/workflow/role_item.html' +], +function (Backbone, Mustache, template) { + 'use strict'; + var RoleItemView = Backbone.View.extend({ + className: 'well roles-item', + + events: { + 'click .fa-times': 'removeAndNotify', + 'change select.role-default-assigned-users': 'updateAssignedUsers', + 'change select.role-default-assigned-groups': 'updateAssignedGroups' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + model: this.model, + required: !this.options.nullable + })); + this.$usersSelect = this.$('select.role-default-assigned-users'); + this.$groupsSelect = this.$('select.role-default-assigned-groups'); + this.fillUserList(); + this.fillGroupList(); + return this; + }, + + fillUserList: function () { + + this.$usersSelect.selectize({ + plugins: ['remove_button'], + valueField: 'login', + searchField: ['name'], + render: { + item: function(item, escape) { + return '
                              ' + escape(item.name) + '
                              '; + }, + option: function(item, escape) { + return '
                              ' + escape(item.name) + '
                              '; + } + }, + options:this.options.userList.models.map(function(user){ + return {login:user.getLogin(), name:user.getName()}; + }) + }); + + _.each(this.model.getDefaultAssignedUsers(),function(user){ + this.$usersSelect[0].selectize.addItem(user.login); + },this); + + this.validate(); + }, + + fillGroupList: function () { + + this.$groupsSelect.selectize({ + plugins: ['remove_button'], + valueField: 'id', + searchField: ['id'], + render: { + item: function(item, escape) { + return '
                              ' + escape(item.id) + '
                              '; + }, + option: function(item, escape) { + return '
                              ' + escape(item.id) + '
                              '; + } + }, + options:this.options.groupList.models.map(function(group){ + return {id:group.id}; + }) + }); + + _.each(this.model.getDefaultAssignedGroups(),function(group){ + this.$groupsSelect[0].selectize.addItem(group.id); + },this); + + this.validate(); + + }, + + removeAndNotify: function () { + if (this.options.removable) { + this.remove(); + this.trigger('view:removed'); + } else { + if(_.isFunction(this.options.onError)){ + this.options.onError(App.config.i18n.ALERT_ROLE_IN_USE); + } else { + window.alert(App.config.i18n.ALERT_ROLE_IN_USE); + } + } + }, + + updateAssignedUsers: function () { + this.model.setDefaultAssignedUsers((this.$usersSelect.val()||[]).map(function(login){ + return {login:login}; + })); + this.validate(); + }, + + updateAssignedGroups:function(){ + this.model.setDefaultAssignedGroups((this.$groupsSelect.val()||[]).map(function(id){ + return {id:id}; + })); + this.validate(); + }, + + validate:function(){ + if(!this.options.nullable){ + if(!this.hasSelectedGroups() && !this.hasSelectedUsers()){ + this.isValid = false; + this.$el.addClass('invalid'); + this.$('select').prop('required',true); + }else{ + this.isValid = true; + this.$el.removeClass('invalid'); + this.$('select').prop('required',false); + } + } + }, + + hasSelectedUsers:function(){ + return this.$usersSelect.val() && this.$usersSelect.val().length; + }, + + hasSelectedGroups:function(){ + return this.$groupsSelect.val() && this.$groupsSelect.val().length; + } + + }); + + return RoleItemView; + +}); diff --git a/docdoku-web-front/app/js/common-objects/views/workflow/workflow_list.js b/docdoku-web-front/app/js/common-objects/views/workflow/workflow_list.js new file mode 100644 index 0000000000..909ae92076 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/workflow/workflow_list.js @@ -0,0 +1,55 @@ +/*global define*/ +define([ + 'common-objects/collections/workflow_models', + 'common-objects/views/base', + 'text!common-objects/templates/workflow/workflow_select.html' +], function (WorkflowList, BaseView, template) { + + 'use strict'; + + var DocumentWorkflowListView = BaseView.extend({ + + template: template, + + initialize: function () { + BaseView.prototype.initialize.apply(this, arguments); + this.events['change #select-' + this.cid] = 'onChange'; + }, + + onChange: function () { + this.trigger('workflow:change', this.selected()); + }, + + collection: function () { + var collection = new WorkflowList(); + collection.fetch({reset: true}); + return collection; + }, + + collectionReset: function () { + this.render(); + }, + + collectionToJSON: function () { + var data = BaseView.prototype.collectionToJSON.call(this); + data.unshift({ + id: '' + }); + return data; + }, + + selected: function () { + var id = this.$('#select-' + this.cid).val(); + var model = this.collection.get(id); + return model; + }, + + setValue: function (value) { + this.$('#select-' + this.cid).val(value); + this.trigger('workflow:change', this.selected()); + } + + }); + + return DocumentWorkflowListView; +}); diff --git a/docdoku-web-front/app/js/common-objects/views/workflow/workflow_mapping.js b/docdoku-web-front/app/js/common-objects/views/workflow/workflow_mapping.js new file mode 100644 index 0000000000..0a46c5da14 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/views/workflow/workflow_mapping.js @@ -0,0 +1,90 @@ +/*global define,_,App*/ +define([ + 'common-objects/views/base', + 'common-objects/collections/users', + 'common-objects/collections/user_groups', + 'common-objects/views/workflow/role_item_view', + 'common-objects/models/role', + 'text!common-objects/templates/workflow/workflow_mapping.html' +], function (BaseView, Users, UserGroups, RoleItemView, Role, template) { + 'use strict'; + var DocumentWorkflowMappingView = BaseView.extend({ + + template: template, + + initialize: function () { + BaseView.prototype.initialize.apply(this, arguments); + this.on('workflow:change', this.updateMapping); + this.roles = []; + this.rolesItemViews = []; + this.users = new Users(); + this.groups = new UserGroups(); + var _this = this; + this.users.fetch({reset: true, success: function(){ + _this.groups.fetch({reset:true,success:_this.render}); + }}); + }, + + updateMapping: function (workflowModel) { + + var self = this; + this.rolesInWorkflowModel = []; + this.rolesItemViews = []; + + if (workflowModel) { + workflowModel.get('activityModels').each(function (activityModel) { + activityModel.get('taskModels').each(function (taskModel) { + if (!_.contains(self.rolesInWorkflowModel, taskModel.get('role').getName())) { + self.rolesInWorkflowModel.push(taskModel.get('role').getName()); + self.rolesItemViews.push(new RoleItemView({ + model: new Role(taskModel.get('role').attributes), + userList: self.users, + groupList: self.groups, + nullable: false + }).render()); + } + }); + }); + } + + this.$el.empty(); + + _.each(this.rolesItemViews, function (view) { + this.$el.append(view.$el); + },this); + + }, + + toList: function () { + var list = []; + _.each(this.rolesItemViews, function (view) { + list.push({ + workspaceId: App.config.workspaceId, + roleName: view.model.getName(), + defaultAssignedUsers: view.model.getDefaultAssignedUsers(), + defaultAssignedGroups: view.model.getDefaultAssignedGroups() + }); + }); + return list; + }, + + isValid:function(){ + return _.where(this.rolesItemViews,{isValid:false}).length === 0; + }, + + toResolvedList:function(){ + var list = []; + _.each(this.rolesItemViews, function (view) { + list.push({ + roleName: view.model.getName(), + userLogins: view.model.getDefaultAssignedUsers().map(function(user){return user.login;}), + groupIds: view.model.getDefaultAssignedGroups().map(function(group){return group.id;}) + }); + }); + return list; + } + + }); + + return DocumentWorkflowMappingView; +}); diff --git a/docdoku-web-front/app/js/common-objects/websocket/callState.js b/docdoku-web-front/app/js/common-objects/websocket/callState.js new file mode 100644 index 0000000000..4df495f3c7 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/websocket/callState.js @@ -0,0 +1,12 @@ +/*global define*/ +define(function () { + 'use strict'; + return { + NO_CALL: 'NO_CALL', + INCOMING: 'INCOMING', + OUTGOING: 'OUTGOING', + NEGOTIATING: 'NEGOTIATING', + RUNNING: 'RUNNING', + ENDED: 'ENDED' + }; +}); diff --git a/docdoku-web-front/app/js/common-objects/websocket/channel.js b/docdoku-web-front/app/js/common-objects/websocket/channel.js new file mode 100644 index 0000000000..a518b73c7f --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/websocket/channel.js @@ -0,0 +1,107 @@ +/*global _,define,WebSocket,App*/ +define(['common-objects/websocket/channelStatus'], function (ChannelStatus) { + 'use strict'; + function Channel() { + this.status = ChannelStatus.CLOSED; + this.listeners = []; + } + + Channel.prototype = { + + init: function (url) { + this.url = url; + this.create(); + }, + + create: function () { + var self = this; + + this.ws = new WebSocket(this.url); + + this.ws.onopen = function (event) { + App.log('%c Websocket created','WS'); + self.onopen(event); + }; + + this.ws.onmessage = function (message) { + self.onmessage(message); + }; + + this.ws.onclose = function (event) { + self.onclose(event); + }; + + this.ws.onerror = function (event) { + self.onerror(event); + }; + + }, + + // send string + send: function (message) { + + App.log('C->S: %c' + message,'WS'); + this.ws.send(message); + + }, + + // send object + sendJSON: function (jsonObj) { + + var messageString = JSON.stringify(jsonObj); + this.send(messageString); + + }, + + onopen: function () { + + this.status = ChannelStatus.OPENED; + + _.each(this.listeners, function (listener) { + listener.handlers.onStatusChanged(ChannelStatus.OPENED); + }); + + }, + + onmessage: function (message) { + App.log('S->C: %c' + message.data,'WS'); + + var jsonMessage = JSON.parse(message.data); + if (jsonMessage.type) { + _.each(this.listeners, function (listener) { + if (listener.handlers.isApplicable(jsonMessage.type) && listener.isListening) { + listener.handlers.onMessage(jsonMessage); + } + }); + } + + }, + + onclose: function (event) { + this.status = ChannelStatus.CLOSED; + + App.log('%c Websocket closed\n\t'+event,'WS'); + + _.each(this.listeners, function (listener) { + listener.handlers.onStatusChanged(ChannelStatus.CLOSED); + }); + + }, + + onerror: function (event) { + App.log('%c Websocket error\n\t'+event,'WS'); + }, + + addChannelListener: function (listener) { + this.listeners.push(listener); + }, + + isReady: function () { + return this.status === ChannelStatus.OPENED; + } + + }; + + return Channel; + +}); diff --git a/docdoku-web-front/app/js/common-objects/websocket/channelListener.js b/docdoku-web-front/app/js/common-objects/websocket/channelListener.js new file mode 100644 index 0000000000..947f14aa9f --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/websocket/channelListener.js @@ -0,0 +1,22 @@ +/*global define*/ +'use strict'; +define(function () { + + function ChannelListener(handlers) { + this.handlers = handlers; + this.isListening = true; + } + + ChannelListener.prototype = { + startListen: function () { + this.isListening = true; + }, + + stopListen: function () { + this.isListening = false; + } + }; + + return ChannelListener; + +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/websocket/channelMessagesType.js b/docdoku-web-front/app/js/common-objects/websocket/channelMessagesType.js new file mode 100644 index 0000000000..7826b6bb16 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/websocket/channelMessagesType.js @@ -0,0 +1,30 @@ +/*global define*/ +define(function () { + 'use strict'; + return { + WEBRTC_INVITE: 'WEBRTC_INVITE', + WEBRTC_ACCEPT: 'WEBRTC_ACCEPT', + WEBRTC_REJECT: 'WEBRTC_REJECT', + WEBRTC_HANGUP: 'WEBRTC_HANGUP', + WEBRTC_ROOM_JOIN_EVENT: 'WEBRTC_ROOM_JOIN_EVENT', + WEBRTC_ROOM_REJECT_EVENT: 'WEBRTC_ROOM_REJECT_EVENT', + WEBRTC_OFFER: 'offer', + WEBRTC_ANSWER: 'answer', + WEBRTC_CANDIDATE: 'candidate', + WEBRTC_BYE: 'bye', + COLLABORATIVE_CREATE: 'COLLABORATIVE_CREATE', + COLLABORATIVE_INVITE: 'COLLABORATIVE_INVITE', + COLLABORATIVE_JOIN: 'COLLABORATIVE_JOIN', + COLLABORATIVE_CONTEXT: 'COLLABORATIVE_CONTEXT', + COLLABORATIVE_COMMANDS: 'COLLABORATIVE_COMMANDS', + COLLABORATIVE_EXIT: 'COLLABORATIVE_EXIT', + COLLABORATIVE_KILL: 'COLLABORATIVE_KILL', + COLLABORATIVE_GIVE_HAND: 'COLLABORATIVE_GIVE_HAND', + COLLABORATIVE_KICK_USER: 'COLLABORATIVE_KICK_USER', + COLLABORATIVE_KICK_NOT_INVITED: 'COLLABORATIVE_KICK_NOT_INVITED', + COLLABORATIVE_WITHDRAW_INVITATION: 'COLLABORATIVE_WITHDRAW_INVITATION', + CHAT_MESSAGE: 'CHAT_MESSAGE', + CHAT_MESSAGE_ACK: 'CHAT_MESSAGE_ACK', + USER_STATUS: 'USER_STATUS' + }; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/common-objects/websocket/channelStatus.js b/docdoku-web-front/app/js/common-objects/websocket/channelStatus.js new file mode 100644 index 0000000000..6ad22e3254 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/websocket/channelStatus.js @@ -0,0 +1,8 @@ +/*global define*/ +define(function () { + 'use strict'; + return { + OPENED: 'opened', + CLOSED: 'closed' + }; +}); diff --git a/docdoku-web-front/app/js/common-objects/websocket/rejectCallReason.js b/docdoku-web-front/app/js/common-objects/websocket/rejectCallReason.js new file mode 100644 index 0000000000..6c433387f2 --- /dev/null +++ b/docdoku-web-front/app/js/common-objects/websocket/rejectCallReason.js @@ -0,0 +1,10 @@ +/*global define*/ +define(function () { + 'use strict'; + return { + REJECTED: 'REJECTED', + BUSY: 'BUSY', + TIMEOUT: 'TIMEOUT', + OFFLINE: 'OFFLINE' + }; +}); diff --git a/docdoku-web-front/app/js/dmu/controls/OrbitControls.js b/docdoku-web-front/app/js/dmu/controls/OrbitControls.js new file mode 100644 index 0000000000..c903d0ad7f --- /dev/null +++ b/docdoku-web-front/app/js/dmu/controls/OrbitControls.js @@ -0,0 +1,695 @@ +/** + * @author qiao / https://github.com/qiao + * @author mrdoob / http://mrdoob.com + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + * @author erich666 / http://erichaines.com + */ +/*global THREE, console, App */ + +// This set of controls performs orbiting, dollying (zooming), and panning. It maintains +// the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is +// supported. +// +// Orbit - left mouse / touch: one finger move +// Zoom - middle mouse, or mousewheel / touch: two finger spread or squish +// Pan - right mouse, or arrow keys / touch: three finter swipe +// +// This is a drop-in replacement for (most) TrackballControls used in examples. +// That is, include this js file and wherever you see: +// controls = new THREE.TrackballControls(camera); +// controls.target.z = 150; +// Simple substitute "OrbitControls" and the control should work as-is. +THREE.OrbitControls = function (object, domElement) { + 'use strict'; + this.object = object; + this.domElement = ( domElement !== undefined ) ? domElement : document; + + // API + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the control orbits around + // and where it pans with respect to. + this.target = new THREE.Vector3(); + // center is old, deprecated; use "target" instead + this.center = this.target; + + // This option actually enables dollying in and out; left as "zoom" for + // backwards compatibility + this.noZoom = false; + this.zoomSpeed = 1.0; + // Limits to how far you can dolly in and out + this.minDistance = 0; + this.maxDistance = 200000; + + // Set to true to disable this control + this.noRotate = false; + this.rotateSpeed = 1.0; + + // Set to true to disable this control + this.noPan = false; + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // Set to true to disable use of the keys + this.noKeys = false; + // The four arrow keys + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; + + //////////// + // internals + + var scope = this; + + var EPS = 0.000001; + + var _deltaZoom = 0; + var rotateStart = new THREE.Vector2(); + var rotateEnd = new THREE.Vector2(); + var rotateDelta = new THREE.Vector2(); + + var panStart = new THREE.Vector2(); + var panEnd = new THREE.Vector2(); + var panDelta = new THREE.Vector2(); + + var dollyStart = new THREE.Vector2(); + var dollyEnd = new THREE.Vector2(); + var dollyDelta = new THREE.Vector2(); + + var phiDelta = 0; + var thetaDelta = 0; + var scale = 1; + var pan = new THREE.Vector3(); + + var lastPosition = new THREE.Vector3(); + + var STATE = { NONE: -1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 }; + var state = STATE.NONE; + + // events + + var changeEvent = { type: 'change' }; + + function getAutoRotationAngle() { + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed * App.SceneOptions.rotateSpeed; + } + + function getZoomScale() { + + return Math.pow(0.95, scope.zoomSpeed * App.SceneOptions.zoomSpeed); + + } + + function onMouseMove(event) { + + if (scope.enabled === false) { + return null; + } + + event.preventDefault(); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + if (state === STATE.ROTATE) { + + if (scope.noRotate === true) { + return null; + } + + rotateEnd.set(event.clientX, event.clientY); + rotateDelta.subVectors(rotateEnd, rotateStart); + + // rotating across whole screen goes 360 degrees around + scope.rotateLeft(2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed * App.SceneOptions.rotateSpeed); + // rotating up and down along whole screen attempts to go 360, but limited to 180 + scope.rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed * App.SceneOptions.rotateSpeed); + + rotateStart.copy(rotateEnd); + + } else if (state === STATE.DOLLY) { + + if (scope.noZoom === true) { + return null; + } + + dollyEnd.set(event.clientX, event.clientY); + dollyDelta.subVectors(dollyEnd, dollyStart); + + if (dollyDelta.y > 0) { + _deltaZoom = dollyDelta.y * scope.zoomSpeed * App.SceneOptions.zoomSpeed; + // scope.dollyIn(); + + } else { + _deltaZoom = dollyDelta.y * scope.zoomSpeed * App.SceneOptions.zoomSpeed; + //scope.dollyOut(); + + } + + dollyStart.copy(dollyEnd); + + } else if (state === STATE.PAN) { + + if (scope.noPan === true) { + return; + } + + panEnd.set(event.clientX, event.clientY); + panDelta.subVectors(panEnd, panStart); + + scope.pan(panDelta); + + panStart.copy(panEnd); + + } + + // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be + scope.update(); + + } + + function onMouseUp(/* event */) { + + if (scope.enabled === false) { + return; + } + + // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be + scope.domElement.removeEventListener('mousemove', onMouseMove, false); + scope.domElement.removeEventListener('mouseup', onMouseUp, false); + + state = STATE.NONE; + + } + + function onMouseDown(event) { + + if (scope.enabled === false) { + return; + } + event.preventDefault(); + + if (event.button === 0) { + if (scope.noRotate === true) { + return; + } + + state = STATE.ROTATE; + + rotateStart.set(event.clientX, event.clientY); + + } else if (event.button === 1) { + if (scope.noZoom === true) { + return; + } + + state = STATE.DOLLY; + + dollyStart.set(event.clientX, event.clientY); + + } else if (event.button === 2) { + if (scope.noPan === true) { + return; + } + + state = STATE.PAN; + + panStart.set(event.clientX, event.clientY); + + } + + // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be + scope.domElement.addEventListener('mousemove', onMouseMove, false); + scope.domElement.addEventListener('mouseup', onMouseUp, false); + + } + + function onMouseWheel(event) { + + if (scope.enabled === false || scope.noZoom === true) { + return; + } + + var delta = 0; + + if (event.wheelDelta) { // WebKit / Opera / Explorer 9 + + delta = event.wheelDelta; + + } else if (event.detail) { // Firefox + + delta = -event.detail * 20; + + } + + //if (delta) { + // _deltaZoom = delta * scope.zoomSpeed * App.SceneOptions.zoomSpeed; + //} + + if (delta > 0) { + + scope.dollyOut(); + + } else { + + scope.dollyIn(); + + } + + } + + function onKeyDown(event) { + + if (scope.enabled === false) { + return; + } + if (scope.noKeys === true) { + return; + } + if (scope.noPan === true) { + return; + } + + // pan a pixel - I guess for precise positioning? + // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be + var needUpdate = false; + + switch (event.keyCode) { + + case scope.keys.UP: + scope.pan(new THREE.Vector2(0, scope.keyPanSpeed * App.SceneOptions.panSpeed)); + needUpdate = true; + break; + case scope.keys.BOTTOM: + scope.pan(new THREE.Vector2(0, -scope.keyPanSpeed * App.SceneOptions.panSpeed)); + needUpdate = true; + break; + case scope.keys.LEFT: + scope.pan(new THREE.Vector2(scope.keyPanSpeed * App.SceneOptions.panSpeed, 0)); + needUpdate = true; + break; + case scope.keys.RIGHT: + scope.pan(new THREE.Vector2(-scope.keyPanSpeed * App.SceneOptions.panSpeed, 0)); + needUpdate = true; + break; + } + + // Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be + if (needUpdate) { + + scope.update(); + + } + + } + + function touchstart(event) { + + if (scope.enabled === false) { + return; + } + + switch (event.touches.length) { + + case 1: // one-fingered touch: rotate + if (scope.noRotate === true) { + return; + } + + state = STATE.TOUCH_ROTATE; + + rotateStart.set(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY); + break; + + case 2: // two-fingered touch: dolly + if (scope.noZoom === true) { + return; + } + + state = STATE.TOUCH_DOLLY; + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + var distance = Math.sqrt(dx * dx + dy * dy); + dollyStart.set(0, distance); + break; + + case 3: // three-fingered touch: pan + if (scope.noPan === true) { + return; + } + + state = STATE.TOUCH_PAN; + + panStart.set(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY); + break; + + default: + state = STATE.NONE; + + } + } + + function touchmove(event) { + + if (scope.enabled === false) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + switch (event.touches.length) { + + case 1: // one-fingered touch: rotate + if (scope.noRotate === true) { + return; + } + if (state !== STATE.TOUCH_ROTATE) { + return; + } + + rotateEnd.set(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY); + rotateDelta.subVectors(rotateEnd, rotateStart); + + // rotating across whole screen goes 360 degrees around + scope.rotateLeft(2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed * App.SceneOptions.rotateSpeed); + // rotating up and down along whole screen attempts to go 360, but limited to 180 + scope.rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed * App.SceneOptions.rotateSpeed); + + rotateStart.copy(rotateEnd); + break; + + case 2: // two-fingered touch: dolly + if (scope.noZoom === true) { + return; + } + if (state !== STATE.TOUCH_DOLLY) { + return; + } + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + var distance = Math.sqrt(dx * dx + dy * dy); + + dollyEnd.set(0, distance); + dollyDelta.subVectors(dollyEnd, dollyStart); + + if (dollyDelta.y > 0) { + + + _deltaZoom = dollyDelta.y * scope.zoomSpeed * App.SceneOptions.zoomSpeed; + + + //scope.dollyOut(); + + } else { + + _deltaZoom = dollyDelta.y * scope.zoomSpeed * App.SceneOptions.zoomSpeed; + // scope.dollyIn(); + + } + + dollyStart.copy(dollyEnd); + break; + + case 3: // three-fingered touch: pan + if (scope.noPan === true) { + return; + } + if (state !== STATE.TOUCH_PAN) { + return; + } + + panEnd.set(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY); + panDelta.subVectors(panEnd, panStart); + + scope.pan(panDelta); + + panStart.copy(panEnd); + break; + + default: + state = STATE.NONE; + + } + + } + + function touchend(/* event */) { + + if (scope.enabled === false) { + return; + } + + state = STATE.NONE; + } + + + this.rotateLeft = function (angle) { + + if (angle === undefined) { + + angle = getAutoRotationAngle(); + + } + + thetaDelta -= angle; + + }; + + this.rotateUp = function (angle) { + + if (angle === undefined) { + + angle = getAutoRotationAngle(); + + } + + phiDelta -= angle; + + }; + + // pass in distance in world space to move left + this.panLeft = function (distance) { + + distance *= App.SceneOptions.panSpeed; + + var panOffset = new THREE.Vector3(); + var te = this.object.matrix.elements; + // get X column of matrix + panOffset.set(te[0], te[1], te[2]); + panOffset.multiplyScalar(-distance); + + pan.add(panOffset); + + }; + + // pass in distance in world space to move up + this.panUp = function (distance) { + + distance *= App.SceneOptions.panSpeed; + + var panOffset = new THREE.Vector3(); + var te = this.object.matrix.elements; + // get Y column of matrix + panOffset.set(te[4], te[5], te[6]); + panOffset.multiplyScalar(distance); + + pan.add(panOffset); + }; + + // main entry point; pass in Vector2 of change desired in pixel space, + // right and down are positive + this.pan = function (delta) { + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + if (scope.object.fov !== undefined) { + + // perspective + var position = scope.object.position; + var offset = position.clone().sub(scope.target); + var targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan((scope.object.fov / 2) * Math.PI / 180.0); + // we actually don't use screenWidth, since perspective camera is fixed to screen height + scope.panLeft(2 * delta.x * targetDistance / element.clientHeight); + scope.panUp(2 * delta.y * targetDistance / element.clientHeight); + + } else if (scope.object.top !== undefined) { + + // orthographic + scope.panLeft(delta.x * (scope.object.right - scope.object.left) / element.clientWidth); + scope.panUp(delta.y * (scope.object.top - scope.object.bottom) / element.clientHeight); + + } else { + + // camera neither orthographic or perspective - warn user + console.warn('WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.'); + + } + + }; + + this.dollyIn = function (dollyScale) { + + if (dollyScale === undefined) { + + dollyScale = getZoomScale(); + + } + + scale /= dollyScale; + + }; + + this.dollyOut = function (dollyScale) { + + if (dollyScale === undefined) { + + dollyScale = getZoomScale(); + + } + + scale *= dollyScale; + + }; + + this.update = function () { + + var position = this.object.position; + var offset = position.clone().sub(this.target); + + // angle from z-axis around y-axis + + var theta = Math.atan2(offset.x, offset.z); + + // angle from y-axis + + var phi = Math.atan2(Math.sqrt(offset.x * offset.x + offset.z * offset.z), offset.y); + + if (this.autoRotate) { + + this.rotateLeft(getAutoRotationAngle()); + + } + + theta += thetaDelta; + phi += phiDelta; + + // restrict phi to be between desired limits + phi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, phi)); + + // restrict phi to be betwee EPS and PI-EPS + phi = Math.max(EPS, Math.min(Math.PI - EPS, phi)); + + var radius = offset.length() * scale; + + // restrict radius to be between desired limits + radius = Math.max(this.minDistance, Math.min(this.maxDistance, radius)); + + // move target to panned location + this.target.add(pan); + + offset.x = radius * Math.sin(phi) * Math.sin(theta); + offset.y = radius * Math.cos(phi); + offset.z = radius * Math.sin(phi) * Math.cos(theta); + + position.copy(this.target).add(offset); + + this.object.lookAt(this.target); + + thetaDelta = 0; + phiDelta = 0; + scale = 1; + pan.set(0, 0, 0); + + if (lastPosition.distanceTo(this.object.position) > 0.001) { + this.dispatchEvent(changeEvent); + lastPosition.copy(this.object.position); + } + }; + + + this.unbindEvents = function () { + + this.domElement.removeEventListener('contextmenu', function (event) { + event.preventDefault(); + }, false); + this.domElement.removeEventListener('mousedown', onMouseDown, false); + this.domElement.removeEventListener('mouseout', onMouseUp, false);// leave + this.domElement.removeEventListener('mousewheel', onMouseWheel, false); + this.domElement.removeEventListener('DOMMouseScroll', onMouseWheel, false); // firefox + this.domElement.removeEventListener('keydown', onKeyDown, false); + this.domElement.removeEventListener('touchstart', touchstart, false); + this.domElement.removeEventListener('touchend', touchend, false); + this.domElement.removeEventListener('touchmove', touchmove, false); + + }; + + this.bindEvents = function () { + + this.domElement.addEventListener('contextmenu', function (event) { + event.preventDefault(); + }, false); + this.domElement.addEventListener('mousedown', onMouseDown, false); + this.domElement.addEventListener('mousewheel', onMouseWheel, false); + this.domElement.addEventListener('DOMMouseScroll', onMouseWheel, false); // firefox + + this.domElement.addEventListener('keydown', onKeyDown, false); + + this.domElement.addEventListener('mouseout', onMouseUp, false); // leave + + this.domElement.addEventListener('touchstart', touchstart, false); + this.domElement.addEventListener('touchend', touchend, false); + this.domElement.addEventListener('touchmove', touchmove, false); + + }; + + this.handleResize = function () { + }; + + this.getTarget = function () { + return scope.target; + }; + + this.getCamPos = function () { + return scope.object.position; + }; + + this.getObject = function () { + return scope.object; + }; + + this.getCamUp = function () { + return scope.object.up; + }; + + this.setCamPos = function (camPos) { + scope.object.position.copy(camPos); + }; + + this.setCamUp = function (camUp) { + scope.object.up.copy(camUp); + }; + + this.setTarget = function (target) { + scope.target.copy(target); + }; + +}; + +THREE.OrbitControls.prototype = Object.create(THREE.EventDispatcher.prototype); diff --git a/docdoku-web-front/app/js/dmu/controls/PointerLockControls.js b/docdoku-web-front/app/js/dmu/controls/PointerLockControls.js new file mode 100644 index 0000000000..6b0f9c4d1b --- /dev/null +++ b/docdoku-web-front/app/js/dmu/controls/PointerLockControls.js @@ -0,0 +1,298 @@ +/** + * @author mrdoob / http://mrdoob.com/ + */ +/*global THREE*/ +THREE.PointerLockControls = function (camera) { + 'use strict'; + var keyCodes = { + FORWARD: 90, // Z + FORWARD_2: 38, // Up + LEFT: 81, // Q + LEFT_2: 37, // Left + RIGHT: 68, // D + RIGHT_2: 39, // Right + BACKWARD: 83, // S + BACKWARD_2: 40, // Down + UP: 32, // SPACE + DOWN: 17 // CTRL + }; + + var scope = this; + var moveSpeed = 1; + + var changeEvent = { type: 'change' }; + + camera.rotation.set(0, 0, 0); + + var pitchObject = new THREE.Object3D(); + pitchObject.add(camera); + + var yawObject = new THREE.Object3D(); + yawObject.position.y = 10; + yawObject.add(pitchObject); + + var moveForward = false; + var moveBackward = false; + var moveLeft = false; + var moveRight = false; + var moveUp = false; + var moveDown = false; + + var velocity = new THREE.Vector3(); + + this.target = new THREE.Vector3(); + + var PI_2 = Math.PI / 2; + + var onMouseMove = function (event) { + + if (scope.enabled === false) { + return; + } + + event.preventDefault(); + + var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0; + var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0; + + yawObject.rotation.y -= movementX * 0.002; + pitchObject.rotation.x -= movementY * 0.002; + + pitchObject.rotation.x = Math.max(-PI_2, Math.min(PI_2, pitchObject.rotation.x)); + + scope.dispatchEvent(changeEvent); + + }; + + var onKeyDown = function (event) { + + if (scope.enabled === false) { + return; + } + + event.preventDefault(); + + switch (event.keyCode) { + + case keyCodes.FORWARD: + case keyCodes.FORWARD_2: + moveForward = true; + break; + + case keyCodes.LEFT: + case keyCodes.LEFT_2: + moveLeft = true; + break; + + case keyCodes.BACKWARD: + case keyCodes.BACKWARD_2: + moveBackward = true; + break; + + case keyCodes.RIGHT: + case keyCodes.RIGHT_2: + moveRight = true; + break; + + case keyCodes.UP: + moveUp = true; + break; + + case keyCodes.DOWN: + moveDown = true; + break; + + default: + break; + } + }; + + var onKeyUp = function (event) { + + if (scope.enabled === false) { + return; + } + + event.preventDefault(); + + switch (event.keyCode) { + + case keyCodes.FORWARD: + case keyCodes.FORWARD_2: + moveForward = false; + break; + + case keyCodes.LEFT: + case keyCodes.LEFT_2: + moveLeft = false; + break; + + case keyCodes.BACKWARD: + case keyCodes.BACKWARD_2: + moveBackward = false; + break; + + case keyCodes.RIGHT: + case keyCodes.RIGHT_2: + moveRight = false; + break; + + case keyCodes.UP: + moveUp = false; + break; + + case keyCodes.DOWN: + moveDown = false; + break; + + default: + break; + } + + }; + + this.unbindEvents = function () { + document.removeEventListener('mousemove', onMouseMove, false); + document.removeEventListener('keydown', onKeyDown, false); + document.removeEventListener('keyup', onKeyUp, false); + }; + + this.bindEvents = function () { + document.addEventListener('mousemove', onMouseMove, false); + document.addEventListener('keydown', onKeyDown, false); + document.addEventListener('keyup', onKeyUp, false); + }; + + this.enabled = false; + + this.getObject = function () { + return yawObject; + }; + + this.getCamUp = function () { + //return new THREE.Vector3(0,1,0).applyQuaternion(pitchObject.quaternion).applyQuaternion(yawObject); + return yawObject.up; + }; + + this.getTarget = function () { + /* + var target = scope.getDirection(scope.target).multiplyScalar(1000); + target.x += yawObject.position.x; + target.y += yawObject.position.y; + target.z += yawObject.position.z; + return target.clone();*/ + return scope.getDirection(scope.target).multiplyScalar(2000).add(yawObject.position); + }; + + this.getCamPos = function () { + return yawObject.position.clone(); + }; + + + var worldX = new THREE.Vector3(1, 0, 0); + var worldY = new THREE.Vector3(0, 1, 0); + this.setCamUp = function (camUp) { + var xAngle = camUp.angleTo(worldX); + var yAngle = camUp.angleTo(worldY); + pitchObject.rotation.x = xAngle; + yawObject.rotation.y = yAngle; + }; + + this.setCamPos = function (camPos) { + yawObject.position.copy(camPos); + }; + + this.setTarget = function (target) { + // nothing to do ? + //scope.target = target; + }; + + this.getDirection = function () { + + // assumes the camera itself is not rotated + + var direction = new THREE.Vector3(0, 0, -1); + var rotation = new THREE.Euler(0, 0, 0, 'YXZ'); + + return function (v) { + + rotation.set(pitchObject.rotation.x, yawObject.rotation.y, 0); + + v.copy(direction).applyEuler(rotation); + + return v; + + }; + + }(); + + this.update = function (delta) { + + if (scope.enabled === false) { + return; + } + + delta *= 100; + + velocity.x += ( -velocity.x ) * 0.08 * delta; + velocity.y += ( -velocity.y ) * 0.08 * delta; + velocity.z += ( -velocity.z ) * 0.08 * delta; + + + if (moveForward) { + velocity.z -= moveSpeed * delta; + } + if (moveBackward) { + velocity.z += moveSpeed * delta; + } + + if (moveUp) { + velocity.y += moveSpeed * delta; + } + if (moveDown) { + velocity.y -= moveSpeed * delta; + } + + if (moveLeft) { + velocity.x -= moveSpeed * delta; + } + if (moveRight) { + velocity.x += moveSpeed * delta; + } + + if (velocity.x < 1 && velocity.x > -1) { + velocity.x = 0; + } + if (velocity.y < 1 && velocity.y > -1) { + velocity.y = 0; + } + if (velocity.z < 1 && velocity.z > -1) { + velocity.z = 0; + } + + yawObject.translateX(velocity.x); + yawObject.translateY(velocity.y); + yawObject.translateZ(velocity.z); + if (velocity.x || velocity.y || velocity.z) { + scope.dispatchEvent(changeEvent); + } + }; + + this.moveToPosition = function (vector) { + yawObject.position.copy(vector); + }; + + this.lookAt = function (vector) { + camera.lookAt(vector); + }; + + this.resetCamera = function (camera) { + pitchObject.add(camera); + }; + + this.handleResize = function () { + }; + +}; + +THREE.PointerLockControls.prototype = Object.create(THREE.EventDispatcher.prototype); diff --git a/docdoku-web-front/app/js/dmu/controls/TrackballControls.js b/docdoku-web-front/app/js/dmu/controls/TrackballControls.js new file mode 100644 index 0000000000..e5f0e21b9c --- /dev/null +++ b/docdoku-web-front/app/js/dmu/controls/TrackballControls.js @@ -0,0 +1,649 @@ +/*global App,THREE*/ + +THREE.TrackballControls = function (object, domElement) { + 'use strict'; + var _this = this; + var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 }; + + this.object = object; + this.domElement = ( domElement !== undefined ) ? domElement : document; + + // API + + this.enabled = true; + + this.screen = { left: 0, top: 0, width: 0, height: 0 }; + + this.noRotate = false; + this.noZoom = false; + this.noPan = false; + this.noRoll = false; + + this.staticMoving = false; + this.dynamicDampingFactor = 0.2; + + this.minDistance = 0; + this.maxDistance = Infinity; + + this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; + + // internals + + this.target = new THREE.Vector3(); + + var lastPosition = new THREE.Vector3(); + + var epsilon = 0.001; + + var _state = STATE.NONE, + _prevState = STATE.NONE, + + _eye = new THREE.Vector3(), + + _rotateStart = new THREE.Vector3(), + _rotateEnd = new THREE.Vector3(), + + _zoomStart = new THREE.Vector2(), + _zoomEnd = new THREE.Vector2(), + + _touchZoomDistanceStart = 0, + _touchZoomDistanceEnd = 0, + + _panStart = new THREE.Vector2(), + _panEnd = new THREE.Vector2(); + + // for reset + + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.up0 = this.object.up.clone(); + + // events + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start'}; + var endEvent = { type: 'end'}; + + + // methods + + this.handleResize = function () { + + if (this.domElement === document) { + + this.screen.left = 0; + this.screen.top = 0; + this.screen.width = window.innerWidth; + this.screen.height = window.innerHeight; + + } else { + + var box = this.domElement.getBoundingClientRect(); + // adjustments come from similar code in the jquery offset() function + var d = this.domElement.ownerDocument.documentElement; + this.screen.left = box.left + window.pageXOffset - d.clientLeft; + this.screen.top = box.top + window.pageYOffset - d.clientTop; + this.screen.width = box.width; + this.screen.height = box.height; + + } + + }; + + this.handleEvent = function (event) { + + if (typeof this[ event.type ] === 'function') { + + this[ event.type ](event); + + } + + }; + + this.getMouseOnScreen = function (pageX, pageY, optionalTarget) { + + return ( optionalTarget || new THREE.Vector2() ).set( + ( pageX - _this.screen.left ) / _this.screen.width, + ( pageY - _this.screen.top ) / _this.screen.height + ); + + }; + + this.getMouseProjectionOnBall = (function () { + + var objectUp = new THREE.Vector3(); + + + return function (pageX, pageY, projection) { + + var mouseOnBall = new THREE.Vector3( + ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / (_this.screen.width * 0.5), + ( _this.screen.height * 0.5 + _this.screen.top - pageY ) / (_this.screen.height * 0.5), + 0.0 + ); + + var length = mouseOnBall.length(); + + if (_this.noRoll) { + + if (length < Math.SQRT1_2) { + + mouseOnBall.z = Math.sqrt(1.0 - length * length); + + } else { + + mouseOnBall.z = 0.5 / length; + + } + + } else if (length > 1.0) { + + mouseOnBall.normalize(); + + } else { + + mouseOnBall.z = Math.sqrt(1.0 - length * length); + + } + + _eye.copy(_this.object.position).sub(_this.target); + + projection.copy(_this.object.up).setLength(mouseOnBall.y); + projection.add(objectUp.copy(_this.object.up).cross(_eye).setLength(mouseOnBall.x)); + projection.add(_eye.setLength(mouseOnBall.z)); + + return projection; + }; + + }()); + + this.rotateCamera = (function () { + + var axis = new THREE.Vector3(), + quaternion = new THREE.Quaternion(); + + + return function () { + + var angle = Math.acos(_rotateStart.dot(_rotateEnd) / _rotateStart.length() / _rotateEnd.length()); + + if (angle) { + + axis.crossVectors(_rotateStart, _rotateEnd).normalize(); + + angle *= App.SceneOptions.rotateSpeed; + + quaternion.setFromAxisAngle(axis, -angle); + + _eye.applyQuaternion(quaternion); + _this.object.up.applyQuaternion(quaternion); + + _rotateEnd.applyQuaternion(quaternion); + + if (_this.staticMoving) { + + _rotateStart.copy(_rotateEnd); + + } else { + + quaternion.setFromAxisAngle(axis, angle * ( _this.dynamicDampingFactor - 1.0 )); + _rotateStart.applyQuaternion(quaternion); + + } + + } + }; + + }()); + + this.zoomCamera = function () { + + var factor; + + if (_state === STATE.TOUCH_ZOOM) { + + factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; + _touchZoomDistanceStart = _touchZoomDistanceEnd; + _eye.multiplyScalar(factor); + + } else { + + factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * App.SceneOptions.zoomSpeed; + + if (factor !== 1.0 && factor > 0.0) { + + _eye.multiplyScalar(factor); + + if (_this.staticMoving) { + + _zoomStart.copy(_zoomEnd); + + } else { + + _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; + + } + + } + + } + + }; + + this.panCamera = (function () { + + var mouseChange = new THREE.Vector2(), + objectUp = new THREE.Vector3(), + pan = new THREE.Vector3(); + + return function () { + + mouseChange.copy(_panEnd).sub(_panStart); + + if (mouseChange.lengthSq()) { + + mouseChange.multiplyScalar(_eye.length() * App.SceneOptions.panSpeed); + + pan.copy(_eye).cross(_this.object.up).setLength(mouseChange.x); + pan.add(objectUp.copy(_this.object.up).setLength(mouseChange.y)); + + _this.object.position.add(pan); + _this.target.add(pan); + + if (_this.staticMoving) { + + _panStart.copy(_panEnd); + + } else { + + _panStart.add(mouseChange.subVectors(_panEnd, _panStart).multiplyScalar(_this.dynamicDampingFactor)); + + } + + } + }; + + }()); + + this.checkDistances = function () { + + if (!_this.noZoom || !_this.noPan) { + + if (_eye.lengthSq() > _this.maxDistance * _this.maxDistance) { + + _this.object.position.addVectors(_this.target, _eye.setLength(_this.maxDistance)); + + } + + if (_eye.lengthSq() < _this.minDistance * _this.minDistance) { + + _this.object.position.addVectors(_this.target, _eye.setLength(_this.minDistance)); + + } + + } + + }; + + this.update = function () { + + _eye.subVectors(_this.object.position, _this.target); + + if (!_this.noRotate) { + + _this.rotateCamera(); + + } + + if (!_this.noZoom) { + + _this.zoomCamera(); + + } + + if (!_this.noPan) { + + _this.panCamera(); + + } + + _this.object.position.addVectors(_this.target, _eye); + + _this.checkDistances(); + + _this.object.lookAt(_this.target); + + if (lastPosition.distanceToSquared(_this.object.position) > epsilon) { + + _this.dispatchEvent(changeEvent); + + lastPosition.copy(_this.object.position); + + } + + }; + + this.reset = function () { + + _state = STATE.NONE; + _prevState = STATE.NONE; + + _this.target.copy(_this.target0); + _this.object.position.copy(_this.position0); + _this.object.up.copy(_this.up0); + + _eye.subVectors(_this.object.position, _this.target); + + _this.object.lookAt(_this.target); + + _this.dispatchEvent(changeEvent); + + lastPosition.copy(_this.object.position); + + }; + + this.moveToPosition = function (vector) { + _this.object.position.set(vector.x, vector.y, vector.z); + }; + + // listeners + + function keydown(event) { + + if (_this.enabled === false) { + return; + } + + window.removeEventListener('keydown', keydown); + + _prevState = _state; + + if (_state === STATE.NONE) { + if (event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate) { + _state = STATE.ROTATE; + } else if (event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom) { + _state = STATE.ZOOM; + } else if (event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan) { + _state = STATE.PAN; + } + } + + } + + function keyup() { + + if (_this.enabled === false) { + return; + } + + _state = _prevState; + + window.addEventListener('keydown', keydown, false); + + } + + function mousemove(event) { + + if (_this.enabled === false) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + if (_state === STATE.ROTATE && !_this.noRotate) { + + _rotateEnd = _this.getMouseProjectionOnBall(event.pageX, event.pageY, _rotateEnd); + + } else if (_state === STATE.ZOOM && !_this.noZoom) { + + _zoomEnd = _this.getMouseOnScreen(event.pageX, event.pageY, _zoomEnd); + + } else if (_state === STATE.PAN && !_this.noPan) { + + _panEnd = _this.getMouseOnScreen(event.pageX, event.pageY, _panEnd); + + } + + } + + function mouseup(event) { + + if (_this.enabled === false) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + _state = STATE.NONE; + + document.removeEventListener('mousemove', mousemove); + document.removeEventListener('mouseup', mouseup); + _this.dispatchEvent(endEvent); + + } + + function mousedown(event) { + + if (_this.enabled === false) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + if (_state === STATE.NONE) { + + _state = event.button; + + } + + if (_state === STATE.ROTATE && !_this.noRotate) { + + _rotateStart = _this.getMouseProjectionOnBall(event.pageX, event.pageY, _rotateStart); + _rotateEnd.copy(_rotateStart); + + } else if (_state === STATE.ZOOM && !_this.noZoom) { + + _zoomStart = _this.getMouseOnScreen(event.pageX, event.pageY, _zoomStart); + _zoomEnd.copy(_zoomStart); + + } else if (_state === STATE.PAN && !_this.noPan) { + + _panStart = _this.getMouseOnScreen(event.pageX, event.pageY, _panStart); + _panEnd.copy(_panStart); + + } + + document.addEventListener('mousemove', mousemove, false); + document.addEventListener('mouseup', mouseup, false); + _this.dispatchEvent(startEvent); + + + } + + function mousewheel(event) { + + if (_this.enabled === false) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + var delta = 0; + + if (event.wheelDelta) { // WebKit / Opera / Explorer 9 + + delta = event.wheelDelta / 40; + + } else if (event.detail) { // Firefox + + delta = -event.detail / 3; + + } + + _zoomStart.y += delta * 0.01; + _this.dispatchEvent(startEvent); + _this.dispatchEvent(endEvent); + + } + + function touchstart(event) { + + if (_this.enabled === false) { + return; + } + + switch (event.touches.length) { + + case 1: + _state = STATE.TOUCH_ROTATE; + _rotateEnd.copy(_this.getMouseProjectionOnBall(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _rotateStart)); + break; + + case 2: + _state = STATE.TOUCH_ZOOM; + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt(dx * dx + dy * dy); + break; + + case 3: + _state = STATE.TOUCH_PAN; + _panEnd.copy(_this.getMouseOnScreen(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _panStart)); + break; + + default: + _state = STATE.NONE; + + } + _this.dispatchEvent(startEvent); + + + } + + function touchmove(event) { + + if (_this.enabled === false) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + switch (event.touches.length) { + + case 1: + _rotateEnd = _this.getMouseProjectionOnBall(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _rotateEnd); + break; + + case 2: + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + _touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy); + break; + + case 3: + _panEnd = _this.getMouseOnScreen(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _panEnd); + break; + + default: + _state = STATE.NONE; + + } + + } + + function touchend(event) { + + if (_this.enabled === false) { + return; + } + + switch (event.touches.length) { + + case 1: + _rotateStart.copy(_this.getMouseProjectionOnBall(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _rotateEnd)); + break; + + case 2: + _touchZoomDistanceStart = _touchZoomDistanceEnd = 0; + break; + + case 3: + _panStart.copy(_this.getMouseOnScreen(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, _panEnd)); + break; + + } + + _state = STATE.NONE; + _this.dispatchEvent(endEvent); + + } + + this.domElement.addEventListener('contextmenu', function (event) { + event.preventDefault(); + }, false); + this.domElement.addEventListener('mousedown', mousedown, false); + this.domElement.addEventListener('mousewheel', mousewheel, false); + this.domElement.addEventListener('DOMMouseScroll', mousewheel, false); // firefox + this.domElement.addEventListener('touchstart', touchstart, false); + this.domElement.addEventListener('touchend', touchend, false); + this.domElement.addEventListener('touchmove', touchmove, false); + + window.addEventListener('keydown', keydown, false); + window.addEventListener('keyup', keyup, false); + + + this.unbindEvents = function () { + this.domElement.removeEventListener('mousedown', mousedown, false); + this.domElement.removeEventListener('mousewheel', mousewheel, false); + this.domElement.removeEventListener('DOMMouseScroll', mousewheel, false); // firefox + this.domElement.removeEventListener('touchstart', touchstart, false); + this.domElement.removeEventListener('touchend', touchend, false); + this.domElement.removeEventListener('touchmove', touchmove, false); + }; + + this.bindEvents = function () { + this.domElement.addEventListener('mousedown', mousedown, false); + this.domElement.addEventListener('mousewheel', mousewheel, false); + this.domElement.addEventListener('DOMMouseScroll', mousewheel, false); // firefox + this.domElement.addEventListener('touchstart', touchstart, false); + this.domElement.addEventListener('touchend', touchend, false); + this.domElement.addEventListener('touchmove', touchmove, false); + }; + + this.handleResize(); + + this.getTarget = function () { + return _this.target; + }; + + this.getCamPos = function () { + return _this.object.position; + }; + + this.getCamUp = function () { + return _this.object.up; + }; + + this.getObject = function () { + return _this.object; + }; + + this.setCamPos = function (camPos) { + _this.object.position.copy(camPos); + }; + + this.setCamUp = function (camUp) { + _this.object.up.copy(camUp); + }; + + this.setTarget = function (target) { + _this.target.copy(target); + }; +}; + +THREE.TrackballControls.prototype = Object.create(THREE.EventDispatcher.prototype); diff --git a/docdoku-web-front/app/js/dmu/controls/TransformControls.js b/docdoku-web-front/app/js/dmu/controls/TransformControls.js new file mode 100644 index 0000000000..7e4d8d92d8 --- /dev/null +++ b/docdoku-web-front/app/js/dmu/controls/TransformControls.js @@ -0,0 +1,1109 @@ +/** + * @author arodic / https://github.com/arodic + */ +/*global App,THREE,intersectObjects*/ +/*jshint sub:true*/ +(function () { + 'use strict'; + + var GizmoMaterial = function (parameters) { + + THREE.MeshBasicMaterial.call(this); + + this.depthTest = false; + this.depthWrite = false; + this.side = THREE.FrontSide; + this.transparent = true; + + this.setValues(parameters); + + this.oldColor = this.color.clone(); + this.oldOpacity = this.opacity; + + this.highlight = function (highlighted) { + + if (highlighted) { + + this.color.setRGB(1, 1, 0); + this.opacity = 1; + + } else { + + this.color.copy(this.oldColor); + this.opacity = this.oldOpacity; + + } + + }; + + }; + + GizmoMaterial.prototype = Object.create(THREE.MeshBasicMaterial.prototype); + + var GizmoLineMaterial = function (parameters) { + + THREE.LineBasicMaterial.call(this); + + this.depthTest = false; + this.depthWrite = false; + this.transparent = true; + this.linewidth = 1; + + this.setValues(parameters); + + this.oldColor = this.color.clone(); + this.oldOpacity = this.opacity; + + this.highlight = function (highlighted) { + + if (highlighted) { + + this.color.setRGB(1, 1, 0); + this.opacity = 1; + + } else { + + this.color.copy(this.oldColor); + this.opacity = this.oldOpacity; + + } + + }; + + }; + + GizmoLineMaterial.prototype = Object.create(THREE.LineBasicMaterial.prototype); + + THREE.TransformGizmo = function () { + + var scope = this; + var showPickers = false; //debug + var showActivePlane = false; //debug + + this.init = function () { + + THREE.Object3D.call(this); + + this.handles = new THREE.Object3D(); + this.pickers = new THREE.Object3D(); + this.planes = new THREE.Object3D(); + + this.add(this.handles); + this.add(this.pickers); + this.add(this.planes); + + //// PLANES + + var planeGeometry = new THREE.PlaneGeometry(50, 50, 2, 2); + var planeMaterial = new THREE.MeshBasicMaterial({ wireframe: true }); + planeMaterial.side = THREE.DoubleSide; + + var planes = { + 'XY': new THREE.Mesh(planeGeometry, planeMaterial), + 'YZ': new THREE.Mesh(planeGeometry, planeMaterial), + 'XZ': new THREE.Mesh(planeGeometry, planeMaterial), + 'XYZE': new THREE.Mesh(planeGeometry, planeMaterial) + }; + + this.activePlane = planes['XYZE']; + + planes['YZ'].rotation.set(0, Math.PI / 2, 0); + planes['XZ'].rotation.set(-Math.PI / 2, 0, 0); + + for (var i in planes) { + planes[i].name = i; + this.planes.add(planes[i]); + this.planes[i] = planes[i]; + planes[i].visible = false; + } + + //// HANDLES AND PICKERS + + var setupGizmos = function (gizmoMap, parent) { + + for (var name in gizmoMap) { + + for (i = gizmoMap[name].length; i--;) { + + var object = gizmoMap[name][i][0]; + var position = gizmoMap[name][i][1]; + var rotation = gizmoMap[name][i][2]; + + object.name = name; + + if (position) { + object.position.set(position[0], position[1], position[2]); + } + if (rotation) { + object.rotation.set(rotation[0], rotation[1], rotation[2]); + } + + parent.add(object); + + } + + } + + }; + + setupGizmos(this.handleGizmos, this.handles); + setupGizmos(this.pickerGizmos, this.pickers); + + // reset Transformations + + this.traverse(function (child) { + if (child instanceof THREE.Mesh) { + child.updateMatrix(); + + var tempGeometry = new THREE.Geometry(); + tempGeometry.merge(child.geometry, child.matrix); + + child.geometry = tempGeometry; + child.position.set(0, 0, 0); + child.rotation.set(0, 0, 0); + child.scale.set(1, 1, 1); + } + }); + + }; + + this.hide = function () { + this.traverse(function (child) { + child.visible = false; + }); + }; + + this.show = function () { + this.traverse(function (child) { + child.visible = true; + if (child.parent === scope.pickers) { + child.visible = showPickers; + } + if (child.parent === scope.planes) { + child.visible = false; + } + }); + this.activePlane.visible = showActivePlane; + }; + + this.highlight = function (axis) { + this.traverse(function (child) { + if (child.material && child.material.highlight) { + if (child.name === axis) { + child.material.highlight(true); + } else { + child.material.highlight(false); + } + } + }); + }; + + }; + + THREE.TransformGizmo.prototype = Object.create(THREE.Object3D.prototype); + + THREE.TransformGizmo.prototype.update = function (rotation, eye) { + + var vec1 = new THREE.Vector3(0, 0, 0); + var vec2 = new THREE.Vector3(0, 1, 0); + var lookAtMatrix = new THREE.Matrix4(); + + this.traverse(function (child) { + if (child.name.search('E') !== -1) { + child.quaternion.setFromRotationMatrix(lookAtMatrix.lookAt(eye, vec1, vec2)); + } else if (child.name.search('X') !== -1 || child.name.search('Y') !== -1 || child.name.search('Z') !== -1) { + child.quaternion.setFromEuler(rotation); + } + }); + + }; + + THREE.TransformGizmoTranslate = function () { + + THREE.TransformGizmo.call(this); + + var arrowGeometry = new THREE.Geometry(); + var mesh = new THREE.Mesh(new THREE.CylinderGeometry(0, 0.05, 0.2, 12, 1, false)); + mesh.position.y = 0.5; + mesh.updateMatrix(); + + arrowGeometry.merge(mesh.geometry, mesh.matrix); + + var lineXGeometry = new THREE.Geometry(); + lineXGeometry.vertices.push(new THREE.Vector3(0, 0, 0), new THREE.Vector3(1, 0, 0)); + + var lineYGeometry = new THREE.Geometry(); + lineYGeometry.vertices.push(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 1, 0)); + + var lineZGeometry = new THREE.Geometry(); + lineZGeometry.vertices.push(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, 1)); + + this.handleGizmos = { + X: [ + [ new THREE.Mesh(arrowGeometry, new GizmoMaterial({ color: 0xff0000 })), [ 0.5, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ], + [ new THREE.Line(lineXGeometry, new GizmoLineMaterial({ color: 0xff0000 })) ] + ], + Y: [ + [ new THREE.Mesh(arrowGeometry, new GizmoMaterial({ color: 0x00ff00 })), [ 0, 0.5, 0 ] ], + [ new THREE.Line(lineYGeometry, new GizmoLineMaterial({ color: 0x00ff00 })) ] + ], + Z: [ + [ new THREE.Mesh(arrowGeometry, new GizmoMaterial({ color: 0x0000ff })), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ] ], + [ new THREE.Line(lineZGeometry, new GizmoLineMaterial({ color: 0x0000ff })) ] + ], + XYZ: [ + [ new THREE.Mesh(new THREE.OctahedronGeometry(0.1, 0), new GizmoMaterial({ color: 0xffffff, opacity: 0.25 })), [ 0, 0, 0 ], [ 0, 0, 0 ] ] + ], + XY: [ + [ new THREE.Mesh(new THREE.PlaneGeometry(0.29, 0.29), new GizmoMaterial({ color: 0xffff00, opacity: 0.25 })), [ 0.15, 0.15, 0 ] ] + ], + YZ: [ + [ new THREE.Mesh(new THREE.PlaneGeometry(0.29, 0.29), new GizmoMaterial({ color: 0x00ffff, opacity: 0.25 })), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ] ] + ], + XZ: [ + [ new THREE.Mesh(new THREE.PlaneGeometry(0.29, 0.29), new GizmoMaterial({ color: 0xff00ff, opacity: 0.25 })), [ 0.15, 0, 0.15 ], [ -Math.PI / 2, 0, 0 ] ] + ] + }; + + this.pickerGizmos = { + X: [ + [ new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0, 1, 4, 1, false), new GizmoMaterial({ color: 0xff0000, opacity: 0.25 })), [ 0.6, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ] + ], + Y: [ + [ new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0, 1, 4, 1, false), new GizmoMaterial({ color: 0x00ff00, opacity: 0.25 })), [ 0, 0.6, 0 ] ] + ], + Z: [ + [ new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0, 1, 4, 1, false), new GizmoMaterial({ color: 0x0000ff, opacity: 0.25 })), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ] ] + ], + XYZ: [ + [ new THREE.Mesh(new THREE.OctahedronGeometry(0.2, 0), new GizmoMaterial({ color: 0xffffff, opacity: 0.25 })) ] + ], + XY: [ + [ new THREE.Mesh(new THREE.PlaneGeometry(0.4, 0.4), new GizmoMaterial({ color: 0xffff00, opacity: 0.25 })), [ 0.2, 0.2, 0 ] ] + ], + YZ: [ + [ new THREE.Mesh(new THREE.PlaneGeometry(0.4, 0.4), new GizmoMaterial({ color: 0x00ffff, opacity: 0.25 })), [ 0, 0.2, 0.2 ], [ 0, Math.PI / 2, 0 ] ] + ], + XZ: [ + [ new THREE.Mesh(new THREE.PlaneGeometry(0.4, 0.4), new GizmoMaterial({ color: 0xff00ff, opacity: 0.25 })), [ 0.2, 0, 0.2 ], [ -Math.PI / 2, 0, 0 ] ] + ] + }; + + this.setActivePlane = function (axis, eye) { + + var tempMatrix = new THREE.Matrix4(); + eye.applyMatrix4(tempMatrix.getInverse(tempMatrix.extractRotation(this.planes[ 'XY' ].matrixWorld))); + + if (axis === 'X') { + this.activePlane = this.planes[ 'XY' ]; + if (Math.abs(eye.y) > Math.abs(eye.z)) { + this.activePlane = this.planes[ 'XZ' ]; + } + } + + if (axis === 'Y') { + this.activePlane = this.planes[ 'XY' ]; + if (Math.abs(eye.x) > Math.abs(eye.z)) { + this.activePlane = this.planes[ 'YZ' ]; + } + } + + if (axis === 'Z') { + this.activePlane = this.planes[ 'XZ' ]; + if (Math.abs(eye.x) > Math.abs(eye.y)) { + this.activePlane = this.planes[ 'YZ' ]; + } + } + + if (axis === 'XYZ') { + this.activePlane = this.planes[ 'XYZE' ]; + } + + if (axis === 'XY') { + this.activePlane = this.planes[ 'XY' ]; + } + + if (axis === 'YZ') { + this.activePlane = this.planes[ 'YZ' ]; + } + + if (axis === 'XZ') { + this.activePlane = this.planes[ 'XZ' ]; + } + + this.hide(); + this.show(); + }; + + this.init(); + + }; + + THREE.TransformGizmoTranslate.prototype = Object.create(THREE.TransformGizmo.prototype); + + THREE.TransformGizmoRotate = function () { + + THREE.TransformGizmo.call(this); + + var CircleGeometry = function (radius, facing, arc) { + + var geometry = new THREE.Geometry(); + arc = arc ? arc : 1; + for (var i = 0; i <= 64 * arc; ++i) { + if (facing === 'x') { + geometry.vertices.push(new THREE.Vector3(0, Math.cos(i / 32 * Math.PI), Math.sin(i / 32 * Math.PI)).multiplyScalar(radius)); + } + if (facing === 'y') { + geometry.vertices.push(new THREE.Vector3(Math.cos(i / 32 * Math.PI), 0, Math.sin(i / 32 * Math.PI)).multiplyScalar(radius)); + } + if (facing === 'z') { + geometry.vertices.push(new THREE.Vector3(Math.sin(i / 32 * Math.PI), Math.cos(i / 32 * Math.PI), 0).multiplyScalar(radius)); + } + } + + return geometry; + }; + + this.handleGizmos = { + X: [ + [ new THREE.Line(new CircleGeometry(1, 'x', 0.5), new GizmoLineMaterial({ color: 0xff0000 })) ] + ], + Y: [ + [ new THREE.Line(new CircleGeometry(1, 'y', 0.5), new GizmoLineMaterial({ color: 0x00ff00 })) ] + ], + Z: [ + [ new THREE.Line(new CircleGeometry(1, 'z', 0.5), new GizmoLineMaterial({ color: 0x0000ff })) ] + ], + E: [ + [ new THREE.Line(new CircleGeometry(1.25, 'z', 1), new GizmoLineMaterial({ color: 0xcccc00 })) ] + ], + XYZE: [ + [ new THREE.Line(new CircleGeometry(1, 'z', 1), new GizmoLineMaterial({ color: 0x787878 })) ] + ] + }; + + this.pickerGizmos = { + X: [ + [ new THREE.Mesh(new THREE.TorusGeometry(1, 0.12, 4, 12, Math.PI), new GizmoMaterial({ color: 0xff0000, opacity: 0.25 })), [ 0, 0, 0 ], [ 0, -Math.PI / 2, -Math.PI / 2 ] ] + ], + Y: [ + [ new THREE.Mesh(new THREE.TorusGeometry(1, 0.12, 4, 12, Math.PI), new GizmoMaterial({ color: 0x00ff00, opacity: 0.25 })), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ] ] + ], + Z: [ + [ new THREE.Mesh(new THREE.TorusGeometry(1, 0.12, 4, 12, Math.PI), new GizmoMaterial({ color: 0x0000ff, opacity: 0.25 })), [ 0, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ] + ], + E: [ + [ new THREE.Mesh(new THREE.TorusGeometry(1.25, 0.12, 2, 24), new GizmoMaterial({ color: 0xffff00, opacity: 0.25 })) ] + ], + XYZE: [ + [ new THREE.Mesh(new THREE.Geometry()) ]// TODO + ] + }; + + this.setActivePlane = function (axis) { + + if (axis === 'E') { + this.activePlane = this.planes[ 'XYZE' ]; + } + + if (axis === 'X') { + this.activePlane = this.planes[ 'YZ' ]; + } + + if (axis === 'Y') { + this.activePlane = this.planes[ 'XZ' ]; + } + + if (axis === 'Z') { + this.activePlane = this.planes[ 'XY' ]; + } + + this.hide(); + this.show(); + + }; + + this.update = function (rotation, eye2) { + + THREE.TransformGizmo.prototype.update.apply(this, arguments); + + var group = { + handles: this['handles'], + pickers: this['pickers'] + }; + + var tempMatrix = new THREE.Matrix4(); + var worldRotation = new THREE.Euler(0, 0, 1); + var tempQuaternion = new THREE.Quaternion(); + var unitX = new THREE.Vector3(1, 0, 0); + var unitY = new THREE.Vector3(0, 1, 0); + var unitZ = new THREE.Vector3(0, 0, 1); + var quaternionX = new THREE.Quaternion(); + var quaternionY = new THREE.Quaternion(); + var quaternionZ = new THREE.Quaternion(); + var eye = eye2.clone(); + + worldRotation.copy(this.planes['XY'].rotation); + tempQuaternion.setFromEuler(worldRotation); + + tempMatrix.makeRotationFromQuaternion(tempQuaternion).getInverse(tempMatrix); + eye.applyMatrix4(tempMatrix); + + this.traverse(function (child) { + + tempQuaternion.setFromEuler(worldRotation); + + if (child.name === 'X') { + quaternionX.setFromAxisAngle(unitX, Math.atan2(-eye.y, eye.z)); + tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionX); + child.quaternion.copy(tempQuaternion); + } + + if (child.name === 'Y') { + quaternionY.setFromAxisAngle(unitY, Math.atan2(eye.x, eye.z)); + tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionY); + child.quaternion.copy(tempQuaternion); + } + + if (child.name === 'Z') { + quaternionZ.setFromAxisAngle(unitZ, Math.atan2(eye.y, eye.x)); + tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionZ); + child.quaternion.copy(tempQuaternion); + } + + }); + + }; + + this.init(); + + }; + + THREE.TransformGizmoRotate.prototype = Object.create(THREE.TransformGizmo.prototype); + + THREE.TransformGizmoScale = function () { + + THREE.TransformGizmo.call(this); + + var arrowGeometry = new THREE.Geometry(); + var mesh = new THREE.Mesh(new THREE.BoxGeometry(0.125, 0.125, 0.125)); + mesh.position.y = 0.5; + mesh.updateMatrix(); + + arrowGeometry.merge(mesh.geometry, mesh.matrix); + + var lineXGeometry = new THREE.Geometry(); + lineXGeometry.vertices.push(new THREE.Vector3(0, 0, 0), new THREE.Vector3(1, 0, 0)); + + var lineYGeometry = new THREE.Geometry(); + lineYGeometry.vertices.push(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 1, 0)); + + var lineZGeometry = new THREE.Geometry(); + lineZGeometry.vertices.push(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, 1)); + + this.handleGizmos = { + X: [ + [ new THREE.Mesh(arrowGeometry, new GizmoMaterial({ color: 0xff0000 })), [ 0.5, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ], + [ new THREE.Line(lineXGeometry, new GizmoLineMaterial({ color: 0xff0000 })) ] + ], + Y: [ + [ new THREE.Mesh(arrowGeometry, new GizmoMaterial({ color: 0x00ff00 })), [ 0, 0.5, 0 ] ], + [ new THREE.Line(lineYGeometry, new GizmoLineMaterial({ color: 0x00ff00 })) ] + ], + Z: [ + [ new THREE.Mesh(arrowGeometry, new GizmoMaterial({ color: 0x0000ff })), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ] ], + [ new THREE.Line(lineZGeometry, new GizmoLineMaterial({ color: 0x0000ff })) ] + ], + XYZ: [ + [ new THREE.Mesh(new THREE.BoxGeometry(0.125, 0.125, 0.125), new GizmoMaterial({ color: 0xffffff, opacity: 0.25 })) ] + ] + }; + + this.pickerGizmos = { + X: [ + [ new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0, 1, 4, 1, false), new GizmoMaterial({ color: 0xff0000, opacity: 0.25 })), [ 0.6, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ] + ], + Y: [ + [ new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0, 1, 4, 1, false), new GizmoMaterial({ color: 0x00ff00, opacity: 0.25 })), [ 0, 0.6, 0 ] ] + ], + Z: [ + [ new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0, 1, 4, 1, false), new GizmoMaterial({ color: 0x0000ff, opacity: 0.25 })), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ] ] + ], + XYZ: [ + [ new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.4, 0.4), new GizmoMaterial({ color: 0xffffff, opacity: 0.25 })) ] + ] + }; + + this.setActivePlane = function (axis, eye) { + + var tempMatrix = new THREE.Matrix4(); + eye.applyMatrix4(tempMatrix.getInverse(tempMatrix.extractRotation(this.planes[ 'XY' ].matrixWorld))); + + if (axis === 'X') { + this.activePlane = this.planes[ 'XY' ]; + if (Math.abs(eye.y) > Math.abs(eye.z)) { + this.activePlane = this.planes[ 'XZ' ]; + } + } + + if (axis === 'Y') { + this.activePlane = this.planes[ 'XY' ]; + if (Math.abs(eye.x) > Math.abs(eye.z)) { + this.activePlane = this.planes[ 'YZ' ]; + } + } + + if (axis === 'Z') { + this.activePlane = this.planes[ 'XZ' ]; + if (Math.abs(eye.x) > Math.abs(eye.y)) { + this.activePlane = this.planes[ 'YZ' ]; + } + } + + if (axis === 'XYZ') { + this.activePlane = this.planes[ 'XYZE' ]; + } + + this.hide(); + this.show(); + + }; + + this.init(); + + }; + + THREE.TransformGizmoScale.prototype = Object.create(THREE.TransformGizmo.prototype); + + THREE.TransformControls = function (domElement) { + + // TODO: Make non-uniform scale and rotate play nice in hierarchies + // TODO: ADD RXYZ contol + + THREE.Object3D.call(this); + + this.domElement = ( domElement !== undefined ) ? domElement : document; + + this.gizmo = {}; + this.gizmo['translate'] = new THREE.TransformGizmoTranslate(); + this.gizmo['rotate'] = new THREE.TransformGizmoRotate(); + this.gizmo['scale'] = new THREE.TransformGizmoScale(); + + this.add(this.gizmo['translate']); + this.add(this.gizmo['rotate']); + this.add(this.gizmo['scale']); + + this.gizmo['translate'].hide(); + this.gizmo['rotate'].hide(); + this.gizmo['scale'].hide(); + + this.object = undefined; + this.snap = null; + this.space = 'world'; + this.size = 1; + this.axis = null; + var camera = null; + + this.enabled = false; + + var scope = this; + + var _dragging = false; + var _mode = 'translate'; + var _plane = 'XY'; + + var changeEvent = { type: 'change' }; + + var ray = new THREE.Raycaster(); + var projector = new THREE.Projector(); + var pointerVector = new THREE.Vector3(); + + var point = new THREE.Vector3(); + var offset = new THREE.Vector3(); + + var rotation = new THREE.Vector3(); + var offsetRotation = new THREE.Vector3(); + var scale = 1; + + var lookAtMatrix = new THREE.Matrix4(); + var eye = new THREE.Vector3(); + + var tempMatrix = new THREE.Matrix4(); + var tempVector = new THREE.Vector3(); + var tempQuaternion = new THREE.Quaternion(); + var unitX = new THREE.Vector3(1, 0, 0); + var unitY = new THREE.Vector3(0, 1, 0); + var unitZ = new THREE.Vector3(0, 0, 1); + + var quaternionXYZ = new THREE.Quaternion(); + var quaternionX = new THREE.Quaternion(); + var quaternionY = new THREE.Quaternion(); + var quaternionZ = new THREE.Quaternion(); + var quaternionE = new THREE.Quaternion(); + + var oldPosition = new THREE.Vector3(); + var oldScale = new THREE.Vector3(); + var oldRotationMatrix = new THREE.Matrix4(); + + var parentRotationMatrix = new THREE.Matrix4(); + var parentScale = new THREE.Vector3(); + + var worldPosition = new THREE.Vector3(); + var worldRotation = new THREE.Euler(); + var worldRotationMatrix = new THREE.Matrix4(); + var camPosition = new THREE.Vector3(); + var camRotation = new THREE.Euler(); + + function onPointerHover(event) { + + if (scope.object === undefined || _dragging === true) { + return; + } + + event.preventDefault(); + + var pointer = event.touches ? event.touches[ 0 ] : event; + + var intersect = intersectObjects(pointer, scope.gizmo[_mode].pickers.children); + + if (intersect) { + + scope.axis = intersect.object.name; + scope.update(); + scope.dispatchEvent(changeEvent); + + } else if (scope.axis !== null) { + + scope.axis = null; + scope.update(); + scope.dispatchEvent(changeEvent); + + } + + } + + function onPointerDown(event) { + + if (scope.object === undefined || _dragging === true) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + var pointer = event.touches ? event.touches[ 0 ] : event; + + if (pointer.button === 0 || pointer.button === undefined) { + + var intersect = intersectObjects(pointer, scope.gizmo[_mode].pickers.children); + + if (intersect) { + + scope.axis = intersect.object.name; + + scope.update(); + + eye.copy(camPosition).sub(worldPosition).normalize(); + + scope.gizmo[_mode].setActivePlane(scope.axis, eye); + + var planeIntersect = intersectObjects(pointer, [scope.gizmo[_mode].activePlane]); + + oldPosition.copy(scope.object.position); + oldScale.copy(scope.object.scale); + + oldRotationMatrix.extractRotation(scope.object.matrix); + worldRotationMatrix.extractRotation(scope.object.matrixWorld); + + parentRotationMatrix.extractRotation(scope.object.parent.matrixWorld); + parentScale.setFromMatrixScale(tempMatrix.getInverse(scope.object.parent.matrixWorld)); + + offset.copy(planeIntersect.point); + + } + + } + + _dragging = true; + + } + + function onPointerMove(event) { + + if (scope.object === undefined || scope.axis === null || _dragging === false) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + var pointer = event.touches ? event.touches[0] : event; + + var planeIntersect = intersectObjects(pointer, [scope.gizmo[_mode].activePlane]); + point.copy(planeIntersect.point); + + if (_mode === 'translate') { + point.sub(offset); + point.multiply(parentScale); + + // limit the translation + if (Math.abs(point.x) > 1000 || Math.abs(point.y) > 1000 || Math.abs(point.z) > 1000) { + _dragging = false; + return; + } + if (scope.space === 'local') { + + point.applyMatrix4(tempMatrix.getInverse(worldRotationMatrix)); + + if (scope.axis.search('X') === -1) { + point.x = 0; + } + if (scope.axis.search('Y') === -1) { + point.y = 0; + } + if (scope.axis.search('Z') === -1) { + point.z = 0; + } + + point.applyMatrix4(oldRotationMatrix); + + scope.object.position.copy(oldPosition); + scope.object.position.add(point); + + } + + if (scope.space === 'world' || scope.axis.search('XYZ') !== -1) { + + if (scope.axis.search('X') === -1) { + point.x = 0; + } + if (scope.axis.search('Y') === -1) { + point.y = 0; + } + if (scope.axis.search('Z') === -1) { + point.z = 0; + } + + point.applyMatrix4(tempMatrix.getInverse(parentRotationMatrix)); + + scope.object.position.copy(oldPosition); + scope.object.position.add(point); + + } + + if (scope.snap !== null) { + + if (scope.axis.search('X') !== -1) { + scope.object.position.x = Math.round(scope.object.position.x / scope.snap) * scope.snap; + } + if (scope.axis.search('Y') !== -1) { + scope.object.position.y = Math.round(scope.object.position.y / scope.snap) * scope.snap; + } + if (scope.axis.search('Z') !== -1) { + scope.object.position.z = Math.round(scope.object.position.z / scope.snap) * scope.snap; + } + + } + + } else if (_mode === 'scale') { + + point.sub(offset); + point.multiply(parentScale); + + if (scope.space === 'local') { + + if (scope.axis === 'XYZ') { + + scale = 1 + ( ( point.y ) / 50 ); + + scope.object.scale.x = oldScale.x * scale; + scope.object.scale.y = oldScale.y * scale; + scope.object.scale.z = oldScale.z * scale; + + } else { + + point.applyMatrix4(tempMatrix.getInverse(worldRotationMatrix)); + + if (scope.axis === 'X') { + scope.object.scale.x = oldScale.x * ( 1 + point.x / 50 ); + } + if (scope.axis === 'Y') { + scope.object.scale.y = oldScale.y * ( 1 + point.y / 50 ); + } + if (scope.axis === 'Z') { + scope.object.scale.z = oldScale.z * ( 1 + point.z / 50 ); + } + + } + + } + + } else if (_mode === 'rotate') { + + point.sub(worldPosition); + point.multiply(parentScale); + tempVector.copy(offset).sub(worldPosition); + tempVector.multiply(parentScale); + + if (scope.axis === 'E') { + + point.applyMatrix4(tempMatrix.getInverse(lookAtMatrix)); + tempVector.applyMatrix4(tempMatrix.getInverse(lookAtMatrix)); + + rotation.set(Math.atan2(point.z, point.y), Math.atan2(point.x, point.z), Math.atan2(point.y, point.x)); + offsetRotation.set(Math.atan2(tempVector.z, tempVector.y), Math.atan2(tempVector.x, tempVector.z), Math.atan2(tempVector.y, tempVector.x)); + + tempQuaternion.setFromRotationMatrix(tempMatrix.getInverse(parentRotationMatrix)); + + quaternionE.setFromAxisAngle(eye, rotation.z - offsetRotation.z); + quaternionXYZ.setFromRotationMatrix(worldRotationMatrix); + + tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionE); + tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionXYZ); + + scope.object.quaternion.copy(tempQuaternion); + + } else if (scope.axis === 'XYZE') { + + quaternionE.setFromEuler(point.clone().cross(tempVector).normalize()); // rotation axis + + tempQuaternion.setFromRotationMatrix(tempMatrix.getInverse(parentRotationMatrix)); + quaternionX.setFromAxisAngle(quaternionE, -point.clone().angleTo(tempVector)); + quaternionXYZ.setFromRotationMatrix(worldRotationMatrix); + + tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionX); + tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionXYZ); + + scope.object.quaternion.copy(tempQuaternion); + + } else if (scope.space === 'local') { + + point.applyMatrix4(tempMatrix.getInverse(worldRotationMatrix)); + + tempVector.applyMatrix4(tempMatrix.getInverse(worldRotationMatrix)); + + rotation.set(Math.atan2(point.z, point.y), Math.atan2(point.x, point.z), Math.atan2(point.y, point.x)); + offsetRotation.set(Math.atan2(tempVector.z, tempVector.y), Math.atan2(tempVector.x, tempVector.z), Math.atan2(tempVector.y, tempVector.x)); + + quaternionXYZ.setFromRotationMatrix(oldRotationMatrix); + quaternionX.setFromAxisAngle(unitX, rotation.x - offsetRotation.x); + quaternionY.setFromAxisAngle(unitY, rotation.y - offsetRotation.y); + quaternionZ.setFromAxisAngle(unitZ, rotation.z - offsetRotation.z); + + if (scope.axis === 'X') { + quaternionXYZ.multiplyQuaternions(quaternionXYZ, quaternionX); + } + if (scope.axis === 'Y') { + quaternionXYZ.multiplyQuaternions(quaternionXYZ, quaternionY); + } + if (scope.axis === 'Z') { + quaternionXYZ.multiplyQuaternions(quaternionXYZ, quaternionZ); + } + + scope.object.quaternion.copy(quaternionXYZ); + + } else if (scope.space === 'world') { + + rotation.set(Math.atan2(point.z, point.y), Math.atan2(point.x, point.z), Math.atan2(point.y, point.x)); + offsetRotation.set(Math.atan2(tempVector.z, tempVector.y), Math.atan2(tempVector.x, tempVector.z), Math.atan2(tempVector.y, tempVector.x)); + + tempQuaternion.setFromRotationMatrix(tempMatrix.getInverse(parentRotationMatrix)); + + quaternionX.setFromAxisAngle(unitX, rotation.x - offsetRotation.x); + quaternionY.setFromAxisAngle(unitY, rotation.y - offsetRotation.y); + quaternionZ.setFromAxisAngle(unitZ, rotation.z - offsetRotation.z); + quaternionXYZ.setFromRotationMatrix(worldRotationMatrix); + + if (scope.axis === 'X') { + tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionX); + } + if (scope.axis === 'Y') { + tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionY); + } + if (scope.axis === 'Z') { + tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionZ); + } + + tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionXYZ); + + scope.object.quaternion.copy(tempQuaternion); + + } + + } + + App.collaborativeController.sendEditedObjects(); + + scope.update(); + scope.dispatchEvent(changeEvent); + + } + + function onPointerUp(event) { + + _dragging = false; + onPointerHover(event); + + } + + function intersectObjects(pointer, objects) { + + var rect = domElement.getBoundingClientRect(); + var x = (pointer.clientX - rect.left) / rect.width; + var y = (pointer.clientY - rect.top) / rect.height; + pointerVector.set(( x ) * 2 - 1, -( y ) * 2 + 1, 0.5); + + projector.unprojectVector(pointerVector, camera); + ray.set(camPosition, pointerVector.sub(camPosition).normalize()); + + var intersections = ray.intersectObjects(objects, true); + return intersections[0] ? intersections[0] : false; + + } + + this.unbindEvents = function () { + scope.domElement.removeEventListener('mousedown', onPointerDown, false); + scope.domElement.removeEventListener('touchstart', onPointerDown, false); + + scope.domElement.removeEventListener('mousemove', onPointerHover, false); + scope.domElement.removeEventListener('touchmove', onPointerHover, false); + + scope.domElement.removeEventListener('mousemove', onPointerMove, false); + scope.domElement.removeEventListener('touchmove', onPointerMove, false); + + scope.domElement.removeEventListener('mouseup', onPointerUp, false); + scope.domElement.removeEventListener('mouseout', onPointerUp, false); + scope.domElement.removeEventListener('touchend', onPointerUp, false); + scope.domElement.removeEventListener('touchcancel', onPointerUp, false); + scope.domElement.removeEventListener('touchleave', onPointerUp, false); + }; + + this.bindEvents = function () { + scope.domElement.addEventListener('mousedown', onPointerDown, false); + scope.domElement.addEventListener('touchstart', onPointerDown, false); + + scope.domElement.addEventListener('mousemove', onPointerHover, false); + scope.domElement.addEventListener('touchmove', onPointerHover, false); + + scope.domElement.addEventListener('mousemove', onPointerMove, false); + scope.domElement.addEventListener('touchmove', onPointerMove, false); + + scope.domElement.addEventListener('mouseup', onPointerUp, false); + scope.domElement.addEventListener('mouseout', onPointerUp, false); + scope.domElement.addEventListener('touchend', onPointerUp, false); + scope.domElement.addEventListener('touchcancel', onPointerUp, false); + scope.domElement.addEventListener('touchleave', onPointerUp, false); + }; + + this.attach = function (object) { + + scope.object = object; + + this.gizmo['translate'].hide(); + this.gizmo['rotate'].hide(); + this.gizmo['scale'].hide(); + this.gizmo[_mode].show(); + + scope.update(); + + }; + + this.detach = function (object) { + + scope.object = undefined; + this.axis = undefined; + + this.gizmo['translate'].hide(); + this.gizmo['rotate'].hide(); + this.gizmo['scale'].hide(); + + }; + + this.getObject = function () { + return scope.object; + }; + + this.setMode = function (mode) { + + _mode = mode ? mode : _mode; + + if (_mode === 'scale') { + scope.space = 'local'; + } + + this.gizmo['translate'].hide(); + this.gizmo['rotate'].hide(); + this.gizmo['scale'].hide(); + this.gizmo[_mode].show(); + + this.update(); + scope.dispatchEvent(changeEvent); + + }; + + this.getMode = function () { + return _mode; + }; + + this.setSnap = function (snap) { + + scope.snap = snap; + + }; + this.setCamera = function (pCamera) { + camera = pCamera; + }; + + this.setSize = function (size) { + + scope.size = size; + this.update(); + scope.dispatchEvent(changeEvent); + + }; + + this.setSpace = function (space) { + + scope.space = space; + this.update(); + scope.dispatchEvent(changeEvent); + + }; + + this.update = function () { + + if (scope.object === undefined) { + return; + } + + scope.object.updateMatrixWorld(); + worldPosition.setFromMatrixPosition(scope.object.matrixWorld); + worldRotation.setFromRotationMatrix(tempMatrix.extractRotation(scope.object.matrixWorld)); + + camera.updateMatrixWorld(); + camPosition.setFromMatrixPosition(camera.matrixWorld); + camRotation.setFromRotationMatrix(tempMatrix.extractRotation(camera.matrixWorld)); + + scale = worldPosition.distanceTo(camPosition) / 6 * scope.size; + this.position.copy(worldPosition); + this.scale.set(scale, scale, scale); + + eye.copy(camPosition).sub(worldPosition).normalize(); + + if (scope.space === 'local') { + this.gizmo[_mode].update(worldRotation, eye); + } + + else if (scope.space === 'world') { + this.gizmo[_mode].update(new THREE.Euler(), eye); + } + + this.gizmo[_mode].highlight(scope.axis); + + }; + + }; + + THREE.TransformControls.prototype = Object.create(THREE.Object3D.prototype); + +}()); diff --git a/docdoku-web-front/app/js/dmu/loaders/BinaryLoader.js b/docdoku-web-front/app/js/dmu/loaders/BinaryLoader.js new file mode 100644 index 0000000000..6b382efb2b --- /dev/null +++ b/docdoku-web-front/app/js/dmu/loaders/BinaryLoader.js @@ -0,0 +1,708 @@ +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.BinaryLoader = function ( showStatus ) { + 'use strict'; + THREE.Loader.call( this, showStatus ); + +}; + +THREE.BinaryLoader.prototype = Object.create( THREE.Loader.prototype ); + +// Load models generated by slim OBJ converter with BINARY option (converter_obj_three_slim.py -t binary) +// - binary models consist of two files: JS and BIN +// - parameters +// - url (required) +// - callback (required) +// - texturePath (optional: if not specified, textures will be assumed to be in the same folder as JS model file) +// - binaryPath (optional: if not specified, binary file will be assumed to be in the same folder as JS model file) + +THREE.BinaryLoader.prototype.load = function ( url, callback, texturePath, binaryPath ) { + 'use strict'; + // todo: unify load API to for easier SceneLoader use + + texturePath = texturePath || this.extractUrlBase( url ); + binaryPath = binaryPath || this.extractUrlBase( url ); + + var callbackProgress = this.showProgress ? THREE.Loader.prototype.updateProgress : undefined; + + this.onLoadStart(); + + // #1 load JS part via web worker + + this.loadAjaxJSON( this, url, callback, texturePath, binaryPath, callbackProgress ); + +}; + +THREE.BinaryLoader.prototype.loadAjaxJSON = function ( context, url, callback, texturePath, binaryPath, callbackProgress ) { + 'use strict'; + var xhr = new XMLHttpRequest(); + + texturePath = texturePath && ( typeof texturePath === 'string' ) ? texturePath : this.extractUrlBase( url ); + binaryPath = binaryPath && ( typeof binaryPath === 'string' ) ? binaryPath : this.extractUrlBase( url ); + + xhr.onreadystatechange = function () { + + if ( xhr.readyState == 4 ) { + + if ( xhr.status == 200 || xhr.status == 0 ) { + + var json = JSON.parse( xhr.responseText ); + context.loadAjaxBuffers( json, callback, binaryPath, texturePath, callbackProgress ); + + } else { + + console.error( 'THREE.BinaryLoader: Couldn\'t load [' + url + '] [' + xhr.status + ']' ); + + } + + } + + }; + + xhr.open( 'GET', url, true ); + xhr.send(); + +}; + +THREE.BinaryLoader.prototype.loadAjaxBuffers = function ( json, callback, binaryPath, texturePath, callbackProgress ) { + 'use strict'; + var scope = this; + + var xhr = new XMLHttpRequest(), + url = binaryPath + json.buffers; + + xhr.addEventListener( 'load', function ( event ) { + + var buffer = xhr.response; + + if ( buffer === undefined ) { + + // IEWEBGL needs this + buffer = ( new Uint8Array( xhr.responseBody ) ).buffer; + + } + + if ( buffer.byteLength == 0 ) { // iOS and other XMLHttpRequest level 1 + + var buffer = new ArrayBuffer( xhr.responseText.length ); + + var bufView = new Uint8Array( buffer ); + + for ( var i = 0, l = xhr.responseText.length; i < l; i ++ ) { + + bufView[ i ] = xhr.responseText.charCodeAt( i ) & 0xff; + + } + + } + + scope.createBinModel( buffer, callback, texturePath, json.materials ); + + }, false ); + + if ( callbackProgress !== undefined ) { + + xhr.addEventListener( 'progress', function ( event ) { + + if ( event.lengthComputable ) { + + callbackProgress( event ); + + } + + }, false ); + + } + + xhr.addEventListener( 'error', function ( event ) { + + console.error( 'THREE.BinaryLoader: Couldn\'t load [' + url + '] [' + xhr.status + ']' ); + + }, false ); + + + xhr.open( 'GET', url, true ); + xhr.responseType = 'arraybuffer'; + if ( xhr.overrideMimeType ){ + xhr.overrideMimeType( 'text/plain; charset=x-user-defined' ); + } + xhr.send(); + +}; + +// Binary AJAX parser + +THREE.BinaryLoader.prototype.createBinModel = function ( data, callback, texturePath, jsonMaterials ) { + 'use strict'; + var Model = function ( texturePath ) { + + var scope = this, + currentOffset = 0, + md, + normals = [], + uvs = [], + start_tri_flat, start_tri_smooth, start_tri_flat_uv, start_tri_smooth_uv, + start_quad_flat, start_quad_smooth, start_quad_flat_uv, start_quad_smooth_uv, + tri_size, quad_size, + len_tri_flat, len_tri_smooth, len_tri_flat_uv, len_tri_smooth_uv, + len_quad_flat, len_quad_smooth, len_quad_flat_uv, len_quad_smooth_uv; + + + THREE.Geometry.call( this ); + + md = parseMetaData( data, currentOffset ); + + currentOffset += md.header_bytes; + /* + md.vertex_index_bytes = Uint32Array.BYTES_PER_ELEMENT; + md.material_index_bytes = Uint16Array.BYTES_PER_ELEMENT; + md.normal_index_bytes = Uint32Array.BYTES_PER_ELEMENT; + md.uv_index_bytes = Uint32Array.BYTES_PER_ELEMENT; + */ + // buffers sizes + + tri_size = md.vertex_index_bytes * 3 + md.material_index_bytes; + quad_size = md.vertex_index_bytes * 4 + md.material_index_bytes; + + len_tri_flat = md.ntri_flat * ( tri_size ); + len_tri_smooth = md.ntri_smooth * ( tri_size + md.normal_index_bytes * 3 ); + len_tri_flat_uv = md.ntri_flat_uv * ( tri_size + md.uv_index_bytes * 3 ); + len_tri_smooth_uv = md.ntri_smooth_uv * ( tri_size + md.normal_index_bytes * 3 + md.uv_index_bytes * 3 ); + + len_quad_flat = md.nquad_flat * ( quad_size ); + len_quad_smooth = md.nquad_smooth * ( quad_size + md.normal_index_bytes * 4 ); + len_quad_flat_uv = md.nquad_flat_uv * ( quad_size + md.uv_index_bytes * 4 ); + len_quad_smooth_uv = md.nquad_smooth_uv * ( quad_size + md.normal_index_bytes * 4 + md.uv_index_bytes * 4 ); + + // read buffers + + currentOffset += init_vertices( currentOffset ); + + currentOffset += init_normals( currentOffset ); + currentOffset += handlePadding( md.nnormals * 3 ); + + currentOffset += init_uvs( currentOffset ); + + start_tri_flat = currentOffset; + start_tri_smooth = start_tri_flat + len_tri_flat + handlePadding( md.ntri_flat * 2 ); + start_tri_flat_uv = start_tri_smooth + len_tri_smooth + handlePadding( md.ntri_smooth * 2 ); + start_tri_smooth_uv = start_tri_flat_uv + len_tri_flat_uv + handlePadding( md.ntri_flat_uv * 2 ); + + start_quad_flat = start_tri_smooth_uv + len_tri_smooth_uv + handlePadding( md.ntri_smooth_uv * 2 ); + start_quad_smooth = start_quad_flat + len_quad_flat + handlePadding( md.nquad_flat * 2 ); + start_quad_flat_uv = start_quad_smooth + len_quad_smooth + handlePadding( md.nquad_smooth * 2 ); + start_quad_smooth_uv= start_quad_flat_uv + len_quad_flat_uv + handlePadding( md.nquad_flat_uv * 2 ); + + // have to first process faces with uvs + // so that face and uv indices match + + init_triangles_flat_uv( start_tri_flat_uv ); + init_triangles_smooth_uv( start_tri_smooth_uv ); + + init_quads_flat_uv( start_quad_flat_uv ); + init_quads_smooth_uv( start_quad_smooth_uv ); + + // now we can process untextured faces + + init_triangles_flat( start_tri_flat ); + init_triangles_smooth( start_tri_smooth ); + + init_quads_flat( start_quad_flat ); + init_quads_smooth( start_quad_smooth ); + + this.computeFaceNormals(); + + function handlePadding( n ) { + + return ( n % 4 ) ? ( 4 - n % 4 ) : 0; + + } + + function parseMetaData( data, offset ) { + + return { + + 'signature' :parseString( data, offset, 12 ), + 'header_bytes' :parseUChar8( data, offset + 12 ), + + 'vertex_coordinate_bytes' :parseUChar8( data, offset + 13 ), + 'normal_coordinate_bytes' :parseUChar8( data, offset + 14 ), + 'uv_coordinate_bytes' :parseUChar8( data, offset + 15 ), + + 'vertex_index_bytes' :parseUChar8( data, offset + 16 ), + 'normal_index_bytes' :parseUChar8( data, offset + 17 ), + 'uv_index_bytes' :parseUChar8( data, offset + 18 ), + 'material_index_bytes' :parseUChar8( data, offset + 19 ), + + 'nvertices' :parseUInt32( data, offset + 20 ), + 'nnormals' :parseUInt32( data, offset + 20 + 4 ), + 'nuvs' :parseUInt32( data, offset + 20 + 4*2 ), + + 'ntri_flat' :parseUInt32( data, offset + 20 + 4*3 ), + 'ntri_smooth' :parseUInt32( data, offset + 20 + 4*4 ), + 'ntri_flat_uv' :parseUInt32( data, offset + 20 + 4*5 ), + 'ntri_smooth_uv' :parseUInt32( data, offset + 20 + 4*6 ), + + 'nquad_flat' :parseUInt32( data, offset + 20 + 4*7 ), + 'nquad_smooth' :parseUInt32( data, offset + 20 + 4*8 ), + 'nquad_flat_uv' :parseUInt32( data, offset + 20 + 4*9 ), + 'nquad_smooth_uv' :parseUInt32( data, offset + 20 + 4*10 ) + + }; + } + + function parseString( data, offset, length ) { + + var charArray = new Uint8Array( data, offset, length ); + + var text = ''; + + for ( var i = 0; i < length; i ++ ) { + + text += String.fromCharCode( charArray[ offset + i ] ); + + } + + return text; + + } + + function parseUChar8( data, offset ) { + + var charArray = new Uint8Array( data, offset, 1 ); + + return charArray[ 0 ]; + + } + + function parseUInt32( data, offset ) { + + var intArray = new Uint32Array( data, offset, 1 ); + + return intArray[ 0 ]; + + } + + function init_vertices( start ) { + + var nElements = md.nvertices; + + var coordArray = new Float32Array( data, start, nElements * 3 ); + + var i, x, y, z; + + for( i = 0; i < nElements; i ++ ) { + + x = coordArray[ i * 3 ]; + y = coordArray[ i * 3 + 1 ]; + z = coordArray[ i * 3 + 2 ]; + + scope.vertices.push( new THREE.Vector3( x, y, z ) ); + + } + + return nElements * 3 * Float32Array.BYTES_PER_ELEMENT; + + } + + function init_normals( start ) { + + var nElements = md.nnormals; + + if ( nElements ) { + + var normalArray = new Int8Array( data, start, nElements * 3 ); + + var i, x, y, z; + + for( i = 0; i < nElements; i ++ ) { + + x = normalArray[ i * 3 ]; + y = normalArray[ i * 3 + 1 ]; + z = normalArray[ i * 3 + 2 ]; + + normals.push( x/127, y/127, z/127 ); + + } + + } + + return nElements * 3 * Int8Array.BYTES_PER_ELEMENT; + + } + + function init_uvs( start ) { + + var nElements = md.nuvs; + + if ( nElements ) { + + var uvArray = new Float32Array( data, start, nElements * 2 ); + + var i, u, v; + + for( i = 0; i < nElements; i ++ ) { + + u = uvArray[ i * 2 ]; + v = uvArray[ i * 2 + 1 ]; + + uvs.push( u, v ); + + } + + } + + return nElements * 2 * Float32Array.BYTES_PER_ELEMENT; + + } + + function init_uvs3( nElements, offset ) { + + var i, uva, uvb, uvc, u1, u2, u3, v1, v2, v3; + + var uvIndexBuffer = new Uint32Array( data, offset, 3 * nElements ); + + for( i = 0; i < nElements; i ++ ) { + + uva = uvIndexBuffer[ i * 3 ]; + uvb = uvIndexBuffer[ i * 3 + 1 ]; + uvc = uvIndexBuffer[ i * 3 + 2 ]; + + u1 = uvs[ uva*2 ]; + v1 = uvs[ uva*2 + 1 ]; + + u2 = uvs[ uvb*2 ]; + v2 = uvs[ uvb*2 + 1 ]; + + u3 = uvs[ uvc*2 ]; + v3 = uvs[ uvc*2 + 1 ]; + + scope.faceVertexUvs[ 0 ].push( [ + new THREE.Vector2( u1, v1 ), + new THREE.Vector2( u2, v2 ), + new THREE.Vector2( u3, v3 ) + ] ); + + } + + } + + function init_uvs4( nElements, offset ) { + + var i, uva, uvb, uvc, uvd, u1, u2, u3, u4, v1, v2, v3, v4; + + var uvIndexBuffer = new Uint32Array( data, offset, 4 * nElements ); + + for( i = 0; i < nElements; i ++ ) { + + uva = uvIndexBuffer[ i * 4 ]; + uvb = uvIndexBuffer[ i * 4 + 1 ]; + uvc = uvIndexBuffer[ i * 4 + 2 ]; + uvd = uvIndexBuffer[ i * 4 + 3 ]; + + u1 = uvs[ uva*2 ]; + v1 = uvs[ uva*2 + 1 ]; + + u2 = uvs[ uvb*2 ]; + v2 = uvs[ uvb*2 + 1 ]; + + u3 = uvs[ uvc*2 ]; + v3 = uvs[ uvc*2 + 1 ]; + + u4 = uvs[ uvd*2 ]; + v4 = uvs[ uvd*2 + 1 ]; + + scope.faceVertexUvs[ 0 ].push( [ + new THREE.Vector2( u1, v1 ), + new THREE.Vector2( u2, v2 ), + new THREE.Vector2( u4, v4 ) + ] ); + + scope.faceVertexUvs[ 0 ].push( [ + new THREE.Vector2( u2, v2 ), + new THREE.Vector2( u3, v3 ), + new THREE.Vector2( u4, v4 ) + ] ); + + } + + } + + function init_faces3_flat( nElements, offsetVertices, offsetMaterials ) { + + var i, a, b, c, m; + + var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 3 * nElements ); + var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); + + for( i = 0; i < nElements; i ++ ) { + + a = vertexIndexBuffer[ i * 3 ]; + b = vertexIndexBuffer[ i * 3 + 1 ]; + c = vertexIndexBuffer[ i * 3 + 2 ]; + + m = materialIndexBuffer[ i ]; + + scope.faces.push( new THREE.Face3( a, b, c, null, null, m ) ); + + } + + } + + function init_faces4_flat( nElements, offsetVertices, offsetMaterials ) { + + var i, a, b, c, d, m; + + var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 4 * nElements ); + var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); + + for( i = 0; i < nElements; i ++ ) { + + a = vertexIndexBuffer[ i * 4 ]; + b = vertexIndexBuffer[ i * 4 + 1 ]; + c = vertexIndexBuffer[ i * 4 + 2 ]; + d = vertexIndexBuffer[ i * 4 + 3 ]; + + m = materialIndexBuffer[ i ]; + + scope.faces.push( new THREE.Face3( a, b, d, null, null, m ) ); + scope.faces.push( new THREE.Face3( b, c, d, null, null, m ) ); + + } + + } + + function init_faces3_smooth( nElements, offsetVertices, offsetNormals, offsetMaterials ) { + + var i, a, b, c, m; + var na, nb, nc; + + var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 3 * nElements ); + var normalIndexBuffer = new Uint32Array( data, offsetNormals, 3 * nElements ); + var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); + + for( i = 0; i < nElements; i ++ ) { + + a = vertexIndexBuffer[ i * 3 ]; + b = vertexIndexBuffer[ i * 3 + 1 ]; + c = vertexIndexBuffer[ i * 3 + 2 ]; + + na = normalIndexBuffer[ i * 3 ]; + nb = normalIndexBuffer[ i * 3 + 1 ]; + nc = normalIndexBuffer[ i * 3 + 2 ]; + + m = materialIndexBuffer[ i ]; + + var nax = normals[ na*3 ], + nay = normals[ na*3 + 1 ], + naz = normals[ na*3 + 2 ], + + nbx = normals[ nb*3 ], + nby = normals[ nb*3 + 1 ], + nbz = normals[ nb*3 + 2 ], + + ncx = normals[ nc*3 ], + ncy = normals[ nc*3 + 1 ], + ncz = normals[ nc*3 + 2 ]; + + scope.faces.push( new THREE.Face3( a, b, c, [ + new THREE.Vector3( nax, nay, naz ), + new THREE.Vector3( nbx, nby, nbz ), + new THREE.Vector3( ncx, ncy, ncz ) + ], null, m ) ); + + } + + } + + function init_faces4_smooth( nElements, offsetVertices, offsetNormals, offsetMaterials ) { + + var i, a, b, c, d, m; + var na, nb, nc, nd; + + var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 4 * nElements ); + var normalIndexBuffer = new Uint32Array( data, offsetNormals, 4 * nElements ); + var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); + + for( i = 0; i < nElements; i ++ ) { + + a = vertexIndexBuffer[ i * 4 ]; + b = vertexIndexBuffer[ i * 4 + 1 ]; + c = vertexIndexBuffer[ i * 4 + 2 ]; + d = vertexIndexBuffer[ i * 4 + 3 ]; + + na = normalIndexBuffer[ i * 4 ]; + nb = normalIndexBuffer[ i * 4 + 1 ]; + nc = normalIndexBuffer[ i * 4 + 2 ]; + nd = normalIndexBuffer[ i * 4 + 3 ]; + + m = materialIndexBuffer[ i ]; + + var nax = normals[ na*3 ], + nay = normals[ na*3 + 1 ], + naz = normals[ na*3 + 2 ], + + nbx = normals[ nb*3 ], + nby = normals[ nb*3 + 1 ], + nbz = normals[ nb*3 + 2 ], + + ncx = normals[ nc*3 ], + ncy = normals[ nc*3 + 1 ], + ncz = normals[ nc*3 + 2 ], + + ndx = normals[ nd*3 ], + ndy = normals[ nd*3 + 1 ], + ndz = normals[ nd*3 + 2 ]; + + scope.faces.push( new THREE.Face3( a, b, d, [ + new THREE.Vector3( nax, nay, naz ), + new THREE.Vector3( nbx, nby, nbz ), + new THREE.Vector3( ndx, ndy, ndz ) + ], null, m ) ); + + scope.faces.push( new THREE.Face3( b, c, d, [ + new THREE.Vector3( nbx, nby, nbz ), + new THREE.Vector3( ncx, ncy, ncz ), + new THREE.Vector3( ndx, ndy, ndz ) + ], null, m ) ); + + } + + } + + function init_triangles_flat( start ) { + + var nElements = md.ntri_flat; + + if ( nElements ) { + + var offsetMaterials = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + init_faces3_flat( nElements, start, offsetMaterials ); + + } + + } + + function init_triangles_flat_uv( start ) { + + var nElements = md.ntri_flat_uv; + + if ( nElements ) { + + var offsetUvs = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + + init_faces3_flat( nElements, start, offsetMaterials ); + init_uvs3( nElements, offsetUvs ); + + } + + } + + function init_triangles_smooth( start ) { + + var nElements = md.ntri_smooth; + + if ( nElements ) { + + var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + var offsetMaterials = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + + init_faces3_smooth( nElements, start, offsetNormals, offsetMaterials ); + + } + + } + + function init_triangles_smooth_uv( start ) { + + var nElements = md.ntri_smooth_uv; + + if ( nElements ) { + + var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + var offsetUvs = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; + + init_faces3_smooth( nElements, start, offsetNormals, offsetMaterials ); + init_uvs3( nElements, offsetUvs ); + + } + + } + + function init_quads_flat( start ) { + + var nElements = md.nquad_flat; + + if ( nElements ) { + + var offsetMaterials = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + init_faces4_flat( nElements, start, offsetMaterials ); + + } + + } + + function init_quads_flat_uv( start ) { + + var nElements = md.nquad_flat_uv; + + if ( nElements ) { + + var offsetUvs = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + + init_faces4_flat( nElements, start, offsetMaterials ); + init_uvs4( nElements, offsetUvs ); + + } + + } + + function init_quads_smooth( start ) { + + var nElements = md.nquad_smooth; + + if ( nElements ) { + + var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + var offsetMaterials = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + + init_faces4_smooth( nElements, start, offsetNormals, offsetMaterials ); + + } + + } + + function init_quads_smooth_uv( start ) { + + var nElements = md.nquad_smooth_uv; + + if ( nElements ) { + + var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + var offsetUvs = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; + + init_faces4_smooth( nElements, start, offsetNormals, offsetMaterials ); + init_uvs4( nElements, offsetUvs ); + + } + + } + + }; + + Model.prototype = Object.create( THREE.Geometry.prototype ); + + var geometry = new Model( texturePath ); + var materials = this.initMaterials( jsonMaterials, texturePath ); + + if ( this.needsTangents( materials ) ) geometry.computeTangents(); + + callback( geometry, materials ); + +}; diff --git a/docdoku-web-front/app/js/dmu/loaders/ColladaLoader.js b/docdoku-web-front/app/js/dmu/loaders/ColladaLoader.js new file mode 100644 index 0000000000..b97362b0a2 --- /dev/null +++ b/docdoku-web-front/app/js/dmu/loaders/ColladaLoader.js @@ -0,0 +1,5001 @@ +/** + * @author Tim Knip / http://www.floorplanner.com/ / tim at floorplanner.com + * @author Tony Parisi / http://www.tonyparisi.com/ + */ + +THREE.ColladaLoader = function () { + + var COLLADA = null; + var scene = null; + var daeScene; + + var readyCallbackFunc = null; + + var sources = {}; + var images = {}; + var animations = {}; + var controllers = {}; + var geometries = {}; + var materials = {}; + var effects = {}; + var cameras = {}; + var lights = {}; + + var animData; + var visualScenes; + var baseUrl; + var morphs; + var skins; + + var flip_uv = true; + var preferredShading = THREE.SmoothShading; + + var options = { + // Force Geometry to always be centered at the local origin of the + // containing Mesh. + centerGeometry: false, + + // Axis conversion is done for geometries, animations, and controllers. + // If we ever pull cameras or lights out of the COLLADA file, they'll + // need extra work. + convertUpAxis: false, + + subdivideFaces: true, + + upAxis: 'Y', + + // For reflective or refractive materials we'll use this cubemap + defaultEnvMap: null + + }; + + var colladaUnit = 1.0; + var colladaUp = 'Y'; + var upConversion = null; + + function load ( url, readyCallback, progressCallback ) { + + var length = 0; + + if ( document.implementation && document.implementation.createDocument ) { + + var request = new XMLHttpRequest(); + + request.onreadystatechange = function() { + + if( request.readyState == 4 ) { + + if( request.status == 0 || request.status == 200 ) { + + + if ( request.responseXML ) { + + readyCallbackFunc = readyCallback; + parse( request.responseXML, undefined, url ); + + } else if ( request.responseText ) { + + readyCallbackFunc = readyCallback; + var xmlParser = new DOMParser(); + var responseXML = xmlParser.parseFromString( request.responseText, "application/xml" ); + parse( responseXML, undefined, url ); + + } else { + + console.error( "ColladaLoader: Empty or non-existing file (" + url + ")" ); + + } + + } + + } else if ( request.readyState == 3 ) { + + if ( progressCallback ) { + + if ( length == 0 ) { + + length = request.getResponseHeader( "Content-Length" ); + + } + + progressCallback( { total: length, loaded: request.responseText.length } ); + + } + + } + + } + + request.open( "GET", url, true ); + request.send( null ); + + } else { + + alert( "Don't know how to parse XML!" ); + + } + + } + + function parse( doc, callBack, url ) { + + COLLADA = doc; + callBack = callBack || readyCallbackFunc; + + if ( url !== undefined ) { + + var parts = url.split( '/' ); + parts.pop(); + baseUrl = ( parts.length < 1 ? '.' : parts.join( '/' ) ) + '/'; + + } + + parseAsset(); + setUpConversion(); + images = parseLib( "library_images image", _Image, "image" ); + materials = parseLib( "library_materials material", Material, "material" ); + effects = parseLib( "library_effects effect", Effect, "effect" ); + geometries = parseLib( "library_geometries geometry", Geometry, "geometry" ); + cameras = parseLib( "library_cameras camera", Camera, "camera" ); + lights = parseLib( "library_lights light", Light, "light" ); + controllers = parseLib( "library_controllers controller", Controller, "controller" ); + animations = parseLib( "library_animations animation", Animation, "animation" ); + visualScenes = parseLib( "library_visual_scenes visual_scene", VisualScene, "visual_scene" ); + + morphs = []; + skins = []; + + daeScene = parseScene(); + scene = new THREE.Object3D(); + + for ( var i = 0; i < daeScene.nodes.length; i ++ ) { + + scene.add( createSceneGraph( daeScene.nodes[ i ] ) ); + + } + + // unit conversion + scene.scale.multiplyScalar( colladaUnit ); + + createAnimations(); + + var result = { + + scene: scene, + morphs: morphs, + skins: skins, + animations: animData, + dae: { + images: images, + materials: materials, + cameras: cameras, + lights: lights, + effects: effects, + geometries: geometries, + controllers: controllers, + animations: animations, + visualScenes: visualScenes, + scene: daeScene + } + + }; + + if ( callBack ) { + + callBack( result ); + + } + + return result; + + } + + function setPreferredShading ( shading ) { + + preferredShading = shading; + + } + + function parseAsset () { + + var elements = COLLADA.querySelectorAll('asset'); + + var element = elements[0]; + + if ( element && element.childNodes ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'unit': + + var meter = child.getAttribute( 'meter' ); + + if ( meter ) { + + colladaUnit = parseFloat( meter ); + + } + + break; + + case 'up_axis': + + colladaUp = child.textContent.charAt(0); + break; + + } + + } + + } + + } + + function parseLib ( q, classSpec, prefix ) { + + var elements = COLLADA.querySelectorAll(q); + + var lib = {}; + + var i = 0; + + var elementsLength = elements.length; + + for ( var j = 0; j < elementsLength; j ++ ) { + + var element = elements[j]; + var daeElement = ( new classSpec() ).parse( element ); + + if ( !daeElement.id || daeElement.id.length == 0 ) daeElement.id = prefix + ( i ++ ); + lib[ daeElement.id ] = daeElement; + + } + + return lib; + + } + + function parseScene() { + + var sceneElement = COLLADA.querySelectorAll('scene instance_visual_scene')[0]; + + if ( sceneElement ) { + + var url = sceneElement.getAttribute( 'url' ).replace( /^#/, '' ); + return visualScenes[ url.length > 0 ? url : 'visual_scene0' ]; + + } else { + + return null; + + } + + } + + function createAnimations() { + + animData = []; + + // fill in the keys + recurseHierarchy( scene ); + + } + + function recurseHierarchy( node ) { + + var n = daeScene.getChildById( node.id, true ), + newData = null; + + if ( n && n.keys ) { + + newData = { + fps: 60, + hierarchy: [ { + node: n, + keys: n.keys, + sids: n.sids + } ], + node: node, + name: 'animation_' + node.name, + length: 0 + }; + + animData.push(newData); + + for ( var i = 0, il = n.keys.length; i < il; i++ ) { + + newData.length = Math.max( newData.length, n.keys[i].time ); + + } + + } else { + + newData = { + hierarchy: [ { + keys: [], + sids: [] + } ] + } + + } + + for ( var i = 0, il = node.children.length; i < il; i++ ) { + + var d = recurseHierarchy( node.children[i] ); + + for ( var j = 0, jl = d.hierarchy.length; j < jl; j ++ ) { + + newData.hierarchy.push( { + keys: [], + sids: [] + } ); + + } + + } + + return newData; + + } + + function calcAnimationBounds () { + + var start = 1000000; + var end = -start; + var frames = 0; + var ID; + for ( var id in animations ) { + + var animation = animations[ id ]; + ID = ID || animation.id; + for ( var i = 0; i < animation.sampler.length; i ++ ) { + + var sampler = animation.sampler[ i ]; + + sampler.create(); + + start = Math.min( start, sampler.startTime ); + end = Math.max( end, sampler.endTime ); + frames = Math.max( frames, sampler.input.length ); + + } + + } + + return { start:start, end:end, frames:frames,ID:ID }; + + } + + function createMorph ( geometry, ctrl ) { + + var morphCtrl = ctrl instanceof InstanceController ? controllers[ ctrl.url ] : ctrl; + + if ( !morphCtrl || !morphCtrl.morph ) { + + console.log("could not find morph controller!"); + return; + + } + + var morph = morphCtrl.morph; + + for ( var i = 0; i < morph.targets.length; i ++ ) { + + var target_id = morph.targets[ i ]; + var daeGeometry = geometries[ target_id ]; + + if ( !daeGeometry.mesh || + !daeGeometry.mesh.primitives || + !daeGeometry.mesh.primitives.length ) { + continue; + } + + var target = daeGeometry.mesh.primitives[ 0 ].geometry; + + if ( target.vertices.length === geometry.vertices.length ) { + + geometry.morphTargets.push( { name: "target_1", vertices: target.vertices } ); + + } + + } + + geometry.morphTargets.push( { name: "target_Z", vertices: geometry.vertices } ); + + }; + + function createSkin ( geometry, ctrl, applyBindShape ) { + + var skinCtrl = controllers[ ctrl.url ]; + + if ( !skinCtrl || !skinCtrl.skin ) { + + console.log( "could not find skin controller!" ); + return; + + } + + if ( !ctrl.skeleton || !ctrl.skeleton.length ) { + + console.log( "could not find the skeleton for the skin!" ); + return; + + } + + var skin = skinCtrl.skin; + var skeleton = daeScene.getChildById( ctrl.skeleton[ 0 ] ); + var hierarchy = []; + + applyBindShape = applyBindShape !== undefined ? applyBindShape : true; + + var bones = []; + geometry.skinWeights = []; + geometry.skinIndices = []; + + //createBones( geometry.bones, skin, hierarchy, skeleton, null, -1 ); + //createWeights( skin, geometry.bones, geometry.skinIndices, geometry.skinWeights ); + + /* + geometry.animation = { + name: 'take_001', + fps: 30, + length: 2, + JIT: true, + hierarchy: hierarchy + }; + */ + + if ( applyBindShape ) { + + for ( var i = 0; i < geometry.vertices.length; i ++ ) { + + geometry.vertices[ i ].applyMatrix4( skin.bindShapeMatrix ); + + } + + } + + } + + function setupSkeleton ( node, bones, frame, parent ) { + + node.world = node.world || new THREE.Matrix4(); + node.localworld = node.localworld || new THREE.Matrix4(); + node.world.copy( node.matrix ); + node.localworld.copy( node.matrix ); + + if ( node.channels && node.channels.length ) { + + var channel = node.channels[ 0 ]; + var m = channel.sampler.output[ frame ]; + + if ( m instanceof THREE.Matrix4 ) { + + node.world.copy( m ); + node.localworld.copy(m); + if(frame == 0) + node.matrix.copy(m); + } + + } + + if ( parent ) { + + node.world.multiplyMatrices( parent, node.world ); + + } + + bones.push( node ); + + for ( var i = 0; i < node.nodes.length; i ++ ) { + + setupSkeleton( node.nodes[ i ], bones, frame, node.world ); + + } + + } + + function setupSkinningMatrices ( bones, skin ) { + + // FIXME: this is dumb... + + for ( var i = 0; i < bones.length; i ++ ) { + + var bone = bones[ i ]; + var found = -1; + + if ( bone.type != 'JOINT' ) continue; + + for ( var j = 0; j < skin.joints.length; j ++ ) { + + if ( bone.sid == skin.joints[ j ] ) { + + found = j; + break; + + } + + } + + if ( found >= 0 ) { + + var inv = skin.invBindMatrices[ found ]; + + bone.invBindMatrix = inv; + bone.skinningMatrix = new THREE.Matrix4(); + bone.skinningMatrix.multiplyMatrices(bone.world, inv); // (IBMi * JMi) + bone.animatrix = new THREE.Matrix4(); + + bone.animatrix.copy(bone.localworld); + bone.weights = []; + + for ( var j = 0; j < skin.weights.length; j ++ ) { + + for (var k = 0; k < skin.weights[ j ].length; k ++ ) { + + var w = skin.weights[ j ][ k ]; + + if ( w.joint == found ) { + + bone.weights.push( w ); + + } + + } + + } + + } else { + + console.warn( "ColladaLoader: Could not find joint '" + bone.sid + "'." ); + + bone.skinningMatrix = new THREE.Matrix4(); + bone.weights = []; + + } + } + + } + + //Walk the Collada tree and flatten the bones into a list, extract the position, quat and scale from the matrix + function flattenSkeleton(skeleton) { + + var list = []; + var walk = function(parentid, node, list) { + + var bone = {}; + bone.name = node.sid; + bone.parent = parentid; + bone.matrix = node.matrix; + var data = [new THREE.Vector3(),new THREE.Quaternion(),new THREE.Vector3()]; + bone.matrix.decompose(data[0],data[1],data[2]); + + bone.pos = [data[0].x,data[0].y,data[0].z]; + + bone.scl = [data[2].x,data[2].y,data[2].z]; + bone.rotq = [data[1].x,data[1].y,data[1].z,data[1].w]; + list.push(bone); + + for(var i in node.nodes) { + + walk(node.sid,node.nodes[i],list); + + } + + }; + + walk(-1,skeleton,list); + return list; + + } + + //Move the vertices into the pose that is proper for the start of the animation + function skinToBindPose(geometry,skeleton,skinController) { + + var bones = []; + setupSkeleton( skeleton, bones, -1 ); + setupSkinningMatrices( bones, skinController.skin ); + v = new THREE.Vector3(); + var skinned = []; + + for(var i =0; i < geometry.vertices.length; i++) { + + skinned.push(new THREE.Vector3()); + + } + + for ( i = 0; i < bones.length; i ++ ) { + + if ( bones[ i ].type != 'JOINT' ) continue; + + for ( j = 0; j < bones[ i ].weights.length; j ++ ) { + + w = bones[ i ].weights[ j ]; + vidx = w.index; + weight = w.weight; + + o = geometry.vertices[vidx]; + s = skinned[vidx]; + + v.x = o.x; + v.y = o.y; + v.z = o.z; + + v.applyMatrix4( bones[i].skinningMatrix ); + + s.x += (v.x * weight); + s.y += (v.y * weight); + s.z += (v.z * weight); + } + + } + + for(var i =0; i < geometry.vertices.length; i++) { + + geometry.vertices[i] = skinned[i]; + + } + + } + + function applySkin ( geometry, instanceCtrl, frame ) { + + var skinController = controllers[ instanceCtrl.url ]; + + frame = frame !== undefined ? frame : 40; + + if ( !skinController || !skinController.skin ) { + + console.log( 'ColladaLoader: Could not find skin controller.' ); + return; + + } + + if ( !instanceCtrl.skeleton || !instanceCtrl.skeleton.length ) { + + console.log( 'ColladaLoader: Could not find the skeleton for the skin. ' ); + return; + + } + + var animationBounds = calcAnimationBounds(); + var skeleton = daeScene.getChildById( instanceCtrl.skeleton[0], true ) || + daeScene.getChildBySid( instanceCtrl.skeleton[0], true ); + + //flatten the skeleton into a list of bones + var bonelist = flattenSkeleton(skeleton); + var joints = skinController.skin.joints; + + //sort that list so that the order reflects the order in the joint list + var sortedbones = []; + for(var i = 0; i < joints.length; i++) { + + for(var j =0; j < bonelist.length; j++) { + + if(bonelist[j].name == joints[i]) { + + sortedbones[i] = bonelist[j]; + + } + + } + + } + + //hook up the parents by index instead of name + for(var i = 0; i < sortedbones.length; i++) { + + for(var j =0; j < sortedbones.length; j++) { + + if(sortedbones[i].parent == sortedbones[j].name) { + + sortedbones[i].parent = j; + + } + + } + + } + + + var i, j, w, vidx, weight; + var v = new THREE.Vector3(), o, s; + + // move vertices to bind shape + for ( i = 0; i < geometry.vertices.length; i ++ ) { + geometry.vertices[i].applyMatrix4( skinController.skin.bindShapeMatrix ); + } + + var skinIndices = []; + var skinWeights = []; + var weights = skinController.skin.weights; + + //hook up the skin weights + // TODO - this might be a good place to choose greatest 4 weights + for(var i =0; i < weights.length; i++) { + + var indicies = new THREE.Vector4(weights[i][0]?weights[i][0].joint:0,weights[i][1]?weights[i][1].joint:0,weights[i][2]?weights[i][2].joint:0,weights[i][3]?weights[i][3].joint:0); + var weight = new THREE.Vector4(weights[i][0]?weights[i][0].weight:0,weights[i][1]?weights[i][1].weight:0,weights[i][2]?weights[i][2].weight:0,weights[i][3]?weights[i][3].weight:0); + + skinIndices.push(indicies); + skinWeights.push(weight); + + } + + geometry.skinIndices = skinIndices; + geometry.skinWeights = skinWeights; + geometry.bones = sortedbones; + // process animation, or simply pose the rig if no animation + + //create an animation for the animated bones + //NOTE: this has no effect when using morphtargets + var animationdata = {"name":animationBounds.ID,"fps":30,"length":animationBounds.frames/30,"hierarchy":[]}; + + for(var j =0; j < sortedbones.length; j++) { + + animationdata.hierarchy.push({parent:sortedbones[j].parent, name:sortedbones[j].name, keys:[]}); + + } + + console.log( 'ColladaLoader:', animationBounds.ID + ' has ' + sortedbones.length + ' bones.' ); + + + + skinToBindPose(geometry,skeleton,skinController); + + + for ( frame = 0; frame < animationBounds.frames; frame ++ ) { + + var bones = []; + var skinned = []; + // process the frame and setup the rig with a fresh + // transform, possibly from the bone's animation channel(s) + + setupSkeleton( skeleton, bones, frame ); + setupSkinningMatrices( bones, skinController.skin ); + + for(var i = 0; i < bones.length; i ++) { + + for(var j = 0; j < animationdata.hierarchy.length; j ++) { + + if(animationdata.hierarchy[j].name == bones[i].sid) { + + var key = {}; + key.time = (frame/30); + key.matrix = bones[i].animatrix; + + if(frame == 0) + bones[i].matrix = key.matrix; + + var data = [new THREE.Vector3(),new THREE.Quaternion(),new THREE.Vector3()]; + key.matrix.decompose(data[0],data[1],data[2]); + + key.pos = [data[0].x,data[0].y,data[0].z]; + + key.scl = [data[2].x,data[2].y,data[2].z]; + key.rot = data[1]; + + animationdata.hierarchy[j].keys.push(key); + + } + + } + + } + + geometry.animation = animationdata; + + } + + }; + + function createSceneGraph ( node, parent ) { + + var obj = new THREE.Object3D(); + var skinned = false; + var skinController; + var morphController; + var i, j; + + // FIXME: controllers + + for ( i = 0; i < node.controllers.length; i ++ ) { + + var controller = controllers[ node.controllers[ i ].url ]; + + switch ( controller.type ) { + + case 'skin': + + if ( geometries[ controller.skin.source ] ) { + + var inst_geom = new InstanceGeometry(); + + inst_geom.url = controller.skin.source; + inst_geom.instance_material = node.controllers[ i ].instance_material; + + node.geometries.push( inst_geom ); + skinned = true; + skinController = node.controllers[ i ]; + + } else if ( controllers[ controller.skin.source ] ) { + + // urgh: controller can be chained + // handle the most basic case... + + var second = controllers[ controller.skin.source ]; + morphController = second; + // skinController = node.controllers[i]; + + if ( second.morph && geometries[ second.morph.source ] ) { + + var inst_geom = new InstanceGeometry(); + + inst_geom.url = second.morph.source; + inst_geom.instance_material = node.controllers[ i ].instance_material; + + node.geometries.push( inst_geom ); + + } + + } + + break; + + case 'morph': + + if ( geometries[ controller.morph.source ] ) { + + var inst_geom = new InstanceGeometry(); + + inst_geom.url = controller.morph.source; + inst_geom.instance_material = node.controllers[ i ].instance_material; + + node.geometries.push( inst_geom ); + morphController = node.controllers[ i ]; + + } + + console.log( 'ColladaLoader: Morph-controller partially supported.' ); + + default: + break; + + } + + } + + // geometries + + var double_sided_materials = {}; + + for ( i = 0; i < node.geometries.length; i ++ ) { + + var instance_geometry = node.geometries[i]; + var instance_materials = instance_geometry.instance_material; + var geometry = geometries[ instance_geometry.url ]; + var used_materials = {}; + var used_materials_array = []; + var num_materials = 0; + var first_material; + + if ( geometry ) { + + if ( !geometry.mesh || !geometry.mesh.primitives ) + continue; + + if ( obj.name.length == 0 ) { + + obj.name = geometry.id; + + } + + // collect used fx for this geometry-instance + + if ( instance_materials ) { + + for ( j = 0; j < instance_materials.length; j ++ ) { + + var instance_material = instance_materials[ j ]; + var mat = materials[ instance_material.target ]; + var effect_id = mat.instance_effect.url; + var shader = effects[ effect_id ].shader; + var material3js = shader.material; + + if ( geometry.doubleSided ) { + + if ( !( instance_material.symbol in double_sided_materials ) ) { + + var _copied_material = material3js.clone(); + _copied_material.side = THREE.DoubleSide; + double_sided_materials[ instance_material.symbol ] = _copied_material; + + } + + material3js = double_sided_materials[ instance_material.symbol ]; + + } + + material3js.opacity = !material3js.opacity ? 1 : material3js.opacity; + used_materials[ instance_material.symbol ] = num_materials; + used_materials_array.push( material3js ); + first_material = material3js; + first_material.name = mat.name == null || mat.name === '' ? mat.id : mat.name; + num_materials ++; + + } + + } + + var mesh; + var material = first_material || new THREE.MeshLambertMaterial( { color: 0xdddddd, shading: THREE.FlatShading, side: geometry.doubleSided ? THREE.DoubleSide : THREE.FrontSide } ); + var geom = geometry.mesh.geometry3js; + + if ( num_materials > 1 ) { + + material = new THREE.MeshFaceMaterial( used_materials_array ); + + for ( j = 0; j < geom.faces.length; j ++ ) { + + var face = geom.faces[ j ]; + face.materialIndex = used_materials[ face.daeMaterial ] + + } + + } + + if ( skinController !== undefined ) { + + + applySkin( geom, skinController ); + + if(geom.morphTargets.length > 0) { + + material.morphTargets = true; + material.skinning = false; + + } else { + + material.morphTargets = false; + material.skinning = true; + + } + + + mesh = new THREE.SkinnedMesh( geom, material, false ); + + + //mesh.skeleton = skinController.skeleton; + //mesh.skinController = controllers[ skinController.url ]; + //mesh.skinInstanceController = skinController; + mesh.name = 'skin_' + skins.length; + + + + //mesh.animationHandle.setKey(0); + skins.push( mesh ); + + } else if ( morphController !== undefined ) { + + createMorph( geom, morphController ); + + material.morphTargets = true; + + mesh = new THREE.Mesh( geom, material ); + mesh.name = 'morph_' + morphs.length; + + morphs.push( mesh ); + + } else { + + mesh = new THREE.Mesh( geom, material ); + // mesh.geom.name = geometry.id; + + } + + // N.B.: TP says this is not a great default behavior. It's a nice + // optimization to flatten the hierarchy but this should be done + // only if requested by the user via a flag. For now I undid it + // and fixed the character animation example that uses it + // node.geometries.length > 1 ? obj.add( mesh ) : obj = mesh; + obj.add(mesh); + + } + + } + + for ( i = 0; i < node.cameras.length; i ++ ) { + + var instance_camera = node.cameras[i]; + var cparams = cameras[instance_camera.url]; + + var cam = new THREE.PerspectiveCamera(cparams.yfov, parseFloat(cparams.aspect_ratio), + parseFloat(cparams.znear), parseFloat(cparams.zfar)); + + obj.add(cam); + } + + for ( i = 0; i < node.lights.length; i ++ ) { + + var light = null; + var instance_light = node.lights[i]; + var lparams = lights[instance_light.url]; + + if ( lparams && lparams.technique ) { + + var color = lparams.color.getHex(); + var intensity = lparams.intensity; + var distance = lparams.distance; + var angle = lparams.falloff_angle; + var exponent; // Intentionally undefined, don't know what this is yet + + switch ( lparams.technique ) { + + case 'directional': + + light = new THREE.DirectionalLight( color, intensity, distance ); + light.position.set(0, 0, 1); + break; + + case 'point': + + light = new THREE.PointLight( color, intensity, distance ); + break; + + case 'spot': + + light = new THREE.SpotLight( color, intensity, distance, angle, exponent ); + light.position.set(0, 0, 1); + break; + + case 'ambient': + + light = new THREE.AmbientLight( color ); + break; + + } + + } + + if (light) { + obj.add(light); + } + } + + obj.name = node.name || node.id || ""; + obj.id = node.id || ""; + obj.layer = node.layer || ""; + obj.matrix = node.matrix; + obj.matrix.decompose( obj.position, obj.quaternion, obj.scale ); + + if ( options.centerGeometry && obj.geometry ) { + + var delta = obj.geometry.center(); + delta.multiply( obj.scale ); + delta.applyQuaternion( obj.quaternion ); + + obj.position.sub( delta ); + + } + + for ( i = 0; i < node.nodes.length; i ++ ) { + + obj.add( createSceneGraph( node.nodes[i], node ) ); + + } + + return obj; + + }; + + function getJointId( skin, id ) { + + for ( var i = 0; i < skin.joints.length; i ++ ) { + + if ( skin.joints[ i ] == id ) { + + return i; + + } + + } + + }; + + function getLibraryNode( id ) { + + var nodes = COLLADA.querySelectorAll('library_nodes node'); + + for ( var i = 0; i < nodes.length; i++ ) { + + var attObj = nodes[i].attributes.getNamedItem('id'); + if ( attObj && attObj.value === id ) { + return nodes[i]; + } + } + + return undefined; + + }; + + function getChannelsForNode (node ) { + + var channels = []; + var startTime = 1000000; + var endTime = -1000000; + + for ( var id in animations ) { + + var animation = animations[id]; + + for ( var i = 0; i < animation.channel.length; i ++ ) { + + var channel = animation.channel[i]; + var sampler = animation.sampler[i]; + var id = channel.target.split('/')[0]; + + if ( id == node.id ) { + + sampler.create(); + channel.sampler = sampler; + startTime = Math.min(startTime, sampler.startTime); + endTime = Math.max(endTime, sampler.endTime); + channels.push(channel); + + } + + } + + } + + if ( channels.length ) { + + node.startTime = startTime; + node.endTime = endTime; + + } + + return channels; + + }; + + function calcFrameDuration( node ) { + + var minT = 10000000; + + for ( var i = 0; i < node.channels.length; i ++ ) { + + var sampler = node.channels[i].sampler; + + for ( var j = 0; j < sampler.input.length - 1; j ++ ) { + + var t0 = sampler.input[ j ]; + var t1 = sampler.input[ j + 1 ]; + minT = Math.min( minT, t1 - t0 ); + + } + } + + return minT; + + }; + + function calcMatrixAt( node, t ) { + + var animated = {}; + + var i, j; + + for ( i = 0; i < node.channels.length; i ++ ) { + + var channel = node.channels[ i ]; + animated[ channel.sid ] = channel; + + } + + var matrix = new THREE.Matrix4(); + + for ( i = 0; i < node.transforms.length; i ++ ) { + + var transform = node.transforms[ i ]; + var channel = animated[ transform.sid ]; + + if ( channel !== undefined ) { + + var sampler = channel.sampler; + var value; + + for ( j = 0; j < sampler.input.length - 1; j ++ ) { + + if ( sampler.input[ j + 1 ] > t ) { + + value = sampler.output[ j ]; + //console.log(value.flatten) + break; + + } + + } + + if ( value !== undefined ) { + + if ( value instanceof THREE.Matrix4 ) { + + matrix.multiplyMatrices( matrix, value ); + + } else { + + // FIXME: handle other types + + matrix.multiplyMatrices( matrix, transform.matrix ); + + } + + } else { + + matrix.multiplyMatrices( matrix, transform.matrix ); + + } + + } else { + + matrix.multiplyMatrices( matrix, transform.matrix ); + + } + + } + + return matrix; + + }; + + function bakeAnimations ( node ) { + + if ( node.channels && node.channels.length ) { + + var keys = [], + sids = []; + + for ( var i = 0, il = node.channels.length; i < il; i++ ) { + + var channel = node.channels[i], + fullSid = channel.fullSid, + sampler = channel.sampler, + input = sampler.input, + transform = node.getTransformBySid( channel.sid ), + member; + + if ( channel.arrIndices ) { + + member = []; + + for ( var j = 0, jl = channel.arrIndices.length; j < jl; j++ ) { + + member[ j ] = getConvertedIndex( channel.arrIndices[ j ] ); + + } + + } else { + + member = getConvertedMember( channel.member ); + + } + + if ( transform ) { + + if ( sids.indexOf( fullSid ) === -1 ) { + + sids.push( fullSid ); + + } + + for ( var j = 0, jl = input.length; j < jl; j++ ) { + + var time = input[j], + data = sampler.getData( transform.type, j, member ), + key = findKey( keys, time ); + + if ( !key ) { + + key = new Key( time ); + var timeNdx = findTimeNdx( keys, time ); + keys.splice( timeNdx == -1 ? keys.length : timeNdx, 0, key ); + + } + + key.addTarget( fullSid, transform, member, data ); + + } + + } else { + + console.log( 'Could not find transform "' + channel.sid + '" in node ' + node.id ); + + } + + } + + // post process + for ( var i = 0; i < sids.length; i++ ) { + + var sid = sids[ i ]; + + for ( var j = 0; j < keys.length; j++ ) { + + var key = keys[ j ]; + + if ( !key.hasTarget( sid ) ) { + + interpolateKeys( keys, key, j, sid ); + + } + + } + + } + + node.keys = keys; + node.sids = sids; + + } + + }; + + function findKey ( keys, time) { + + var retVal = null; + + for ( var i = 0, il = keys.length; i < il && retVal == null; i++ ) { + + var key = keys[i]; + + if ( key.time === time ) { + + retVal = key; + + } else if ( key.time > time ) { + + break; + + } + + } + + return retVal; + + }; + + function findTimeNdx ( keys, time) { + + var ndx = -1; + + for ( var i = 0, il = keys.length; i < il && ndx == -1; i++ ) { + + var key = keys[i]; + + if ( key.time >= time ) { + + ndx = i; + + } + + } + + return ndx; + + }; + + function interpolateKeys ( keys, key, ndx, fullSid ) { + + var prevKey = getPrevKeyWith( keys, fullSid, ndx ? ndx-1 : 0 ), + nextKey = getNextKeyWith( keys, fullSid, ndx+1 ); + + if ( prevKey && nextKey ) { + + var scale = (key.time - prevKey.time) / (nextKey.time - prevKey.time), + prevTarget = prevKey.getTarget( fullSid ), + nextData = nextKey.getTarget( fullSid ).data, + prevData = prevTarget.data, + data; + + if ( prevTarget.type === 'matrix' ) { + + data = prevData; + + } else if ( prevData.length ) { + + data = []; + + for ( var i = 0; i < prevData.length; ++i ) { + + data[ i ] = prevData[ i ] + ( nextData[ i ] - prevData[ i ] ) * scale; + + } + + } else { + + data = prevData + ( nextData - prevData ) * scale; + + } + + key.addTarget( fullSid, prevTarget.transform, prevTarget.member, data ); + + } + + }; + + // Get next key with given sid + + function getNextKeyWith( keys, fullSid, ndx ) { + + for ( ; ndx < keys.length; ndx++ ) { + + var key = keys[ ndx ]; + + if ( key.hasTarget( fullSid ) ) { + + return key; + + } + + } + + return null; + + }; + + // Get previous key with given sid + + function getPrevKeyWith( keys, fullSid, ndx ) { + + ndx = ndx >= 0 ? ndx : ndx + keys.length; + + for ( ; ndx >= 0; ndx-- ) { + + var key = keys[ ndx ]; + + if ( key.hasTarget( fullSid ) ) { + + return key; + + } + + } + + return null; + + }; + + function _Image() { + + this.id = ""; + this.init_from = ""; + + }; + + _Image.prototype.parse = function(element) { + + this.id = element.getAttribute('id'); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + if ( child.nodeName == 'init_from' ) { + + this.init_from = child.textContent; + + } + + } + + return this; + + }; + + function Controller() { + + this.id = ""; + this.name = ""; + this.type = ""; + this.skin = null; + this.morph = null; + + }; + + Controller.prototype.parse = function( element ) { + + this.id = element.getAttribute('id'); + this.name = element.getAttribute('name'); + this.type = "none"; + + for ( var i = 0; i < element.childNodes.length; i++ ) { + + var child = element.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'skin': + + this.skin = (new Skin()).parse(child); + this.type = child.nodeName; + break; + + case 'morph': + + this.morph = (new Morph()).parse(child); + this.type = child.nodeName; + break; + + default: + break; + + } + } + + return this; + + }; + + function Morph() { + + this.method = null; + this.source = null; + this.targets = null; + this.weights = null; + + }; + + Morph.prototype.parse = function( element ) { + + var sources = {}; + var inputs = []; + var i; + + this.method = element.getAttribute( 'method' ); + this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); + + for ( i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'source': + + var source = ( new Source() ).parse( child ); + sources[ source.id ] = source; + break; + + case 'targets': + + inputs = this.parseInputs( child ); + break; + + default: + + console.log( child.nodeName ); + break; + + } + + } + + for ( i = 0; i < inputs.length; i ++ ) { + + var input = inputs[ i ]; + var source = sources[ input.source ]; + + switch ( input.semantic ) { + + case 'MORPH_TARGET': + + this.targets = source.read(); + break; + + case 'MORPH_WEIGHT': + + this.weights = source.read(); + break; + + default: + break; + + } + } + + return this; + + }; + + Morph.prototype.parseInputs = function(element) { + + var inputs = []; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + if ( child.nodeType != 1) continue; + + switch ( child.nodeName ) { + + case 'input': + + inputs.push( (new Input()).parse(child) ); + break; + + default: + break; + } + } + + return inputs; + + }; + + function Skin() { + + this.source = ""; + this.bindShapeMatrix = null; + this.invBindMatrices = []; + this.joints = []; + this.weights = []; + + }; + + Skin.prototype.parse = function( element ) { + + var sources = {}; + var joints, weights; + + this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); + this.invBindMatrices = []; + this.joints = []; + this.weights = []; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'bind_shape_matrix': + + var f = _floats(child.textContent); + this.bindShapeMatrix = getConvertedMat4( f ); + break; + + case 'source': + + var src = new Source().parse(child); + sources[ src.id ] = src; + break; + + case 'joints': + + joints = child; + break; + + case 'vertex_weights': + + weights = child; + break; + + default: + + console.log( child.nodeName ); + break; + + } + } + + this.parseJoints( joints, sources ); + this.parseWeights( weights, sources ); + + return this; + + }; + + Skin.prototype.parseJoints = function ( element, sources ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + + var input = ( new Input() ).parse( child ); + var source = sources[ input.source ]; + + if ( input.semantic == 'JOINT' ) { + + this.joints = source.read(); + + } else if ( input.semantic == 'INV_BIND_MATRIX' ) { + + this.invBindMatrices = source.read(); + + } + + break; + + default: + break; + } + + } + + }; + + Skin.prototype.parseWeights = function ( element, sources ) { + + var v, vcount, inputs = []; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + + inputs.push( ( new Input() ).parse( child ) ); + break; + + case 'v': + + v = _ints( child.textContent ); + break; + + case 'vcount': + + vcount = _ints( child.textContent ); + break; + + default: + break; + + } + + } + + var index = 0; + + for ( var i = 0; i < vcount.length; i ++ ) { + + var numBones = vcount[i]; + var vertex_weights = []; + + for ( var j = 0; j < numBones; j++ ) { + + var influence = {}; + + for ( var k = 0; k < inputs.length; k ++ ) { + + var input = inputs[ k ]; + var value = v[ index + input.offset ]; + + switch ( input.semantic ) { + + case 'JOINT': + + influence.joint = value;//this.joints[value]; + break; + + case 'WEIGHT': + + influence.weight = sources[ input.source ].data[ value ]; + break; + + default: + break; + + } + + } + + vertex_weights.push( influence ); + index += inputs.length; + } + + for ( var j = 0; j < vertex_weights.length; j ++ ) { + + vertex_weights[ j ].index = i; + + } + + this.weights.push( vertex_weights ); + + } + + }; + + function VisualScene () { + + this.id = ""; + this.name = ""; + this.nodes = []; + this.scene = new THREE.Object3D(); + + }; + + VisualScene.prototype.getChildById = function( id, recursive ) { + + for ( var i = 0; i < this.nodes.length; i ++ ) { + + var node = this.nodes[ i ].getChildById( id, recursive ); + + if ( node ) { + + return node; + + } + + } + + return null; + + }; + + VisualScene.prototype.getChildBySid = function( sid, recursive ) { + + for ( var i = 0; i < this.nodes.length; i ++ ) { + + var node = this.nodes[ i ].getChildBySid( sid, recursive ); + + if ( node ) { + + return node; + + } + + } + + return null; + + }; + + VisualScene.prototype.parse = function( element ) { + + this.id = element.getAttribute( 'id' ); + this.name = element.getAttribute( 'name' ); + this.nodes = []; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'node': + + this.nodes.push( ( new Node() ).parse( child ) ); + break; + + default: + break; + + } + + } + + return this; + + }; + + function Node() { + + this.id = ""; + this.name = ""; + this.sid = ""; + this.nodes = []; + this.controllers = []; + this.transforms = []; + this.geometries = []; + this.channels = []; + this.matrix = new THREE.Matrix4(); + + }; + + Node.prototype.getChannelForTransform = function( transformSid ) { + + for ( var i = 0; i < this.channels.length; i ++ ) { + + var channel = this.channels[i]; + var parts = channel.target.split('/'); + var id = parts.shift(); + var sid = parts.shift(); + var dotSyntax = (sid.indexOf(".") >= 0); + var arrSyntax = (sid.indexOf("(") >= 0); + var arrIndices; + var member; + + if ( dotSyntax ) { + + parts = sid.split("."); + sid = parts.shift(); + member = parts.shift(); + + } else if ( arrSyntax ) { + + arrIndices = sid.split("("); + sid = arrIndices.shift(); + + for ( var j = 0; j < arrIndices.length; j ++ ) { + + arrIndices[ j ] = parseInt( arrIndices[ j ].replace( /\)/, '' ) ); + + } + + } + + if ( sid == transformSid ) { + + channel.info = { sid: sid, dotSyntax: dotSyntax, arrSyntax: arrSyntax, arrIndices: arrIndices }; + return channel; + + } + + } + + return null; + + }; + + Node.prototype.getChildById = function ( id, recursive ) { + + if ( this.id == id ) { + + return this; + + } + + if ( recursive ) { + + for ( var i = 0; i < this.nodes.length; i ++ ) { + + var n = this.nodes[ i ].getChildById( id, recursive ); + + if ( n ) { + + return n; + + } + + } + + } + + return null; + + }; + + Node.prototype.getChildBySid = function ( sid, recursive ) { + + if ( this.sid == sid ) { + + return this; + + } + + if ( recursive ) { + + for ( var i = 0; i < this.nodes.length; i ++ ) { + + var n = this.nodes[ i ].getChildBySid( sid, recursive ); + + if ( n ) { + + return n; + + } + + } + } + + return null; + + }; + + Node.prototype.getTransformBySid = function ( sid ) { + + for ( var i = 0; i < this.transforms.length; i ++ ) { + + if ( this.transforms[ i ].sid == sid ) return this.transforms[ i ]; + + } + + return null; + + }; + + Node.prototype.parse = function( element ) { + + var url; + + this.id = element.getAttribute('id'); + this.sid = element.getAttribute('sid'); + this.name = element.getAttribute('name'); + this.type = element.getAttribute('type'); + this.layer = element.getAttribute('layer'); + + this.type = this.type == 'JOINT' ? this.type : 'NODE'; + + this.nodes = []; + this.transforms = []; + this.geometries = []; + this.cameras = []; + this.lights = []; + this.controllers = []; + this.matrix = new THREE.Matrix4(); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'node': + + this.nodes.push( ( new Node() ).parse( child ) ); + break; + + case 'instance_camera': + + this.cameras.push( ( new InstanceCamera() ).parse( child ) ); + break; + + case 'instance_controller': + + this.controllers.push( ( new InstanceController() ).parse( child ) ); + break; + + case 'instance_geometry': + + this.geometries.push( ( new InstanceGeometry() ).parse( child ) ); + break; + + case 'instance_light': + + this.lights.push( ( new InstanceLight() ).parse( child ) ); + break; + + case 'instance_node': + + url = child.getAttribute( 'url' ).replace( /^#/, '' ); + var iNode = getLibraryNode( url ); + + if ( iNode ) { + + this.nodes.push( ( new Node() ).parse( iNode )) ; + + } + + break; + + case 'rotate': + case 'translate': + case 'scale': + case 'matrix': + case 'lookat': + case 'skew': + + this.transforms.push( ( new Transform() ).parse( child ) ); + break; + + case 'extra': + break; + + default: + + console.log( child.nodeName ); + break; + + } + + } + + this.channels = getChannelsForNode( this ); + bakeAnimations( this ); + + this.updateMatrix(); + + return this; + + }; + + Node.prototype.updateMatrix = function () { + + this.matrix.identity(); + + for ( var i = 0; i < this.transforms.length; i ++ ) { + + this.transforms[ i ].apply( this.matrix ); + + } + + }; + + function Transform () { + + this.sid = ""; + this.type = ""; + this.data = []; + this.obj = null; + + }; + + Transform.prototype.parse = function ( element ) { + + this.sid = element.getAttribute( 'sid' ); + this.type = element.nodeName; + this.data = _floats( element.textContent ); + this.convert(); + + return this; + + }; + + Transform.prototype.convert = function () { + + switch ( this.type ) { + + case 'matrix': + + this.obj = getConvertedMat4( this.data ); + break; + + case 'rotate': + + this.angle = THREE.Math.degToRad( this.data[3] ); + + case 'translate': + + fixCoords( this.data, -1 ); + this.obj = new THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] ); + break; + + case 'scale': + + fixCoords( this.data, 1 ); + this.obj = new THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] ); + break; + + default: + console.log( 'Can not convert Transform of type ' + this.type ); + break; + + } + + }; + + Transform.prototype.apply = function () { + + var m1 = new THREE.Matrix4(); + + return function ( matrix ) { + + switch ( this.type ) { + + case 'matrix': + + matrix.multiply( this.obj ); + + break; + + case 'translate': + + matrix.multiply( m1.makeTranslation( this.obj.x, this.obj.y, this.obj.z ) ); + + break; + + case 'rotate': + + matrix.multiply( m1.makeRotationAxis( this.obj, this.angle ) ); + + break; + + case 'scale': + + matrix.scale( this.obj ); + + break; + + } + + }; + + }(); + + Transform.prototype.update = function ( data, member ) { + + var members = [ 'X', 'Y', 'Z', 'ANGLE' ]; + + switch ( this.type ) { + + case 'matrix': + + if ( ! member ) { + + this.obj.copy( data ); + + } else if ( member.length === 1 ) { + + switch ( member[ 0 ] ) { + + case 0: + + this.obj.n11 = data[ 0 ]; + this.obj.n21 = data[ 1 ]; + this.obj.n31 = data[ 2 ]; + this.obj.n41 = data[ 3 ]; + + break; + + case 1: + + this.obj.n12 = data[ 0 ]; + this.obj.n22 = data[ 1 ]; + this.obj.n32 = data[ 2 ]; + this.obj.n42 = data[ 3 ]; + + break; + + case 2: + + this.obj.n13 = data[ 0 ]; + this.obj.n23 = data[ 1 ]; + this.obj.n33 = data[ 2 ]; + this.obj.n43 = data[ 3 ]; + + break; + + case 3: + + this.obj.n14 = data[ 0 ]; + this.obj.n24 = data[ 1 ]; + this.obj.n34 = data[ 2 ]; + this.obj.n44 = data[ 3 ]; + + break; + + } + + } else if ( member.length === 2 ) { + + var propName = 'n' + ( member[ 0 ] + 1 ) + ( member[ 1 ] + 1 ); + this.obj[ propName ] = data; + + } else { + + console.log('Incorrect addressing of matrix in transform.'); + + } + + break; + + case 'translate': + case 'scale': + + if ( Object.prototype.toString.call( member ) === '[object Array]' ) { + + member = members[ member[ 0 ] ]; + + } + + switch ( member ) { + + case 'X': + + this.obj.x = data; + break; + + case 'Y': + + this.obj.y = data; + break; + + case 'Z': + + this.obj.z = data; + break; + + default: + + this.obj.x = data[ 0 ]; + this.obj.y = data[ 1 ]; + this.obj.z = data[ 2 ]; + break; + + } + + break; + + case 'rotate': + + if ( Object.prototype.toString.call( member ) === '[object Array]' ) { + + member = members[ member[ 0 ] ]; + + } + + switch ( member ) { + + case 'X': + + this.obj.x = data; + break; + + case 'Y': + + this.obj.y = data; + break; + + case 'Z': + + this.obj.z = data; + break; + + case 'ANGLE': + + this.angle = THREE.Math.degToRad( data ); + break; + + default: + + this.obj.x = data[ 0 ]; + this.obj.y = data[ 1 ]; + this.obj.z = data[ 2 ]; + this.angle = THREE.Math.degToRad( data[ 3 ] ); + break; + + } + break; + + } + + }; + + function InstanceController() { + + this.url = ""; + this.skeleton = []; + this.instance_material = []; + + }; + + InstanceController.prototype.parse = function ( element ) { + + this.url = element.getAttribute('url').replace(/^#/, ''); + this.skeleton = []; + this.instance_material = []; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'skeleton': + + this.skeleton.push( child.textContent.replace(/^#/, '') ); + break; + + case 'bind_material': + + var instances = child.querySelectorAll('instance_material'); + + for ( var j = 0; j < instances.length; j ++ ){ + + var instance = instances[j]; + this.instance_material.push( (new InstanceMaterial()).parse(instance) ); + + } + + + break; + + case 'extra': + break; + + default: + break; + + } + } + + return this; + + }; + + function InstanceMaterial () { + + this.symbol = ""; + this.target = ""; + + }; + + InstanceMaterial.prototype.parse = function ( element ) { + + this.symbol = element.getAttribute('symbol'); + this.target = element.getAttribute('target').replace(/^#/, ''); + return this; + + }; + + function InstanceGeometry() { + + this.url = ""; + this.instance_material = []; + + }; + + InstanceGeometry.prototype.parse = function ( element ) { + + this.url = element.getAttribute('url').replace(/^#/, ''); + this.instance_material = []; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + if ( child.nodeType != 1 ) continue; + + if ( child.nodeName == 'bind_material' ) { + + var instances = child.querySelectorAll('instance_material'); + + for ( var j = 0; j < instances.length; j ++ ) { + + var instance = instances[j]; + this.instance_material.push( (new InstanceMaterial()).parse(instance) ); + + } + + break; + + } + + } + + return this; + + }; + + function Geometry() { + + this.id = ""; + this.mesh = null; + + }; + + Geometry.prototype.parse = function ( element ) { + + this.id = element.getAttribute('id'); + + extractDoubleSided( this, element ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + + switch ( child.nodeName ) { + + case 'mesh': + + this.mesh = (new Mesh(this)).parse(child); + break; + + case 'extra': + + // console.log( child ); + break; + + default: + break; + } + } + + return this; + + }; + + function Mesh( geometry ) { + + this.geometry = geometry.id; + this.primitives = []; + this.vertices = null; + this.geometry3js = null; + + }; + + Mesh.prototype.parse = function( element ) { + + this.primitives = []; + + var i, j; + + for ( i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'source': + + _source( child ); + break; + + case 'vertices': + + this.vertices = ( new Vertices() ).parse( child ); + break; + + case 'triangles': + + this.primitives.push( ( new Triangles().parse( child ) ) ); + break; + + case 'polygons': + + this.primitives.push( ( new Polygons().parse( child ) ) ); + break; + + case 'polylist': + + this.primitives.push( ( new Polylist().parse( child ) ) ); + break; + + default: + break; + + } + + } + + this.geometry3js = new THREE.Geometry(); + + var vertexData = sources[ this.vertices.input['POSITION'].source ].data; + + for ( i = 0; i < vertexData.length; i += 3 ) { + + this.geometry3js.vertices.push( getConvertedVec3( vertexData, i ).clone() ); + + } + + for ( i = 0; i < this.primitives.length; i ++ ) { + + var primitive = this.primitives[ i ]; + primitive.setVertices( this.vertices ); + this.handlePrimitive( primitive, this.geometry3js ); + + } + + this.geometry3js.computeFaceNormals(); + + if ( this.geometry3js.calcNormals ) { + + this.geometry3js.computeVertexNormals(); + delete this.geometry3js.calcNormals; + + } + + // this.geometry3js.computeBoundingBox(); + + return this; + + }; + + Mesh.prototype.handlePrimitive = function( primitive, geom ) { + + var j, k, pList = primitive.p, inputs = primitive.inputs; + var input, index, idx32; + var source, numParams; + var vcIndex = 0, vcount = 3, maxOffset = 0; + var texture_sets = []; + + for ( j = 0; j < inputs.length; j ++ ) { + + input = inputs[ j ]; + + var offset = input.offset + 1; + maxOffset = (maxOffset < offset)? offset : maxOffset; + + switch ( input.semantic ) { + + case 'TEXCOORD': + texture_sets.push( input.set ); + break; + + } + + } + + for ( var pCount = 0; pCount < pList.length; ++pCount ) { + + var p = pList[ pCount ], i = 0; + + while ( i < p.length ) { + + var vs = []; + var ns = []; + var ts = null; + var cs = []; + + if ( primitive.vcount ) { + + vcount = primitive.vcount.length ? primitive.vcount[ vcIndex ++ ] : primitive.vcount; + + } else { + + vcount = p.length / maxOffset; + + } + + + for ( j = 0; j < vcount; j ++ ) { + + for ( k = 0; k < inputs.length; k ++ ) { + + input = inputs[ k ]; + source = sources[ input.source ]; + + index = p[ i + ( j * maxOffset ) + input.offset ]; + numParams = source.accessor.params.length; + idx32 = index * numParams; + + switch ( input.semantic ) { + + case 'VERTEX': + + vs.push( index ); + + break; + + case 'NORMAL': + + ns.push( getConvertedVec3( source.data, idx32 ) ); + + break; + + case 'TEXCOORD': + + ts = ts || { }; + if ( ts[ input.set ] === undefined ) ts[ input.set ] = []; + // invert the V + ts[ input.set ].push( new THREE.Vector2( source.data[ idx32 ], source.data[ idx32 + 1 ] ) ); + + break; + + case 'COLOR': + + cs.push( new THREE.Color().setRGB( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) ); + + break; + + default: + + break; + + } + + } + + } + + if ( ns.length == 0 ) { + + // check the vertices inputs + input = this.vertices.input.NORMAL; + + if ( input ) { + + source = sources[ input.source ]; + numParams = source.accessor.params.length; + + for ( var ndx = 0, len = vs.length; ndx < len; ndx++ ) { + + ns.push( getConvertedVec3( source.data, vs[ ndx ] * numParams ) ); + + } + + } else { + + geom.calcNormals = true; + + } + + } + + if ( !ts ) { + + ts = { }; + // check the vertices inputs + input = this.vertices.input.TEXCOORD; + + if ( input ) { + + texture_sets.push( input.set ); + source = sources[ input.source ]; + numParams = source.accessor.params.length; + + for ( var ndx = 0, len = vs.length; ndx < len; ndx++ ) { + + idx32 = vs[ ndx ] * numParams; + if ( ts[ input.set ] === undefined ) ts[ input.set ] = [ ]; + // invert the V + ts[ input.set ].push( new THREE.Vector2( source.data[ idx32 ], 1.0 - source.data[ idx32 + 1 ] ) ); + + } + + } + + } + + if ( cs.length == 0 ) { + + // check the vertices inputs + input = this.vertices.input.COLOR; + + if ( input ) { + + source = sources[ input.source ]; + numParams = source.accessor.params.length; + + for ( var ndx = 0, len = vs.length; ndx < len; ndx++ ) { + + idx32 = vs[ ndx ] * numParams; + cs.push( new THREE.Color().setRGB( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) ); + + } + + } + + } + + var face = null, faces = [], uv, uvArr; + + if ( vcount === 3 ) { + + faces.push( new THREE.Face3( vs[0], vs[1], vs[2], ns, cs.length ? cs : new THREE.Color() ) ); + + } else if ( vcount === 4 ) { + + faces.push( new THREE.Face3( vs[0], vs[1], vs[3], [ns[0], ns[1], ns[3]], cs.length ? [cs[0], cs[1], cs[3]] : new THREE.Color() ) ); + + faces.push( new THREE.Face3( vs[1], vs[2], vs[3], [ns[1], ns[2], ns[3]], cs.length ? [cs[1], cs[2], cs[3]] : new THREE.Color() ) ); + + } else if ( vcount > 4 && options.subdivideFaces ) { + + var clr = cs.length ? cs : new THREE.Color(), + vec1, vec2, vec3, v1, v2, norm; + + // subdivide into multiple Face3s + + for ( k = 1; k < vcount - 1; ) { + + // FIXME: normals don't seem to be quite right + + faces.push( new THREE.Face3( vs[0], vs[k], vs[k+1], [ ns[0], ns[k++], ns[k] ], clr ) ); + + } + + } + + if ( faces.length ) { + + for ( var ndx = 0, len = faces.length; ndx < len; ndx ++ ) { + + face = faces[ndx]; + face.daeMaterial = primitive.material; + geom.faces.push( face ); + + for ( k = 0; k < texture_sets.length; k++ ) { + + uv = ts[ texture_sets[k] ]; + + if ( vcount > 4 ) { + + // Grab the right UVs for the vertices in this face + uvArr = [ uv[0], uv[ndx+1], uv[ndx+2] ]; + + } else if ( vcount === 4 ) { + + if ( ndx === 0 ) { + + uvArr = [ uv[0], uv[1], uv[3] ]; + + } else { + + uvArr = [ uv[1].clone(), uv[2], uv[3].clone() ]; + + } + + } else { + + uvArr = [ uv[0], uv[1], uv[2] ]; + + } + + if ( geom.faceVertexUvs[k] === undefined ) { + + geom.faceVertexUvs[k] = []; + + } + + geom.faceVertexUvs[k].push( uvArr ); + + } + + } + + } else { + + console.log( 'dropped face with vcount ' + vcount + ' for geometry with id: ' + geom.id ); + + } + + i += maxOffset * vcount; + + } + } + + }; + + function Polygons () { + + this.material = ""; + this.count = 0; + this.inputs = []; + this.vcount = null; + this.p = []; + this.geometry = new THREE.Geometry(); + + }; + + Polygons.prototype.setVertices = function ( vertices ) { + + for ( var i = 0; i < this.inputs.length; i ++ ) { + + if ( this.inputs[ i ].source == vertices.id ) { + + this.inputs[ i ].source = vertices.input[ 'POSITION' ].source; + + } + + } + + }; + + Polygons.prototype.parse = function ( element ) { + + this.material = element.getAttribute( 'material' ); + this.count = _attr_as_int( element, 'count', 0 ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'input': + + this.inputs.push( ( new Input() ).parse( element.childNodes[ i ] ) ); + break; + + case 'vcount': + + this.vcount = _ints( child.textContent ); + break; + + case 'p': + + this.p.push( _ints( child.textContent ) ); + break; + + case 'ph': + + console.warn( 'polygon holes not yet supported!' ); + break; + + default: + break; + + } + + } + + return this; + + }; + + function Polylist () { + + Polygons.call( this ); + + this.vcount = []; + + }; + + Polylist.prototype = Object.create( Polygons.prototype ); + + function Triangles () { + + Polygons.call( this ); + + this.vcount = 3; + + }; + + Triangles.prototype = Object.create( Polygons.prototype ); + + function Accessor() { + + this.source = ""; + this.count = 0; + this.stride = 0; + this.params = []; + + }; + + Accessor.prototype.parse = function ( element ) { + + this.params = []; + this.source = element.getAttribute( 'source' ); + this.count = _attr_as_int( element, 'count', 0 ); + this.stride = _attr_as_int( element, 'stride', 0 ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + if ( child.nodeName == 'param' ) { + + var param = {}; + param[ 'name' ] = child.getAttribute( 'name' ); + param[ 'type' ] = child.getAttribute( 'type' ); + this.params.push( param ); + + } + + } + + return this; + + }; + + function Vertices() { + + this.input = {}; + + }; + + Vertices.prototype.parse = function ( element ) { + + this.id = element.getAttribute('id'); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + if ( element.childNodes[i].nodeName == 'input' ) { + + var input = ( new Input() ).parse( element.childNodes[ i ] ); + this.input[ input.semantic ] = input; + + } + + } + + return this; + + }; + + function Input () { + + this.semantic = ""; + this.offset = 0; + this.source = ""; + this.set = 0; + + }; + + Input.prototype.parse = function ( element ) { + + this.semantic = element.getAttribute('semantic'); + this.source = element.getAttribute('source').replace(/^#/, ''); + this.set = _attr_as_int(element, 'set', -1); + this.offset = _attr_as_int(element, 'offset', 0); + + if ( this.semantic == 'TEXCOORD' && this.set < 0 ) { + + this.set = 0; + + } + + return this; + + }; + + function Source ( id ) { + + this.id = id; + this.type = null; + + }; + + Source.prototype.parse = function ( element ) { + + this.id = element.getAttribute( 'id' ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + + switch ( child.nodeName ) { + + case 'bool_array': + + this.data = _bools( child.textContent ); + this.type = child.nodeName; + break; + + case 'float_array': + + this.data = _floats( child.textContent ); + this.type = child.nodeName; + break; + + case 'int_array': + + this.data = _ints( child.textContent ); + this.type = child.nodeName; + break; + + case 'IDREF_array': + case 'Name_array': + + this.data = _strings( child.textContent ); + this.type = child.nodeName; + break; + + case 'technique_common': + + for ( var j = 0; j < child.childNodes.length; j ++ ) { + + if ( child.childNodes[ j ].nodeName == 'accessor' ) { + + this.accessor = ( new Accessor() ).parse( child.childNodes[ j ] ); + break; + + } + } + break; + + default: + // console.log(child.nodeName); + break; + + } + + } + + return this; + + }; + + Source.prototype.read = function () { + + var result = []; + + //for (var i = 0; i < this.accessor.params.length; i++) { + + var param = this.accessor.params[ 0 ]; + + //console.log(param.name + " " + param.type); + + switch ( param.type ) { + + case 'IDREF': + case 'Name': case 'name': + case 'float': + + return this.data; + + case 'float4x4': + + for ( var j = 0; j < this.data.length; j += 16 ) { + + var s = this.data.slice( j, j + 16 ); + var m = getConvertedMat4( s ); + result.push( m ); + } + + break; + + default: + + console.log( 'ColladaLoader: Source: Read dont know how to read ' + param.type + '.' ); + break; + + } + + //} + + return result; + + }; + + function Material () { + + this.id = ""; + this.name = ""; + this.instance_effect = null; + + }; + + Material.prototype.parse = function ( element ) { + + this.id = element.getAttribute( 'id' ); + this.name = element.getAttribute( 'name' ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + if ( element.childNodes[ i ].nodeName == 'instance_effect' ) { + + this.instance_effect = ( new InstanceEffect() ).parse( element.childNodes[ i ] ); + break; + + } + + } + + return this; + + }; + + function ColorOrTexture () { + + this.color = new THREE.Color(); + this.color.setRGB( Math.random(), Math.random(), Math.random() ); + this.color.a = 1.0; + + this.texture = null; + this.texcoord = null; + this.texOpts = null; + + }; + + ColorOrTexture.prototype.isColor = function () { + + return ( this.texture == null ); + + }; + + ColorOrTexture.prototype.isTexture = function () { + + return ( this.texture != null ); + + }; + + ColorOrTexture.prototype.parse = function ( element ) { + + if (element.nodeName == 'transparent') { + + this.opaque = element.getAttribute('opaque'); + + } + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'color': + + var rgba = _floats( child.textContent ); + this.color = new THREE.Color(); + this.color.setRGB( rgba[0], rgba[1], rgba[2] ); + this.color.a = rgba[3]; + break; + + case 'texture': + + this.texture = child.getAttribute('texture'); + this.texcoord = child.getAttribute('texcoord'); + // Defaults from: + // https://collada.org/mediawiki/index.php/Maya_texture_placement_MAYA_extension + this.texOpts = { + offsetU: 0, + offsetV: 0, + repeatU: 1, + repeatV: 1, + wrapU: 1, + wrapV: 1 + }; + this.parseTexture( child ); + break; + + default: + break; + + } + + } + + return this; + + }; + + ColorOrTexture.prototype.parseTexture = function ( element ) { + + if ( ! element.childNodes ) return this; + + // This should be supported by Maya, 3dsMax, and MotionBuilder + + if ( element.childNodes[1] && element.childNodes[1].nodeName === 'extra' ) { + + element = element.childNodes[1]; + + if ( element.childNodes[1] && element.childNodes[1].nodeName === 'technique' ) { + + element = element.childNodes[1]; + + } + + } + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'offsetU': + case 'offsetV': + case 'repeatU': + case 'repeatV': + + this.texOpts[ child.nodeName ] = parseFloat( child.textContent ); + + break; + + case 'wrapU': + case 'wrapV': + + // some dae have a value of true which becomes NaN via parseInt + + if ( child.textContent.toUpperCase() === 'TRUE' ) { + + this.texOpts[ child.nodeName ] = 1; + + } else { + + this.texOpts[ child.nodeName ] = parseInt( child.textContent ); + + } + break; + + default: + + this.texOpts[ child.nodeName ] = child.textContent; + + break; + + } + + } + + return this; + + }; + + function Shader ( type, effect ) { + + this.type = type; + this.effect = effect; + this.material = null; + + }; + + Shader.prototype.parse = function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'ambient': + case 'emission': + case 'diffuse': + case 'specular': + case 'transparent': + + this[ child.nodeName ] = ( new ColorOrTexture() ).parse( child ); + break; + + case 'bump': + + // If 'bumptype' is 'heightfield', create a 'bump' property + // Else if 'bumptype' is 'normalmap', create a 'normal' property + // (Default to 'bump') + var bumpType = child.getAttribute( 'bumptype' ); + if ( bumpType ) { + if ( bumpType.toLowerCase() === "heightfield" ) { + this[ 'bump' ] = ( new ColorOrTexture() ).parse( child ); + } else if ( bumpType.toLowerCase() === "normalmap" ) { + this[ 'normal' ] = ( new ColorOrTexture() ).parse( child ); + } else { + console.error( "Shader.prototype.parse: Invalid value for attribute 'bumptype' (" + bumpType + + ") - valid bumptypes are 'HEIGHTFIELD' and 'NORMALMAP' - defaulting to 'HEIGHTFIELD'" ); + this[ 'bump' ] = ( new ColorOrTexture() ).parse( child ); + } + } else { + console.warn( "Shader.prototype.parse: Attribute 'bumptype' missing from bump node - defaulting to 'HEIGHTFIELD'" ); + this[ 'bump' ] = ( new ColorOrTexture() ).parse( child ); + } + + break; + + case 'shininess': + case 'reflectivity': + case 'index_of_refraction': + case 'transparency': + + var f = child.querySelectorAll('float'); + + if ( f.length > 0 ) + this[ child.nodeName ] = parseFloat( f[ 0 ].textContent ); + + break; + + default: + break; + + } + + } + + this.create(); + return this; + + }; + + Shader.prototype.create = function() { + + var props = {}; + + var transparent = false; + + if (this['transparency'] !== undefined && this['transparent'] !== undefined) { + // convert transparent color RBG to average value + var transparentColor = this['transparent']; + var transparencyLevel = (this.transparent.color.r + this.transparent.color.g + this.transparent.color.b) / 3 * this.transparency; + + if (transparencyLevel > 0) { + transparent = true; + props[ 'transparent' ] = true; + props[ 'opacity' ] = 1 - transparencyLevel; + + } + + } + + var keys = { + 'diffuse':'map', + 'ambient':'lightMap' , + 'specular':'specularMap', + 'emission':'emissionMap', + 'bump':'bumpMap', + 'normal':'normalMap' + }; + + for ( var prop in this ) { + + switch ( prop ) { + + case 'ambient': + case 'emission': + case 'diffuse': + case 'specular': + case 'bump': + case 'normal': + + var cot = this[ prop ]; + + if ( cot instanceof ColorOrTexture ) { + + if ( cot.isTexture() ) { + + var samplerId = cot.texture; + var surfaceId = this.effect.sampler[samplerId]; + + if ( surfaceId !== undefined && surfaceId.source !== undefined ) { + + var surface = this.effect.surface[surfaceId.source]; + var image = images[surface.init_from]; + + if (image) { + + var url = baseUrl + image.init_from; + + var texture; + var loader = THREE.Loader.Handlers.get( url ); + + if ( loader !== null ) { + + texture = loader.load( url ); + + } else { + + texture = new THREE.Texture(); + loader = new THREE.ImageLoader(); + loader.load( url, function ( image ) { + + texture.image = image; + texture.needsUpdate = true; + + } ); + + } + + texture.wrapS = cot.texOpts.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + texture.wrapT = cot.texOpts.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + texture.offset.x = cot.texOpts.offsetU; + texture.offset.y = cot.texOpts.offsetV; + texture.repeat.x = cot.texOpts.repeatU; + texture.repeat.y = cot.texOpts.repeatV; + props[keys[prop]] = texture; + + // Texture with baked lighting? + if (prop === 'emission') props['emissive'] = 0xffffff; + + } + + } + + } else if ( prop === 'diffuse' || !transparent ) { + + if ( prop === 'emission' ) { + + props[ 'emissive' ] = cot.color.getHex(); + + } else { + + props[ prop ] = cot.color.getHex(); + + } + + } + + } + + break; + + case 'shininess': + + props[ prop ] = this[ prop ]; + break; + + case 'reflectivity': + + props[ prop ] = this[ prop ]; + if( props[ prop ] > 0.0 ) props['envMap'] = options.defaultEnvMap; + props['combine'] = THREE.MixOperation; //mix regular shading with reflective component + break; + + case 'index_of_refraction': + + props[ 'refractionRatio' ] = this[ prop ]; //TODO: "index_of_refraction" becomes "refractionRatio" in shader, but I'm not sure if the two are actually comparable + if ( this[ prop ] !== 1.0 ) props['envMap'] = options.defaultEnvMap; + break; + + case 'transparency': + // gets figured out up top + break; + + default: + break; + + } + + } + + props[ 'shading' ] = preferredShading; + props[ 'side' ] = this.effect.doubleSided ? THREE.DoubleSide : THREE.FrontSide; + + switch ( this.type ) { + + case 'constant': + + if (props.emissive != undefined) props.color = props.emissive; + this.material = new THREE.MeshBasicMaterial( props ); + break; + + case 'phong': + case 'blinn': + + if (props.diffuse != undefined) props.color = props.diffuse; + this.material = new THREE.MeshPhongMaterial( props ); + break; + + case 'lambert': + default: + + if (props.diffuse != undefined) props.color = props.diffuse; + this.material = new THREE.MeshLambertMaterial( props ); + break; + + } + + return this.material; + + }; + + function Surface ( effect ) { + + this.effect = effect; + this.init_from = null; + this.format = null; + + }; + + Surface.prototype.parse = function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'init_from': + + this.init_from = child.textContent; + break; + + case 'format': + + this.format = child.textContent; + break; + + default: + + console.log( "unhandled Surface prop: " + child.nodeName ); + break; + + } + + } + + return this; + + }; + + function Sampler2D ( effect ) { + + this.effect = effect; + this.source = null; + this.wrap_s = null; + this.wrap_t = null; + this.minfilter = null; + this.magfilter = null; + this.mipfilter = null; + + }; + + Sampler2D.prototype.parse = function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'source': + + this.source = child.textContent; + break; + + case 'minfilter': + + this.minfilter = child.textContent; + break; + + case 'magfilter': + + this.magfilter = child.textContent; + break; + + case 'mipfilter': + + this.mipfilter = child.textContent; + break; + + case 'wrap_s': + + this.wrap_s = child.textContent; + break; + + case 'wrap_t': + + this.wrap_t = child.textContent; + break; + + default: + + console.log( "unhandled Sampler2D prop: " + child.nodeName ); + break; + + } + + } + + return this; + + }; + + function Effect () { + + this.id = ""; + this.name = ""; + this.shader = null; + this.surface = {}; + this.sampler = {}; + + }; + + Effect.prototype.create = function () { + + if ( this.shader == null ) { + + return null; + + } + + }; + + Effect.prototype.parse = function ( element ) { + + this.id = element.getAttribute( 'id' ); + this.name = element.getAttribute( 'name' ); + + extractDoubleSided( this, element ); + + this.shader = null; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'profile_COMMON': + + this.parseTechnique( this.parseProfileCOMMON( child ) ); + break; + + default: + break; + + } + + } + + return this; + + }; + + Effect.prototype.parseNewparam = function ( element ) { + + var sid = element.getAttribute( 'sid' ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'surface': + + this.surface[sid] = ( new Surface( this ) ).parse( child ); + break; + + case 'sampler2D': + + this.sampler[sid] = ( new Sampler2D( this ) ).parse( child ); + break; + + case 'extra': + + break; + + default: + + console.log( child.nodeName ); + break; + + } + + } + + }; + + Effect.prototype.parseProfileCOMMON = function ( element ) { + + var technique; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'profile_COMMON': + + this.parseProfileCOMMON( child ); + break; + + case 'technique': + + technique = child; + break; + + case 'newparam': + + this.parseNewparam( child ); + break; + + case 'image': + + var _image = ( new _Image() ).parse( child ); + images[ _image.id ] = _image; + break; + + case 'extra': + break; + + default: + + console.log( child.nodeName ); + break; + + } + + } + + return technique; + + }; + + Effect.prototype.parseTechnique= function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'constant': + case 'lambert': + case 'blinn': + case 'phong': + + this.shader = ( new Shader( child.nodeName, this ) ).parse( child ); + break; + case 'extra': + this.parseExtra(child); + break; + default: + break; + + } + + } + + }; + + Effect.prototype.parseExtra = function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique': + this.parseExtraTechnique( child ); + break; + default: + break; + + } + + } + + }; + + Effect.prototype.parseExtraTechnique= function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[i]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'bump': + this.shader.parse( element ); + break; + default: + break; + + } + + } + + }; + + function InstanceEffect () { + + this.url = ""; + + }; + + InstanceEffect.prototype.parse = function ( element ) { + + this.url = element.getAttribute( 'url' ).replace( /^#/, '' ); + return this; + + }; + + function Animation() { + + this.id = ""; + this.name = ""; + this.source = {}; + this.sampler = []; + this.channel = []; + + }; + + Animation.prototype.parse = function ( element ) { + + this.id = element.getAttribute( 'id' ); + this.name = element.getAttribute( 'name' ); + this.source = {}; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'animation': + + var anim = ( new Animation() ).parse( child ); + + for ( var src in anim.source ) { + + this.source[ src ] = anim.source[ src ]; + + } + + for ( var j = 0; j < anim.channel.length; j ++ ) { + + this.channel.push( anim.channel[ j ] ); + this.sampler.push( anim.sampler[ j ] ); + + } + + break; + + case 'source': + + var src = ( new Source() ).parse( child ); + this.source[ src.id ] = src; + break; + + case 'sampler': + + this.sampler.push( ( new Sampler( this ) ).parse( child ) ); + break; + + case 'channel': + + this.channel.push( ( new Channel( this ) ).parse( child ) ); + break; + + default: + break; + + } + + } + + return this; + + }; + + function Channel( animation ) { + + this.animation = animation; + this.source = ""; + this.target = ""; + this.fullSid = null; + this.sid = null; + this.dotSyntax = null; + this.arrSyntax = null; + this.arrIndices = null; + this.member = null; + + }; + + Channel.prototype.parse = function ( element ) { + + this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); + this.target = element.getAttribute( 'target' ); + + var parts = this.target.split( '/' ); + + var id = parts.shift(); + var sid = parts.shift(); + + var dotSyntax = ( sid.indexOf(".") >= 0 ); + var arrSyntax = ( sid.indexOf("(") >= 0 ); + + if ( dotSyntax ) { + + parts = sid.split("."); + this.sid = parts.shift(); + this.member = parts.shift(); + + } else if ( arrSyntax ) { + + var arrIndices = sid.split("("); + this.sid = arrIndices.shift(); + + for (var j = 0; j < arrIndices.length; j ++ ) { + + arrIndices[j] = parseInt( arrIndices[j].replace(/\)/, '') ); + + } + + this.arrIndices = arrIndices; + + } else { + + this.sid = sid; + + } + + this.fullSid = sid; + this.dotSyntax = dotSyntax; + this.arrSyntax = arrSyntax; + + return this; + + }; + + function Sampler ( animation ) { + + this.id = ""; + this.animation = animation; + this.inputs = []; + this.input = null; + this.output = null; + this.strideOut = null; + this.interpolation = null; + this.startTime = null; + this.endTime = null; + this.duration = 0; + + }; + + Sampler.prototype.parse = function ( element ) { + + this.id = element.getAttribute( 'id' ); + this.inputs = []; + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + + this.inputs.push( (new Input()).parse( child ) ); + break; + + default: + break; + + } + + } + + return this; + + }; + + Sampler.prototype.create = function () { + + for ( var i = 0; i < this.inputs.length; i ++ ) { + + var input = this.inputs[ i ]; + var source = this.animation.source[ input.source ]; + + switch ( input.semantic ) { + + case 'INPUT': + + this.input = source.read(); + break; + + case 'OUTPUT': + + this.output = source.read(); + this.strideOut = source.accessor.stride; + break; + + case 'INTERPOLATION': + + this.interpolation = source.read(); + break; + + case 'IN_TANGENT': + + break; + + case 'OUT_TANGENT': + + break; + + default: + + console.log(input.semantic); + break; + + } + + } + + this.startTime = 0; + this.endTime = 0; + this.duration = 0; + + if ( this.input.length ) { + + this.startTime = 100000000; + this.endTime = -100000000; + + for ( var i = 0; i < this.input.length; i ++ ) { + + this.startTime = Math.min( this.startTime, this.input[ i ] ); + this.endTime = Math.max( this.endTime, this.input[ i ] ); + + } + + this.duration = this.endTime - this.startTime; + + } + + }; + + Sampler.prototype.getData = function ( type, ndx, member ) { + + var data; + + if ( type === 'matrix' && this.strideOut === 16 ) { + + data = this.output[ ndx ]; + + } else if ( this.strideOut > 1 ) { + + data = []; + ndx *= this.strideOut; + + for ( var i = 0; i < this.strideOut; ++i ) { + + data[ i ] = this.output[ ndx + i ]; + + } + + if ( this.strideOut === 3 ) { + + switch ( type ) { + + case 'rotate': + case 'translate': + + fixCoords( data, -1 ); + break; + + case 'scale': + + fixCoords( data, 1 ); + break; + + } + + } else if ( this.strideOut === 4 && type === 'matrix' ) { + + fixCoords( data, -1 ); + + } + + } else { + + data = this.output[ ndx ]; + + if ( member && type == 'translate' ) { + data = getConvertedTranslation( member, data ); + } + + } + + return data; + + }; + + function Key ( time ) { + + this.targets = []; + this.time = time; + + }; + + Key.prototype.addTarget = function ( fullSid, transform, member, data ) { + + this.targets.push( { + sid: fullSid, + member: member, + transform: transform, + data: data + } ); + + }; + + Key.prototype.apply = function ( opt_sid ) { + + for ( var i = 0; i < this.targets.length; ++i ) { + + var target = this.targets[ i ]; + + if ( !opt_sid || target.sid === opt_sid ) { + + target.transform.update( target.data, target.member ); + + } + + } + + }; + + Key.prototype.getTarget = function ( fullSid ) { + + for ( var i = 0; i < this.targets.length; ++i ) { + + if ( this.targets[ i ].sid === fullSid ) { + + return this.targets[ i ]; + + } + + } + + return null; + + }; + + Key.prototype.hasTarget = function ( fullSid ) { + + for ( var i = 0; i < this.targets.length; ++i ) { + + if ( this.targets[ i ].sid === fullSid ) { + + return true; + + } + + } + + return false; + + }; + + // TODO: Currently only doing linear interpolation. Should support full COLLADA spec. + Key.prototype.interpolate = function ( nextKey, time ) { + + for ( var i = 0, l = this.targets.length; i < l; i ++ ) { + + var target = this.targets[ i ], + nextTarget = nextKey.getTarget( target.sid ), + data; + + if ( target.transform.type !== 'matrix' && nextTarget ) { + + var scale = ( time - this.time ) / ( nextKey.time - this.time ), + nextData = nextTarget.data, + prevData = target.data; + + if ( scale < 0 ) scale = 0; + if ( scale > 1 ) scale = 1; + + if ( prevData.length ) { + + data = []; + + for ( var j = 0; j < prevData.length; ++j ) { + + data[ j ] = prevData[ j ] + ( nextData[ j ] - prevData[ j ] ) * scale; + + } + + } else { + + data = prevData + ( nextData - prevData ) * scale; + + } + + } else { + + data = target.data; + + } + + target.transform.update( data, target.member ); + + } + + }; + + // Camera + function Camera() { + + this.id = ""; + this.name = ""; + this.technique = ""; + + }; + + Camera.prototype.parse = function ( element ) { + + this.id = element.getAttribute( 'id' ); + this.name = element.getAttribute( 'name' ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'optics': + + this.parseOptics( child ); + break; + + default: + break; + + } + + } + + return this; + + }; + + Camera.prototype.parseOptics = function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + if ( element.childNodes[ i ].nodeName == 'technique_common' ) { + + var technique = element.childNodes[ i ]; + + for ( var j = 0; j < technique.childNodes.length; j ++ ) { + + this.technique = technique.childNodes[ j ].nodeName; + + if ( this.technique == 'perspective' ) { + + var perspective = technique.childNodes[ j ]; + + for ( var k = 0; k < perspective.childNodes.length; k ++ ) { + + var param = perspective.childNodes[ k ]; + + switch ( param.nodeName ) { + + case 'yfov': + this.yfov = param.textContent; + break; + case 'xfov': + this.xfov = param.textContent; + break; + case 'znear': + this.znear = param.textContent; + break; + case 'zfar': + this.zfar = param.textContent; + break; + case 'aspect_ratio': + this.aspect_ratio = param.textContent; + break; + + } + + } + + } else if ( this.technique == 'orthographic' ) { + + var orthographic = technique.childNodes[ j ]; + + for ( var k = 0; k < orthographic.childNodes.length; k ++ ) { + + var param = orthographic.childNodes[ k ]; + + switch ( param.nodeName ) { + + case 'xmag': + this.xmag = param.textContent; + break; + case 'ymag': + this.ymag = param.textContent; + break; + case 'znear': + this.znear = param.textContent; + break; + case 'zfar': + this.zfar = param.textContent; + break; + case 'aspect_ratio': + this.aspect_ratio = param.textContent; + break; + + } + + } + + } + + } + + } + + } + + return this; + + }; + + function InstanceCamera() { + + this.url = ""; + + }; + + InstanceCamera.prototype.parse = function ( element ) { + + this.url = element.getAttribute('url').replace(/^#/, ''); + + return this; + + }; + + // Light + + function Light() { + + this.id = ""; + this.name = ""; + this.technique = ""; + + }; + + Light.prototype.parse = function ( element ) { + + this.id = element.getAttribute( 'id' ); + this.name = element.getAttribute( 'name' ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + if ( child.nodeType != 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique_common': + + this.parseCommon( child ); + break; + + case 'technique': + + this.parseTechnique( child ); + break; + + default: + break; + + } + + } + + return this; + + }; + + Light.prototype.parseCommon = function ( element ) { + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + switch ( element.childNodes[ i ].nodeName ) { + + case 'directional': + case 'point': + case 'spot': + case 'ambient': + + this.technique = element.childNodes[ i ].nodeName; + + var light = element.childNodes[ i ]; + + for ( var j = 0; j < light.childNodes.length; j ++ ) { + + var child = light.childNodes[j]; + + switch ( child.nodeName ) { + + case 'color': + + var rgba = _floats( child.textContent ); + this.color = new THREE.Color(0); + this.color.setRGB( rgba[0], rgba[1], rgba[2] ); + this.color.a = rgba[3]; + break; + + case 'falloff_angle': + + this.falloff_angle = parseFloat( child.textContent ); + break; + + case 'quadratic_attenuation': + var f = parseFloat( child.textContent ); + this.distance = f ? Math.sqrt( 1/f ) : 0; + } + + } + + } + + } + + return this; + + }; + + Light.prototype.parseTechnique = function ( element ) { + + this.profile = element.getAttribute( 'profile' ); + + for ( var i = 0; i < element.childNodes.length; i ++ ) { + + var child = element.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'intensity': + + this.intensity = parseFloat(child.textContent); + break; + + } + + } + + return this; + + }; + + function InstanceLight() { + + this.url = ""; + + }; + + InstanceLight.prototype.parse = function ( element ) { + + this.url = element.getAttribute('url').replace(/^#/, ''); + + return this; + + }; + + function _source( element ) { + + var id = element.getAttribute( 'id' ); + + if ( sources[ id ] != undefined ) { + + return sources[ id ]; + + } + + sources[ id ] = ( new Source(id )).parse( element ); + return sources[ id ]; + + }; + + function _nsResolver( nsPrefix ) { + + if ( nsPrefix == "dae" ) { + + return "http://www.collada.org/2005/11/COLLADASchema"; + + } + + return null; + + }; + + function _bools( str ) { + + var raw = _strings( str ); + var data = []; + + for ( var i = 0, l = raw.length; i < l; i ++ ) { + + data.push( (raw[i] == 'true' || raw[i] == '1') ? true : false ); + + } + + return data; + + }; + + function _floats( str ) { + + var raw = _strings(str); + var data = []; + + for ( var i = 0, l = raw.length; i < l; i ++ ) { + + data.push( parseFloat( raw[ i ] ) ); + + } + + return data; + + }; + + function _ints( str ) { + + var raw = _strings( str ); + var data = []; + + for ( var i = 0, l = raw.length; i < l; i ++ ) { + + data.push( parseInt( raw[ i ], 10 ) ); + + } + + return data; + + }; + + function _strings( str ) { + + return ( str.length > 0 ) ? _trimString( str ).split( /\s+/ ) : []; + + }; + + function _trimString( str ) { + + return str.replace( /^\s+/, "" ).replace( /\s+$/, "" ); + + }; + + function _attr_as_float( element, name, defaultValue ) { + + if ( element.hasAttribute( name ) ) { + + return parseFloat( element.getAttribute( name ) ); + + } else { + + return defaultValue; + + } + + }; + + function _attr_as_int( element, name, defaultValue ) { + + if ( element.hasAttribute( name ) ) { + + return parseInt( element.getAttribute( name ), 10) ; + + } else { + + return defaultValue; + + } + + }; + + function _attr_as_string( element, name, defaultValue ) { + + if ( element.hasAttribute( name ) ) { + + return element.getAttribute( name ); + + } else { + + return defaultValue; + + } + + }; + + function _format_float( f, num ) { + + if ( f === undefined ) { + + var s = '0.'; + + while ( s.length < num + 2 ) { + + s += '0'; + + } + + return s; + + } + + num = num || 2; + + var parts = f.toString().split( '.' ); + parts[ 1 ] = parts.length > 1 ? parts[ 1 ].substr( 0, num ) : "0"; + + while( parts[ 1 ].length < num ) { + + parts[ 1 ] += '0'; + + } + + return parts.join( '.' ); + + }; + + function extractDoubleSided( obj, element ) { + + obj.doubleSided = false; + + var node = element.querySelectorAll('extra double_sided')[0]; + + if ( node ) { + + if ( node && parseInt( node.textContent, 10 ) === 1 ) { + + obj.doubleSided = true; + + } + + } + + }; + + // Up axis conversion + + function setUpConversion() { + + if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) { + + upConversion = null; + + } else { + + switch ( colladaUp ) { + + case 'X': + + upConversion = options.upAxis === 'Y' ? 'XtoY' : 'XtoZ'; + break; + + case 'Y': + + upConversion = options.upAxis === 'X' ? 'YtoX' : 'YtoZ'; + break; + + case 'Z': + + upConversion = options.upAxis === 'X' ? 'ZtoX' : 'ZtoY'; + break; + + } + + } + + }; + + function fixCoords( data, sign ) { + + if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) { + + return; + + } + + switch ( upConversion ) { + + case 'XtoY': + + var tmp = data[ 0 ]; + data[ 0 ] = sign * data[ 1 ]; + data[ 1 ] = tmp; + break; + + case 'XtoZ': + + var tmp = data[ 2 ]; + data[ 2 ] = data[ 1 ]; + data[ 1 ] = data[ 0 ]; + data[ 0 ] = tmp; + break; + + case 'YtoX': + + var tmp = data[ 0 ]; + data[ 0 ] = data[ 1 ]; + data[ 1 ] = sign * tmp; + break; + + case 'YtoZ': + + var tmp = data[ 1 ]; + data[ 1 ] = sign * data[ 2 ]; + data[ 2 ] = tmp; + break; + + case 'ZtoX': + + var tmp = data[ 0 ]; + data[ 0 ] = data[ 1 ]; + data[ 1 ] = data[ 2 ]; + data[ 2 ] = tmp; + break; + + case 'ZtoY': + + var tmp = data[ 1 ]; + data[ 1 ] = data[ 2 ]; + data[ 2 ] = sign * tmp; + break; + + } + + }; + + function getConvertedTranslation( axis, data ) { + + if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) { + + return data; + + } + + switch ( axis ) { + case 'X': + data = upConversion == 'XtoY' ? data * -1 : data; + break; + case 'Y': + data = upConversion == 'YtoZ' || upConversion == 'YtoX' ? data * -1 : data; + break; + case 'Z': + data = upConversion == 'ZtoY' ? data * -1 : data ; + break; + default: + break; + } + + return data; + }; + + function getConvertedVec3( data, offset ) { + + var arr = [ data[ offset ], data[ offset + 1 ], data[ offset + 2 ] ]; + fixCoords( arr, -1 ); + return new THREE.Vector3( arr[ 0 ], arr[ 1 ], arr[ 2 ] ); + + }; + + function getConvertedMat4( data ) { + + if ( options.convertUpAxis ) { + + // First fix rotation and scale + + // Columns first + var arr = [ data[ 0 ], data[ 4 ], data[ 8 ] ]; + fixCoords( arr, -1 ); + data[ 0 ] = arr[ 0 ]; + data[ 4 ] = arr[ 1 ]; + data[ 8 ] = arr[ 2 ]; + arr = [ data[ 1 ], data[ 5 ], data[ 9 ] ]; + fixCoords( arr, -1 ); + data[ 1 ] = arr[ 0 ]; + data[ 5 ] = arr[ 1 ]; + data[ 9 ] = arr[ 2 ]; + arr = [ data[ 2 ], data[ 6 ], data[ 10 ] ]; + fixCoords( arr, -1 ); + data[ 2 ] = arr[ 0 ]; + data[ 6 ] = arr[ 1 ]; + data[ 10 ] = arr[ 2 ]; + // Rows second + arr = [ data[ 0 ], data[ 1 ], data[ 2 ] ]; + fixCoords( arr, -1 ); + data[ 0 ] = arr[ 0 ]; + data[ 1 ] = arr[ 1 ]; + data[ 2 ] = arr[ 2 ]; + arr = [ data[ 4 ], data[ 5 ], data[ 6 ] ]; + fixCoords( arr, -1 ); + data[ 4 ] = arr[ 0 ]; + data[ 5 ] = arr[ 1 ]; + data[ 6 ] = arr[ 2 ]; + arr = [ data[ 8 ], data[ 9 ], data[ 10 ] ]; + fixCoords( arr, -1 ); + data[ 8 ] = arr[ 0 ]; + data[ 9 ] = arr[ 1 ]; + data[ 10 ] = arr[ 2 ]; + + // Now fix translation + arr = [ data[ 3 ], data[ 7 ], data[ 11 ] ]; + fixCoords( arr, -1 ); + data[ 3 ] = arr[ 0 ]; + data[ 7 ] = arr[ 1 ]; + data[ 11 ] = arr[ 2 ]; + + } + + return new THREE.Matrix4( + data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7], + data[8], data[9], data[10], data[11], + data[12], data[13], data[14], data[15] + ); + + }; + + function getConvertedIndex( index ) { + + if ( index > -1 && index < 3 ) { + + var members = ['X', 'Y', 'Z'], + indices = { X: 0, Y: 1, Z: 2 }; + + index = getConvertedMember( members[ index ] ); + index = indices[ index ]; + + } + + return index; + + }; + + function getConvertedMember( member ) { + + if ( options.convertUpAxis ) { + + switch ( member ) { + + case 'X': + + switch ( upConversion ) { + + case 'XtoY': + case 'XtoZ': + case 'YtoX': + + member = 'Y'; + break; + + case 'ZtoX': + + member = 'Z'; + break; + + } + + break; + + case 'Y': + + switch ( upConversion ) { + + case 'XtoY': + case 'YtoX': + case 'ZtoX': + + member = 'X'; + break; + + case 'XtoZ': + case 'YtoZ': + case 'ZtoY': + + member = 'Z'; + break; + + } + + break; + + case 'Z': + + switch ( upConversion ) { + + case 'XtoZ': + + member = 'X'; + break; + + case 'YtoZ': + case 'ZtoX': + case 'ZtoY': + + member = 'Y'; + break; + + } + + break; + + } + + } + + return member; + + }; + + return { + + load: load, + parse: parse, + setPreferredShading: setPreferredShading, + applySkin: applySkin, + geometries : geometries, + options: options + + }; + +}; diff --git a/docdoku-web-front/app/js/dmu/loaders/MTLLoader.js b/docdoku-web-front/app/js/dmu/loaders/MTLLoader.js new file mode 100644 index 0000000000..9c492bbeaa --- /dev/null +++ b/docdoku-web-front/app/js/dmu/loaders/MTLLoader.js @@ -0,0 +1,435 @@ +THREE.MTLLoader = function( baseUrl, options, crossOrigin ) { + + this.baseUrl = baseUrl; + this.options = options; + this.crossOrigin = crossOrigin; + +}; + +THREE.MTLLoader.prototype = { + + constructor: THREE.MTLLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.XHRLoader(); + loader.setCrossOrigin( this.crossOrigin ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( text ) ); + + }, onProgress, onError ); + + }, + + /** + * Parses loaded MTL file + * @param text - Content of MTL file + * @return {THREE.MTLLoader.MaterialCreator} + */ + parse: function ( text ) { + + var lines = text.split( "\n" ); + var info = {}; + var delimiter_pattern = /\s+/; + var materialsInfo = {}; + + for ( var i = 0; i < lines.length; i ++ ) { + + var line = lines[ i ]; + line = line.trim(); + + if ( line.length === 0 || line.charAt( 0 ) === '#' ) { + + // Blank line or comment ignore + continue; + + } + + var pos = line.indexOf( ' ' ); + + var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; + key = key.toLowerCase(); + + var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ""; + value = value.trim(); + + if ( key === "newmtl" ) { + + // New material + + info = { name: value }; + materialsInfo[ value ] = info; + + } else if ( info ) { + + if ( key === "ka" || key === "kd" || key === "ks" ) { + + var ss = value.split( delimiter_pattern, 3 ); + info[ key ] = [ parseFloat( ss[0] ), parseFloat( ss[1] ), parseFloat( ss[2] ) ]; + + } else { + + info[ key ] = value; + + } + + } + + } + + var materialCreator = new THREE.MTLLoader.MaterialCreator( this.baseUrl, this.options ); + materialCreator.crossOrigin = this.crossOrigin + materialCreator.setMaterials( materialsInfo ); + return materialCreator; + + } + +}; + +/** + * Create a new THREE-MTLLoader.MaterialCreator + * @param baseUrl - Url relative to which textures are loaded + * @param options - Set of options on how to construct the materials + * side: Which side to apply the material + * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide + * wrap: What type of wrapping to apply for textures + * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping + * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 + * Default: false, assumed to be already normalized + * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's + * Default: false + * invertTransparency: If transparency need to be inverted (inversion is needed if d = 0 is fully opaque) + * Default: false (d = 1 is fully opaque) + * @constructor + */ + +THREE.MTLLoader.MaterialCreator = function( baseUrl, options ) { + + this.baseUrl = baseUrl; + this.options = options; + this.materialsInfo = {}; + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + + this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide; + this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping; + +}; + +THREE.MTLLoader.MaterialCreator.prototype = { + + constructor: THREE.MTLLoader.MaterialCreator, + + setMaterials: function( materialsInfo ) { + + this.materialsInfo = this.convert( materialsInfo ); + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + + }, + + convert: function( materialsInfo ) { + + if ( !this.options ) return materialsInfo; + + var converted = {}; + + for ( var mn in materialsInfo ) { + + // Convert materials info into normalized form based on options + + var mat = materialsInfo[ mn ]; + + var covmat = {}; + + converted[ mn ] = covmat; + + for ( var prop in mat ) { + + var save = true; + var value = mat[ prop ]; + var lprop = prop.toLowerCase(); + + switch ( lprop ) { + + case 'kd': + case 'ka': + case 'ks': + + // Diffuse color (color under white light) using RGB values + + if ( this.options && this.options.normalizeRGB ) { + + value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ]; + + } + + if ( this.options && this.options.ignoreZeroRGBs ) { + + if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 1 ] === 0 ) { + + // ignore + + save = false; + + } + } + + break; + + case 'd': + + // According to MTL format (http://paulbourke.net/dataformats/mtl/): + // d is dissolve for current material + // factor of 1.0 is fully opaque, a factor of 0 is fully dissolved (completely transparent) + + if ( this.options && this.options.invertTransparency ) { + + value = 1 - value; + + } + + break; + + default: + + break; + } + + if ( save ) { + + covmat[ lprop ] = value; + + } + + } + + } + + return converted; + + }, + + preload: function () { + + for ( var mn in this.materialsInfo ) { + + this.create( mn ); + + } + + }, + + getIndex: function( materialName ) { + + return this.nameLookup[ materialName ]; + + }, + + getAsArray: function() { + + var index = 0; + + for ( var mn in this.materialsInfo ) { + + this.materialsArray[ index ] = this.create( mn ); + this.nameLookup[ mn ] = index; + index ++; + + } + + return this.materialsArray; + + }, + + create: function ( materialName ) { + + if ( this.materials[ materialName ] === undefined ) { + + this.createMaterial_( materialName ); + + } + + return this.materials[ materialName ]; + + }, + + createMaterial_: function ( materialName ) { + + // Create material + + var mat = this.materialsInfo[ materialName ]; + var params = { + + name: materialName, + side: this.side + + }; + + for ( var prop in mat ) { + + var value = mat[ prop ]; + + switch ( prop.toLowerCase() ) { + + // Ns is material specular exponent + + case 'kd': + + // Diffuse color (color under white light) using RGB values + + params[ 'diffuse' ] = new THREE.Color().fromArray( value ); + + break; + + case 'ka': + + // Ambient color (color under shadow) using RGB values + + break; + + case 'ks': + + // Specular color (color when light is reflected from shiny surface) using RGB values + params[ 'specular' ] = new THREE.Color().fromArray( value ); + + break; + + case 'map_kd': + + // Diffuse texture map + + params[ 'map' ] = this.loadTexture( this.baseUrl + value ); + params[ 'map' ].wrapS = this.wrap; + params[ 'map' ].wrapT = this.wrap; + + break; + + case 'ns': + + // The specular exponent (defines the focus of the specular highlight) + // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. + + params['shininess'] = value; + + break; + + case 'd': + + // According to MTL format (http://paulbourke.net/dataformats/mtl/): + // d is dissolve for current material + // factor of 1.0 is fully opaque, a factor of 0 is fully dissolved (completely transparent) + + if ( value < 1 ) { + + params['transparent'] = true; + params['opacity'] = value; + + } + + break; + + case 'map_bump': + case 'bump': + + // Bump texture map + + if ( params[ 'bumpMap' ] ) break; // Avoid loading twice. + + params[ 'bumpMap' ] = this.loadTexture( this.baseUrl + value ); + params[ 'bumpMap' ].wrapS = this.wrap; + params[ 'bumpMap' ].wrapT = this.wrap; + + break; + + default: + break; + + } + + } + + if ( params[ 'diffuse' ] ) { + + params[ 'color' ] = params[ 'diffuse' ]; + + } + + this.materials[ materialName ] = new THREE.MeshPhongMaterial( params ); + return this.materials[ materialName ]; + + }, + + + loadTexture: function ( url, mapping, onLoad, onError ) { + + var texture; + var loader = THREE.Loader.Handlers.get( url ); + + if ( loader !== null ) { + + texture = loader.load( url, onLoad ); + + } else { + + texture = new THREE.Texture(); + + loader = new THREE.ImageLoader(); + loader.crossOrigin = this.crossOrigin; + loader.load( url, function ( image ) { + + texture.image = THREE.MTLLoader.ensurePowerOfTwo_( image ); + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } ); + + } + + if ( mapping !== undefined ) texture.mapping = mapping; + + return texture; + + } + +}; + +THREE.MTLLoader.ensurePowerOfTwo_ = function ( image ) { + + if ( ! THREE.Math.isPowerOfTwo( image.width ) || ! THREE.Math.isPowerOfTwo( image.height ) ) { + + var canvas = document.createElement( "canvas" ); + canvas.width = THREE.MTLLoader.nextHighestPowerOfTwo_( image.width ); + canvas.height = THREE.MTLLoader.nextHighestPowerOfTwo_( image.height ); + + var ctx = canvas.getContext("2d"); + ctx.drawImage( image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height ); + return canvas; + + } + + return image; + +}; + +THREE.MTLLoader.nextHighestPowerOfTwo_ = function( x ) { + + -- x; + + for ( var i = 1; i < 32; i <<= 1 ) { + + x = x | x >> i; + + } + + return x + 1; + +}; + +THREE.EventDispatcher.prototype.apply( THREE.MTLLoader.prototype ); diff --git a/docdoku-web-front/app/js/dmu/loaders/OBJLoader.js b/docdoku-web-front/app/js/dmu/loaders/OBJLoader.js new file mode 100644 index 0000000000..de35edb45c --- /dev/null +++ b/docdoku-web-front/app/js/dmu/loaders/OBJLoader.js @@ -0,0 +1,364 @@ +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.OBJLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.OBJLoader.prototype = { + + constructor: THREE.OBJLoader, + + load: function ( url, texturePath, onLoad) { + + var scope = this; + + var loader = new THREE.XHRLoader( scope.manager ); + + loader.setCrossOrigin( scope.crossOrigin ); + + loader.load( url, function ( text ) { + + var object = scope.parse(text); + + if(!scope.mtlFile){ + + onLoad(object); + + }else{ + + var mtlLoader = new THREE.MTLLoader(texturePath); + mtlLoader.crossOrigin = scope.crossOrigin; + mtlLoader.load( texturePath + scope.mtlFile, function ( materials ) { + + var materialsCreator = materials; + materialsCreator.preload(); + object.traverse( function ( o ) { + if ( o instanceof THREE.Mesh ) { + + if ( o.material.name ) { + + var material = materialsCreator.create( o.material.name ); + + if ( material ) o.material = material; + + } + } + + }); + + onLoad(object); + }); + } + + }); + + }, + + parse: function ( text ) { + + var scope = this; + + function vector( x, y, z ) { + + return new THREE.Vector3( parseFloat( x ), parseFloat( y ), parseFloat( z ) ); + + } + + function uv( u, v ) { + + return new THREE.Vector2( parseFloat( u ), parseFloat( v ) ); + + } + + function face3( a, b, c, normals ) { + + return new THREE.Face3( a, b, c, normals ); + + } + + var object = new THREE.Object3D(); + var geometry, material, mesh; + + function parseVertexIndex( index ) { + + index = parseInt( index ); + + return index >= 0 ? index - 1 : index + vertices.length; + + } + + function parseNormalIndex( index ) { + + index = parseInt( index ); + + return index >= 0 ? index - 1 : index + normals.length; + + } + + function parseUVIndex( index ) { + + index = parseInt( index ); + + return index >= 0 ? index - 1 : index + uvs.length; + + } + + function add_face( a, b, c, normals_inds ) { + + if ( normals_inds === undefined ) { + + geometry.faces.push( face3( + vertices[ parseVertexIndex( a ) ] - 1, + vertices[ parseVertexIndex( b ) ] - 1, + vertices[ parseVertexIndex( c ) ] - 1 + ) ); + + } else { + + geometry.faces.push( face3( + vertices[ parseVertexIndex( a ) ] - 1, + vertices[ parseVertexIndex( b ) ] - 1, + vertices[ parseVertexIndex( c ) ] - 1, + [ + normals[ parseNormalIndex( normals_inds[ 0 ] ) ].clone(), + normals[ parseNormalIndex( normals_inds[ 1 ] ) ].clone(), + normals[ parseNormalIndex( normals_inds[ 2 ] ) ].clone() + ] + ) ); + + } + + } + + function add_uvs( a, b, c ) { + + geometry.faceVertexUvs[ 0 ].push( [ + uvs[ parseUVIndex( a ) ].clone(), + uvs[ parseUVIndex( b ) ].clone(), + uvs[ parseUVIndex( c ) ].clone() + ] ); + + } + + function handle_face_line(faces, uvs, normals_inds) { + + if ( faces[ 3 ] === undefined ) { + + add_face( faces[ 0 ], faces[ 1 ], faces[ 2 ], normals_inds ); + + if ( uvs !== undefined && uvs.length > 0 ) { + + add_uvs( uvs[ 0 ], uvs[ 1 ], uvs[ 2 ] ); + + } + + } else { + + if ( normals_inds !== undefined && normals_inds.length > 0 ) { + + add_face( faces[ 0 ], faces[ 1 ], faces[ 3 ], [ normals_inds[ 0 ], normals_inds[ 1 ], normals_inds[ 3 ] ] ); + add_face( faces[ 1 ], faces[ 2 ], faces[ 3 ], [ normals_inds[ 1 ], normals_inds[ 2 ], normals_inds[ 3 ] ] ); + + } else { + + add_face( faces[ 0 ], faces[ 1 ], faces[ 3 ] ); + add_face( faces[ 1 ], faces[ 2 ], faces[ 3 ] ); + + } + + if ( uvs !== undefined && uvs.length > 0 ) { + + add_uvs( uvs[ 0 ], uvs[ 1 ], uvs[ 3 ] ); + add_uvs( uvs[ 1 ], uvs[ 2 ], uvs[ 3 ] ); + + } + + } + + } + + // create mesh if no objects in text + + if ( /^o /gm.test( text ) === false ) { + + geometry = new THREE.Geometry(); + material = new THREE.MeshLambertMaterial(); + mesh = new THREE.Mesh( geometry, material ); + object.add( mesh ); + + } + + var vertices = []; + var normals = []; + var uvs = []; + + // v float float float + + var vertex_pattern = /v( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; + + // vn float float float + + var normal_pattern = /vn( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; + + // vt float float + + var uv_pattern = /vt( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; + + // f vertex vertex vertex ... + + var face_pattern1 = /f( +-?\d+)( +-?\d+)( +-?\d+)( +-?\d+)?/; + + // f vertex/uv vertex/uv vertex/uv ... + + var face_pattern2 = /f( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))?/; + + // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... + + var face_pattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/; + + // f vertex//normal vertex//normal vertex//normal ... + + var face_pattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/ + + // fixes + + text = text.replace( /\\\r\n/g, '' ); // handles line continuations \ + + var lines = text.split( '\n' ); + + for ( var i = 0; i < lines.length; i ++ ) { + + var line = lines[ i ]; + line = line.trim(); + + var result; + + if ( line.length === 0 || line.charAt( 0 ) === '#' ) { + + continue; + + } else if ( ( result = vertex_pattern.exec( line ) ) !== null ) { + + // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] + + vertices.push( + geometry.vertices.push( + vector( + result[ 1 ], result[ 2 ], result[ 3 ] + ) + ) + ); + + } else if ( ( result = normal_pattern.exec( line ) ) !== null ) { + + // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] + + normals.push( + vector( + result[ 1 ], result[ 2 ], result[ 3 ] + ) + ); + + } else if ( ( result = uv_pattern.exec( line ) ) !== null ) { + + // ["vt 0.1 0.2", "0.1", "0.2"] + + uvs.push( + uv( + result[ 1 ], result[ 2 ] + ) + ); + + } else if ( ( result = face_pattern1.exec( line ) ) !== null ) { + + // ["f 1 2 3", "1", "2", "3", undefined] + + handle_face_line( + [ result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] ] + ); + + } else if ( ( result = face_pattern2.exec( line ) ) !== null ) { + + // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined] + + handle_face_line( + [ result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ] ], //faces + [ result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] ] //uv + ); + + } else if ( ( result = face_pattern3.exec( line ) ) !== null ) { + + // ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined] + + handle_face_line( + [ result[ 2 ], result[ 6 ], result[ 10 ], result[ 14 ] ], //faces + [ result[ 3 ], result[ 7 ], result[ 11 ], result[ 15 ] ], //uv + [ result[ 4 ], result[ 8 ], result[ 12 ], result[ 16 ] ] //normal + ); + + } else if ( ( result = face_pattern4.exec( line ) ) !== null ) { + + // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined] + + handle_face_line( + [ result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ] ], //faces + [ ], //uv + [ result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] ] //normal + ); + + } else if ( /^o /.test( line ) ) { + + geometry = new THREE.Geometry(); + material = new THREE.MeshLambertMaterial(); + + mesh = new THREE.Mesh( geometry, material ); + mesh.name = line.substring( 2 ).trim(); + object.add( mesh ); + + } else if ( /^g /.test( line ) ) { + + // group + + } else if ( /^usemtl /.test( line ) ) { + + // material + + material.name = line.substring( 7 ).trim(); + + } else if ( /^mtllib /.test( line ) ) { + + // mtl file + scope.mtlFile = line.replace(/^mtllib /, ''); + + } else if ( /^s /.test( line ) ) { + + // smooth shading + + } else { + + // console.log( "THREE.OBJLoader: Unhandled line " + line ); + + } + + } + + var children = object.children; + + for ( var i = 0, l = children.length; i < l; i ++ ) { + + var geometry = children[ i ].geometry; + + geometry.computeFaceNormals(); + geometry.computeBoundingSphere(); + + } + + return object; + + } + +}; diff --git a/docdoku-web-front/app/js/dmu/loaders/STLLoader.js b/docdoku-web-front/app/js/dmu/loaders/STLLoader.js new file mode 100644 index 0000000000..def9a17b37 --- /dev/null +++ b/docdoku-web-front/app/js/dmu/loaders/STLLoader.js @@ -0,0 +1,391 @@ +/** + * @author aleeper / http://adamleeper.com/ + * @author mrdoob / http://mrdoob.com/ + * @author gero3 / https://github.com/gero3 + * + * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs. + * + * Supports both binary and ASCII encoded files, with automatic detection of type. + * + * Limitations: + * Binary decoding ignores header. There doesn't seem to be much of a use for it. + * There is perhaps some question as to how valid it is to always assume little-endian-ness. + * ASCII decoding assumes file is UTF-8. Seems to work for the examples... + * + * Usage: + * var loader = new THREE.STLLoader(); + * loader.addEventListener( 'load', function ( event ) { + * + * var geometry = event.content; + * scene.add( new THREE.Mesh( geometry ) ); + * + * } ); + * loader.load( './models/stl/slotted_disk.stl' ); + */ + + +THREE.STLLoader = function () {}; + +THREE.STLLoader.prototype = { + + constructor: THREE.STLLoader + +}; + +THREE.STLLoader.prototype.load = function ( url, callback ) { + 'use strict'; + var scope = this; + + var xhr = new XMLHttpRequest(); + + function onloaded( event ) { + + if ( event.target.status === 200 || event.target.status === 0 ) { + + var geometry = scope.parse( event.target.response || event.target.responseText ); + + scope.dispatchEvent( { type: 'load', content: geometry } ); + + if ( callback ){ + callback( geometry ); + } + + } else { + + scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']', response: event.target.statusText } ); + + } + + } + + xhr.addEventListener( 'load', onloaded, false ); + + xhr.addEventListener( 'progress', function ( event ) { + + scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } ); + + }, false ); + + xhr.addEventListener( 'error', function () { + + scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } ); + + }, false ); + + if ( xhr.overrideMimeType ){ + xhr.overrideMimeType( 'text/plain; charset=x-user-defined' ); + } + xhr.open( 'GET', url, true ); + xhr.responseType = 'arraybuffer'; + xhr.send(); + +}; + +THREE.STLLoader.prototype.parse = function ( data ) { + 'use strict'; + var isBinary = function () { + + var expect, face_size, n_faces, reader; + reader = new DataView( binData ); + face_size = (32 / 8 * 3) + ((32 / 8 * 3) * 3) + (16 / 8); + n_faces = reader.getUint32(80,true); + expect = 80 + (32 / 8) + (n_faces * face_size); + return expect === reader.byteLength; + + }; + + var binData = this.ensureBinary( data ); + + return isBinary() ? this.parseBinary( binData ) : this.parseASCII( this.ensureString( data ) ); + +}; + +THREE.STLLoader.prototype.parseBinary = function ( data ) { + 'use strict'; + var reader = new DataView( data ); + var faces = reader.getUint32( 80, true ); + var dataOffset = 84; + var faceLength = 12 * 4 + 2; + + var offset = 0; + + //var geometry = new THREE.BufferGeometry(); + var geometry = new THREE.BufferGeometry(); + + var vertices = new Float32Array( faces * 3 * 3 ); + var normals = new Float32Array( faces * 3 * 3 ); + + for ( var face = 0; face < faces; face ++ ) { + + var start = dataOffset + face * faceLength; + + for ( var i = 1; i <= 3; i ++ ) { + + var vertexstart = start + i * 12; + + vertices[ offset ] = reader.getFloat32( vertexstart, true ); + vertices[ offset + 1 ] = reader.getFloat32( vertexstart + 4, true ); + vertices[ offset + 2 ] = reader.getFloat32( vertexstart + 8, true ); + + normals[ offset ] = reader.getFloat32( start , true ); + normals[ offset + 1 ] = reader.getFloat32( start + 4, true ); + normals[ offset + 2 ] = reader.getFloat32( start + 8, true ); + + offset += 3; + + } + + } + + geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + geometry.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) ); + + return geometry; + +}; + +THREE.STLLoader.prototype.parseASCII = function (data) { + 'use strict'; + var geometry, length, normal, patternFace, patternNormal, patternVertex, result, text; + geometry = new THREE.Geometry(); + patternFace = /facet([\s\S]*?)endfacet/g; + + while ( ( result = patternFace.exec( data ) ) !== null ) { + + text = result[0]; + patternNormal = /normal[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g; + + while ( ( result = patternNormal.exec( text ) ) !== null ) { + + normal = new THREE.Vector3( parseFloat( result[ 1 ] ), parseFloat( result[ 3 ] ), parseFloat( result[ 5 ] ) ); + + } + + patternVertex = /vertex[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g; + + while ( ( result = patternVertex.exec( text ) ) !== null ) { + + geometry.vertices.push( new THREE.Vector3( parseFloat( result[ 1 ] ), parseFloat( result[ 3 ] ), parseFloat( result[ 5 ] ) ) ); + + } + + length = geometry.vertices.length; + + geometry.faces.push( new THREE.Face3( length - 3, length - 2, length - 1, normal ) ); + + } + + geometry.computeBoundingBox(); + geometry.computeBoundingSphere(); + + return geometry; + +}; + +THREE.STLLoader.prototype.ensureString = function (buf) { + 'use strict'; + if (typeof buf !== "string"){ + var array_buffer = new Uint8Array(buf); + var str = ''; + for(var i = 0; i < buf.byteLength; i++) { + str += String.fromCharCode(array_buffer[i]); // implicitly assumes little-endian + } + return str; + } else { + return buf; + } + +}; + +THREE.STLLoader.prototype.ensureBinary = function (buf) { + 'use strict'; + if (typeof buf === "string"){ + var array_buffer = new Uint8Array(buf.length); + for(var i = 0; i < buf.length; i++) { + array_buffer[i] = buf.charCodeAt(i) & 0xff; // implicitly assumes little-endian + } + return array_buffer.buffer || array_buffer; + } else { + return buf; + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.STLLoader.prototype ); + +if ( typeof DataView === 'undefined'){ + + DataView = function(buffer, byteOffset, byteLength){ + 'use strict'; + this.buffer = buffer; + this.byteOffset = byteOffset || 0; + this.byteLength = byteLength || buffer.byteLength || buffer.length; + this._isString = typeof buffer === "string"; + + }; + + DataView.prototype = { + + _getCharCodes:function(buffer,start,length){ + 'use strict'; + start = start || 0; + length = length || buffer.length; + var end = start + length; + var codes = []; + for (var i = start; i < end; i++) { + codes.push(buffer.charCodeAt(i) & 0xff); + } + return codes; + }, + + _getBytes: function (length, byteOffset, littleEndian) { + 'use strict'; + var result; + + // Handle the lack of endianness + if (littleEndian === undefined) { + + littleEndian = this._littleEndian; + + } + + // Handle the lack of byteOffset + if (byteOffset === undefined) { + + byteOffset = this.byteOffset; + + } else { + + byteOffset = this.byteOffset + byteOffset; + + } + + if (length === undefined) { + + length = this.byteLength - byteOffset; + + } + + // Error Checking + if (typeof byteOffset !== 'number') { + + throw new TypeError('DataView byteOffset is not a number'); + + } + + if (length < 0 || byteOffset + length > this.byteLength) { + + throw new Error('DataView length or (byteOffset+length) value is out of bounds'); + + } + + if (this.isString){ + + result = this._getCharCodes(this.buffer, byteOffset, byteOffset + length); + + } else { + + result = this.buffer.slice(byteOffset, byteOffset + length); + + } + + if (!littleEndian && length > 1) { + + if (!(result instanceof Array)) { + + result = Array.prototype.slice.call(result); + + } + + result.reverse(); + } + + return result; + + }, + + // Compatibility functions on a String Buffer + + getFloat64: function (byteOffset, littleEndian) { + 'use strict'; + var b = this._getBytes(8, byteOffset, littleEndian), + + sign = 1 - (2 * (b[7] >> 7)), + exponent = ((((b[7] << 1) & 0xff) << 3) | (b[6] >> 4)) - ((1 << 10) - 1), + + // Binary operators such as | and << operate on 32 bit values, using + and Math.pow(2) instead + mantissa = ((b[6] & 0x0f) * Math.pow(2, 48)) + (b[5] * Math.pow(2, 40)) + (b[4] * Math.pow(2, 32)) + + (b[3] * Math.pow(2, 24)) + (b[2] * Math.pow(2, 16)) + (b[1] * Math.pow(2, 8)) + b[0]; + + if (exponent === 1024) { + if (mantissa !== 0) { + return NaN; + } else { + return sign * Infinity; + } + } + + if (exponent === -1023) { // Denormalized + return sign * mantissa * Math.pow(2, -1022 - 52); + } + + return sign * (1 + mantissa * Math.pow(2, -52)) * Math.pow(2, exponent); + + }, + + getFloat32: function (byteOffset, littleEndian) { + 'use strict'; + var b = this._getBytes(4, byteOffset, littleEndian), + + sign = 1 - (2 * (b[3] >> 7)), + exponent = (((b[3] << 1) & 0xff) | (b[2] >> 7)) - 127, + mantissa = ((b[2] & 0x7f) << 16) | (b[1] << 8) | b[0]; + + if (exponent === 128) { + if (mantissa !== 0) { + return NaN; + } else { + return sign * Infinity; + } + } + + if (exponent === -127) { // Denormalized + return sign * mantissa * Math.pow(2, -126 - 23); + } + + return sign * (1 + mantissa * Math.pow(2, -23)) * Math.pow(2, exponent); + }, + + getInt32: function (byteOffset, littleEndian) { + 'use strict'; + var b = this._getBytes(4, byteOffset, littleEndian); + return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]; + }, + + getUint32: function (byteOffset, littleEndian) { + 'use strict'; + return this.getInt32(byteOffset, littleEndian) >>> 0; + }, + + getInt16: function (byteOffset, littleEndian) { + 'use strict'; + return (this.getUint16(byteOffset, littleEndian) << 16) >> 16; + }, + + getUint16: function (byteOffset, littleEndian) { + 'use strict'; + var b = this._getBytes(2, byteOffset, littleEndian); + return (b[1] << 8) | b[0]; + }, + + getInt8: function (byteOffset) { + 'use strict'; + return (this.getUint8(byteOffset) << 24) >> 24; + }, + + getUint8: function (byteOffset) { + 'use strict'; + return this._getBytes(1, byteOffset)[0]; + } + }; +} diff --git a/docdoku-web-front/app/js/dmu/utils/BufferGeometryUtils.js b/docdoku-web-front/app/js/dmu/utils/BufferGeometryUtils.js new file mode 100644 index 0000000000..e15d08324f --- /dev/null +++ b/docdoku-web-front/app/js/dmu/utils/BufferGeometryUtils.js @@ -0,0 +1,166 @@ +/** + * @author spite / http://www.clicktorelease.com/ + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.BufferGeometryUtils = { + + fromGeometry: function geometryToBufferGeometry(geometry, settings) { + + if (geometry instanceof THREE.BufferGeometry) { + + return geometry; + + } + + settings = settings || { 'vertexColors': THREE.NoColors }; + + var vertices = geometry.vertices; + var faces = geometry.faces; + var faceVertexUvs = geometry.faceVertexUvs; + var vertexColors = settings.vertexColors; + var hasFaceVertexUv = faceVertexUvs[ 0 ].length > 0; + + var bufferGeometry = new THREE.BufferGeometry(); + + bufferGeometry.attributes = { + + position: { + itemSize: 3, + array: new Float32Array(faces.length * 3 * 3) + }, + normal: { + itemSize: 3, + array: new Float32Array(faces.length * 3 * 3) + } + + } + + var positions = bufferGeometry.attributes.position.array; + var normals = bufferGeometry.attributes.normal.array; + + if (vertexColors !== THREE.NoColors) { + + bufferGeometry.attributes.color = { + itemSize: 3, + array: new Float32Array(faces.length * 3 * 3) + }; + + var colors = bufferGeometry.attributes.color.array; + + } + + if (hasFaceVertexUv === true) { + + bufferGeometry.attributes.uv = { + itemSize: 2, + array: new Float32Array(faces.length * 3 * 2) + }; + + var uvs = bufferGeometry.attributes.uv.array; + + } + + var i2 = 0, i3 = 0; + + for (var i = 0; i < faces.length; i++) { + + var face = faces[ i ]; + + var a = vertices[ face.a ]; + var b = vertices[ face.b ]; + var c = vertices[ face.c ]; + + positions[ i3 ] = a.x; + positions[ i3 + 1 ] = a.y; + positions[ i3 + 2 ] = a.z; + + positions[ i3 + 3 ] = b.x; + positions[ i3 + 4 ] = b.y; + positions[ i3 + 5 ] = b.z; + + positions[ i3 + 6 ] = c.x; + positions[ i3 + 7 ] = c.y; + positions[ i3 + 8 ] = c.z; + + var na = face.vertexNormals[ 0 ]; + var nb = face.vertexNormals[ 1 ]; + var nc = face.vertexNormals[ 2 ]; + + normals[ i3 ] = na.x; + normals[ i3 + 1 ] = na.y; + normals[ i3 + 2 ] = na.z; + + normals[ i3 + 3 ] = nb.x; + normals[ i3 + 4 ] = nb.y; + normals[ i3 + 5 ] = nb.z; + + normals[ i3 + 6 ] = nc.x; + normals[ i3 + 7 ] = nc.y; + normals[ i3 + 8 ] = nc.z; + + if (vertexColors === THREE.FaceColors) { + + var fc = face.color; + + colors[ i3 ] = fc.r; + colors[ i3 + 1 ] = fc.g; + colors[ i3 + 2 ] = fc.b; + + colors[ i3 + 3 ] = fc.r; + colors[ i3 + 4 ] = fc.g; + colors[ i3 + 5 ] = fc.b; + + colors[ i3 + 6 ] = fc.r; + colors[ i3 + 7 ] = fc.g; + colors[ i3 + 8 ] = fc.b; + + } else if (vertexColors === THREE.VertexColors) { + + var vca = face.vertexColors[ 0 ]; + var vcb = face.vertexColors[ 1 ]; + var vcc = face.vertexColors[ 2 ]; + + colors[ i3 ] = vca.r; + colors[ i3 + 1 ] = vca.g; + colors[ i3 + 2 ] = vca.b; + + colors[ i3 + 3 ] = vcb.r; + colors[ i3 + 4 ] = vcb.g; + colors[ i3 + 5 ] = vcb.b; + + colors[ i3 + 6 ] = vcc.r; + colors[ i3 + 7 ] = vcc.g; + colors[ i3 + 8 ] = vcc.b; + + } + + if (hasFaceVertexUv === true) { + + var uva = faceVertexUvs[ 0 ][ i ][ 0 ]; + var uvb = faceVertexUvs[ 0 ][ i ][ 1 ]; + var uvc = faceVertexUvs[ 0 ][ i ][ 2 ]; + + uvs[ i2 ] = uva.x; + uvs[ i2 + 1 ] = uva.y; + + uvs[ i2 + 2 ] = uvb.x; + uvs[ i2 + 3 ] = uvb.y; + + uvs[ i2 + 4 ] = uvc.x; + uvs[ i2 + 5 ] = uvc.y; + + } + + i3 += 9; + i2 += 6; + + } + + bufferGeometry.computeBoundingSphere(); + + return bufferGeometry; + + } + +} diff --git a/docdoku-web-front/app/js/dmu/utils/Stats.js b/docdoku-web-front/app/js/dmu/utils/Stats.js new file mode 100644 index 0000000000..0d894a8639 --- /dev/null +++ b/docdoku-web-front/app/js/dmu/utils/Stats.js @@ -0,0 +1,151 @@ +/** + * @author mrdoob / http://mrdoob.com/ + */ + +var Stats = function () { + 'use strict'; + var bar; + var startTime = Date.now(), prevTime = startTime; + var ms = 0, msMin = Infinity, msMax = 0; + var fps = 0, fpsMin = Infinity, fpsMax = 0; + var frames = 0, mode = 0; + + var container = document.createElement('div'); + container.id = 'stats'; + container.style.cssText = 'opacity:0.9;cursor:pointer'; + + var fpsDiv = document.createElement('div'); + fpsDiv.addEventListener('mousedown', function (event) { + event.preventDefault(); + setMode(++mode % 2); + }, false); + fpsDiv.id = 'fps'; + fpsDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#002'; + container.appendChild(fpsDiv); + + var fpsText = document.createElement('div'); + fpsText.id = 'fpsText'; + fpsText.style.cssText = 'color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px'; + fpsText.innerHTML = 'FPS'; + fpsDiv.appendChild(fpsText); + + var fpsGraph = document.createElement('div'); + fpsGraph.id = 'fpsGraph'; + fpsGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0ff'; + fpsDiv.appendChild(fpsGraph); + + while (fpsGraph.children.length < 74) { + + bar = document.createElement('span'); + bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#113'; + fpsGraph.appendChild(bar); + + } + + var msDiv = document.createElement('div'); + msDiv.addEventListener('mousedown', function (event) { + event.preventDefault(); + setMode(++mode % 2); + }, false); + msDiv.id = 'ms'; + msDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#020;display:none'; + container.appendChild(msDiv); + + var msText = document.createElement('div'); + msText.id = 'msText'; + msText.style.cssText = 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px'; + msText.innerHTML = 'MS'; + msDiv.appendChild(msText); + + var msGraph = document.createElement('div'); + msGraph.id = 'msGraph'; + msGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0f0'; + msDiv.appendChild(msGraph); + + while (msGraph.children.length < 74) { + + bar = document.createElement('span'); + bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#131'; + msGraph.appendChild(bar); + + } + + var setMode = function (value) { + + mode = value; + + switch (mode) { + + case 0: + fpsDiv.style.display = 'block'; + msDiv.style.display = 'none'; + break; + case 1: + fpsDiv.style.display = 'none'; + msDiv.style.display = 'block'; + break; + } + + }; + + var updateGraph = function (dom, value) { + + var child = dom.appendChild(dom.firstChild); + child.style.height = value + 'px'; + + }; + + return { + + REVISION: 11, + + domElement: container, + + setMode: setMode, + + begin: function () { + + startTime = Date.now(); + + }, + + end: function () { + + var time = Date.now(); + + ms = time - startTime; + msMin = Math.min(msMin, ms); + msMax = Math.max(msMax, ms); + + msText.textContent = ms + ' MS (' + msMin + '-' + msMax + ')'; + updateGraph(msGraph, Math.min(30, 30 - ( ms / 200 ) * 30)); + + frames++; + + if (time > prevTime + 1000) { + + fps = Math.round(( frames * 1000 ) / ( time - prevTime )); + fpsMin = Math.min(fpsMin, fps); + fpsMax = Math.max(fpsMax, fps); + + fpsText.textContent = fps + ' FPS (' + fpsMin + '-' + fpsMax + ')'; + updateGraph(fpsGraph, Math.min(30, 30 - ( fps / 100 ) * 30)); + + prevTime = time; + frames = 0; + + } + + return time; + + }, + + update: function () { + + startTime = this.end(); + + } + + }; + +}; \ No newline at end of file diff --git a/docdoku-web-front/app/js/lib/charts/helpers.js b/docdoku-web-front/app/js/lib/charts/helpers.js new file mode 100644 index 0000000000..48d95c60c8 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/helpers.js @@ -0,0 +1,7 @@ +function bytesToSize(bytes) { + var sizes = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; + if (bytes == 0) return 'n/a'; + var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); + if (i == 0) { return (bytes / Math.pow(1024, i)) + ' ' + sizes[i]; } + return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i]; +} \ No newline at end of file diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/custom/tooltip.js b/docdoku-web-front/app/js/lib/charts/nv3d/custom/tooltip.js new file mode 100644 index 0000000000..68ab3985e6 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/custom/tooltip.js @@ -0,0 +1,3 @@ +function diskUsageTooltip(key,value){ + return "

                              "+key+"

                              " + "

                              "+value+"

                              "; +} \ No newline at end of file diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/lib/cie.js b/docdoku-web-front/app/js/lib/charts/nv3d/lib/cie.js new file mode 100644 index 0000000000..45f0132963 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/lib/cie.js @@ -0,0 +1,155 @@ +(function(d3) { + var cie = d3.cie = {}; + + cie.lab = function(l, a, b) { + return arguments.length === 1 + ? (l instanceof Lab ? lab(l.l, l.a, l.b) + : (l instanceof Lch ? lch_lab(l.l, l.c, l.h) + : rgb_lab((l = d3.rgb(l)).r, l.g, l.b))) + : lab(+l, +a, +b); + }; + + cie.lch = function(l, c, h) { + return arguments.length === 1 + ? (l instanceof Lch ? lch(l.l, l.c, l.h) + : (l instanceof Lab ? lab_lch(l.l, l.a, l.b) + : lab_lch((l = rgb_lab((l = d3.rgb(l)).r, l.g, l.b)).l, l.a, l.b))) + : lch(+l, +c, +h); + }; + + cie.interpolateLab = function(a, b) { + a = cie.lab(a); + b = cie.lab(b); + var al = a.l, + aa = a.a, + ab = a.b, + bl = b.l - al, + ba = b.a - aa, + bb = b.b - ab; + return function(t) { + return lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + ""; + }; + }; + + cie.interpolateLch = function(a, b) { + a = cie.lch(a); + b = cie.lch(b); + var al = a.l, + ac = a.c, + ah = a.h, + bl = b.l - al, + bc = b.c - ac, + bh = b.h - ah; + if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; // shortest path + return function(t) { + return lch_lab(al + bl * t, ac + bc * t, ah + bh * t) + ""; + }; + }; + + function lab(l, a, b) { + return new Lab(l, a, b); + } + + function Lab(l, a, b) { + this.l = l; + this.a = a; + this.b = b; + } + + Lab.prototype.brighter = function(k) { + return lab(Math.min(100, this.l + K * (arguments.length ? k : 1)), this.a, this.b); + }; + + Lab.prototype.darker = function(k) { + return lab(Math.max(0, this.l - K * (arguments.length ? k : 1)), this.a, this.b); + }; + + Lab.prototype.rgb = function() { + return lab_rgb(this.l, this.a, this.b); + }; + + Lab.prototype.toString = function() { + return this.rgb() + ""; + }; + + function lch(l, c, h) { + return new Lch(l, c, h); + } + + function Lch(l, c, h) { + this.l = l; + this.c = c; + this.h = h; + } + + Lch.prototype.brighter = function(k) { + return lch(Math.min(100, this.l + K * (arguments.length ? k : 1)), this.c, this.h); + }; + + Lch.prototype.darker = function(k) { + return lch(Math.max(0, this.l - K * (arguments.length ? k : 1)), this.c, this.h); + }; + + Lch.prototype.rgb = function() { + return lch_lab(this.l, this.c, this.h).rgb(); + }; + + Lch.prototype.toString = function() { + return this.rgb() + ""; + }; + + // Corresponds roughly to RGB brighter/darker + var K = 18; + + // D65 standard referent + var X = 0.950470, Y = 1, Z = 1.088830; + + function lab_rgb(l, a, b) { + var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200; + x = lab_xyz(x) * X; + y = lab_xyz(y) * Y; + z = lab_xyz(z) * Z; + return d3.rgb( + xyz_rgb( 3.2404542 * x - 1.5371385 * y - 0.4985314 * z), + xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z), + xyz_rgb( 0.0556434 * x - 0.2040259 * y + 1.0572252 * z) + ); + } + + function rgb_lab(r, g, b) { + r = rgb_xyz(r); + g = rgb_xyz(g); + b = rgb_xyz(b); + var x = xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / X), + y = xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / Y), + z = xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / Z); + return lab(116 * y - 16, 500 * (x - y), 200 * (y - z)); + } + + function lab_lch(l, a, b) { + var c = Math.sqrt(a * a + b * b), + h = Math.atan2(b, a) / Math.PI * 180; + return lch(l, c, h); + } + + function lch_lab(l, c, h) { + h = h * Math.PI / 180; + return lab(l, Math.cos(h) * c, Math.sin(h) * c); + } + + function lab_xyz(x) { + return x > 0.206893034 ? x * x * x : (x - 4 / 29) / 7.787037; + } + + function xyz_lab(x) { + return x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; + } + + function xyz_rgb(r) { + return Math.round(255 * (r <= 0.00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - 0.055)); + } + + function rgb_xyz(r) { + return (r /= 255) <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4); + } +})(d3); diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/lib/crossfilter.js b/docdoku-web-front/app/js/lib/charts/nv3d/lib/crossfilter.js new file mode 100644 index 0000000000..1aaabca281 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/lib/crossfilter.js @@ -0,0 +1,1180 @@ +(function(exports){ +crossfilter.version = "1.0.3"; +function crossfilter_identity(d) { + return d; +} +crossfilter.permute = permute; + +function permute(array, index) { + for (var i = 0, n = index.length, copy = new Array(n); i < n; ++i) { + copy[i] = array[index[i]]; + } + return copy; +} +var bisect = crossfilter.bisect = bisect_by(crossfilter_identity); + +bisect.by = bisect_by; + +function bisect_by(f) { + + // Locate the insertion point for x in a to maintain sorted order. The + // arguments lo and hi may be used to specify a subset of the array which + // should be considered; by default the entire array is used. If x is already + // present in a, the insertion point will be before (to the left of) any + // existing entries. The return value is suitable for use as the first + // argument to `array.splice` assuming that a is already sorted. + // + // The returned insertion point i partitions the array a into two halves so + // that all v < x for v in a[lo:i] for the left side and all v >= x for v in + // a[i:hi] for the right side. + function bisectLeft(a, x, lo, hi) { + while (lo < hi) { + var mid = lo + hi >> 1; + if (f(a[mid]) < x) lo = mid + 1; + else hi = mid; + } + return lo; + } + + // Similar to bisectLeft, but returns an insertion point which comes after (to + // the right of) any existing entries of x in a. + // + // The returned insertion point i partitions the array into two halves so that + // all v <= x for v in a[lo:i] for the left side and all v > x for v in + // a[i:hi] for the right side. + function bisectRight(a, x, lo, hi) { + while (lo < hi) { + var mid = lo + hi >> 1; + if (x < f(a[mid])) hi = mid; + else lo = mid + 1; + } + return lo; + } + + bisectRight.right = bisectRight; + bisectRight.left = bisectLeft; + return bisectRight; +} +var heap = crossfilter.heap = heap_by(crossfilter_identity); + +heap.by = heap_by; + +function heap_by(f) { + + // Builds a binary heap within the specified array a[lo:hi]. The heap has the + // property such that the parent a[lo+i] is always less than or equal to its + // two children: a[lo+2*i+1] and a[lo+2*i+2]. + function heap(a, lo, hi) { + var n = hi - lo, + i = (n >>> 1) + 1; + while (--i > 0) sift(a, i, n, lo); + return a; + } + + // Sorts the specified array a[lo:hi] in descending order, assuming it is + // already a heap. + function sort(a, lo, hi) { + var n = hi - lo, + t; + while (--n > 0) t = a[lo], a[lo] = a[lo + n], a[lo + n] = t, sift(a, 1, n, lo); + return a; + } + + // Sifts the element a[lo+i-1] down the heap, where the heap is the contiguous + // slice of array a[lo:lo+n]. This method can also be used to update the heap + // incrementally, without incurring the full cost of reconstructing the heap. + function sift(a, i, n, lo) { + var d = a[--lo + i], + x = f(d), + child; + while ((child = i << 1) <= n) { + if (child < n && f(a[lo + child]) > f(a[lo + child + 1])) child++; + if (x <= f(a[lo + child])) break; + a[lo + i] = a[lo + child]; + i = child; + } + a[lo + i] = d; + } + + heap.sort = sort; + return heap; +} +var heapselect = crossfilter.heapselect = heapselect_by(crossfilter_identity); + +heapselect.by = heapselect_by; + +function heapselect_by(f) { + var heap = heap_by(f); + + // Returns a new array containing the top k elements in the array a[lo:hi]. + // The returned array is not sorted, but maintains the heap property. If k is + // greater than hi - lo, then fewer than k elements will be returned. The + // order of elements in a is unchanged by this operation. + function heapselect(a, lo, hi, k) { + var queue = new Array(k = Math.min(hi - lo, k)), + min, + i, + x, + d; + + for (i = 0; i < k; ++i) queue[i] = a[lo++]; + heap(queue, 0, k); + + if (lo < hi) { + min = f(queue[0]); + do { + if (x = f(d = a[lo]) > min) { + queue[0] = d; + min = f(heap(queue, 0, k)[0]); + } + } while (++lo < hi); + } + + return queue; + } + + return heapselect; +} +var insertionsort = crossfilter.insertionsort = insertionsort_by(crossfilter_identity); + +insertionsort.by = insertionsort_by; + +function insertionsort_by(f) { + + function insertionsort(a, lo, hi) { + for (var i = lo + 1; i < hi; ++i) { + for (var j = i, t = a[i], x = f(t); j > lo && f(a[j - 1]) > x; --j) { + a[j] = a[j - 1]; + } + a[j] = t; + } + return a; + } + + return insertionsort; +} +// Algorithm designed by Vladimir Yaroslavskiy. +// Implementation based on the Dart project; see lib/dart/LICENSE for details. + +var quicksort = crossfilter.quicksort = quicksort_by(crossfilter_identity); + +quicksort.by = quicksort_by; + +function quicksort_by(f) { + var insertionsort = insertionsort_by(f); + + function sort(a, lo, hi) { + return (hi - lo < quicksort_sizeThreshold + ? insertionsort + : quicksort)(a, lo, hi); + } + + function quicksort(a, lo, hi) { + + // Compute the two pivots by looking at 5 elements. + var sixth = (hi - lo) / 6 | 0, + i1 = lo + sixth, + i5 = hi - 1 - sixth, + i3 = lo + hi - 1 >> 1, // The midpoint. + i2 = i3 - sixth, + i4 = i3 + sixth; + + var e1 = a[i1], x1 = f(e1), + e2 = a[i2], x2 = f(e2), + e3 = a[i3], x3 = f(e3), + e4 = a[i4], x4 = f(e4), + e5 = a[i5], x5 = f(e5); + + var t; + + // Sort the selected 5 elements using a sorting network. + if (x1 > x2) t = e1, e1 = e2, e2 = t, t = x1, x1 = x2, x2 = t; + if (x4 > x5) t = e4, e4 = e5, e5 = t, t = x4, x4 = x5, x5 = t; + if (x1 > x3) t = e1, e1 = e3, e3 = t, t = x1, x1 = x3, x3 = t; + if (x2 > x3) t = e2, e2 = e3, e3 = t, t = x2, x2 = x3, x3 = t; + if (x1 > x4) t = e1, e1 = e4, e4 = t, t = x1, x1 = x4, x4 = t; + if (x3 > x4) t = e3, e3 = e4, e4 = t, t = x3, x3 = x4, x4 = t; + if (x2 > x5) t = e2, e2 = e5, e5 = t, t = x2, x2 = x5, x5 = t; + if (x2 > x3) t = e2, e2 = e3, e3 = t, t = x2, x2 = x3, x3 = t; + if (x4 > x5) t = e4, e4 = e5, e5 = t, t = x4, x4 = x5, x5 = t; + + var pivot1 = e2, pivotValue1 = x2, + pivot2 = e4, pivotValue2 = x4; + + // e2 and e4 have been saved in the pivot variables. They will be written + // back, once the partitioning is finished. + a[i1] = e1; + a[i2] = a[lo]; + a[i3] = e3; + a[i4] = a[hi - 1]; + a[i5] = e5; + + var less = lo + 1, // First element in the middle partition. + great = hi - 2; // Last element in the middle partition. + + // Note that for value comparison, <, <=, >= and > coerce to a primitive via + // Object.prototype.valueOf; == and === do not, so in order to be consistent + // with natural order (such as for Date objects), we must do two compares. + var pivotsEqual = pivotValue1 <= pivotValue2 && pivotValue1 >= pivotValue2; + if (pivotsEqual) { + + // Degenerated case where the partitioning becomes a dutch national flag + // problem. + // + // [ | < pivot | == pivot | unpartitioned | > pivot | ] + // ^ ^ ^ ^ ^ + // left less k great right + // + // a[left] and a[right] are undefined and are filled after the + // partitioning. + // + // Invariants: + // 1) for x in ]left, less[ : x < pivot. + // 2) for x in [less, k[ : x == pivot. + // 3) for x in ]great, right[ : x > pivot. + for (var k = less; k <= great; ++k) { + var ek = a[k], xk = f(ek); + if (xk < pivotValue1) { + if (k !== less) { + a[k] = a[less]; + a[less] = ek; + } + ++less; + } else if (xk > pivotValue1) { + + // Find the first element <= pivot in the range [k - 1, great] and + // put [:ek:] there. We know that such an element must exist: + // When k == less, then el3 (which is equal to pivot) lies in the + // interval. Otherwise a[k - 1] == pivot and the search stops at k-1. + // Note that in the latter case invariant 2 will be violated for a + // short amount of time. The invariant will be restored when the + // pivots are put into their final positions. + while (true) { + var greatValue = f(a[great]); + if (greatValue > pivotValue1) { + great--; + // This is the only location in the while-loop where a new + // iteration is started. + continue; + } else if (greatValue < pivotValue1) { + // Triple exchange. + a[k] = a[less]; + a[less++] = a[great]; + a[great--] = ek; + break; + } else { + a[k] = a[great]; + a[great--] = ek; + // Note: if great < k then we will exit the outer loop and fix + // invariant 2 (which we just violated). + break; + } + } + } + } + } else { + + // We partition the list into three parts: + // 1. < pivot1 + // 2. >= pivot1 && <= pivot2 + // 3. > pivot2 + // + // During the loop we have: + // [ | < pivot1 | >= pivot1 && <= pivot2 | unpartitioned | > pivot2 | ] + // ^ ^ ^ ^ ^ + // left less k great right + // + // a[left] and a[right] are undefined and are filled after the + // partitioning. + // + // Invariants: + // 1. for x in ]left, less[ : x < pivot1 + // 2. for x in [less, k[ : pivot1 <= x && x <= pivot2 + // 3. for x in ]great, right[ : x > pivot2 + for (var k = less; k <= great; k++) { + var ek = a[k], xk = f(ek); + if (xk < pivotValue1) { + if (k !== less) { + a[k] = a[less]; + a[less] = ek; + } + ++less; + } else { + if (xk > pivotValue2) { + while (true) { + var greatValue = f(a[great]); + if (greatValue > pivotValue2) { + great--; + if (great < k) break; + // This is the only location inside the loop where a new + // iteration is started. + continue; + } else { + // a[great] <= pivot2. + if (greatValue < pivotValue1) { + // Triple exchange. + a[k] = a[less]; + a[less++] = a[great]; + a[great--] = ek; + } else { + // a[great] >= pivot1. + a[k] = a[great]; + a[great--] = ek; + } + break; + } + } + } + } + } + } + + // Move pivots into their final positions. + // We shrunk the list from both sides (a[left] and a[right] have + // meaningless values in them) and now we move elements from the first + // and third partition into these locations so that we can store the + // pivots. + a[lo] = a[less - 1]; + a[less - 1] = pivot1; + a[hi - 1] = a[great + 1]; + a[great + 1] = pivot2; + + // The list is now partitioned into three partitions: + // [ < pivot1 | >= pivot1 && <= pivot2 | > pivot2 ] + // ^ ^ ^ ^ + // left less great right + + // Recursive descent. (Don't include the pivot values.) + sort(a, lo, less - 1); + sort(a, great + 2, hi); + + if (pivotsEqual) { + // All elements in the second partition are equal to the pivot. No + // need to sort them. + return a; + } + + // In theory it should be enough to call _doSort recursively on the second + // partition. + // The Android source however removes the pivot elements from the recursive + // call if the second partition is too large (more than 2/3 of the list). + if (less < i1 && great > i5) { + var lessValue, greatValue; + while ((lessValue = f(a[less])) <= pivotValue1 && lessValue >= pivotValue1) ++less; + while ((greatValue = f(a[great])) <= pivotValue2 && greatValue >= pivotValue2) --great; + + // Copy paste of the previous 3-way partitioning with adaptions. + // + // We partition the list into three parts: + // 1. == pivot1 + // 2. > pivot1 && < pivot2 + // 3. == pivot2 + // + // During the loop we have: + // [ == pivot1 | > pivot1 && < pivot2 | unpartitioned | == pivot2 ] + // ^ ^ ^ + // less k great + // + // Invariants: + // 1. for x in [ *, less[ : x == pivot1 + // 2. for x in [less, k[ : pivot1 < x && x < pivot2 + // 3. for x in ]great, * ] : x == pivot2 + for (var k = less; k <= great; k++) { + var ek = a[k], xk = f(ek); + if (xk <= pivotValue1 && xk >= pivotValue1) { + if (k !== less) { + a[k] = a[less]; + a[less] = ek; + } + less++; + } else { + if (xk <= pivotValue2 && xk >= pivotValue2) { + while (true) { + var greatValue = f(a[great]); + if (greatValue <= pivotValue2 && greatValue >= pivotValue2) { + great--; + if (great < k) break; + // This is the only location inside the loop where a new + // iteration is started. + continue; + } else { + // a[great] < pivot2. + if (greatValue < pivotValue1) { + // Triple exchange. + a[k] = a[less]; + a[less++] = a[great]; + a[great--] = ek; + } else { + // a[great] == pivot1. + a[k] = a[great]; + a[great--] = ek; + } + break; + } + } + } + } + } + } + + // The second partition has now been cleared of pivot elements and looks + // as follows: + // [ * | > pivot1 && < pivot2 | * ] + // ^ ^ + // less great + // Sort the second partition using recursive descent. + + // The second partition looks as follows: + // [ * | >= pivot1 && <= pivot2 | * ] + // ^ ^ + // less great + // Simply sort it by recursive descent. + + return sort(a, less, great + 1); + } + + return sort; +} + +var quicksort_sizeThreshold = 32; +var crossfilter_array8 = crossfilter_arrayUntyped, + crossfilter_array16 = crossfilter_arrayUntyped, + crossfilter_array32 = crossfilter_arrayUntyped, + crossfilter_arrayLengthen = crossfilter_identity, + crossfilter_arrayWiden = crossfilter_identity; + +if (typeof Uint8Array !== "undefined") { + crossfilter_array8 = function(n) { return new Uint8Array(n); }; + crossfilter_array16 = function(n) { return new Uint16Array(n); }; + crossfilter_array32 = function(n) { return new Uint32Array(n); }; + + crossfilter_arrayLengthen = function(array, length) { + var copy = new array.constructor(length); + copy.set(array); + return copy; + }; + + crossfilter_arrayWiden = function(array, width) { + var copy; + switch (width) { + case 16: copy = crossfilter_array16(array.length); break; + case 32: copy = crossfilter_array32(array.length); break; + default: throw new Error("invalid array width!"); + } + copy.set(array); + return copy; + }; +} + +function crossfilter_arrayUntyped(n) { + return new Array(n); +} +function crossfilter_filterExact(bisect, value) { + return function(values) { + var n = values.length; + return [bisect.left(values, value, 0, n), bisect.right(values, value, 0, n)]; + }; +} + +function crossfilter_filterRange(bisect, range) { + var min = range[0], + max = range[1]; + return function(values) { + var n = values.length; + return [bisect.left(values, min, 0, n), bisect.left(values, max, 0, n)]; + }; +} + +function crossfilter_filterAll(values) { + return [0, values.length]; +} +function crossfilter_null() { + return null; +} +function crossfilter_zero() { + return 0; +} +function crossfilter_reduceIncrement(p) { + return p + 1; +} + +function crossfilter_reduceDecrement(p) { + return p - 1; +} + +function crossfilter_reduceAdd(f) { + return function(p, v) { + return p + +f(v); + }; +} + +function crossfilter_reduceSubtract(f) { + return function(p, v) { + return p - f(v); + }; +} +exports.crossfilter = crossfilter; + +function crossfilter() { + var crossfilter = { + add: add, + dimension: dimension, + groupAll: groupAll, + size: size + }; + + var data = [], // the records + n = 0, // the number of records; data.length + m = 0, // number of dimensions in use + M = 8, // number of dimensions that can fit in `filters` + filters = crossfilter_array8(0), // M bits per record; 1 is filtered out + filterListeners = [], // when the filters change + dataListeners = []; // when data is added + + // Adds the specified new records to this crossfilter. + function add(newData) { + var n0 = n, + n1 = newData.length; + + // If there's actually new data to add… + // Merge the new data into the existing data. + // Lengthen the filter bitset to handle the new records. + // Notify listeners (dimensions and groups) that new data is available. + if (n1) { + data = data.concat(newData); + filters = crossfilter_arrayLengthen(filters, n += n1); + dataListeners.forEach(function(l) { l(newData, n0, n1); }); + } + + return crossfilter; + } + + // Adds a new dimension with the specified value accessor function. + function dimension(value) { + var dimension = { + filter: filter, + filterExact: filterExact, + filterRange: filterRange, + filterAll: filterAll, + top: top, + group: group, + groupAll: groupAll + }; + + var one = 1 << m++, // bit mask, e.g., 00001000 + zero = ~one, // inverted one, e.g., 11110111 + values, // sorted, cached array + index, // value rank ↦ object id + newValues, // temporary array storing newly-added values + newIndex, // temporary array storing newly-added index + sort = quicksort_by(function(i) { return newValues[i]; }), + refilter = crossfilter_filterAll, // for recomputing filter + indexListeners = [], // when data is added + lo0 = 0, + hi0 = 0; + + // Updating a dimension is a two-stage process. First, we must update the + // associated filters for the newly-added records. Once all dimensions have + // updated their filters, the groups are notified to update. + dataListeners.unshift(preAdd); + dataListeners.push(postAdd); + + // Incorporate any existing data into this dimension, and make sure that the + // filter bitset is wide enough to handle the new dimension. + if (m > M) filters = crossfilter_arrayWiden(filters, M <<= 1); + preAdd(data, 0, n); + postAdd(data, 0, n); + + // Incorporates the specified new records into this dimension. + // This function is responsible for updating filters, values, and index. + function preAdd(newData, n0, n1) { + + // Permute new values into natural order using a sorted index. + newValues = newData.map(value); + newIndex = sort(crossfilter_range(n1), 0, n1); + newValues = permute(newValues, newIndex); + + // Bisect newValues to determine which new records are selected. + var bounds = refilter(newValues), lo1 = bounds[0], hi1 = bounds[1], i; + for (i = 0; i < lo1; ++i) filters[newIndex[i] + n0] |= one; + for (i = hi1; i < n1; ++i) filters[newIndex[i] + n0] |= one; + + // If this dimension previously had no data, then we don't need to do the + // more expensive merge operation; use the new values and index as-is. + if (!n0) { + values = newValues; + index = newIndex; + lo0 = lo1; + hi0 = hi1; + return; + } + + var oldValues = values, + oldIndex = index, + i0 = 0, + i1 = 0; + + // Otherwise, create new arrays into which to merge new and old. + values = new Array(n); + index = crossfilter_index(n, n); + + // Merge the old and new sorted values, and old and new index. + for (i = 0; i0 < n0 && i1 < n1; ++i) { + if (oldValues[i0] < newValues[i1]) { + values[i] = oldValues[i0]; + index[i] = oldIndex[i0++]; + } else { + values[i] = newValues[i1]; + index[i] = newIndex[i1++] + n0; + } + } + + // Add any remaining old values. + for (; i0 < n0; ++i0, ++i) { + values[i] = oldValues[i0]; + index[i] = oldIndex[i0]; + } + + // Add any remaining new values. + for (; i1 < n1; ++i1, ++i) { + values[i] = newValues[i1]; + index[i] = newIndex[i1] + n0; + } + + // Bisect again to recompute lo0 and hi0. + bounds = refilter(values), lo0 = bounds[0], hi0 = bounds[1]; + } + + // When all filters have updated, notify index listeners of the new values. + function postAdd(newData, n0, n1) { + indexListeners.forEach(function(l) { l(newValues, newIndex, n0, n1); }); + newValues = newIndex = null; + } + + // Updates the selected values based on the specified bounds [lo, hi]. + // This implementation is used by all the public filter methods. + function filterIndex(bounds) { + var i, + j, + k, + lo1 = bounds[0], + hi1 = bounds[1], + added = [], + removed = []; + + // Fast incremental update based on previous lo index. + if (lo1 < lo0) { + for (i = lo1, j = Math.min(lo0, hi1); i < j; ++i) { + filters[k = index[i]] ^= one; + added.push(k); + } + } else if (lo1 > lo0) { + for (i = lo0, j = Math.min(lo1, hi0); i < j; ++i) { + filters[k = index[i]] ^= one; + removed.push(k); + } + } + + // Fast incremental update based on previous hi index. + if (hi1 > hi0) { + for (i = Math.max(lo1, hi0), j = hi1; i < j; ++i) { + filters[k = index[i]] ^= one; + added.push(k); + } + } else if (hi1 < hi0) { + for (i = Math.max(lo0, hi1), j = hi0; i < j; ++i) { + filters[k = index[i]] ^= one; + removed.push(k); + } + } + + lo0 = lo1; + hi0 = hi1; + filterListeners.forEach(function(l) { l(one, added, removed); }); + return dimension; + } + + // Filters this dimension using the specified range, value, or null. + // If the range is null, this is equivalent to filterAll. + // If the range is an array, this is equivalent to filterRange. + // Otherwise, this is equivalent to filterExact. + function filter(range) { + return range == null + ? filterAll() : Array.isArray(range) + ? filterRange(range) + : filterExact(range); + } + + // Filters this dimension to select the exact value. + function filterExact(value) { + return filterIndex((refilter = crossfilter_filterExact(bisect, value))(values)); + } + + // Filters this dimension to select the specified range [lo, hi]. + // The lower bound is inclusive, and the upper bound is exclusive. + function filterRange(range) { + return filterIndex((refilter = crossfilter_filterRange(bisect, range))(values)); + } + + // Clears any filters on this dimension. + function filterAll() { + return filterIndex((refilter = crossfilter_filterAll)(values)); + } + + // Returns the top K selected records, based on this dimension's order. + // Note: observes this dimension's filter, unlike group and groupAll. + function top(k) { + var array = [], + i = hi0, + j; + + while (--i >= lo0 && k > 0) { + if (!filters[j = index[i]]) { + array.push(data[j]); + --k; + } + } + + return array; + } + + // Adds a new group to this dimension, using the specified key function. + function group(key) { + var group = { + top: top, + all: all, + reduce: reduce, + reduceCount: reduceCount, + reduceSum: reduceSum, + order: order, + orderNatural: orderNatural, + size: size + }; + + var groups, // array of {key, value} + groupIndex, // object id ↦ group id + groupWidth = 8, + groupCapacity = crossfilter_capacity(groupWidth), + k = 0, // cardinality + select, + heap, + reduceAdd, + reduceRemove, + reduceInitial, + update = crossfilter_null, + reset = crossfilter_null, + resetNeeded = true; + + if (arguments.length < 1) key = crossfilter_identity; + + // The group listens to the crossfilter for when any dimension changes, so + // that it can update the associated reduce values. It must also listen to + // the parent dimension for when data is added, and compute new keys. + filterListeners.push(update); + indexListeners.push(add); + + // Incorporate any existing data into the grouping. + add(values, index, 0, n); + + // Incorporates the specified new values into this group. + // This function is responsible for updating groups and groupIndex. + function add(newValues, newIndex, n0, n1) { + var oldGroups = groups, + reIndex = crossfilter_index(k, groupCapacity), + add = reduceAdd, + initial = reduceInitial, + k0 = k, // old cardinality + i0 = 0, // index of old group + i1 = 0, // index of new record + j, // object id + g0, // old group + x0, // old key + x1, // new key + g, // group to add + x; // key of group to add + + // If a reset is needed, we don't need to update the reduce values. + if (resetNeeded) add = initial = crossfilter_null; + + // Reset the new groups (k is a lower bound). + // Also, make sure that groupIndex exists and is long enough. + groups = new Array(k), k = 0; + groupIndex = k0 > 1 ? crossfilter_arrayLengthen(groupIndex, n) : crossfilter_index(n, groupCapacity); + + // Get the first old key (x0 of g0), if it exists. + if (k0) x0 = (g0 = oldGroups[0]).key; + + // Find the first new key (x1), skipping NaN keys. + while (i1 < n1 && !((x1 = key(newValues[i1])) >= x1)) ++i1; + + // While new keys remain… + while (i1 < n1) { + + // Determine the lesser of the two current keys; new and old. + // If there are no old keys remaining, then always add the new key. + if (g0 && x0 <= x1) { + g = g0, x = x0; + + // Record the new index of the old group. + reIndex[i0] = k; + + // Retrieve the next old key. + if (g0 = oldGroups[++i0]) x0 = g0.key; + } else { + g = {key: x1, value: initial()}, x = x1; + } + + // Add the lesser group. + groups[k] = g; + + // Add any selected records belonging to the added group, while + // advancing the new key and populating the associated group index. + while (!(x1 > x)) { + groupIndex[j = newIndex[i1] + n0] = k; + if (!(filters[j] & zero)) g.value = add(g.value, data[j]); + if (++i1 >= n1) break; + x1 = key(newValues[i1]); + } + + groupIncrement(); + } + + // Add any remaining old groups that were greater than all new keys. + // No incremental reduce is needed; these groups have no new records. + // Also record the new index of the old group. + while (i0 < k0) { + groups[reIndex[i0] = k] = oldGroups[i0++]; + groupIncrement(); + } + + // If we added any new groups before any old groups, + // update the group index of all the old records. + if (k > i0) for (i0 = 0; i0 < n0; ++i0) { + groupIndex[i0] = reIndex[groupIndex[i0]]; + } + + // Modify the update and reset behavior based on the cardinality. + // If the cardinality is less than or equal to one, then the groupIndex + // is not needed. If the cardinality is zero, then there are no records + // and therefore no groups to update or reset. Note that we also must + // change the registered listener to point to the new method. + j = filterListeners.indexOf(update); + if (k > 1) { + update = updateMany; + reset = resetMany; + } else { + if (k === 1) { + update = updateOne; + reset = resetOne; + } else { + update = crossfilter_null; + reset = crossfilter_null; + } + groupIndex = null; + } + filterListeners[j] = update; + + // Count the number of added groups, + // and widen the group index as needed. + function groupIncrement() { + if (++k === groupCapacity) { + reIndex = crossfilter_arrayWiden(reIndex, groupWidth <<= 1); + groupIndex = crossfilter_arrayWiden(groupIndex, groupWidth); + groupCapacity = crossfilter_capacity(groupWidth); + } + } + } + + // Reduces the specified selected or deselected records. + // This function is only used when the cardinality is greater than 1. + function updateMany(filterOne, added, removed) { + if (filterOne === one || resetNeeded) return; + + var i, + k, + n, + g; + + // Add the added values. + for (i = 0, n = added.length; i < n; ++i) { + if (!(filters[k = added[i]] & zero)) { + g = groups[groupIndex[k]]; + g.value = reduceAdd(g.value, data[k]); + } + } + + // Remove the removed values. + for (i = 0, n = removed.length; i < n; ++i) { + if ((filters[k = removed[i]] & zero) === filterOne) { + g = groups[groupIndex[k]]; + g.value = reduceRemove(g.value, data[k]); + } + } + } + + // Reduces the specified selected or deselected records. + // This function is only used when the cardinality is 1. + function updateOne(filterOne, added, removed) { + if (filterOne === one || resetNeeded) return; + + var i, + k, + n, + g = groups[0]; + + // Add the added values. + for (i = 0, n = added.length; i < n; ++i) { + if (!(filters[k = added[i]] & zero)) { + g.value = reduceAdd(g.value, data[k]); + } + } + + // Remove the removed values. + for (i = 0, n = removed.length; i < n; ++i) { + if ((filters[k = removed[i]] & zero) === filterOne) { + g.value = reduceRemove(g.value, data[k]); + } + } + } + + // Recomputes the group reduce values from scratch. + // This function is only used when the cardinality is greater than 1. + function resetMany() { + var i, + g; + + // Reset all group values. + for (i = 0; i < k; ++i) { + groups[i].value = reduceInitial(); + } + + // Add any selected records. + for (i = 0; i < n; ++i) { + if (!(filters[i] & zero)) { + g = groups[groupIndex[i]]; + g.value = reduceAdd(g.value, data[i]); + } + } + } + + // Recomputes the group reduce values from scratch. + // This function is only used when the cardinality is 1. + function resetOne() { + var i, + g = groups[0]; + + // Reset the singleton group values. + g.value = reduceInitial(); + + // Add any selected records. + for (i = 0; i < n; ++i) { + if (!(filters[i] & zero)) { + g.value = reduceAdd(g.value, data[i]); + } + } + } + + // Returns the array of group values, in the dimension's natural order. + function all() { + if (resetNeeded) reset(), resetNeeded = false; + return groups; + } + + // Returns a new array containing the top K group values, in reduce order. + function top(k) { + var top = select(all(), 0, groups.length, k); + return heap.sort(top, 0, top.length); + } + + // Sets the reduce behavior for this group to use the specified functions. + // This method lazily recomputes the reduce values, waiting until needed. + function reduce(add, remove, initial) { + reduceAdd = add; + reduceRemove = remove; + reduceInitial = initial; + resetNeeded = true; + return group; + } + + // A convenience method for reducing by count. + function reduceCount() { + return reduce(crossfilter_reduceIncrement, crossfilter_reduceDecrement, crossfilter_zero); + } + + // A convenience method for reducing by sum(value). + function reduceSum(value) { + return reduce(crossfilter_reduceAdd(value), crossfilter_reduceSubtract(value), crossfilter_zero); + } + + // Sets the reduce order, using the specified accessor. + function order(value) { + select = heapselect_by(valueOf); + heap = heap_by(valueOf); + function valueOf(d) { return value(d.value); } + return group; + } + + // A convenience method for natural ordering by reduce value. + function orderNatural() { + return order(crossfilter_identity); + } + + // Returns the cardinality of this group, irrespective of any filters. + function size() { + return k; + } + + return reduceCount().orderNatural(); + } + + // A convenience function for generating a singleton group. + function groupAll() { + var g = group(crossfilter_null), all = g.all; + delete g.all; + delete g.top; + delete g.order; + delete g.orderNatural; + delete g.size; + g.value = function() { return all()[0].value; }; + return g; + } + + return dimension; + } + + // A convenience method for groupAll on a dummy dimension. + // This implementation can be optimized since it is always cardinality 1. + function groupAll() { + var group = { + reduce: reduce, + reduceCount: reduceCount, + reduceSum: reduceSum, + value: value + }; + + var reduceValue, + reduceAdd, + reduceRemove, + reduceInitial, + resetNeeded = true; + + // The group listens to the crossfilter for when any dimension changes, so + // that it can update the reduce value. It must also listen to the parent + // dimension for when data is added. + filterListeners.push(update); + dataListeners.push(add); + + // For consistency; actually a no-op since resetNeeded is true. + add(data, 0, n); + + // Incorporates the specified new values into this group. + function add(newData, n0, n1) { + var i; + + if (resetNeeded) return; + + // Add the added values. + for (i = n0; i < n; ++i) { + if (!filters[i]) { + reduceValue = reduceAdd(reduceValue, data[i]); + } + } + } + + // Reduces the specified selected or deselected records. + function update(filterOne, added, removed) { + var i, + k, + n; + + if (resetNeeded) return; + + // Add the added values. + for (i = 0, n = added.length; i < n; ++i) { + if (!filters[k = added[i]]) { + reduceValue = reduceAdd(reduceValue, data[k]); + } + } + + // Remove the removed values. + for (i = 0, n = removed.length; i < n; ++i) { + if (filters[k = removed[i]] === filterOne) { + reduceValue = reduceRemove(reduceValue, data[k]); + } + } + } + + // Recomputes the group reduce value from scratch. + function reset() { + var i; + + reduceValue = reduceInitial(); + + for (i = 0; i < n; ++i) { + if (!filters[i]) { + reduceValue = reduceAdd(reduceValue, data[i]); + } + } + } + + // Sets the reduce behavior for this group to use the specified functions. + // This method lazily recomputes the reduce value, waiting until needed. + function reduce(add, remove, initial) { + reduceAdd = add; + reduceRemove = remove; + reduceInitial = initial; + resetNeeded = true; + return group; + } + + // A convenience method for reducing by count. + function reduceCount() { + return reduce(crossfilter_reduceIncrement, crossfilter_reduceDecrement, crossfilter_zero); + } + + // A convenience method for reducing by sum(value). + function reduceSum(value) { + return reduce(crossfilter_reduceAdd(value), crossfilter_reduceSubtract(value), crossfilter_zero); + } + + // Returns the computed reduce value. + function value() { + if (resetNeeded) reset(), resetNeeded = false; + return reduceValue; + } + + return reduceCount(); + } + + // Returns the number of records in this crossfilter, irrespective of any filters. + function size() { + return n; + } + + return arguments.length + ? add(arguments[0]) + : crossfilter; +} + +// Returns an array of size n, big enough to store ids up to m. +function crossfilter_index(n, m) { + return (m < 0x101 + ? crossfilter_array8 : m < 0x10001 + ? crossfilter_array16 + : crossfilter_array32)(n); +} + +// Constructs a new array of size n, with sequential values from 0 to n - 1. +function crossfilter_range(n) { + var range = crossfilter_index(n, n); + for (var i = -1; ++i < n;) range[i] = i; + return range; +} + +function crossfilter_capacity(w) { + return w === 8 + ? 0x100 : w === 16 + ? 0x10000 + : 0x100000000; +} +})(this); diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/lib/crossfilter.min.js b/docdoku-web-front/app/js/lib/charts/nv3d/lib/crossfilter.min.js new file mode 100644 index 0000000000..981f0d64ea --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/lib/crossfilter.min.js @@ -0,0 +1 @@ +(function(a){function b(a){return a}function c(a,b){for(var c=0,d=b.length,e=new Array(d);c>1;a(b[f])>1;c>>1)+1;while(--f>0)d(a,f,e,b);return a}function c(a,b,c){var e=c-b,f;while(--e>0)f=a[b],a[b]=a[b+e],a[b+e]=f,d(a,1,e,b);return a}function d(b,c,d,e){var f=b[--e+c],g=a(f),h;while((h=c<<1)<=d){ha(b[e+h+1])&&h++;if(g<=a(b[e+h]))break;b[e+c]=b[e+h],c=h}b[e+c]=f}return b.sort=c,b}function i(a){function c(c,d,e,f){var g=new Array(f=Math.min(e-d,f)),h,i,j,k;for(i=0;ih)g[0]=k,h=a(b(g,0,f)[0]);while(++dc&&a(b[f-1])>h;--f)b[f]=b[f-1];b[f]=g}return b}return b}function m(a){function c(a,c,e){return(e-c>1,j=i-f,k=i+f,l=b[g],m=a(l),n=b[j],o=a(n),p=b[i],q=a(p),r=b[k],s=a(r),t=b[h],u=a(t),v;m>o&&(v=l,l=n,n=v,v=m,m=o,o=v),s>u&&(v=r,r=t,t=v,v=s,s=u,u=v),m>q&&(v=l,l=p,p=v,v=m,m=q,q=v),o>q&&(v=n,n=p,p=v,v=o,o=q,q=v),m>s&&(v=l,l=r,r=v,v=m,m=s,s=v),q>s&&(v=p,p=r,r=v,v=q,q=s,s=v),o>u&&(v=n,n=t,t=v,v=o,o=u,u=v),o>q&&(v=n,n=p,p=v,v=o,o=q,q=v),s>u&&(v=r,r=t,t=v,v=s,s=u,u=v);var w=n,x=o,y=r,z=s;b[g]=l,b[j]=b[d],b[i]=p,b[k]=b[e-1],b[h]=t;var A=d+1,B=e-2,C=x<=z&&x>=z;if(C)for(var D=A;D<=B;++D){var E=b[D],F=a(E);if(Fx)for(;;){var G=a(b[B]);if(G>x){B--;continue}if(Gz)for(;;){var G=a(b[B]);if(G>z){B--;if(Bh){var H,G;while((H=a(b[A]))<=x&&H>=x)++A;while((G=a(b[B]))<=z&&G>=z)--B;for(var D=A;D<=B;D++){var E=b[D],F=a(E);if(F<=x&&F>=x)D!==A&&(b[D]=b[A],b[A]=E),A++;else if(F<=z&&F>=z)for(;;){var G=a(b[B]);if(G<=z&&G>=z){B--;if(BN)for(b=N,c=Math.min(e,O);bO)for(b=Math.max(e,O),c=f;b=N&&a>0)k[d=D[c]]||(b.push(e[d]),--a);return b}function X(a){function K(b,c,g,i){function Q(){++n===m&&(p=s(p,j<<=1),h=s(h,j),m=G(j))}var o=d,p=E(n,m),t=v,u=F,w=n,y=0,z=0,A,B,C,D,K,L;J&&(t=u=x),d=new Array(n),n=0,h=w>1?r(h,f):E(f,m),w&&(C=(B=o[0]).key);while(z=D))++z;while(zL)){h[A=c[z]+g]=n,k[A]&q||(K.value=t(K.value,e[A]));if(++z>=i)break;D=a(b[z])}Q()}while(yy)for(y=0;y1?(H=M,I=O):(n===1?(H=N,I=P):(H=x,I=x),h=null),l[A]=H}function M(a,b,c){if(a===p||J)return;var f,g,i,j;for(f=0,i=b.length;fj&&(k=s(k,j<<=1)),P(e,0,f),Q(e,0,f),o}function t(){function i(a,d,g){var i;if(h)return;for(i=d;i= 0 ? value.substring(i) : (i = value.length, ""), t = []; + while (i > 0) t.push(value.substring(i -= 3, i + 3)); + return t.reverse().join(",") + f; + } + function d3_formatPrefix(d, i) { + var k = Math.pow(10, Math.abs(8 - i) * 3); + return { + scale: i > 8 ? function(d) { + return d / k; + } : function(d) { + return d * k; + }, + symbol: d + }; + } + function d3_ease_clamp(f) { + return function(t) { + return t <= 0 ? 0 : t >= 1 ? 1 : f(t); + }; + } + function d3_ease_reverse(f) { + return function(t) { + return 1 - f(1 - t); + }; + } + function d3_ease_reflect(f) { + return function(t) { + return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t)); + }; + } + function d3_ease_identity(t) { + return t; + } + function d3_ease_poly(e) { + return function(t) { + return Math.pow(t, e); + }; + } + function d3_ease_sin(t) { + return 1 - Math.cos(t * Math.PI / 2); + } + function d3_ease_exp(t) { + return Math.pow(2, 10 * (t - 1)); + } + function d3_ease_circle(t) { + return 1 - Math.sqrt(1 - t * t); + } + function d3_ease_elastic(a, p) { + var s; + if (arguments.length < 2) p = .45; + if (arguments.length < 1) { + a = 1; + s = p / 4; + } else s = p / (2 * Math.PI) * Math.asin(1 / a); + return function(t) { + return 1 + a * Math.pow(2, 10 * -t) * Math.sin((t - s) * 2 * Math.PI / p); + }; + } + function d3_ease_back(s) { + if (!s) s = 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; + } + function d3_ease_bounce(t) { + return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; + } + function d3_eventCancel() { + d3.event.stopPropagation(); + d3.event.preventDefault(); + } + function d3_eventSource() { + var e = d3.event, s; + while (s = e.sourceEvent) e = s; + return e; + } + function d3_eventDispatch(target) { + var dispatch = new d3_dispatch, i = 0, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + dispatch.of = function(thiz, argumentz) { + return function(e1) { + try { + var e0 = e1.sourceEvent = d3.event; + e1.target = target; + d3.event = e1; + dispatch[e1.type].apply(thiz, argumentz); + } finally { + d3.event = e0; + } + }; + }; + return dispatch; + } + function d3_transform(m) { + var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0; + if (r0[0] * r1[1] < r1[0] * r0[1]) { + r0[0] *= -1; + r0[1] *= -1; + kx *= -1; + kz *= -1; + } + this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_transformDegrees; + this.translate = [ m.e, m.f ]; + this.scale = [ kx, ky ]; + this.skew = ky ? Math.atan2(kz, ky) * d3_transformDegrees : 0; + } + function d3_transformDot(a, b) { + return a[0] * b[0] + a[1] * b[1]; + } + function d3_transformNormalize(a) { + var k = Math.sqrt(d3_transformDot(a, a)); + if (k) { + a[0] /= k; + a[1] /= k; + } + return k; + } + function d3_transformCombine(a, b, k) { + a[0] += k * b[0]; + a[1] += k * b[1]; + return a; + } + function d3_interpolateByName(name) { + return name == "transform" ? d3.interpolateTransform : d3.interpolate; + } + function d3_uninterpolateNumber(a, b) { + b = b - (a = +a) ? 1 / (b - a) : 0; + return function(x) { + return (x - a) * b; + }; + } + function d3_uninterpolateClamp(a, b) { + b = b - (a = +a) ? 1 / (b - a) : 0; + return function(x) { + return Math.max(0, Math.min(1, (x - a) * b)); + }; + } + function d3_rgb(r, g, b) { + return new d3_Rgb(r, g, b); + } + function d3_Rgb(r, g, b) { + this.r = r; + this.g = g; + this.b = b; + } + function d3_rgb_hex(v) { + return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16); + } + function d3_rgb_parse(format, rgb, hsl) { + var r = 0, g = 0, b = 0, m1, m2, name; + m1 = /([a-z]+)\((.*)\)/i.exec(format); + if (m1) { + m2 = m1[2].split(","); + switch (m1[1]) { + case "hsl": + { + return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100); + } + case "rgb": + { + return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2])); + } + } + } + if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b); + if (format != null && format.charAt(0) === "#") { + if (format.length === 4) { + r = format.charAt(1); + r += r; + g = format.charAt(2); + g += g; + b = format.charAt(3); + b += b; + } else if (format.length === 7) { + r = format.substring(1, 3); + g = format.substring(3, 5); + b = format.substring(5, 7); + } + r = parseInt(r, 16); + g = parseInt(g, 16); + b = parseInt(b, 16); + } + return rgb(r, g, b); + } + function d3_rgb_hsl(r, g, b) { + var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2; + if (d) { + s = l < .5 ? d / (max + min) : d / (2 - max - min); + if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4; + h *= 60; + } else { + s = h = 0; + } + return d3_hsl(h, s, l); + } + function d3_rgb_lab(r, g, b) { + r = d3_rgb_xyz(r); + g = d3_rgb_xyz(g); + b = d3_rgb_xyz(b); + var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z); + return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z)); + } + function d3_rgb_xyz(r) { + return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4); + } + function d3_rgb_parseNumber(c) { + var f = parseFloat(c); + return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; + } + function d3_hsl(h, s, l) { + return new d3_Hsl(h, s, l); + } + function d3_Hsl(h, s, l) { + this.h = h; + this.s = s; + this.l = l; + } + function d3_hsl_rgb(h, s, l) { + function v(h) { + if (h > 360) h -= 360; else if (h < 0) h += 360; + if (h < 60) return m1 + (m2 - m1) * h / 60; + if (h < 180) return m2; + if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; + return m1; + } + function vv(h) { + return Math.round(v(h) * 255); + } + var m1, m2; + h = h % 360; + if (h < 0) h += 360; + s = s < 0 ? 0 : s > 1 ? 1 : s; + l = l < 0 ? 0 : l > 1 ? 1 : l; + m2 = l <= .5 ? l * (1 + s) : l + s - l * s; + m1 = 2 * l - m2; + return d3_rgb(vv(h + 120), vv(h), vv(h - 120)); + } + function d3_hcl(h, c, l) { + return new d3_Hcl(h, c, l); + } + function d3_Hcl(h, c, l) { + this.h = h; + this.c = c; + this.l = l; + } + function d3_hcl_lab(h, c, l) { + return d3_lab(l, Math.cos(h *= Math.PI / 180) * c, Math.sin(h) * c); + } + function d3_lab(l, a, b) { + return new d3_Lab(l, a, b); + } + function d3_Lab(l, a, b) { + this.l = l; + this.a = a; + this.b = b; + } + function d3_lab_rgb(l, a, b) { + var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200; + x = d3_lab_xyz(x) * d3_lab_X; + y = d3_lab_xyz(y) * d3_lab_Y; + z = d3_lab_xyz(z) * d3_lab_Z; + return d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z)); + } + function d3_lab_hcl(l, a, b) { + return d3_hcl(Math.atan2(b, a) / Math.PI * 180, Math.sqrt(a * a + b * b), l); + } + function d3_lab_xyz(x) { + return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037; + } + function d3_xyz_lab(x) { + return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; + } + function d3_xyz_rgb(r) { + return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055)); + } + function d3_selection(groups) { + d3_arraySubclass(groups, d3_selectionPrototype); + return groups; + } + function d3_selection_selector(selector) { + return function() { + return d3_select(selector, this); + }; + } + function d3_selection_selectorAll(selector) { + return function() { + return d3_selectAll(selector, this); + }; + } + function d3_selection_attr(name, value) { + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrConstant() { + this.setAttribute(name, value); + } + function attrConstantNS() { + this.setAttributeNS(name.space, name.local, value); + } + function attrFunction() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttribute(name); else this.setAttribute(name, x); + } + function attrFunctionNS() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x); + } + name = d3.ns.qualify(name); + return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant; + } + function d3_selection_classedRe(name) { + return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g"); + } + function d3_selection_classed(name, value) { + function classedConstant() { + var i = -1; + while (++i < n) name[i](this, value); + } + function classedFunction() { + var i = -1, x = value.apply(this, arguments); + while (++i < n) name[i](this, x); + } + name = name.trim().split(/\s+/).map(d3_selection_classedName); + var n = name.length; + return typeof value === "function" ? classedFunction : classedConstant; + } + function d3_selection_classedName(name) { + var re = d3_selection_classedRe(name); + return function(node, value) { + if (c = node.classList) return value ? c.add(name) : c.remove(name); + var c = node.className, cb = c.baseVal != null, cv = cb ? c.baseVal : c; + if (value) { + re.lastIndex = 0; + if (!re.test(cv)) { + cv = d3_collapse(cv + " " + name); + if (cb) c.baseVal = cv; else node.className = cv; + } + } else if (cv) { + cv = d3_collapse(cv.replace(re, " ")); + if (cb) c.baseVal = cv; else node.className = cv; + } + }; + } + function d3_selection_style(name, value, priority) { + function styleNull() { + this.style.removeProperty(name); + } + function styleConstant() { + this.style.setProperty(name, value, priority); + } + function styleFunction() { + var x = value.apply(this, arguments); + if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority); + } + return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant; + } + function d3_selection_property(name, value) { + function propertyNull() { + delete this[name]; + } + function propertyConstant() { + this[name] = value; + } + function propertyFunction() { + var x = value.apply(this, arguments); + if (x == null) delete this[name]; else this[name] = x; + } + return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant; + } + function d3_selection_dataNode(data) { + return { + __data__: data + }; + } + function d3_selection_filter(selector) { + return function() { + return d3_selectMatches(this, selector); + }; + } + function d3_selection_sortComparator(comparator) { + if (!arguments.length) comparator = d3.ascending; + return function(a, b) { + return comparator(a && a.__data__, b && b.__data__); + }; + } + function d3_selection_on(type, listener, capture) { + function onRemove() { + var wrapper = this[name]; + if (wrapper) { + this.removeEventListener(type, wrapper, wrapper.$); + delete this[name]; + } + } + function onAdd() { + function wrapper(e) { + var o = d3.event; + d3.event = e; + args[0] = node.__data__; + try { + listener.apply(node, args); + } finally { + d3.event = o; + } + } + var node = this, args = arguments; + onRemove.call(this); + this.addEventListener(type, this[name] = wrapper, wrapper.$ = capture); + wrapper._ = listener; + } + var name = "__on" + type, i = type.indexOf("."); + if (i > 0) type = type.substring(0, i); + return listener ? onAdd : onRemove; + } + function d3_selection_each(groups, callback) { + for (var j = 0, m = groups.length; j < m; j++) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) { + if (node = group[i]) callback(node, i, j); + } + } + return groups; + } + function d3_selection_enter(selection) { + d3_arraySubclass(selection, d3_selection_enterPrototype); + return selection; + } + function d3_transition(groups, id, time) { + d3_arraySubclass(groups, d3_transitionPrototype); + var tweens = new d3_Map, event = d3.dispatch("start", "end"), ease = d3_transitionEase; + groups.id = id; + groups.time = time; + groups.tween = function(name, tween) { + if (arguments.length < 2) return tweens.get(name); + if (tween == null) tweens.remove(name); else tweens.set(name, tween); + return groups; + }; + groups.ease = function(value) { + if (!arguments.length) return ease; + ease = typeof value === "function" ? value : d3.ease.apply(d3, arguments); + return groups; + }; + groups.each = function(type, listener) { + if (arguments.length < 2) return d3_transition_each.call(groups, type); + event.on(type, listener); + return groups; + }; + d3.timer(function(elapsed) { + return d3_selection_each(groups, function(node, i, j) { + function start(elapsed) { + if (lock.active > id) return stop(); + lock.active = id; + tweens.forEach(function(key, value) { + if (value = value.call(node, d, i)) { + tweened.push(value); + } + }); + event.start.call(node, d, i); + if (!tick(elapsed)) d3.timer(tick, 0, time); + return 1; + } + function tick(elapsed) { + if (lock.active !== id) return stop(); + var t = (elapsed - delay) / duration, e = ease(t), n = tweened.length; + while (n > 0) { + tweened[--n].call(node, e); + } + if (t >= 1) { + stop(); + d3_transitionId = id; + event.end.call(node, d, i); + d3_transitionId = 0; + return 1; + } + } + function stop() { + if (!--lock.count) delete node.__transition__; + return 1; + } + var tweened = [], delay = node.delay, duration = node.duration, lock = (node = node.node).__transition__ || (node.__transition__ = { + active: 0, + count: 0 + }), d = node.__data__; + ++lock.count; + delay <= elapsed ? start(elapsed) : d3.timer(start, delay, time); + }); + }, 0, time); + return groups; + } + function d3_transition_each(callback) { + var id = d3_transitionId, ease = d3_transitionEase, delay = d3_transitionDelay, duration = d3_transitionDuration; + d3_transitionId = this.id; + d3_transitionEase = this.ease(); + d3_selection_each(this, function(node, i, j) { + d3_transitionDelay = node.delay; + d3_transitionDuration = node.duration; + callback.call(node = node.node, node.__data__, i, j); + }); + d3_transitionId = id; + d3_transitionEase = ease; + d3_transitionDelay = delay; + d3_transitionDuration = duration; + return this; + } + function d3_tweenNull(d, i, a) { + return a != "" && d3_tweenRemove; + } + function d3_tweenByName(b, name) { + return d3.tween(b, d3_interpolateByName(name)); + } + function d3_timer_step() { + var elapsed, now = Date.now(), t1 = d3_timer_queue; + while (t1) { + elapsed = now - t1.then; + if (elapsed >= t1.delay) t1.flush = t1.callback(elapsed); + t1 = t1.next; + } + var delay = d3_timer_flush() - now; + if (delay > 24) { + if (isFinite(delay)) { + clearTimeout(d3_timer_timeout); + d3_timer_timeout = setTimeout(d3_timer_step, delay); + } + d3_timer_interval = 0; + } else { + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + } + function d3_timer_flush() { + var t0 = null, t1 = d3_timer_queue, then = Infinity; + while (t1) { + if (t1.flush) { + t1 = t0 ? t0.next = t1.next : d3_timer_queue = t1.next; + } else { + then = Math.min(then, t1.then + t1.delay); + t1 = (t0 = t1).next; + } + } + return then; + } + function d3_mousePoint(container, e) { + var svg = container.ownerSVGElement || container; + if (svg.createSVGPoint) { + var point = svg.createSVGPoint(); + if (d3_mouse_bug44083 < 0 && (window.scrollX || window.scrollY)) { + svg = d3.select(document.body).append("svg").style("position", "absolute").style("top", 0).style("left", 0); + var ctm = svg[0][0].getScreenCTM(); + d3_mouse_bug44083 = !(ctm.f || ctm.e); + svg.remove(); + } + if (d3_mouse_bug44083) { + point.x = e.pageX; + point.y = e.pageY; + } else { + point.x = e.clientX; + point.y = e.clientY; + } + point = point.matrixTransform(container.getScreenCTM().inverse()); + return [ point.x, point.y ]; + } + var rect = container.getBoundingClientRect(); + return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ]; + } + function d3_noop() {} + function d3_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [ start, stop ] : [ stop, start ]; + } + function d3_scaleRange(scale) { + return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); + } + function d3_scale_nice(domain, nice) { + var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx; + if (x1 < x0) { + dx = i0, i0 = i1, i1 = dx; + dx = x0, x0 = x1, x1 = dx; + } + if (nice = nice(x1 - x0)) { + domain[i0] = nice.floor(x0); + domain[i1] = nice.ceil(x1); + } + return domain; + } + function d3_scale_niceDefault() { + return Math; + } + function d3_scale_linear(domain, range, interpolate, clamp) { + function rescale() { + var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber; + output = linear(domain, range, uninterpolate, interpolate); + input = linear(range, domain, uninterpolate, d3.interpolate); + return scale; + } + function scale(x) { + return output(x); + } + var output, input; + scale.invert = function(y) { + return input(y); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(Number); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.rangeRound = function(x) { + return scale.range(x).interpolate(d3.interpolateRound); + }; + scale.clamp = function(x) { + if (!arguments.length) return clamp; + clamp = x; + return rescale(); + }; + scale.interpolate = function(x) { + if (!arguments.length) return interpolate; + interpolate = x; + return rescale(); + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m) { + return d3_scale_linearTickFormat(domain, m); + }; + scale.nice = function() { + d3_scale_nice(domain, d3_scale_linearNice); + return rescale(); + }; + scale.copy = function() { + return d3_scale_linear(domain, range, interpolate, clamp); + }; + return rescale(); + } + function d3_scale_linearRebind(scale, linear) { + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); + } + function d3_scale_linearNice(dx) { + dx = Math.pow(10, Math.round(Math.log(dx) / Math.LN10) - 1); + return dx && { + floor: function(x) { + return Math.floor(x / dx) * dx; + }, + ceil: function(x) { + return Math.ceil(x / dx) * dx; + } + }; + } + function d3_scale_linearTickRange(domain, m) { + var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step; + if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2; + extent[0] = Math.ceil(extent[0] / step) * step; + extent[1] = Math.floor(extent[1] / step) * step + step * .5; + extent[2] = step; + return extent; + } + function d3_scale_linearTicks(domain, m) { + return d3.range.apply(d3, d3_scale_linearTickRange(domain, m)); + } + function d3_scale_linearTickFormat(domain, m) { + return d3.format(",." + Math.max(0, -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01)) + "f"); + } + function d3_scale_bilinear(domain, range, uninterpolate, interpolate) { + var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]); + return function(x) { + return i(u(x)); + }; + } + function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { + var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1; + if (domain[k] < domain[0]) { + domain = domain.slice().reverse(); + range = range.slice().reverse(); + } + while (++j <= k) { + u.push(uninterpolate(domain[j - 1], domain[j])); + i.push(interpolate(range[j - 1], range[j])); + } + return function(x) { + var j = d3.bisect(domain, x, 1, k) - 1; + return i[j](u[j](x)); + }; + } + function d3_scale_log(linear, log) { + function scale(x) { + return linear(log(x)); + } + var pow = log.pow; + scale.invert = function(x) { + return pow(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(pow); + log = x[0] < 0 ? d3_scale_logn : d3_scale_logp; + pow = log.pow; + linear.domain(x.map(log)); + return scale; + }; + scale.nice = function() { + linear.domain(d3_scale_nice(linear.domain(), d3_scale_niceDefault)); + return scale; + }; + scale.ticks = function() { + var extent = d3_scaleExtent(linear.domain()), ticks = []; + if (extent.every(isFinite)) { + var i = Math.floor(extent[0]), j = Math.ceil(extent[1]), u = pow(extent[0]), v = pow(extent[1]); + if (log === d3_scale_logn) { + ticks.push(pow(i)); + for (; i++ < j; ) for (var k = 9; k > 0; k--) ticks.push(pow(i) * k); + } else { + for (; i < j; i++) for (var k = 1; k < 10; k++) ticks.push(pow(i) * k); + ticks.push(pow(i)); + } + for (i = 0; ticks[i] < u; i++) {} + for (j = ticks.length; ticks[j - 1] > v; j--) {} + ticks = ticks.slice(i, j); + } + return ticks; + }; + scale.tickFormat = function(n, format) { + if (arguments.length < 2) format = d3_scale_logFormat; + if (arguments.length < 1) return format; + var k = Math.max(.1, n / scale.ticks().length), f = log === d3_scale_logn ? (e = -1e-12, Math.floor) : (e = 1e-12, Math.ceil), e; + return function(d) { + return d / pow(f(log(d) + e)) <= k ? format(d) : ""; + }; + }; + scale.copy = function() { + return d3_scale_log(linear.copy(), log); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_scale_logp(x) { + return Math.log(x < 0 ? 0 : x) / Math.LN10; + } + function d3_scale_logn(x) { + return -Math.log(x > 0 ? 0 : -x) / Math.LN10; + } + function d3_scale_pow(linear, exponent) { + function scale(x) { + return linear(powp(x)); + } + var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent); + scale.invert = function(x) { + return powb(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(powb); + linear.domain(x.map(powp)); + return scale; + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(scale.domain(), m); + }; + scale.tickFormat = function(m) { + return d3_scale_linearTickFormat(scale.domain(), m); + }; + scale.nice = function() { + return scale.domain(d3_scale_nice(scale.domain(), d3_scale_linearNice)); + }; + scale.exponent = function(x) { + if (!arguments.length) return exponent; + var domain = scale.domain(); + powp = d3_scale_powPow(exponent = x); + powb = d3_scale_powPow(1 / exponent); + return scale.domain(domain); + }; + scale.copy = function() { + return d3_scale_pow(linear.copy(), exponent); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_scale_powPow(e) { + return function(x) { + return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e); + }; + } + function d3_scale_ordinal(domain, ranger) { + function scale(x) { + return range[((index.get(x) || index.set(x, domain.push(x))) - 1) % range.length]; + } + function steps(start, step) { + return d3.range(domain.length).map(function(i) { + return start + step * i; + }); + } + var index, range, rangeBand; + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = []; + index = new d3_Map; + var i = -1, n = x.length, xi; + while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); + return scale[ranger.t].apply(scale, ranger.a); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + rangeBand = 0; + ranger = { + t: "range", + a: arguments + }; + return scale; + }; + scale.rangePoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = (stop - start) / (Math.max(1, domain.length - 1) + padding); + range = steps(domain.length < 2 ? (start + stop) / 2 : start + step * padding / 2, step); + rangeBand = 0; + ranger = { + t: "rangePoints", + a: arguments + }; + return scale; + }; + scale.rangeBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding); + range = steps(start + step * outerPadding, step); + if (reverse) range.reverse(); + rangeBand = step * (1 - padding); + ranger = { + t: "rangeBands", + a: arguments + }; + return scale; + }; + scale.rangeRoundBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)), error = stop - start - (domain.length - padding) * step; + range = steps(start + Math.round(error / 2), step); + if (reverse) range.reverse(); + rangeBand = Math.round(step * (1 - padding)); + ranger = { + t: "rangeRoundBands", + a: arguments + }; + return scale; + }; + scale.rangeBand = function() { + return rangeBand; + }; + scale.rangeExtent = function() { + return d3_scaleExtent(ranger.a[0]); + }; + scale.copy = function() { + return d3_scale_ordinal(domain, ranger); + }; + return scale.domain(domain); + } + function d3_scale_quantile(domain, range) { + function rescale() { + var k = 0, n = domain.length, q = range.length; + thresholds = []; + while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q); + return scale; + } + function scale(x) { + if (isNaN(x = +x)) return NaN; + return range[d3.bisect(thresholds, x)]; + } + var thresholds; + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.filter(function(d) { + return !isNaN(d); + }).sort(d3.ascending); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.quantiles = function() { + return thresholds; + }; + scale.copy = function() { + return d3_scale_quantile(domain, range); + }; + return rescale(); + } + function d3_scale_quantize(x0, x1, range) { + function scale(x) { + return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; + } + function rescale() { + kx = range.length / (x1 - x0); + i = range.length - 1; + return scale; + } + var kx, i; + scale.domain = function(x) { + if (!arguments.length) return [ x0, x1 ]; + x0 = +x[0]; + x1 = +x[x.length - 1]; + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.copy = function() { + return d3_scale_quantize(x0, x1, range); + }; + return rescale(); + } + function d3_scale_threshold(domain, range) { + function scale(x) { + return range[d3.bisect(domain, x)]; + } + scale.domain = function(_) { + if (!arguments.length) return domain; + domain = _; + return scale; + }; + scale.range = function(_) { + if (!arguments.length) return range; + range = _; + return scale; + }; + scale.copy = function() { + return d3_scale_threshold(domain, range); + }; + return scale; + } + function d3_scale_identity(domain) { + function identity(x) { + return +x; + } + identity.invert = identity; + identity.domain = identity.range = function(x) { + if (!arguments.length) return domain; + domain = x.map(identity); + return identity; + }; + identity.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + identity.tickFormat = function(m) { + return d3_scale_linearTickFormat(domain, m); + }; + identity.copy = function() { + return d3_scale_identity(domain); + }; + return identity; + } + function d3_svg_arcInnerRadius(d) { + return d.innerRadius; + } + function d3_svg_arcOuterRadius(d) { + return d.outerRadius; + } + function d3_svg_arcStartAngle(d) { + return d.startAngle; + } + function d3_svg_arcEndAngle(d) { + return d.endAngle; + } + function d3_svg_line(projection) { + function line(data) { + function segment() { + segments.push("M", interpolate(projection(points), tension)); + } + var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y); + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]); + } else if (points.length) { + segment(); + points = []; + } + } + if (points.length) segment(); + return segments.length ? segments.join("") : null; + } + var x = d3_svg_lineX, y = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7; + line.x = function(_) { + if (!arguments.length) return x; + x = _; + return line; + }; + line.y = function(_) { + if (!arguments.length) return y; + y = _; + return line; + }; + line.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return line; + }; + line.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + return line; + }; + line.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return line; + }; + return line; + } + function d3_svg_lineX(d) { + return d[0]; + } + function d3_svg_lineY(d) { + return d[1]; + } + function d3_svg_lineLinear(points) { + return points.join("L"); + } + function d3_svg_lineLinearClosed(points) { + return d3_svg_lineLinear(points) + "Z"; + } + function d3_svg_lineStepBefore(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); + return path.join(""); + } + function d3_svg_lineStepAfter(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); + return path.join(""); + } + function d3_svg_lineCardinalOpen(points, tension) { + return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineCardinalClosed(points, tension) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension)); + } + function d3_svg_lineCardinal(points, tension, closed) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineHermite(points, tangents) { + if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) { + return d3_svg_lineLinear(points); + } + var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1; + if (quad) { + path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1]; + p0 = points[1]; + pi = 2; + } + if (tangents.length > 1) { + t = tangents[1]; + p = points[pi]; + pi++; + path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + for (var i = 2; i < tangents.length; i++, pi++) { + p = points[pi]; + t = tangents[i]; + path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + } + } + if (quad) { + var lp = points[pi]; + path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1]; + } + return path; + } + function d3_svg_lineCardinalTangents(points, tension) { + var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length; + while (++i < n) { + p0 = p1; + p1 = p2; + p2 = points[i]; + tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]); + } + return tangents; + } + function d3_svg_lineBasis(points) { + if (points.length < 3) return d3_svg_lineLinear(points); + var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0 ]; + d3_svg_lineBasisBezier(path, px, py); + while (++i < n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + i = -1; + while (++i < 2) { + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBasisOpen(points) { + if (points.length < 4) return d3_svg_lineLinear(points); + var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ]; + while (++i < 3) { + pi = points[i]; + px.push(pi[0]); + py.push(pi[1]); + } + path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); + --i; + while (++i < n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBasisClosed(points) { + var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = []; + while (++i < 4) { + pi = points[i % n]; + px.push(pi[0]); + py.push(pi[1]); + } + path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + --i; + while (++i < m) { + pi = points[i % n]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBundle(points, tension) { + var n = points.length - 1; + if (n) { + var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t; + while (++i <= n) { + p = points[i]; + t = i / n; + p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); + p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); + } + } + return d3_svg_lineBasis(points); + } + function d3_svg_lineDot4(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; + } + function d3_svg_lineBasisBezier(path, x, y) { + path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); + } + function d3_svg_lineSlope(p0, p1) { + return (p1[1] - p0[1]) / (p1[0] - p0[0]); + } + function d3_svg_lineFiniteDifferences(points) { + var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1); + while (++i < j) { + m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2; + } + m[i] = d; + return m; + } + function d3_svg_lineMonotoneTangents(points) { + var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1; + while (++i < j) { + d = d3_svg_lineSlope(points[i], points[i + 1]); + if (Math.abs(d) < 1e-6) { + m[i] = m[i + 1] = 0; + } else { + a = m[i] / d; + b = m[i + 1] / d; + s = a * a + b * b; + if (s > 9) { + s = d * 3 / Math.sqrt(s); + m[i] = s * a; + m[i + 1] = s * b; + } + } + } + i = -1; + while (++i <= j) { + s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i])); + tangents.push([ s || 0, m[i] * s || 0 ]); + } + return tangents; + } + function d3_svg_lineMonotone(points) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); + } + function d3_svg_lineRadial(points) { + var point, i = -1, n = points.length, r, a; + while (++i < n) { + point = points[i]; + r = point[0]; + a = point[1] + d3_svg_arcOffset; + point[0] = r * Math.cos(a); + point[1] = r * Math.sin(a); + } + return points; + } + function d3_svg_area(projection) { + function area(data) { + function segment() { + segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z"); + } + var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() { + return x; + } : d3_functor(x1), fy1 = y0 === y1 ? function() { + return y; + } : d3_functor(y1), x, y; + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]); + points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]); + } else if (points0.length) { + segment(); + points0 = []; + points1 = []; + } + } + if (points0.length) segment(); + return segments.length ? segments.join("") : null; + } + var x0 = d3_svg_lineX, x1 = d3_svg_lineX, y0 = 0, y1 = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7; + area.x = function(_) { + if (!arguments.length) return x1; + x0 = x1 = _; + return area; + }; + area.x0 = function(_) { + if (!arguments.length) return x0; + x0 = _; + return area; + }; + area.x1 = function(_) { + if (!arguments.length) return x1; + x1 = _; + return area; + }; + area.y = function(_) { + if (!arguments.length) return y1; + y0 = y1 = _; + return area; + }; + area.y0 = function(_) { + if (!arguments.length) return y0; + y0 = _; + return area; + }; + area.y1 = function(_) { + if (!arguments.length) return y1; + y1 = _; + return area; + }; + area.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return area; + }; + area.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + interpolateReverse = interpolate.reverse || interpolate; + L = interpolate.closed ? "M" : "L"; + return area; + }; + area.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return area; + }; + return area; + } + function d3_svg_chordSource(d) { + return d.source; + } + function d3_svg_chordTarget(d) { + return d.target; + } + function d3_svg_chordRadius(d) { + return d.radius; + } + function d3_svg_chordStartAngle(d) { + return d.startAngle; + } + function d3_svg_chordEndAngle(d) { + return d.endAngle; + } + function d3_svg_diagonalProjection(d) { + return [ d.x, d.y ]; + } + function d3_svg_diagonalRadialProjection(projection) { + return function() { + var d = projection.apply(this, arguments), r = d[0], a = d[1] + d3_svg_arcOffset; + return [ r * Math.cos(a), r * Math.sin(a) ]; + }; + } + function d3_svg_symbolSize() { + return 64; + } + function d3_svg_symbolType() { + return "circle"; + } + function d3_svg_symbolCircle(size) { + var r = Math.sqrt(size / Math.PI); + return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z"; + } + function d3_svg_axisX(selection, x) { + selection.attr("transform", function(d) { + return "translate(" + x(d) + ",0)"; + }); + } + function d3_svg_axisY(selection, y) { + selection.attr("transform", function(d) { + return "translate(0," + y(d) + ")"; + }); + } + function d3_svg_axisSubdivide(scale, ticks, m) { + subticks = []; + if (m && ticks.length > 1) { + var extent = d3_scaleExtent(scale.domain()), subticks, i = -1, n = ticks.length, d = (ticks[1] - ticks[0]) / ++m, j, v; + while (++i < n) { + for (j = m; --j > 0; ) { + if ((v = +ticks[i] - j * d) >= extent[0]) { + subticks.push(v); + } + } + } + for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1]; ) { + subticks.push(v); + } + } + return subticks; + } + function d3_behavior_zoomDelta() { + if (!d3_behavior_zoomDiv) { + d3_behavior_zoomDiv = d3.select("body").append("div").style("visibility", "hidden").style("top", 0).style("height", 0).style("width", 0).style("overflow-y", "scroll").append("div").style("height", "2000px").node().parentNode; + } + var e = d3.event, delta; + try { + d3_behavior_zoomDiv.scrollTop = 1e3; + d3_behavior_zoomDiv.dispatchEvent(e); + delta = 1e3 - d3_behavior_zoomDiv.scrollTop; + } catch (error) { + delta = e.wheelDelta || -e.detail * 5; + } + return delta; + } + function d3_layout_bundlePath(link) { + var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ]; + while (start !== lca) { + start = start.parent; + points.push(start); + } + var k = points.length; + while (end !== lca) { + points.splice(k, 0, end); + end = end.parent; + } + return points; + } + function d3_layout_bundleAncestors(node) { + var ancestors = [], parent = node.parent; + while (parent != null) { + ancestors.push(node); + node = parent; + parent = parent.parent; + } + ancestors.push(node); + return ancestors; + } + function d3_layout_bundleLeastCommonAncestor(a, b) { + if (a === b) return a; + var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null; + while (aNode === bNode) { + sharedNode = aNode; + aNode = aNodes.pop(); + bNode = bNodes.pop(); + } + return sharedNode; + } + function d3_layout_forceDragstart(d) { + d.fixed |= 2; + } + function d3_layout_forceDragend(d) { + d.fixed &= 1; + } + function d3_layout_forceMouseover(d) { + d.fixed |= 4; + } + function d3_layout_forceMouseout(d) { + d.fixed &= 3; + } + function d3_layout_forceAccumulate(quad, alpha, charges) { + var cx = 0, cy = 0; + quad.charge = 0; + if (!quad.leaf) { + var nodes = quad.nodes, n = nodes.length, i = -1, c; + while (++i < n) { + c = nodes[i]; + if (c == null) continue; + d3_layout_forceAccumulate(c, alpha, charges); + quad.charge += c.charge; + cx += c.charge * c.cx; + cy += c.charge * c.cy; + } + } + if (quad.point) { + if (!quad.leaf) { + quad.point.x += Math.random() - .5; + quad.point.y += Math.random() - .5; + } + var k = alpha * charges[quad.point.index]; + quad.charge += quad.pointCharge = k; + cx += k * quad.point.x; + cy += k * quad.point.y; + } + quad.cx = cx / quad.charge; + quad.cy = cy / quad.charge; + } + function d3_layout_forceLinkDistance(link) { + return 20; + } + function d3_layout_forceLinkStrength(link) { + return 1; + } + function d3_layout_stackX(d) { + return d.x; + } + function d3_layout_stackY(d) { + return d.y; + } + function d3_layout_stackOut(d, y0, y) { + d.y0 = y0; + d.y = y; + } + function d3_layout_stackOrderDefault(data) { + return d3.range(data.length); + } + function d3_layout_stackOffsetZero(data) { + var j = -1, m = data[0].length, y0 = []; + while (++j < m) y0[j] = 0; + return y0; + } + function d3_layout_stackMaxIndex(array) { + var i = 1, j = 0, v = array[0][1], k, n = array.length; + for (; i < n; ++i) { + if ((k = array[i][1]) > v) { + j = i; + v = k; + } + } + return j; + } + function d3_layout_stackReduceSum(d) { + return d.reduce(d3_layout_stackSum, 0); + } + function d3_layout_stackSum(p, d) { + return p + d[1]; + } + function d3_layout_histogramBinSturges(range, values) { + return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); + } + function d3_layout_histogramBinFixed(range, n) { + var x = -1, b = +range[0], m = (range[1] - b) / n, f = []; + while (++x <= n) f[x] = m * x + b; + return f; + } + function d3_layout_histogramRange(values) { + return [ d3.min(values), d3.max(values) ]; + } + function d3_layout_hierarchyRebind(object, hierarchy) { + d3.rebind(object, hierarchy, "sort", "children", "value"); + object.links = d3_layout_hierarchyLinks; + object.nodes = function(d) { + d3_layout_hierarchyInline = true; + return (object.nodes = object)(d); + }; + return object; + } + function d3_layout_hierarchyChildren(d) { + return d.children; + } + function d3_layout_hierarchyValue(d) { + return d.value; + } + function d3_layout_hierarchySort(a, b) { + return b.value - a.value; + } + function d3_layout_hierarchyLinks(nodes) { + return d3.merge(nodes.map(function(parent) { + return (parent.children || []).map(function(child) { + return { + source: parent, + target: child + }; + }); + })); + } + function d3_layout_packSort(a, b) { + return a.value - b.value; + } + function d3_layout_packInsert(a, b) { + var c = a._pack_next; + a._pack_next = b; + b._pack_prev = a; + b._pack_next = c; + c._pack_prev = b; + } + function d3_layout_packSplice(a, b) { + a._pack_next = b; + b._pack_prev = a; + } + function d3_layout_packIntersects(a, b) { + var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r; + return dr * dr - dx * dx - dy * dy > .001; + } + function d3_layout_packSiblings(node) { + function bound(node) { + xMin = Math.min(node.x - node.r, xMin); + xMax = Math.max(node.x + node.r, xMax); + yMin = Math.min(node.y - node.r, yMin); + yMax = Math.max(node.y + node.r, yMax); + } + if (!(nodes = node.children) || !(n = nodes.length)) return; + var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n; + nodes.forEach(d3_layout_packLink); + a = nodes[0]; + a.x = -a.r; + a.y = 0; + bound(a); + if (n > 1) { + b = nodes[1]; + b.x = b.r; + b.y = 0; + bound(b); + if (n > 2) { + c = nodes[2]; + d3_layout_packPlace(a, b, c); + bound(c); + d3_layout_packInsert(a, c); + a._pack_prev = c; + d3_layout_packInsert(c, b); + b = a._pack_next; + for (i = 3; i < n; i++) { + d3_layout_packPlace(a, b, c = nodes[i]); + var isect = 0, s1 = 1, s2 = 1; + for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { + if (d3_layout_packIntersects(j, c)) { + isect = 1; + break; + } + } + if (isect == 1) { + for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { + if (d3_layout_packIntersects(k, c)) { + break; + } + } + } + if (isect) { + if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b); + i--; + } else { + d3_layout_packInsert(a, c); + b = c; + bound(c); + } + } + } + } + var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0; + for (i = 0; i < n; i++) { + c = nodes[i]; + c.x -= cx; + c.y -= cy; + cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y)); + } + node.r = cr; + nodes.forEach(d3_layout_packUnlink); + } + function d3_layout_packLink(node) { + node._pack_next = node._pack_prev = node; + } + function d3_layout_packUnlink(node) { + delete node._pack_next; + delete node._pack_prev; + } + function d3_layout_packTransform(node, x, y, k) { + var children = node.children; + node.x = x += k * node.x; + node.y = y += k * node.y; + node.r *= k; + if (children) { + var i = -1, n = children.length; + while (++i < n) d3_layout_packTransform(children[i], x, y, k); + } + } + function d3_layout_packPlace(a, b, c) { + var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y; + if (db && (dx || dy)) { + var da = b.r + c.r, dc = dx * dx + dy * dy; + da *= da; + db *= db; + var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc); + c.x = a.x + x * dx + y * dy; + c.y = a.y + x * dy - y * dx; + } else { + c.x = a.x + db; + c.y = a.y; + } + } + function d3_layout_clusterY(children) { + return 1 + d3.max(children, function(child) { + return child.y; + }); + } + function d3_layout_clusterX(children) { + return children.reduce(function(x, child) { + return x + child.x; + }, 0) / children.length; + } + function d3_layout_clusterLeft(node) { + var children = node.children; + return children && children.length ? d3_layout_clusterLeft(children[0]) : node; + } + function d3_layout_clusterRight(node) { + var children = node.children, n; + return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; + } + function d3_layout_treeSeparation(a, b) { + return a.parent == b.parent ? 1 : 2; + } + function d3_layout_treeLeft(node) { + var children = node.children; + return children && children.length ? children[0] : node._tree.thread; + } + function d3_layout_treeRight(node) { + var children = node.children, n; + return children && (n = children.length) ? children[n - 1] : node._tree.thread; + } + function d3_layout_treeSearch(node, compare) { + var children = node.children; + if (children && (n = children.length)) { + var child, n, i = -1; + while (++i < n) { + if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) { + node = child; + } + } + } + return node; + } + function d3_layout_treeRightmost(a, b) { + return a.x - b.x; + } + function d3_layout_treeLeftmost(a, b) { + return b.x - a.x; + } + function d3_layout_treeDeepest(a, b) { + return a.depth - b.depth; + } + function d3_layout_treeVisitAfter(node, callback) { + function visit(node, previousSibling) { + var children = node.children; + if (children && (n = children.length)) { + var child, previousChild = null, i = -1, n; + while (++i < n) { + child = children[i]; + visit(child, previousChild); + previousChild = child; + } + } + callback(node, previousSibling); + } + visit(node, null); + } + function d3_layout_treeShift(node) { + var shift = 0, change = 0, children = node.children, i = children.length, child; + while (--i >= 0) { + child = children[i]._tree; + child.prelim += shift; + child.mod += shift; + shift += child.shift + (change += child.change); + } + } + function d3_layout_treeMove(ancestor, node, shift) { + ancestor = ancestor._tree; + node = node._tree; + var change = shift / (node.number - ancestor.number); + ancestor.change += change; + node.change -= change; + node.shift += shift; + node.prelim += shift; + node.mod += shift; + } + function d3_layout_treeAncestor(vim, node, ancestor) { + return vim._tree.ancestor.parent == node.parent ? vim._tree.ancestor : ancestor; + } + function d3_layout_treemapPadNull(node) { + return { + x: node.x, + y: node.y, + dx: node.dx, + dy: node.dy + }; + } + function d3_layout_treemapPad(node, padding) { + var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2]; + if (dx < 0) { + x += dx / 2; + dx = 0; + } + if (dy < 0) { + y += dy / 2; + dy = 0; + } + return { + x: x, + y: y, + dx: dx, + dy: dy + }; + } + function d3_dsv(delimiter, mimeType) { + function dsv(url, callback) { + d3.text(url, mimeType, function(text) { + callback(text && dsv.parse(text)); + }); + } + function formatRow(row) { + return row.map(formatValue).join(delimiter); + } + function formatValue(text) { + return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text; + } + var reParse = new RegExp("\r\n|[" + delimiter + "\r\n]", "g"), reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0); + dsv.parse = function(text) { + var header; + return dsv.parseRows(text, function(row, i) { + if (i) { + var o = {}, j = -1, m = header.length; + while (++j < m) o[header[j]] = row[j]; + return o; + } else { + header = row; + return null; + } + }); + }; + dsv.parseRows = function(text, f) { + function token() { + if (reParse.lastIndex >= text.length) return EOF; + if (eol) { + eol = false; + return EOL; + } + var j = reParse.lastIndex; + if (text.charCodeAt(j) === 34) { + var i = j; + while (i++ < text.length) { + if (text.charCodeAt(i) === 34) { + if (text.charCodeAt(i + 1) !== 34) break; + i++; + } + } + reParse.lastIndex = i + 2; + var c = text.charCodeAt(i + 1); + if (c === 13) { + eol = true; + if (text.charCodeAt(i + 2) === 10) reParse.lastIndex++; + } else if (c === 10) { + eol = true; + } + return text.substring(j + 1, i).replace(/""/g, '"'); + } + var m = reParse.exec(text); + if (m) { + eol = m[0].charCodeAt(0) !== delimiterCode; + return text.substring(j, m.index); + } + reParse.lastIndex = text.length; + return text.substring(j); + } + var EOL = {}, EOF = {}, rows = [], n = 0, t, eol; + reParse.lastIndex = 0; + while ((t = token()) !== EOF) { + var a = []; + while (t !== EOL && t !== EOF) { + a.push(t); + t = token(); + } + if (f && !(a = f(a, n++))) continue; + rows.push(a); + } + return rows; + }; + dsv.format = function(rows) { + return rows.map(formatRow).join("\n"); + }; + return dsv; + } + function d3_geo_type(types, defaultValue) { + return function(object) { + return object && types.hasOwnProperty(object.type) ? types[object.type](object) : defaultValue; + }; + } + function d3_path_circle(radius) { + return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + +2 * radius + "z"; + } + function d3_geo_bounds(o, f) { + if (d3_geo_boundsTypes.hasOwnProperty(o.type)) d3_geo_boundsTypes[o.type](o, f); + } + function d3_geo_boundsFeature(o, f) { + d3_geo_bounds(o.geometry, f); + } + function d3_geo_boundsFeatureCollection(o, f) { + for (var a = o.features, i = 0, n = a.length; i < n; i++) { + d3_geo_bounds(a[i].geometry, f); + } + } + function d3_geo_boundsGeometryCollection(o, f) { + for (var a = o.geometries, i = 0, n = a.length; i < n; i++) { + d3_geo_bounds(a[i], f); + } + } + function d3_geo_boundsLineString(o, f) { + for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { + f.apply(null, a[i]); + } + } + function d3_geo_boundsMultiLineString(o, f) { + for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { + for (var b = a[i], j = 0, m = b.length; j < m; j++) { + f.apply(null, b[j]); + } + } + } + function d3_geo_boundsMultiPolygon(o, f) { + for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { + for (var b = a[i][0], j = 0, m = b.length; j < m; j++) { + f.apply(null, b[j]); + } + } + } + function d3_geo_boundsPoint(o, f) { + f.apply(null, o.coordinates); + } + function d3_geo_boundsPolygon(o, f) { + for (var a = o.coordinates[0], i = 0, n = a.length; i < n; i++) { + f.apply(null, a[i]); + } + } + function d3_geo_greatArcSource(d) { + return d.source; + } + function d3_geo_greatArcTarget(d) { + return d.target; + } + function d3_geo_greatArcInterpolator() { + function interpolate(t) { + var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; + return [ Math.atan2(y, x) / d3_geo_radians, Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_geo_radians ]; + } + var x0, y0, cy0, sy0, kx0, ky0, x1, y1, cy1, sy1, kx1, ky1, d, k; + interpolate.distance = function() { + if (d == null) k = 1 / Math.sin(d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0))))); + return d; + }; + interpolate.source = function(_) { + var cx0 = Math.cos(x0 = _[0] * d3_geo_radians), sx0 = Math.sin(x0); + cy0 = Math.cos(y0 = _[1] * d3_geo_radians); + sy0 = Math.sin(y0); + kx0 = cy0 * cx0; + ky0 = cy0 * sx0; + d = null; + return interpolate; + }; + interpolate.target = function(_) { + var cx1 = Math.cos(x1 = _[0] * d3_geo_radians), sx1 = Math.sin(x1); + cy1 = Math.cos(y1 = _[1] * d3_geo_radians); + sy1 = Math.sin(y1); + kx1 = cy1 * cx1; + ky1 = cy1 * sx1; + d = null; + return interpolate; + }; + return interpolate; + } + function d3_geo_greatArcInterpolate(a, b) { + var i = d3_geo_greatArcInterpolator().source(a).target(b); + i.distance(); + return i; + } + function d3_geom_contourStart(grid) { + var x = 0, y = 0; + while (true) { + if (grid(x, y)) { + return [ x, y ]; + } + if (x === 0) { + x = y + 1; + y = 0; + } else { + x = x - 1; + y = y + 1; + } + } + } + function d3_geom_hullCCW(i1, i2, i3, v) { + var t, a, b, c, d, e, f; + t = v[i1]; + a = t[0]; + b = t[1]; + t = v[i2]; + c = t[0]; + d = t[1]; + t = v[i3]; + e = t[0]; + f = t[1]; + return (f - b) * (c - a) - (d - b) * (e - a) > 0; + } + function d3_geom_polygonInside(p, a, b) { + return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); + } + function d3_geom_polygonIntersect(c, d, a, b) { + var x1 = c[0], x2 = d[0], x3 = a[0], x4 = b[0], y1 = c[1], y2 = d[1], y3 = a[1], y4 = b[1], x13 = x1 - x3, x21 = x2 - x1, x43 = x4 - x3, y13 = y1 - y3, y21 = y2 - y1, y43 = y4 - y3, ua = (x43 * y13 - y43 * x13) / (y43 * x21 - x43 * y21); + return [ x1 + ua * x21, y1 + ua * y21 ]; + } + function d3_voronoi_tessellate(vertices, callback) { + var Sites = { + list: vertices.map(function(v, i) { + return { + index: i, + x: v[0], + y: v[1] + }; + }).sort(function(a, b) { + return a.y < b.y ? -1 : a.y > b.y ? 1 : a.x < b.x ? -1 : a.x > b.x ? 1 : 0; + }), + bottomSite: null + }; + var EdgeList = { + list: [], + leftEnd: null, + rightEnd: null, + init: function() { + EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l"); + EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l"); + EdgeList.leftEnd.r = EdgeList.rightEnd; + EdgeList.rightEnd.l = EdgeList.leftEnd; + EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd); + }, + createHalfEdge: function(edge, side) { + return { + edge: edge, + side: side, + vertex: null, + l: null, + r: null + }; + }, + insert: function(lb, he) { + he.l = lb; + he.r = lb.r; + lb.r.l = he; + lb.r = he; + }, + leftBound: function(p) { + var he = EdgeList.leftEnd; + do { + he = he.r; + } while (he != EdgeList.rightEnd && Geom.rightOf(he, p)); + he = he.l; + return he; + }, + del: function(he) { + he.l.r = he.r; + he.r.l = he.l; + he.edge = null; + }, + right: function(he) { + return he.r; + }, + left: function(he) { + return he.l; + }, + leftRegion: function(he) { + return he.edge == null ? Sites.bottomSite : he.edge.region[he.side]; + }, + rightRegion: function(he) { + return he.edge == null ? Sites.bottomSite : he.edge.region[d3_voronoi_opposite[he.side]]; + } + }; + var Geom = { + bisect: function(s1, s2) { + var newEdge = { + region: { + l: s1, + r: s2 + }, + ep: { + l: null, + r: null + } + }; + var dx = s2.x - s1.x, dy = s2.y - s1.y, adx = dx > 0 ? dx : -dx, ady = dy > 0 ? dy : -dy; + newEdge.c = s1.x * dx + s1.y * dy + (dx * dx + dy * dy) * .5; + if (adx > ady) { + newEdge.a = 1; + newEdge.b = dy / dx; + newEdge.c /= dx; + } else { + newEdge.b = 1; + newEdge.a = dx / dy; + newEdge.c /= dy; + } + return newEdge; + }, + intersect: function(el1, el2) { + var e1 = el1.edge, e2 = el2.edge; + if (!e1 || !e2 || e1.region.r == e2.region.r) { + return null; + } + var d = e1.a * e2.b - e1.b * e2.a; + if (Math.abs(d) < 1e-10) { + return null; + } + var xint = (e1.c * e2.b - e2.c * e1.b) / d, yint = (e2.c * e1.a - e1.c * e2.a) / d, e1r = e1.region.r, e2r = e2.region.r, el, e; + if (e1r.y < e2r.y || e1r.y == e2r.y && e1r.x < e2r.x) { + el = el1; + e = e1; + } else { + el = el2; + e = e2; + } + var rightOfSite = xint >= e.region.r.x; + if (rightOfSite && el.side === "l" || !rightOfSite && el.side === "r") { + return null; + } + return { + x: xint, + y: yint + }; + }, + rightOf: function(he, p) { + var e = he.edge, topsite = e.region.r, rightOfSite = p.x > topsite.x; + if (rightOfSite && he.side === "l") { + return 1; + } + if (!rightOfSite && he.side === "r") { + return 0; + } + if (e.a === 1) { + var dyp = p.y - topsite.y, dxp = p.x - topsite.x, fast = 0, above = 0; + if (!rightOfSite && e.b < 0 || rightOfSite && e.b >= 0) { + above = fast = dyp >= e.b * dxp; + } else { + above = p.x + p.y * e.b > e.c; + if (e.b < 0) { + above = !above; + } + if (!above) { + fast = 1; + } + } + if (!fast) { + var dxs = topsite.x - e.region.l.x; + above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b); + if (e.b < 0) { + above = !above; + } + } + } else { + var yl = e.c - e.a * p.x, t1 = p.y - yl, t2 = p.x - topsite.x, t3 = yl - topsite.y; + above = t1 * t1 > t2 * t2 + t3 * t3; + } + return he.side === "l" ? above : !above; + }, + endPoint: function(edge, side, site) { + edge.ep[side] = site; + if (!edge.ep[d3_voronoi_opposite[side]]) return; + callback(edge); + }, + distance: function(s, t) { + var dx = s.x - t.x, dy = s.y - t.y; + return Math.sqrt(dx * dx + dy * dy); + } + }; + var EventQueue = { + list: [], + insert: function(he, site, offset) { + he.vertex = site; + he.ystar = site.y + offset; + for (var i = 0, list = EventQueue.list, l = list.length; i < l; i++) { + var next = list[i]; + if (he.ystar > next.ystar || he.ystar == next.ystar && site.x > next.vertex.x) { + continue; + } else { + break; + } + } + list.splice(i, 0, he); + }, + del: function(he) { + for (var i = 0, ls = EventQueue.list, l = ls.length; i < l && ls[i] != he; ++i) {} + ls.splice(i, 1); + }, + empty: function() { + return EventQueue.list.length === 0; + }, + nextEvent: function(he) { + for (var i = 0, ls = EventQueue.list, l = ls.length; i < l; ++i) { + if (ls[i] == he) return ls[i + 1]; + } + return null; + }, + min: function() { + var elem = EventQueue.list[0]; + return { + x: elem.vertex.x, + y: elem.ystar + }; + }, + extractMin: function() { + return EventQueue.list.shift(); + } + }; + EdgeList.init(); + Sites.bottomSite = Sites.list.shift(); + var newSite = Sites.list.shift(), newIntStar; + var lbnd, rbnd, llbnd, rrbnd, bisector; + var bot, top, temp, p, v; + var e, pm; + while (true) { + if (!EventQueue.empty()) { + newIntStar = EventQueue.min(); + } + if (newSite && (EventQueue.empty() || newSite.y < newIntStar.y || newSite.y == newIntStar.y && newSite.x < newIntStar.x)) { + lbnd = EdgeList.leftBound(newSite); + rbnd = EdgeList.right(lbnd); + bot = EdgeList.rightRegion(lbnd); + e = Geom.bisect(bot, newSite); + bisector = EdgeList.createHalfEdge(e, "l"); + EdgeList.insert(lbnd, bisector); + p = Geom.intersect(lbnd, bisector); + if (p) { + EventQueue.del(lbnd); + EventQueue.insert(lbnd, p, Geom.distance(p, newSite)); + } + lbnd = bisector; + bisector = EdgeList.createHalfEdge(e, "r"); + EdgeList.insert(lbnd, bisector); + p = Geom.intersect(bisector, rbnd); + if (p) { + EventQueue.insert(bisector, p, Geom.distance(p, newSite)); + } + newSite = Sites.list.shift(); + } else if (!EventQueue.empty()) { + lbnd = EventQueue.extractMin(); + llbnd = EdgeList.left(lbnd); + rbnd = EdgeList.right(lbnd); + rrbnd = EdgeList.right(rbnd); + bot = EdgeList.leftRegion(lbnd); + top = EdgeList.rightRegion(rbnd); + v = lbnd.vertex; + Geom.endPoint(lbnd.edge, lbnd.side, v); + Geom.endPoint(rbnd.edge, rbnd.side, v); + EdgeList.del(lbnd); + EventQueue.del(rbnd); + EdgeList.del(rbnd); + pm = "l"; + if (bot.y > top.y) { + temp = bot; + bot = top; + top = temp; + pm = "r"; + } + e = Geom.bisect(bot, top); + bisector = EdgeList.createHalfEdge(e, pm); + EdgeList.insert(llbnd, bisector); + Geom.endPoint(e, d3_voronoi_opposite[pm], v); + p = Geom.intersect(llbnd, bisector); + if (p) { + EventQueue.del(llbnd); + EventQueue.insert(llbnd, p, Geom.distance(p, bot)); + } + p = Geom.intersect(bisector, rrbnd); + if (p) { + EventQueue.insert(bisector, p, Geom.distance(p, bot)); + } + } else { + break; + } + } + for (lbnd = EdgeList.right(EdgeList.leftEnd); lbnd != EdgeList.rightEnd; lbnd = EdgeList.right(lbnd)) { + callback(lbnd.edge); + } + } + function d3_geom_quadtreeNode() { + return { + leaf: true, + nodes: [], + point: null + }; + } + function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { + if (!f(node, x1, y1, x2, y2)) { + var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes; + if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); + if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); + if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); + if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); + } + } + function d3_geom_quadtreePoint(p) { + return { + x: p[0], + y: p[1] + }; + } + function d3_time_utc() { + this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]); + } + function d3_time_formatAbbreviate(name) { + return name.substring(0, 3); + } + function d3_time_parse(date, template, string, j) { + var c, p, i = 0, n = template.length, m = string.length; + while (i < n) { + if (j >= m) return -1; + c = template.charCodeAt(i++); + if (c == 37) { + p = d3_time_parsers[template.charAt(i++)]; + if (!p || (j = p(date, string, j)) < 0) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + return j; + } + function d3_time_formatRe(names) { + return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i"); + } + function d3_time_formatLookup(names) { + var map = new d3_Map, i = -1, n = names.length; + while (++i < n) map.set(names[i].toLowerCase(), i); + return map; + } + function d3_time_parseWeekdayAbbrev(date, string, i) { + d3_time_dayAbbrevRe.lastIndex = 0; + var n = d3_time_dayAbbrevRe.exec(string.substring(i)); + return n ? i += n[0].length : -1; + } + function d3_time_parseWeekday(date, string, i) { + d3_time_dayRe.lastIndex = 0; + var n = d3_time_dayRe.exec(string.substring(i)); + return n ? i += n[0].length : -1; + } + function d3_time_parseMonthAbbrev(date, string, i) { + d3_time_monthAbbrevRe.lastIndex = 0; + var n = d3_time_monthAbbrevRe.exec(string.substring(i)); + return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i += n[0].length) : -1; + } + function d3_time_parseMonth(date, string, i) { + d3_time_monthRe.lastIndex = 0; + var n = d3_time_monthRe.exec(string.substring(i)); + return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i += n[0].length) : -1; + } + function d3_time_parseLocaleFull(date, string, i) { + return d3_time_parse(date, d3_time_formats.c.toString(), string, i); + } + function d3_time_parseLocaleDate(date, string, i) { + return d3_time_parse(date, d3_time_formats.x.toString(), string, i); + } + function d3_time_parseLocaleTime(date, string, i) { + return d3_time_parse(date, d3_time_formats.X.toString(), string, i); + } + function d3_time_parseFullYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 4)); + return n ? (date.y = +n[0], i += n[0].length) : -1; + } + function d3_time_parseYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.y = d3_time_expandYear(+n[0]), i += n[0].length) : -1; + } + function d3_time_expandYear(d) { + return d + (d > 68 ? 1900 : 2e3); + } + function d3_time_parseMonthNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.m = n[0] - 1, i += n[0].length) : -1; + } + function d3_time_parseDay(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.d = +n[0], i += n[0].length) : -1; + } + function d3_time_parseHour24(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.H = +n[0], i += n[0].length) : -1; + } + function d3_time_parseMinutes(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.M = +n[0], i += n[0].length) : -1; + } + function d3_time_parseSeconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.S = +n[0], i += n[0].length) : -1; + } + function d3_time_parseMilliseconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 3)); + return n ? (date.L = +n[0], i += n[0].length) : -1; + } + function d3_time_parseAmPm(date, string, i) { + var n = d3_time_amPmLookup.get(string.substring(i, i += 2).toLowerCase()); + return n == null ? -1 : (date.p = n, i); + } + function d3_time_zone(d) { + var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = ~~(Math.abs(z) / 60), zm = Math.abs(z) % 60; + return zs + d3_time_zfill2(zh) + d3_time_zfill2(zm); + } + function d3_time_formatIsoNative(date) { + return date.toISOString(); + } + function d3_time_interval(local, step, number) { + function round(date) { + var d0 = local(date), d1 = offset(d0, 1); + return date - d0 < d1 - date ? d0 : d1; + } + function ceil(date) { + step(date = local(new d3_time(date - 1)), 1); + return date; + } + function offset(date, k) { + step(date = new d3_time(+date), k); + return date; + } + function range(t0, t1, dt) { + var time = ceil(t0), times = []; + if (dt > 1) { + while (time < t1) { + if (!(number(time) % dt)) times.push(new Date(+time)); + step(time, 1); + } + } else { + while (time < t1) times.push(new Date(+time)), step(time, 1); + } + return times; + } + function range_utc(t0, t1, dt) { + try { + d3_time = d3_time_utc; + var utc = new d3_time_utc; + utc._ = t0; + return range(utc, t1, dt); + } finally { + d3_time = Date; + } + } + local.floor = local; + local.round = round; + local.ceil = ceil; + local.offset = offset; + local.range = range; + var utc = local.utc = d3_time_interval_utc(local); + utc.floor = utc; + utc.round = d3_time_interval_utc(round); + utc.ceil = d3_time_interval_utc(ceil); + utc.offset = d3_time_interval_utc(offset); + utc.range = range_utc; + return local; + } + function d3_time_interval_utc(method) { + return function(date, k) { + try { + d3_time = d3_time_utc; + var utc = new d3_time_utc; + utc._ = date; + return method(utc, k)._; + } finally { + d3_time = Date; + } + }; + } + function d3_time_scale(linear, methods, format) { + function scale(x) { + return linear(x); + } + scale.invert = function(x) { + return d3_time_scaleDate(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(d3_time_scaleDate); + linear.domain(x); + return scale; + }; + scale.nice = function(m) { + return scale.domain(d3_scale_nice(scale.domain(), function() { + return m; + })); + }; + scale.ticks = function(m, k) { + var extent = d3_time_scaleExtent(scale.domain()); + if (typeof m !== "function") { + var span = extent[1] - extent[0], target = span / m, i = d3.bisect(d3_time_scaleSteps, target); + if (i == d3_time_scaleSteps.length) return methods.year(extent, m); + if (!i) return linear.ticks(m).map(d3_time_scaleDate); + if (Math.log(target / d3_time_scaleSteps[i - 1]) < Math.log(d3_time_scaleSteps[i] / target)) --i; + m = methods[i]; + k = m[1]; + m = m[0].range; + } + return m(extent[0], new Date(+extent[1] + 1), k); + }; + scale.tickFormat = function() { + return format; + }; + scale.copy = function() { + return d3_time_scale(linear.copy(), methods, format); + }; + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); + } + function d3_time_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [ start, stop ] : [ stop, start ]; + } + function d3_time_scaleDate(t) { + return new Date(t); + } + function d3_time_scaleFormat(formats) { + return function(date) { + var i = formats.length - 1, f = formats[i]; + while (!f[1](date)) f = formats[--i]; + return f[0](date); + }; + } + function d3_time_scaleSetYear(y) { + var d = new Date(y, 0, 1); + d.setFullYear(y); + return d; + } + function d3_time_scaleGetYear(d) { + var y = d.getFullYear(), d0 = d3_time_scaleSetYear(y), d1 = d3_time_scaleSetYear(y + 1); + return y + (d - d0) / (d1 - d0); + } + function d3_time_scaleUTCSetYear(y) { + var d = new Date(Date.UTC(y, 0, 1)); + d.setUTCFullYear(y); + return d; + } + function d3_time_scaleUTCGetYear(d) { + var y = d.getUTCFullYear(), d0 = d3_time_scaleUTCSetYear(y), d1 = d3_time_scaleUTCSetYear(y + 1); + return y + (d - d0) / (d1 - d0); + } + if (!Date.now) Date.now = function() { + return +(new Date); + }; + try { + document.createElement("div").style.setProperty("opacity", 0, ""); + } catch (error) { + var d3_style_prototype = CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty; + d3_style_prototype.setProperty = function(name, value, priority) { + d3_style_setProperty.call(this, name, value + "", priority); + }; + } + d3 = { + version: "2.10.2" + }; + var d3_array = d3_arraySlice; + try { + d3_array(document.documentElement.childNodes)[0].nodeType; + } catch (e) { + d3_array = d3_arrayCopy; + } + var d3_arraySubclass = [].__proto__ ? function(array, prototype) { + array.__proto__ = prototype; + } : function(array, prototype) { + for (var property in prototype) array[property] = prototype[property]; + }; + d3.map = function(object) { + var map = new d3_Map; + for (var key in object) map.set(key, object[key]); + return map; + }; + d3_class(d3_Map, { + has: function(key) { + return d3_map_prefix + key in this; + }, + get: function(key) { + return this[d3_map_prefix + key]; + }, + set: function(key, value) { + return this[d3_map_prefix + key] = value; + }, + remove: function(key) { + key = d3_map_prefix + key; + return key in this && delete this[key]; + }, + keys: function() { + var keys = []; + this.forEach(function(key) { + keys.push(key); + }); + return keys; + }, + values: function() { + var values = []; + this.forEach(function(key, value) { + values.push(value); + }); + return values; + }, + entries: function() { + var entries = []; + this.forEach(function(key, value) { + entries.push({ + key: key, + value: value + }); + }); + return entries; + }, + forEach: function(f) { + for (var key in this) { + if (key.charCodeAt(0) === d3_map_prefixCode) { + f.call(this, key.substring(1), this[key]); + } + } + } + }); + var d3_map_prefix = "\0", d3_map_prefixCode = d3_map_prefix.charCodeAt(0); + d3.functor = d3_functor; + d3.rebind = function(target, source) { + var i = 1, n = arguments.length, method; + while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); + return target; + }; + d3.ascending = function(a, b) { + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; + }; + d3.descending = function(a, b) { + return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; + }; + d3.mean = function(array, f) { + var n = array.length, a, m = 0, i = -1, j = 0; + if (arguments.length === 1) { + while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j; + } else { + while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j; + } + return j ? m : undefined; + }; + d3.median = function(array, f) { + if (arguments.length > 1) array = array.map(f); + array = array.filter(d3_number); + return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined; + }; + d3.min = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; + while (++i < n) if ((b = array[i]) != null && a > b) a = b; + } else { + while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; + } + return a; + }; + d3.max = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; + while (++i < n) if ((b = array[i]) != null && b > a) a = b; + } else { + while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; + } + return a; + }; + d3.extent = function(array, f) { + var i = -1, n = array.length, a, b, c; + if (arguments.length === 1) { + while (++i < n && ((a = c = array[i]) == null || a != a)) a = c = undefined; + while (++i < n) if ((b = array[i]) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } else { + while (++i < n && ((a = c = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } + return [ a, c ]; + }; + d3.random = { + normal: function(µ, σ) { + var n = arguments.length; + if (n < 2) σ = 1; + if (n < 1) µ = 0; + return function() { + var x, y, r; + do { + x = Math.random() * 2 - 1; + y = Math.random() * 2 - 1; + r = x * x + y * y; + } while (!r || r > 1); + return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r); + }; + }, + logNormal: function(µ, σ) { + var n = arguments.length; + if (n < 2) σ = 1; + if (n < 1) µ = 0; + var random = d3.random.normal(); + return function() { + return Math.exp(µ + σ * random()); + }; + }, + irwinHall: function(m) { + return function() { + for (var s = 0, j = 0; j < m; j++) s += Math.random(); + return s / m; + }; + } + }; + d3.sum = function(array, f) { + var s = 0, n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (!isNaN(a = +array[i])) s += a; + } else { + while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a; + } + return s; + }; + d3.quantile = function(values, p) { + var H = (values.length - 1) * p + 1, h = Math.floor(H), v = values[h - 1], e = H - h; + return e ? v + e * (values[h] - v) : v; + }; + d3.transpose = function(matrix) { + return d3.zip.apply(d3, matrix); + }; + d3.zip = function() { + if (!(n = arguments.length)) return []; + for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m; ) { + for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n; ) { + zip[j] = arguments[j][i]; + } + } + return zips; + }; + d3.bisector = function(f) { + return { + left: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (f.call(a, a[mid], mid) < x) lo = mid + 1; else hi = mid; + } + return lo; + }, + right: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (x < f.call(a, a[mid], mid)) hi = mid; else lo = mid + 1; + } + return lo; + } + }; + }; + var d3_bisector = d3.bisector(function(d) { + return d; + }); + d3.bisectLeft = d3_bisector.left; + d3.bisect = d3.bisectRight = d3_bisector.right; + d3.first = function(array, f) { + var i = 0, n = array.length, a = array[0], b; + if (arguments.length === 1) f = d3.ascending; + while (++i < n) { + if (f.call(array, a, b = array[i]) > 0) { + a = b; + } + } + return a; + }; + d3.last = function(array, f) { + var i = 0, n = array.length, a = array[0], b; + if (arguments.length === 1) f = d3.ascending; + while (++i < n) { + if (f.call(array, a, b = array[i]) <= 0) { + a = b; + } + } + return a; + }; + d3.nest = function() { + function map(array, depth) { + if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array; + var i = -1, n = array.length, key = keys[depth++], keyValue, object, valuesByKey = new d3_Map, values, o = {}; + while (++i < n) { + if (values = valuesByKey.get(keyValue = key(object = array[i]))) { + values.push(object); + } else { + valuesByKey.set(keyValue, [ object ]); + } + } + valuesByKey.forEach(function(keyValue, values) { + o[keyValue] = map(values, depth); + }); + return o; + } + function entries(map, depth) { + if (depth >= keys.length) return map; + var a = [], sortKey = sortKeys[depth++], key; + for (key in map) { + a.push({ + key: key, + values: entries(map[key], depth) + }); + } + if (sortKey) a.sort(function(a, b) { + return sortKey(a.key, b.key); + }); + return a; + } + var nest = {}, keys = [], sortKeys = [], sortValues, rollup; + nest.map = function(array) { + return map(array, 0); + }; + nest.entries = function(array) { + return entries(map(array, 0), 0); + }; + nest.key = function(d) { + keys.push(d); + return nest; + }; + nest.sortKeys = function(order) { + sortKeys[keys.length - 1] = order; + return nest; + }; + nest.sortValues = function(order) { + sortValues = order; + return nest; + }; + nest.rollup = function(f) { + rollup = f; + return nest; + }; + return nest; + }; + d3.keys = function(map) { + var keys = []; + for (var key in map) keys.push(key); + return keys; + }; + d3.values = function(map) { + var values = []; + for (var key in map) values.push(map[key]); + return values; + }; + d3.entries = function(map) { + var entries = []; + for (var key in map) entries.push({ + key: key, + value: map[key] + }); + return entries; + }; + d3.permute = function(array, indexes) { + var permutes = [], i = -1, n = indexes.length; + while (++i < n) permutes[i] = array[indexes[i]]; + return permutes; + }; + d3.merge = function(arrays) { + return Array.prototype.concat.apply([], arrays); + }; + d3.split = function(array, f) { + var arrays = [], values = [], value, i = -1, n = array.length; + if (arguments.length < 2) f = d3_splitter; + while (++i < n) { + if (f.call(values, value = array[i], i)) { + values = []; + } else { + if (!values.length) arrays.push(values); + values.push(value); + } + } + return arrays; + }; + d3.range = function(start, stop, step) { + if (arguments.length < 3) { + step = 1; + if (arguments.length < 2) { + stop = start; + start = 0; + } + } + if ((stop - start) / step === Infinity) throw new Error("infinite range"); + var range = [], k = d3_range_integerScale(Math.abs(step)), i = -1, j; + start *= k, stop *= k, step *= k; + if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k); + return range; + }; + d3.requote = function(s) { + return s.replace(d3_requote_re, "\\$&"); + }; + var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; + d3.round = function(x, n) { + return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); + }; + d3.xhr = function(url, mime, callback) { + var req = new XMLHttpRequest; + if (arguments.length < 3) callback = mime, mime = null; else if (mime && req.overrideMimeType) req.overrideMimeType(mime); + req.open("GET", url, true); + if (mime) req.setRequestHeader("Accept", mime); + req.onreadystatechange = function() { + if (req.readyState === 4) { + var s = req.status; + callback(!s && req.response || s >= 200 && s < 300 || s === 304 ? req : null); + } + }; + req.send(null); + }; + d3.text = function(url, mime, callback) { + function ready(req) { + callback(req && req.responseText); + } + if (arguments.length < 3) { + callback = mime; + mime = null; + } + d3.xhr(url, mime, ready); + }; + d3.json = function(url, callback) { + d3.text(url, "application/json", function(text) { + callback(text ? JSON.parse(text) : null); + }); + }; + d3.html = function(url, callback) { + d3.text(url, "text/html", function(text) { + if (text != null) { + var range = document.createRange(); + range.selectNode(document.body); + text = range.createContextualFragment(text); + } + callback(text); + }); + }; + d3.xml = function(url, mime, callback) { + function ready(req) { + callback(req && req.responseXML); + } + if (arguments.length < 3) { + callback = mime; + mime = null; + } + d3.xhr(url, mime, ready); + }; + var d3_nsPrefix = { + svg: "http://www.w3.org/2000/svg", + xhtml: "http://www.w3.org/1999/xhtml", + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" + }; + d3.ns = { + prefix: d3_nsPrefix, + qualify: function(name) { + var i = name.indexOf(":"), prefix = name; + if (i >= 0) { + prefix = name.substring(0, i); + name = name.substring(i + 1); + } + return d3_nsPrefix.hasOwnProperty(prefix) ? { + space: d3_nsPrefix[prefix], + local: name + } : name; + } + }; + d3.dispatch = function() { + var dispatch = new d3_dispatch, i = -1, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + return dispatch; + }; + d3_dispatch.prototype.on = function(type, listener) { + var i = type.indexOf("."), name = ""; + if (i > 0) { + name = type.substring(i + 1); + type = type.substring(0, i); + } + return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener); + }; + d3.format = function(specifier) { + var match = d3_format_re.exec(specifier), fill = match[1] || " ", sign = match[3] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, suffix = "", integer = false; + if (precision) precision = +precision.substring(1); + if (zfill) { + fill = "0"; + if (comma) width -= Math.floor((width - 1) / 4); + } + switch (type) { + case "n": + comma = true; + type = "g"; + break; + case "%": + scale = 100; + suffix = "%"; + type = "f"; + break; + case "p": + scale = 100; + suffix = "%"; + type = "r"; + break; + case "d": + integer = true; + precision = 0; + break; + case "s": + scale = -1; + type = "r"; + break; + } + if (type == "r" && !precision) type = "g"; + type = d3_format_types.get(type) || d3_format_typeDefault; + return function(value) { + if (integer && value % 1) return ""; + var negative = value < 0 && (value = -value) ? "-" : sign; + if (scale < 0) { + var prefix = d3.formatPrefix(value, precision); + value = prefix.scale(value); + suffix = prefix.symbol; + } else { + value *= scale; + } + value = type(value, precision); + if (zfill) { + var length = value.length + negative.length; + if (length < width) value = (new Array(width - length + 1)).join(fill) + value; + if (comma) value = d3_format_group(value); + value = negative + value; + } else { + if (comma) value = d3_format_group(value); + value = negative + value; + var length = value.length; + if (length < width) value = (new Array(width - length + 1)).join(fill) + value; + } + return value + suffix; + }; + }; + var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/; + var d3_format_types = d3.map({ + g: function(x, p) { + return x.toPrecision(p); + }, + e: function(x, p) { + return x.toExponential(p); + }, + f: function(x, p) { + return x.toFixed(p); + }, + r: function(x, p) { + return d3.round(x, p = d3_format_precision(x, p)).toFixed(Math.max(0, Math.min(20, p))); + } + }); + var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "μ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix); + d3.formatPrefix = function(value, precision) { + var i = 0; + if (value) { + if (value < 0) value *= -1; + if (precision) value = d3.round(value, d3_format_precision(value, precision)); + i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); + i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3)); + } + return d3_formatPrefixes[8 + i / 3]; + }; + var d3_ease_quad = d3_ease_poly(2), d3_ease_cubic = d3_ease_poly(3), d3_ease_default = function() { + return d3_ease_identity; + }; + var d3_ease = d3.map({ + linear: d3_ease_default, + poly: d3_ease_poly, + quad: function() { + return d3_ease_quad; + }, + cubic: function() { + return d3_ease_cubic; + }, + sin: function() { + return d3_ease_sin; + }, + exp: function() { + return d3_ease_exp; + }, + circle: function() { + return d3_ease_circle; + }, + elastic: d3_ease_elastic, + back: d3_ease_back, + bounce: function() { + return d3_ease_bounce; + } + }); + var d3_ease_mode = d3.map({ + "in": d3_ease_identity, + out: d3_ease_reverse, + "in-out": d3_ease_reflect, + "out-in": function(f) { + return d3_ease_reflect(d3_ease_reverse(f)); + } + }); + d3.ease = function(name) { + var i = name.indexOf("-"), t = i >= 0 ? name.substring(0, i) : name, m = i >= 0 ? name.substring(i + 1) : "in"; + t = d3_ease.get(t) || d3_ease_default; + m = d3_ease_mode.get(m) || d3_ease_identity; + return d3_ease_clamp(m(t.apply(null, Array.prototype.slice.call(arguments, 1)))); + }; + d3.event = null; + d3.transform = function(string) { + var g = document.createElementNS(d3.ns.prefix.svg, "g"); + return (d3.transform = function(string) { + g.setAttribute("transform", string); + var t = g.transform.baseVal.consolidate(); + return new d3_transform(t ? t.matrix : d3_transformIdentity); + })(string); + }; + d3_transform.prototype.toString = function() { + return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")"; + }; + var d3_transformDegrees = 180 / Math.PI, d3_transformIdentity = { + a: 1, + b: 0, + c: 0, + d: 1, + e: 0, + f: 0 + }; + d3.interpolate = function(a, b) { + var i = d3.interpolators.length, f; + while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ; + return f; + }; + d3.interpolateNumber = function(a, b) { + b -= a; + return function(t) { + return a + b * t; + }; + }; + d3.interpolateRound = function(a, b) { + b -= a; + return function(t) { + return Math.round(a + b * t); + }; + }; + d3.interpolateString = function(a, b) { + var m, i, j, s0 = 0, s1 = 0, s = [], q = [], n, o; + d3_interpolate_number.lastIndex = 0; + for (i = 0; m = d3_interpolate_number.exec(b); ++i) { + if (m.index) s.push(b.substring(s0, s1 = m.index)); + q.push({ + i: s.length, + x: m[0] + }); + s.push(null); + s0 = d3_interpolate_number.lastIndex; + } + if (s0 < b.length) s.push(b.substring(s0)); + for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) { + o = q[i]; + if (o.x == m[0]) { + if (o.i) { + if (s[o.i + 1] == null) { + s[o.i - 1] += o.x; + s.splice(o.i, 1); + for (j = i + 1; j < n; ++j) q[j].i--; + } else { + s[o.i - 1] += o.x + s[o.i + 1]; + s.splice(o.i, 2); + for (j = i + 1; j < n; ++j) q[j].i -= 2; + } + } else { + if (s[o.i + 1] == null) { + s[o.i] = o.x; + } else { + s[o.i] = o.x + s[o.i + 1]; + s.splice(o.i + 1, 1); + for (j = i + 1; j < n; ++j) q[j].i--; + } + } + q.splice(i, 1); + n--; + i--; + } else { + o.x = d3.interpolateNumber(parseFloat(m[0]), parseFloat(o.x)); + } + } + while (i < n) { + o = q.pop(); + if (s[o.i + 1] == null) { + s[o.i] = o.x; + } else { + s[o.i] = o.x + s[o.i + 1]; + s.splice(o.i + 1, 1); + } + n--; + } + if (s.length === 1) { + return s[0] == null ? q[0].x : function() { + return b; + }; + } + return function(t) { + for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + }; + d3.interpolateTransform = function(a, b) { + var s = [], q = [], n, A = d3.transform(a), B = d3.transform(b), ta = A.translate, tb = B.translate, ra = A.rotate, rb = B.rotate, wa = A.skew, wb = B.skew, ka = A.scale, kb = B.scale; + if (ta[0] != tb[0] || ta[1] != tb[1]) { + s.push("translate(", null, ",", null, ")"); + q.push({ + i: 1, + x: d3.interpolateNumber(ta[0], tb[0]) + }, { + i: 3, + x: d3.interpolateNumber(ta[1], tb[1]) + }); + } else if (tb[0] || tb[1]) { + s.push("translate(" + tb + ")"); + } else { + s.push(""); + } + if (ra != rb) { + if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360; + q.push({ + i: s.push(s.pop() + "rotate(", null, ")") - 2, + x: d3.interpolateNumber(ra, rb) + }); + } else if (rb) { + s.push(s.pop() + "rotate(" + rb + ")"); + } + if (wa != wb) { + q.push({ + i: s.push(s.pop() + "skewX(", null, ")") - 2, + x: d3.interpolateNumber(wa, wb) + }); + } else if (wb) { + s.push(s.pop() + "skewX(" + wb + ")"); + } + if (ka[0] != kb[0] || ka[1] != kb[1]) { + n = s.push(s.pop() + "scale(", null, ",", null, ")"); + q.push({ + i: n - 4, + x: d3.interpolateNumber(ka[0], kb[0]) + }, { + i: n - 2, + x: d3.interpolateNumber(ka[1], kb[1]) + }); + } else if (kb[0] != 1 || kb[1] != 1) { + s.push(s.pop() + "scale(" + kb + ")"); + } + n = q.length; + return function(t) { + var i = -1, o; + while (++i < n) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + }; + d3.interpolateRgb = function(a, b) { + a = d3.rgb(a); + b = d3.rgb(b); + var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab; + return function(t) { + return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t)); + }; + }; + d3.interpolateHsl = function(a, b) { + a = d3.hsl(a); + b = d3.hsl(b); + var h0 = a.h, s0 = a.s, l0 = a.l, h1 = b.h - h0, s1 = b.s - s0, l1 = b.l - l0; + if (h1 > 180) h1 -= 360; else if (h1 < -180) h1 += 360; + return function(t) { + return d3_hsl_rgb(h0 + h1 * t, s0 + s1 * t, l0 + l1 * t) + ""; + }; + }; + d3.interpolateLab = function(a, b) { + a = d3.lab(a); + b = d3.lab(b); + var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab; + return function(t) { + return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + ""; + }; + }; + d3.interpolateHcl = function(a, b) { + a = d3.hcl(a); + b = d3.hcl(b); + var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al; + if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + ""; + }; + }; + d3.interpolateArray = function(a, b) { + var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i; + for (i = 0; i < n0; ++i) x.push(d3.interpolate(a[i], b[i])); + for (; i < na; ++i) c[i] = a[i]; + for (; i < nb; ++i) c[i] = b[i]; + return function(t) { + for (i = 0; i < n0; ++i) c[i] = x[i](t); + return c; + }; + }; + d3.interpolateObject = function(a, b) { + var i = {}, c = {}, k; + for (k in a) { + if (k in b) { + i[k] = d3_interpolateByName(k)(a[k], b[k]); + } else { + c[k] = a[k]; + } + } + for (k in b) { + if (!(k in a)) { + c[k] = b[k]; + } + } + return function(t) { + for (k in i) c[k] = i[k](t); + return c; + }; + }; + var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g; + d3.interpolators = [ d3.interpolateObject, function(a, b) { + return b instanceof Array && d3.interpolateArray(a, b); + }, function(a, b) { + return (typeof a === "string" || typeof b === "string") && d3.interpolateString(a + "", b + ""); + }, function(a, b) { + return (typeof b === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Rgb || b instanceof d3_Hsl) && d3.interpolateRgb(a, b); + }, function(a, b) { + return !isNaN(a = +a) && !isNaN(b = +b) && d3.interpolateNumber(a, b); + } ]; + d3.rgb = function(r, g, b) { + return arguments.length === 1 ? r instanceof d3_Rgb ? d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : d3_rgb(~~r, ~~g, ~~b); + }; + d3_Rgb.prototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + var r = this.r, g = this.g, b = this.b, i = 30; + if (!r && !g && !b) return d3_rgb(i, i, i); + if (r && r < i) r = i; + if (g && g < i) g = i; + if (b && b < i) b = i; + return d3_rgb(Math.min(255, Math.floor(r / k)), Math.min(255, Math.floor(g / k)), Math.min(255, Math.floor(b / k))); + }; + d3_Rgb.prototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return d3_rgb(Math.floor(k * this.r), Math.floor(k * this.g), Math.floor(k * this.b)); + }; + d3_Rgb.prototype.hsl = function() { + return d3_rgb_hsl(this.r, this.g, this.b); + }; + d3_Rgb.prototype.toString = function() { + return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b); + }; + var d3_rgb_names = d3.map({ + aliceblue: "#f0f8ff", + antiquewhite: "#faebd7", + aqua: "#00ffff", + aquamarine: "#7fffd4", + azure: "#f0ffff", + beige: "#f5f5dc", + bisque: "#ffe4c4", + black: "#000000", + blanchedalmond: "#ffebcd", + blue: "#0000ff", + blueviolet: "#8a2be2", + brown: "#a52a2a", + burlywood: "#deb887", + cadetblue: "#5f9ea0", + chartreuse: "#7fff00", + chocolate: "#d2691e", + coral: "#ff7f50", + cornflowerblue: "#6495ed", + cornsilk: "#fff8dc", + crimson: "#dc143c", + cyan: "#00ffff", + darkblue: "#00008b", + darkcyan: "#008b8b", + darkgoldenrod: "#b8860b", + darkgray: "#a9a9a9", + darkgreen: "#006400", + darkgrey: "#a9a9a9", + darkkhaki: "#bdb76b", + darkmagenta: "#8b008b", + darkolivegreen: "#556b2f", + darkorange: "#ff8c00", + darkorchid: "#9932cc", + darkred: "#8b0000", + darksalmon: "#e9967a", + darkseagreen: "#8fbc8f", + darkslateblue: "#483d8b", + darkslategray: "#2f4f4f", + darkslategrey: "#2f4f4f", + darkturquoise: "#00ced1", + darkviolet: "#9400d3", + deeppink: "#ff1493", + deepskyblue: "#00bfff", + dimgray: "#696969", + dimgrey: "#696969", + dodgerblue: "#1e90ff", + firebrick: "#b22222", + floralwhite: "#fffaf0", + forestgreen: "#228b22", + fuchsia: "#ff00ff", + gainsboro: "#dcdcdc", + ghostwhite: "#f8f8ff", + gold: "#ffd700", + goldenrod: "#daa520", + gray: "#808080", + green: "#008000", + greenyellow: "#adff2f", + grey: "#808080", + honeydew: "#f0fff0", + hotpink: "#ff69b4", + indianred: "#cd5c5c", + indigo: "#4b0082", + ivory: "#fffff0", + khaki: "#f0e68c", + lavender: "#e6e6fa", + lavenderblush: "#fff0f5", + lawngreen: "#7cfc00", + lemonchiffon: "#fffacd", + lightblue: "#add8e6", + lightcoral: "#f08080", + lightcyan: "#e0ffff", + lightgoldenrodyellow: "#fafad2", + lightgray: "#d3d3d3", + lightgreen: "#90ee90", + lightgrey: "#d3d3d3", + lightpink: "#ffb6c1", + lightsalmon: "#ffa07a", + lightseagreen: "#20b2aa", + lightskyblue: "#87cefa", + lightslategray: "#778899", + lightslategrey: "#778899", + lightsteelblue: "#b0c4de", + lightyellow: "#ffffe0", + lime: "#00ff00", + limegreen: "#32cd32", + linen: "#faf0e6", + magenta: "#ff00ff", + maroon: "#800000", + mediumaquamarine: "#66cdaa", + mediumblue: "#0000cd", + mediumorchid: "#ba55d3", + mediumpurple: "#9370db", + mediumseagreen: "#3cb371", + mediumslateblue: "#7b68ee", + mediumspringgreen: "#00fa9a", + mediumturquoise: "#48d1cc", + mediumvioletred: "#c71585", + midnightblue: "#191970", + mintcream: "#f5fffa", + mistyrose: "#ffe4e1", + moccasin: "#ffe4b5", + navajowhite: "#ffdead", + navy: "#000080", + oldlace: "#fdf5e6", + olive: "#808000", + olivedrab: "#6b8e23", + orange: "#ffa500", + orangered: "#ff4500", + orchid: "#da70d6", + palegoldenrod: "#eee8aa", + palegreen: "#98fb98", + paleturquoise: "#afeeee", + palevioletred: "#db7093", + papayawhip: "#ffefd5", + peachpuff: "#ffdab9", + peru: "#cd853f", + pink: "#ffc0cb", + plum: "#dda0dd", + powderblue: "#b0e0e6", + purple: "#800080", + red: "#ff0000", + rosybrown: "#bc8f8f", + royalblue: "#4169e1", + saddlebrown: "#8b4513", + salmon: "#fa8072", + sandybrown: "#f4a460", + seagreen: "#2e8b57", + seashell: "#fff5ee", + sienna: "#a0522d", + silver: "#c0c0c0", + skyblue: "#87ceeb", + slateblue: "#6a5acd", + slategray: "#708090", + slategrey: "#708090", + snow: "#fffafa", + springgreen: "#00ff7f", + steelblue: "#4682b4", + tan: "#d2b48c", + teal: "#008080", + thistle: "#d8bfd8", + tomato: "#ff6347", + turquoise: "#40e0d0", + violet: "#ee82ee", + wheat: "#f5deb3", + white: "#ffffff", + whitesmoke: "#f5f5f5", + yellow: "#ffff00", + yellowgreen: "#9acd32" + }); + d3_rgb_names.forEach(function(key, value) { + d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb)); + }); + d3.hsl = function(h, s, l) { + return arguments.length === 1 ? h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : d3_hsl(+h, +s, +l); + }; + d3_Hsl.prototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return d3_hsl(this.h, this.s, this.l / k); + }; + d3_Hsl.prototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return d3_hsl(this.h, this.s, k * this.l); + }; + d3_Hsl.prototype.rgb = function() { + return d3_hsl_rgb(this.h, this.s, this.l); + }; + d3_Hsl.prototype.toString = function() { + return this.rgb().toString(); + }; + d3.hcl = function(h, c, l) { + return arguments.length === 1 ? h instanceof d3_Hcl ? d3_hcl(h.h, h.c, h.l) : h instanceof d3_Lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : d3_hcl(+h, +c, +l); + }; + d3_Hcl.prototype.brighter = function(k) { + return d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1))); + }; + d3_Hcl.prototype.darker = function(k) { + return d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1))); + }; + d3_Hcl.prototype.rgb = function() { + return d3_hcl_lab(this.h, this.c, this.l).rgb(); + }; + d3_Hcl.prototype.toString = function() { + return this.rgb() + ""; + }; + d3.lab = function(l, a, b) { + return arguments.length === 1 ? l instanceof d3_Lab ? d3_lab(l.l, l.a, l.b) : l instanceof d3_Hcl ? d3_hcl_lab(l.l, l.c, l.h) : d3_rgb_lab((l = d3.rgb(l)).r, l.g, l.b) : d3_lab(+l, +a, +b); + }; + var d3_lab_K = 18; + var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883; + d3_Lab.prototype.brighter = function(k) { + return d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_Lab.prototype.darker = function(k) { + return d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_Lab.prototype.rgb = function() { + return d3_lab_rgb(this.l, this.a, this.b); + }; + d3_Lab.prototype.toString = function() { + return this.rgb() + ""; + }; + var d3_select = function(s, n) { + return n.querySelector(s); + }, d3_selectAll = function(s, n) { + return n.querySelectorAll(s); + }, d3_selectRoot = document.documentElement, d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector, d3_selectMatches = function(n, s) { + return d3_selectMatcher.call(n, s); + }; + if (typeof Sizzle === "function") { + d3_select = function(s, n) { + return Sizzle(s, n)[0] || null; + }; + d3_selectAll = function(s, n) { + return Sizzle.uniqueSort(Sizzle(s, n)); + }; + d3_selectMatches = Sizzle.matchesSelector; + } + var d3_selectionPrototype = []; + d3.selection = function() { + return d3_selectionRoot; + }; + d3.selection.prototype = d3_selectionPrototype; + d3_selectionPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, group, node; + if (typeof selector !== "function") selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(subnode = selector.call(node, node.__data__, i)); + if (subnode && "__data__" in node) subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + d3_selectionPrototype.selectAll = function(selector) { + var subgroups = [], subgroup, node; + if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i))); + subgroup.parentNode = node; + } + } + } + return d3_selection(subgroups); + }; + d3_selectionPrototype.attr = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(); + name = d3.ns.qualify(name); + return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name); + } + for (value in name) this.each(d3_selection_attr(value, name[value])); + return this; + } + return this.each(d3_selection_attr(name, value)); + }; + d3_selectionPrototype.classed = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(), n = (name = name.trim().split(/^|\s+/g)).length, i = -1; + if (value = node.classList) { + while (++i < n) if (!value.contains(name[i])) return false; + } else { + value = node.className; + if (value.baseVal != null) value = value.baseVal; + while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false; + } + return true; + } + for (value in name) this.each(d3_selection_classed(value, name[value])); + return this; + } + return this.each(d3_selection_classed(name, value)); + }; + d3_selectionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.each(d3_selection_style(priority, name[priority], value)); + return this; + } + if (n < 2) return window.getComputedStyle(this.node(), null).getPropertyValue(name); + priority = ""; + } + return this.each(d3_selection_style(name, value, priority)); + }; + d3_selectionPrototype.property = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") return this.node()[name]; + for (value in name) this.each(d3_selection_property(value, name[value])); + return this; + } + return this.each(d3_selection_property(name, value)); + }; + d3_selectionPrototype.text = function(value) { + return arguments.length < 1 ? this.node().textContent : this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.textContent = v == null ? "" : v; + } : value == null ? function() { + this.textContent = ""; + } : function() { + this.textContent = value; + }); + }; + d3_selectionPrototype.html = function(value) { + return arguments.length < 1 ? this.node().innerHTML : this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.innerHTML = v == null ? "" : v; + } : value == null ? function() { + this.innerHTML = ""; + } : function() { + this.innerHTML = value; + }); + }; + d3_selectionPrototype.append = function(name) { + function append() { + return this.appendChild(document.createElementNS(this.namespaceURI, name)); + } + function appendNS() { + return this.appendChild(document.createElementNS(name.space, name.local)); + } + name = d3.ns.qualify(name); + return this.select(name.local ? appendNS : append); + }; + d3_selectionPrototype.insert = function(name, before) { + function insert() { + return this.insertBefore(document.createElementNS(this.namespaceURI, name), d3_select(before, this)); + } + function insertNS() { + return this.insertBefore(document.createElementNS(name.space, name.local), d3_select(before, this)); + } + name = d3.ns.qualify(name); + return this.select(name.local ? insertNS : insert); + }; + d3_selectionPrototype.remove = function() { + return this.each(function() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); + }); + }; + d3_selectionPrototype.data = function(value, key) { + function bind(group, groupData) { + var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), n1 = Math.max(n, m), updateNodes = [], enterNodes = [], exitNodes = [], node, nodeData; + if (key) { + var nodeByKeyValue = new d3_Map, keyValues = [], keyValue, j = groupData.length; + for (i = -1; ++i < n; ) { + keyValue = key.call(node = group[i], node.__data__, i); + if (nodeByKeyValue.has(keyValue)) { + exitNodes[j++] = node; + } else { + nodeByKeyValue.set(keyValue, node); + } + keyValues.push(keyValue); + } + for (i = -1; ++i < m; ) { + keyValue = key.call(groupData, nodeData = groupData[i], i); + if (nodeByKeyValue.has(keyValue)) { + updateNodes[i] = node = nodeByKeyValue.get(keyValue); + node.__data__ = nodeData; + enterNodes[i] = exitNodes[i] = null; + } else { + enterNodes[i] = d3_selection_dataNode(nodeData); + updateNodes[i] = exitNodes[i] = null; + } + nodeByKeyValue.remove(keyValue); + } + for (i = -1; ++i < n; ) { + if (nodeByKeyValue.has(keyValues[i])) { + exitNodes[i] = group[i]; + } + } + } else { + for (i = -1; ++i < n0; ) { + node = group[i]; + nodeData = groupData[i]; + if (node) { + node.__data__ = nodeData; + updateNodes[i] = node; + enterNodes[i] = exitNodes[i] = null; + } else { + enterNodes[i] = d3_selection_dataNode(nodeData); + updateNodes[i] = exitNodes[i] = null; + } + } + for (; i < m; ++i) { + enterNodes[i] = d3_selection_dataNode(groupData[i]); + updateNodes[i] = exitNodes[i] = null; + } + for (; i < n1; ++i) { + exitNodes[i] = group[i]; + enterNodes[i] = updateNodes[i] = null; + } + } + enterNodes.update = updateNodes; + enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode; + enter.push(enterNodes); + update.push(updateNodes); + exit.push(exitNodes); + } + var i = -1, n = this.length, group, node; + if (!arguments.length) { + value = new Array(n = (group = this[0]).length); + while (++i < n) { + if (node = group[i]) { + value[i] = node.__data__; + } + } + return value; + } + var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]); + if (typeof value === "function") { + while (++i < n) { + bind(group = this[i], value.call(group, group.parentNode.__data__, i)); + } + } else { + while (++i < n) { + bind(group = this[i], value); + } + } + update.enter = function() { + return enter; + }; + update.exit = function() { + return exit; + }; + return update; + }; + d3_selectionPrototype.datum = d3_selectionPrototype.map = function(value) { + return arguments.length < 1 ? this.property("__data__") : this.property("__data__", value); + }; + d3_selectionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i)) { + subgroup.push(node); + } + } + } + return d3_selection(subgroups); + }; + d3_selectionPrototype.order = function() { + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) { + if (node = group[i]) { + if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next); + next = node; + } + } + } + return this; + }; + d3_selectionPrototype.sort = function(comparator) { + comparator = d3_selection_sortComparator.apply(this, arguments); + for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator); + return this.order(); + }; + d3_selectionPrototype.on = function(type, listener, capture) { + var n = arguments.length; + if (n < 3) { + if (typeof type !== "string") { + if (n < 2) listener = false; + for (capture in type) this.each(d3_selection_on(capture, type[capture], listener)); + return this; + } + if (n < 2) return (n = this.node()["__on" + type]) && n._; + capture = false; + } + return this.each(d3_selection_on(type, listener, capture)); + }; + d3_selectionPrototype.each = function(callback) { + return d3_selection_each(this, function(node, i, j) { + callback.call(node, node.__data__, i, j); + }); + }; + d3_selectionPrototype.call = function(callback) { + callback.apply(this, (arguments[0] = this, arguments)); + return this; + }; + d3_selectionPrototype.empty = function() { + return !this.node(); + }; + d3_selectionPrototype.node = function(callback) { + for (var j = 0, m = this.length; j < m; j++) { + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + var node = group[i]; + if (node) return node; + } + } + return null; + }; + d3_selectionPrototype.transition = function() { + var subgroups = [], subgroup, node; + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + subgroup.push((node = group[i]) ? { + node: node, + delay: d3_transitionDelay, + duration: d3_transitionDuration + } : null); + } + } + return d3_transition(subgroups, d3_transitionId || ++d3_transitionNextId, Date.now()); + }; + var d3_selectionRoot = d3_selection([ [ document ] ]); + d3_selectionRoot[0].parentNode = d3_selectRoot; + d3.select = function(selector) { + return typeof selector === "string" ? d3_selectionRoot.select(selector) : d3_selection([ [ selector ] ]); + }; + d3.selectAll = function(selector) { + return typeof selector === "string" ? d3_selectionRoot.selectAll(selector) : d3_selection([ d3_array(selector) ]); + }; + var d3_selection_enterPrototype = []; + d3.selection.enter = d3_selection_enter; + d3.selection.enter.prototype = d3_selection_enterPrototype; + d3_selection_enterPrototype.append = d3_selectionPrototype.append; + d3_selection_enterPrototype.insert = d3_selectionPrototype.insert; + d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; + d3_selection_enterPrototype.node = d3_selectionPrototype.node; + d3_selection_enterPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, upgroup, group, node; + for (var j = -1, m = this.length; ++j < m; ) { + upgroup = (group = this[j]).update; + subgroups.push(subgroup = []); + subgroup.parentNode = group.parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i)); + subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + var d3_transitionPrototype = [], d3_transitionNextId = 0, d3_transitionId = 0, d3_transitionDefaultDelay = 0, d3_transitionDefaultDuration = 250, d3_transitionDefaultEase = d3.ease("cubic-in-out"), d3_transitionDelay = d3_transitionDefaultDelay, d3_transitionDuration = d3_transitionDefaultDuration, d3_transitionEase = d3_transitionDefaultEase; + d3_transitionPrototype.call = d3_selectionPrototype.call; + d3.transition = function(selection) { + return arguments.length ? d3_transitionId ? selection.transition() : selection : d3_selectionRoot.transition(); + }; + d3.transition.prototype = d3_transitionPrototype; + d3_transitionPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, node; + if (typeof selector !== "function") selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if ((node = group[i]) && (subnode = selector.call(node.node, node.node.__data__, i))) { + if ("__data__" in node.node) subnode.__data__ = node.node.__data__; + subgroup.push({ + node: subnode, + delay: node.delay, + duration: node.duration + }); + } else { + subgroup.push(null); + } + } + } + return d3_transition(subgroups, this.id, this.time).ease(this.ease()); + }; + d3_transitionPrototype.selectAll = function(selector) { + var subgroups = [], subgroup, subnodes, node; + if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subnodes = selector.call(node.node, node.node.__data__, i); + subgroups.push(subgroup = []); + for (var k = -1, o = subnodes.length; ++k < o; ) { + subgroup.push({ + node: subnodes[k], + delay: node.delay, + duration: node.duration + }); + } + } + } + } + return d3_transition(subgroups, this.id, this.time).ease(this.ease()); + }; + d3_transitionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node.node, node.node.__data__, i)) { + subgroup.push(node); + } + } + } + return d3_transition(subgroups, this.id, this.time).ease(this.ease()); + }; + d3_transitionPrototype.attr = function(name, value) { + if (arguments.length < 2) { + for (value in name) this.attrTween(value, d3_tweenByName(name[value], value)); + return this; + } + return this.attrTween(name, d3_tweenByName(value, name)); + }; + d3_transitionPrototype.attrTween = function(nameNS, tween) { + function attrTween(d, i) { + var f = tween.call(this, d, i, this.getAttribute(name)); + return f === d3_tweenRemove ? (this.removeAttribute(name), null) : f && function(t) { + this.setAttribute(name, f(t)); + }; + } + function attrTweenNS(d, i) { + var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); + return f === d3_tweenRemove ? (this.removeAttributeNS(name.space, name.local), null) : f && function(t) { + this.setAttributeNS(name.space, name.local, f(t)); + }; + } + var name = d3.ns.qualify(nameNS); + return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.styleTween(priority, d3_tweenByName(name[priority], priority), value); + return this; + } + priority = ""; + } + return this.styleTween(name, d3_tweenByName(value, name), priority); + }; + d3_transitionPrototype.styleTween = function(name, tween, priority) { + if (arguments.length < 3) priority = ""; + return this.tween("style." + name, function(d, i) { + var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name)); + return f === d3_tweenRemove ? (this.style.removeProperty(name), null) : f && function(t) { + this.style.setProperty(name, f(t), priority); + }; + }); + }; + d3_transitionPrototype.text = function(value) { + return this.tween("text", function(d, i) { + this.textContent = typeof value === "function" ? value.call(this, d, i) : value; + }); + }; + d3_transitionPrototype.remove = function() { + return this.each("end.transition", function() { + var p; + if (!this.__transition__ && (p = this.parentNode)) p.removeChild(this); + }); + }; + d3_transitionPrototype.delay = function(value) { + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node.delay = value.call(node = node.node, node.__data__, i, j) | 0; + } : (value = value | 0, function(node) { + node.delay = value; + })); + }; + d3_transitionPrototype.duration = function(value) { + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node.duration = Math.max(1, value.call(node = node.node, node.__data__, i, j) | 0); + } : (value = Math.max(1, value | 0), function(node) { + node.duration = value; + })); + }; + d3_transitionPrototype.transition = function() { + return this.select(d3_this); + }; + d3.tween = function(b, interpolate) { + function tweenFunction(d, i, a) { + var v = b.call(this, d, i); + return v == null ? a != "" && d3_tweenRemove : a != v && interpolate(a, v); + } + function tweenString(d, i, a) { + return a != b && interpolate(a, b); + } + return typeof b === "function" ? tweenFunction : b == null ? d3_tweenNull : (b += "", tweenString); + }; + var d3_tweenRemove = {}; + var d3_timer_queue = null, d3_timer_interval, d3_timer_timeout; + d3.timer = function(callback, delay, then) { + var found = false, t0, t1 = d3_timer_queue; + if (arguments.length < 3) { + if (arguments.length < 2) delay = 0; else if (!isFinite(delay)) return; + then = Date.now(); + } + while (t1) { + if (t1.callback === callback) { + t1.then = then; + t1.delay = delay; + found = true; + break; + } + t0 = t1; + t1 = t1.next; + } + if (!found) d3_timer_queue = { + callback: callback, + then: then, + delay: delay, + next: d3_timer_queue + }; + if (!d3_timer_interval) { + d3_timer_timeout = clearTimeout(d3_timer_timeout); + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + }; + d3.timer.flush = function() { + var elapsed, now = Date.now(), t1 = d3_timer_queue; + while (t1) { + elapsed = now - t1.then; + if (!t1.delay) t1.flush = t1.callback(elapsed); + t1 = t1.next; + } + d3_timer_flush(); + }; + var d3_timer_frame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { + setTimeout(callback, 17); + }; + d3.mouse = function(container) { + return d3_mousePoint(container, d3_eventSource()); + }; + var d3_mouse_bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0; + d3.touches = function(container, touches) { + if (arguments.length < 2) touches = d3_eventSource().touches; + return touches ? d3_array(touches).map(function(touch) { + var point = d3_mousePoint(container, touch); + point.identifier = touch.identifier; + return point; + }) : []; + }; + d3.scale = {}; + d3.scale.linear = function() { + return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3.interpolate, false); + }; + d3.scale.log = function() { + return d3_scale_log(d3.scale.linear(), d3_scale_logp); + }; + var d3_scale_logFormat = d3.format(".0e"); + d3_scale_logp.pow = function(x) { + return Math.pow(10, x); + }; + d3_scale_logn.pow = function(x) { + return -Math.pow(10, -x); + }; + d3.scale.pow = function() { + return d3_scale_pow(d3.scale.linear(), 1); + }; + d3.scale.sqrt = function() { + return d3.scale.pow().exponent(.5); + }; + d3.scale.ordinal = function() { + return d3_scale_ordinal([], { + t: "range", + a: [ [] ] + }); + }; + d3.scale.category10 = function() { + return d3.scale.ordinal().range(d3_category10); + }; + d3.scale.category20 = function() { + return d3.scale.ordinal().range(d3_category20); + }; + d3.scale.category20b = function() { + return d3.scale.ordinal().range(d3_category20b); + }; + d3.scale.category20c = function() { + return d3.scale.ordinal().range(d3_category20c); + }; + var d3_category10 = [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" ]; + var d3_category20 = [ "#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5" ]; + var d3_category20b = [ "#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6" ]; + var d3_category20c = [ "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9" ]; + d3.scale.quantile = function() { + return d3_scale_quantile([], []); + }; + d3.scale.quantize = function() { + return d3_scale_quantize(0, 1, [ 0, 1 ]); + }; + d3.scale.threshold = function() { + return d3_scale_threshold([ .5 ], [ 0, 1 ]); + }; + d3.scale.identity = function() { + return d3_scale_identity([ 0, 1 ]); + }; + d3.svg = {}; + d3.svg.arc = function() { + function arc() { + var r0 = innerRadius.apply(this, arguments), r1 = outerRadius.apply(this, arguments), a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, da = (a1 < a0 && (da = a0, a0 = a1, a1 = da), a1 - a0), df = da < Math.PI ? "0" : "1", c0 = Math.cos(a0), s0 = Math.sin(a0), c1 = Math.cos(a1), s1 = Math.sin(a1); + return da >= d3_svg_arcMax ? r0 ? "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "M0," + r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + -r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + r0 + "Z" : "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "Z" : r0 ? "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L" + r0 * c1 + "," + r0 * s1 + "A" + r0 + "," + r0 + " 0 " + df + ",0 " + r0 * c0 + "," + r0 * s0 + "Z" : "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L0,0" + "Z"; + } + var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + arc.innerRadius = function(v) { + if (!arguments.length) return innerRadius; + innerRadius = d3_functor(v); + return arc; + }; + arc.outerRadius = function(v) { + if (!arguments.length) return outerRadius; + outerRadius = d3_functor(v); + return arc; + }; + arc.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return arc; + }; + arc.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return arc; + }; + arc.centroid = function() { + var r = (innerRadius.apply(this, arguments) + outerRadius.apply(this, arguments)) / 2, a = (startAngle.apply(this, arguments) + endAngle.apply(this, arguments)) / 2 + d3_svg_arcOffset; + return [ Math.cos(a) * r, Math.sin(a) * r ]; + }; + return arc; + }; + var d3_svg_arcOffset = -Math.PI / 2, d3_svg_arcMax = 2 * Math.PI - 1e-6; + d3.svg.line = function() { + return d3_svg_line(d3_identity); + }; + var d3_svg_lineInterpolators = d3.map({ + linear: d3_svg_lineLinear, + "linear-closed": d3_svg_lineLinearClosed, + "step-before": d3_svg_lineStepBefore, + "step-after": d3_svg_lineStepAfter, + basis: d3_svg_lineBasis, + "basis-open": d3_svg_lineBasisOpen, + "basis-closed": d3_svg_lineBasisClosed, + bundle: d3_svg_lineBundle, + cardinal: d3_svg_lineCardinal, + "cardinal-open": d3_svg_lineCardinalOpen, + "cardinal-closed": d3_svg_lineCardinalClosed, + monotone: d3_svg_lineMonotone + }); + d3_svg_lineInterpolators.forEach(function(key, value) { + value.key = key; + value.closed = /-closed$/.test(key); + }); + var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ]; + d3.svg.line.radial = function() { + var line = d3_svg_line(d3_svg_lineRadial); + line.radius = line.x, delete line.x; + line.angle = line.y, delete line.y; + return line; + }; + d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter; + d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore; + d3.svg.area = function() { + return d3_svg_area(d3_identity); + }; + d3.svg.area.radial = function() { + var area = d3_svg_area(d3_svg_lineRadial); + area.radius = area.x, delete area.x; + area.innerRadius = area.x0, delete area.x0; + area.outerRadius = area.x1, delete area.x1; + area.angle = area.y, delete area.y; + area.startAngle = area.y0, delete area.y0; + area.endAngle = area.y1, delete area.y1; + return area; + }; + d3.svg.chord = function() { + function chord(d, i) { + var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i); + return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z"; + } + function subgroup(self, f, d, i) { + var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) + d3_svg_arcOffset, a1 = endAngle.call(self, subgroup, i) + d3_svg_arcOffset; + return { + r: r, + a0: a0, + a1: a1, + p0: [ r * Math.cos(a0), r * Math.sin(a0) ], + p1: [ r * Math.cos(a1), r * Math.sin(a1) ] + }; + } + function equals(a, b) { + return a.a0 == b.a0 && a.a1 == b.a1; + } + function arc(r, p, a) { + return "A" + r + "," + r + " 0 " + +(a > Math.PI) + ",1 " + p; + } + function curve(r0, p0, r1, p1) { + return "Q 0,0 " + p1; + } + var source = d3_svg_chordSource, target = d3_svg_chordTarget, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + chord.radius = function(v) { + if (!arguments.length) return radius; + radius = d3_functor(v); + return chord; + }; + chord.source = function(v) { + if (!arguments.length) return source; + source = d3_functor(v); + return chord; + }; + chord.target = function(v) { + if (!arguments.length) return target; + target = d3_functor(v); + return chord; + }; + chord.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return chord; + }; + chord.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return chord; + }; + return chord; + }; + d3.svg.diagonal = function() { + function diagonal(d, i) { + var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, { + x: p0.x, + y: m + }, { + x: p3.x, + y: m + }, p3 ]; + p = p.map(projection); + return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3]; + } + var source = d3_svg_chordSource, target = d3_svg_chordTarget, projection = d3_svg_diagonalProjection; + diagonal.source = function(x) { + if (!arguments.length) return source; + source = d3_functor(x); + return diagonal; + }; + diagonal.target = function(x) { + if (!arguments.length) return target; + target = d3_functor(x); + return diagonal; + }; + diagonal.projection = function(x) { + if (!arguments.length) return projection; + projection = x; + return diagonal; + }; + return diagonal; + }; + d3.svg.diagonal.radial = function() { + var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection; + diagonal.projection = function(x) { + return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection; + }; + return diagonal; + }; + d3.svg.mouse = d3.mouse; + d3.svg.touches = d3.touches; + d3.svg.symbol = function() { + function symbol(d, i) { + return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i)); + } + var type = d3_svg_symbolType, size = d3_svg_symbolSize; + symbol.type = function(x) { + if (!arguments.length) return type; + type = d3_functor(x); + return symbol; + }; + symbol.size = function(x) { + if (!arguments.length) return size; + size = d3_functor(x); + return symbol; + }; + return symbol; + }; + var d3_svg_symbols = d3.map({ + circle: d3_svg_symbolCircle, + cross: function(size) { + var r = Math.sqrt(size / 5) / 2; + return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z"; + }, + diamond: function(size) { + var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30; + return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z"; + }, + square: function(size) { + var r = Math.sqrt(size) / 2; + return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z"; + }, + "triangle-down": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z"; + }, + "triangle-up": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z"; + } + }); + d3.svg.symbolTypes = d3_svg_symbols.keys(); + var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * Math.PI / 180); + d3.svg.axis = function() { + function axis(g) { + g.each(function() { + var g = d3.select(this); + var ticks = tickValues == null ? scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain() : tickValues, tickFormat = tickFormat_ == null ? scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String : tickFormat_; + var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide), subtick = g.selectAll(".minor").data(subticks, String), subtickEnter = subtick.enter().insert("line", "g").attr("class", "tick minor").style("opacity", 1e-6), subtickExit = d3.transition(subtick.exit()).style("opacity", 1e-6).remove(), subtickUpdate = d3.transition(subtick).style("opacity", 1); + var tick = g.selectAll("g").data(ticks, String), tickEnter = tick.enter().insert("g", "path").style("opacity", 1e-6), tickExit = d3.transition(tick.exit()).style("opacity", 1e-6).remove(), tickUpdate = d3.transition(tick).style("opacity", 1), tickTransform; + var range = d3_scaleRange(scale), path = g.selectAll(".domain").data([ 0 ]), pathEnter = path.enter().append("path").attr("class", "domain"), pathUpdate = d3.transition(path); + var scale1 = scale.copy(), scale0 = this.__chart__ || scale1; + this.__chart__ = scale1; + tickEnter.append("line").attr("class", "tick"); + tickEnter.append("text"); + var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"); + switch (orient) { + case "bottom": + { + tickTransform = d3_svg_axisX; + subtickEnter.attr("y2", tickMinorSize); + subtickUpdate.attr("x2", 0).attr("y2", tickMinorSize); + lineEnter.attr("y2", tickMajorSize); + textEnter.attr("y", Math.max(tickMajorSize, 0) + tickPadding); + lineUpdate.attr("x2", 0).attr("y2", tickMajorSize); + textUpdate.attr("x", 0).attr("y", Math.max(tickMajorSize, 0) + tickPadding); + text.attr("dy", ".71em").attr("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + tickEndSize + "V0H" + range[1] + "V" + tickEndSize); + break; + } + case "top": + { + tickTransform = d3_svg_axisX; + subtickEnter.attr("y2", -tickMinorSize); + subtickUpdate.attr("x2", 0).attr("y2", -tickMinorSize); + lineEnter.attr("y2", -tickMajorSize); + textEnter.attr("y", -(Math.max(tickMajorSize, 0) + tickPadding)); + lineUpdate.attr("x2", 0).attr("y2", -tickMajorSize); + textUpdate.attr("x", 0).attr("y", -(Math.max(tickMajorSize, 0) + tickPadding)); + text.attr("dy", "0em").attr("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + -tickEndSize + "V0H" + range[1] + "V" + -tickEndSize); + break; + } + case "left": + { + tickTransform = d3_svg_axisY; + subtickEnter.attr("x2", -tickMinorSize); + subtickUpdate.attr("x2", -tickMinorSize).attr("y2", 0); + lineEnter.attr("x2", -tickMajorSize); + textEnter.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)); + lineUpdate.attr("x2", -tickMajorSize).attr("y2", 0); + textUpdate.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)).attr("y", 0); + text.attr("dy", ".32em").attr("text-anchor", "end"); + pathUpdate.attr("d", "M" + -tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize); + break; + } + case "right": + { + tickTransform = d3_svg_axisY; + subtickEnter.attr("x2", tickMinorSize); + subtickUpdate.attr("x2", tickMinorSize).attr("y2", 0); + lineEnter.attr("x2", tickMajorSize); + textEnter.attr("x", Math.max(tickMajorSize, 0) + tickPadding); + lineUpdate.attr("x2", tickMajorSize).attr("y2", 0); + textUpdate.attr("x", Math.max(tickMajorSize, 0) + tickPadding).attr("y", 0); + text.attr("dy", ".32em").attr("text-anchor", "start"); + pathUpdate.attr("d", "M" + tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize); + break; + } + } + if (scale.ticks) { + tickEnter.call(tickTransform, scale0); + tickUpdate.call(tickTransform, scale1); + tickExit.call(tickTransform, scale1); + subtickEnter.call(tickTransform, scale0); + subtickUpdate.call(tickTransform, scale1); + subtickExit.call(tickTransform, scale1); + } else { + var dx = scale1.rangeBand() / 2, x = function(d) { + return scale1(d) + dx; + }; + tickEnter.call(tickTransform, x); + tickUpdate.call(tickTransform, x); + } + }); + } + var scale = d3.scale.linear(), orient = "bottom", tickMajorSize = 6, tickMinorSize = 6, tickEndSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_, tickSubdivide = 0; + axis.scale = function(x) { + if (!arguments.length) return scale; + scale = x; + return axis; + }; + axis.orient = function(x) { + if (!arguments.length) return orient; + orient = x; + return axis; + }; + axis.ticks = function() { + if (!arguments.length) return tickArguments_; + tickArguments_ = arguments; + return axis; + }; + axis.tickValues = function(x) { + if (!arguments.length) return tickValues; + tickValues = x; + return axis; + }; + axis.tickFormat = function(x) { + if (!arguments.length) return tickFormat_; + tickFormat_ = x; + return axis; + }; + axis.tickSize = function(x, y, z) { + if (!arguments.length) return tickMajorSize; + var n = arguments.length - 1; + tickMajorSize = +x; + tickMinorSize = n > 1 ? +y : tickMajorSize; + tickEndSize = n > 0 ? +arguments[n] : tickMajorSize; + return axis; + }; + axis.tickPadding = function(x) { + if (!arguments.length) return tickPadding; + tickPadding = +x; + return axis; + }; + axis.tickSubdivide = function(x) { + if (!arguments.length) return tickSubdivide; + tickSubdivide = +x; + return axis; + }; + return axis; + }; + d3.svg.brush = function() { + function brush(g) { + g.each(function() { + var g = d3.select(this), bg = g.selectAll(".background").data([ 0 ]), fg = g.selectAll(".extent").data([ 0 ]), tz = g.selectAll(".resize").data(resizes, String), e; + g.style("pointer-events", "all").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart); + bg.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair"); + fg.enter().append("rect").attr("class", "extent").style("cursor", "move"); + tz.enter().append("g").attr("class", function(d) { + return "resize " + d; + }).style("cursor", function(d) { + return d3_svg_brushCursor[d]; + }).append("rect").attr("x", function(d) { + return /[ew]$/.test(d) ? -3 : null; + }).attr("y", function(d) { + return /^[ns]/.test(d) ? -3 : null; + }).attr("width", 6).attr("height", 6).style("visibility", "hidden"); + tz.style("display", brush.empty() ? "none" : null); + tz.exit().remove(); + if (x) { + e = d3_scaleRange(x); + bg.attr("x", e[0]).attr("width", e[1] - e[0]); + redrawX(g); + } + if (y) { + e = d3_scaleRange(y); + bg.attr("y", e[0]).attr("height", e[1] - e[0]); + redrawY(g); + } + redraw(g); + }); + } + function redraw(g) { + g.selectAll(".resize").attr("transform", function(d) { + return "translate(" + extent[+/e$/.test(d)][0] + "," + extent[+/^s/.test(d)][1] + ")"; + }); + } + function redrawX(g) { + g.select(".extent").attr("x", extent[0][0]); + g.selectAll(".extent,.n>rect,.s>rect").attr("width", extent[1][0] - extent[0][0]); + } + function redrawY(g) { + g.select(".extent").attr("y", extent[0][1]); + g.selectAll(".extent,.e>rect,.w>rect").attr("height", extent[1][1] - extent[0][1]); + } + function brushstart() { + function mouse() { + var touches = d3.event.changedTouches; + return touches ? d3.touches(target, touches)[0] : d3.mouse(target); + } + function keydown() { + if (d3.event.keyCode == 32) { + if (!dragging) { + center = null; + origin[0] -= extent[1][0]; + origin[1] -= extent[1][1]; + dragging = 2; + } + d3_eventCancel(); + } + } + function keyup() { + if (d3.event.keyCode == 32 && dragging == 2) { + origin[0] += extent[1][0]; + origin[1] += extent[1][1]; + dragging = 0; + d3_eventCancel(); + } + } + function brushmove() { + var point = mouse(), moved = false; + if (offset) { + point[0] += offset[0]; + point[1] += offset[1]; + } + if (!dragging) { + if (d3.event.altKey) { + if (!center) center = [ (extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2 ]; + origin[0] = extent[+(point[0] < center[0])][0]; + origin[1] = extent[+(point[1] < center[1])][1]; + } else center = null; + } + if (resizingX && move1(point, x, 0)) { + redrawX(g); + moved = true; + } + if (resizingY && move1(point, y, 1)) { + redrawY(g); + moved = true; + } + if (moved) { + redraw(g); + event_({ + type: "brush", + mode: dragging ? "move" : "resize" + }); + } + } + function move1(point, scale, i) { + var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], size = extent[1][i] - extent[0][i], min, max; + if (dragging) { + r0 -= position; + r1 -= size + position; + } + min = Math.max(r0, Math.min(r1, point[i])); + if (dragging) { + max = (min += position) + size; + } else { + if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); + if (position < min) { + max = min; + min = position; + } else { + max = position; + } + } + if (extent[0][i] !== min || extent[1][i] !== max) { + extentDomain = null; + extent[0][i] = min; + extent[1][i] = max; + return true; + } + } + function brushend() { + brushmove(); + g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); + d3.select("body").style("cursor", null); + w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null); + event_({ + type: "brushend" + }); + d3_eventCancel(); + } + var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), center, origin = mouse(), offset; + var w = d3.select(window).on("mousemove.brush", brushmove).on("mouseup.brush", brushend).on("touchmove.brush", brushmove).on("touchend.brush", brushend).on("keydown.brush", keydown).on("keyup.brush", keyup); + if (dragging) { + origin[0] = extent[0][0] - origin[0]; + origin[1] = extent[0][1] - origin[1]; + } else if (resizing) { + var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing); + offset = [ extent[1 - ex][0] - origin[0], extent[1 - ey][1] - origin[1] ]; + origin[0] = extent[ex][0]; + origin[1] = extent[ey][1]; + } else if (d3.event.altKey) center = origin.slice(); + g.style("pointer-events", "none").selectAll(".resize").style("display", null); + d3.select("body").style("cursor", eventTarget.style("cursor")); + event_({ + type: "brushstart" + }); + brushmove(); + d3_eventCancel(); + } + var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, resizes = d3_svg_brushResizes[0], extent = [ [ 0, 0 ], [ 0, 0 ] ], extentDomain; + brush.x = function(z) { + if (!arguments.length) return x; + x = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.y = function(z) { + if (!arguments.length) return y; + y = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.extent = function(z) { + var x0, x1, y0, y1, t; + if (!arguments.length) { + z = extentDomain || extent; + if (x) { + x0 = z[0][0], x1 = z[1][0]; + if (!extentDomain) { + x0 = extent[0][0], x1 = extent[1][0]; + if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + } + } + if (y) { + y0 = z[0][1], y1 = z[1][1]; + if (!extentDomain) { + y0 = extent[0][1], y1 = extent[1][1]; + if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + } + } + return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ]; + } + extentDomain = [ [ 0, 0 ], [ 0, 0 ] ]; + if (x) { + x0 = z[0], x1 = z[1]; + if (y) x0 = x0[0], x1 = x1[0]; + extentDomain[0][0] = x0, extentDomain[1][0] = x1; + if (x.invert) x0 = x(x0), x1 = x(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + extent[0][0] = x0 | 0, extent[1][0] = x1 | 0; + } + if (y) { + y0 = z[0], y1 = z[1]; + if (x) y0 = y0[1], y1 = y1[1]; + extentDomain[0][1] = y0, extentDomain[1][1] = y1; + if (y.invert) y0 = y(y0), y1 = y(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + extent[0][1] = y0 | 0, extent[1][1] = y1 | 0; + } + return brush; + }; + brush.clear = function() { + extentDomain = null; + extent[0][0] = extent[0][1] = extent[1][0] = extent[1][1] = 0; + return brush; + }; + brush.empty = function() { + return x && extent[0][0] === extent[1][0] || y && extent[0][1] === extent[1][1]; + }; + return d3.rebind(brush, event, "on"); + }; + var d3_svg_brushCursor = { + n: "ns-resize", + e: "ew-resize", + s: "ns-resize", + w: "ew-resize", + nw: "nwse-resize", + ne: "nesw-resize", + se: "nwse-resize", + sw: "nesw-resize" + }; + var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ]; + d3.behavior = {}; + d3.behavior.drag = function() { + function drag() { + this.on("mousedown.drag", mousedown).on("touchstart.drag", mousedown); + } + function mousedown() { + function point() { + var p = target.parentNode; + return touchId ? d3.touches(p).filter(function(p) { + return p.identifier === touchId; + })[0] : d3.mouse(p); + } + function dragmove() { + if (!target.parentNode) return dragend(); + var p = point(), dx = p[0] - origin_[0], dy = p[1] - origin_[1]; + moved |= dx | dy; + origin_ = p; + d3_eventCancel(); + event_({ + type: "drag", + x: p[0] + offset[0], + y: p[1] + offset[1], + dx: dx, + dy: dy + }); + } + function dragend() { + event_({ + type: "dragend" + }); + if (moved) { + d3_eventCancel(); + if (d3.event.target === eventTarget) w.on("click.drag", click, true); + } + w.on(touchId ? "touchmove.drag-" + touchId : "mousemove.drag", null).on(touchId ? "touchend.drag-" + touchId : "mouseup.drag", null); + } + function click() { + d3_eventCancel(); + w.on("click.drag", null); + } + var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, touchId = d3.event.touches && d3.event.changedTouches[0].identifier, offset, origin_ = point(), moved = 0; + var w = d3.select(window).on(touchId ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove).on(touchId ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true); + if (origin) { + offset = origin.apply(target, arguments); + offset = [ offset.x - origin_[0], offset.y - origin_[1] ]; + } else { + offset = [ 0, 0 ]; + } + if (!touchId) d3_eventCancel(); + event_({ + type: "dragstart" + }); + } + var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null; + drag.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return drag; + }; + return d3.rebind(drag, event, "on"); + }; + d3.behavior.zoom = function() { + function zoom() { + this.on("mousedown.zoom", mousedown).on("mousewheel.zoom", mousewheel).on("mousemove.zoom", mousemove).on("DOMMouseScroll.zoom", mousewheel).on("dblclick.zoom", dblclick).on("touchstart.zoom", touchstart).on("touchmove.zoom", touchmove).on("touchend.zoom", touchstart); + } + function location(p) { + return [ (p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale ]; + } + function point(l) { + return [ l[0] * scale + translate[0], l[1] * scale + translate[1] ]; + } + function scaleTo(s) { + scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); + } + function translateTo(p, l) { + l = point(l); + translate[0] += p[0] - l[0]; + translate[1] += p[1] - l[1]; + } + function dispatch(event) { + if (x1) x1.domain(x0.range().map(function(x) { + return (x - translate[0]) / scale; + }).map(x0.invert)); + if (y1) y1.domain(y0.range().map(function(y) { + return (y - translate[1]) / scale; + }).map(y0.invert)); + d3.event.preventDefault(); + event({ + type: "zoom", + scale: scale, + translate: translate + }); + } + function mousedown() { + function mousemove() { + moved = 1; + translateTo(d3.mouse(target), l); + dispatch(event_); + } + function mouseup() { + if (moved) d3_eventCancel(); + w.on("mousemove.zoom", null).on("mouseup.zoom", null); + if (moved && d3.event.target === eventTarget) w.on("click.zoom", click, true); + } + function click() { + d3_eventCancel(); + w.on("click.zoom", null); + } + var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, moved = 0, w = d3.select(window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup), l = location(d3.mouse(target)); + window.focus(); + d3_eventCancel(); + } + function mousewheel() { + if (!translate0) translate0 = location(d3.mouse(this)); + scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * scale); + translateTo(d3.mouse(this), translate0); + dispatch(event.of(this, arguments)); + } + function mousemove() { + translate0 = null; + } + function dblclick() { + var p = d3.mouse(this), l = location(p); + scaleTo(d3.event.shiftKey ? scale / 2 : scale * 2); + translateTo(p, l); + dispatch(event.of(this, arguments)); + } + function touchstart() { + var touches = d3.touches(this), now = Date.now(); + scale0 = scale; + translate0 = {}; + touches.forEach(function(t) { + translate0[t.identifier] = location(t); + }); + d3_eventCancel(); + if (touches.length === 1) { + if (now - touchtime < 500) { + var p = touches[0], l = location(touches[0]); + scaleTo(scale * 2); + translateTo(p, l); + dispatch(event.of(this, arguments)); + } + touchtime = now; + } + } + function touchmove() { + var touches = d3.touches(this), p0 = touches[0], l0 = translate0[p0.identifier]; + if (p1 = touches[1]) { + var p1, l1 = translate0[p1.identifier]; + p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ]; + l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ]; + scaleTo(d3.event.scale * scale0); + } + translateTo(p0, l0); + touchtime = null; + dispatch(event.of(this, arguments)); + } + var translate = [ 0, 0 ], translate0, scale = 1, scale0, scaleExtent = d3_behavior_zoomInfinity, event = d3_eventDispatch(zoom, "zoom"), x0, x1, y0, y1, touchtime; + zoom.translate = function(x) { + if (!arguments.length) return translate; + translate = x.map(Number); + return zoom; + }; + zoom.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return zoom; + }; + zoom.scaleExtent = function(x) { + if (!arguments.length) return scaleExtent; + scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number); + return zoom; + }; + zoom.x = function(z) { + if (!arguments.length) return x1; + x1 = z; + x0 = z.copy(); + return zoom; + }; + zoom.y = function(z) { + if (!arguments.length) return y1; + y1 = z; + y0 = z.copy(); + return zoom; + }; + return d3.rebind(zoom, event, "on"); + }; + var d3_behavior_zoomDiv, d3_behavior_zoomInfinity = [ 0, Infinity ]; + d3.layout = {}; + d3.layout.bundle = function() { + return function(links) { + var paths = [], i = -1, n = links.length; + while (++i < n) paths.push(d3_layout_bundlePath(links[i])); + return paths; + }; + }; + d3.layout.chord = function() { + function relayout() { + var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j; + chords = []; + groups = []; + k = 0, i = -1; + while (++i < n) { + x = 0, j = -1; + while (++j < n) { + x += matrix[i][j]; + } + groupSums.push(x); + subgroupIndex.push(d3.range(n)); + k += x; + } + if (sortGroups) { + groupIndex.sort(function(a, b) { + return sortGroups(groupSums[a], groupSums[b]); + }); + } + if (sortSubgroups) { + subgroupIndex.forEach(function(d, i) { + d.sort(function(a, b) { + return sortSubgroups(matrix[i][a], matrix[i][b]); + }); + }); + } + k = (2 * Math.PI - padding * n) / k; + x = 0, i = -1; + while (++i < n) { + x0 = x, j = -1; + while (++j < n) { + var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k; + subgroups[di + "-" + dj] = { + index: di, + subindex: dj, + startAngle: a0, + endAngle: a1, + value: v + }; + } + groups[di] = { + index: di, + startAngle: x0, + endAngle: x, + value: (x - x0) / k + }; + x += padding; + } + i = -1; + while (++i < n) { + j = i - 1; + while (++j < n) { + var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i]; + if (source.value || target.value) { + chords.push(source.value < target.value ? { + source: target, + target: source + } : { + source: source, + target: target + }); + } + } + } + if (sortChords) resort(); + } + function resort() { + chords.sort(function(a, b) { + return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2); + }); + } + var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords; + chord.matrix = function(x) { + if (!arguments.length) return matrix; + n = (matrix = x) && matrix.length; + chords = groups = null; + return chord; + }; + chord.padding = function(x) { + if (!arguments.length) return padding; + padding = x; + chords = groups = null; + return chord; + }; + chord.sortGroups = function(x) { + if (!arguments.length) return sortGroups; + sortGroups = x; + chords = groups = null; + return chord; + }; + chord.sortSubgroups = function(x) { + if (!arguments.length) return sortSubgroups; + sortSubgroups = x; + chords = null; + return chord; + }; + chord.sortChords = function(x) { + if (!arguments.length) return sortChords; + sortChords = x; + if (chords) resort(); + return chord; + }; + chord.chords = function() { + if (!chords) relayout(); + return chords; + }; + chord.groups = function() { + if (!groups) relayout(); + return groups; + }; + return chord; + }; + d3.layout.force = function() { + function repulse(node) { + return function(quad, x1, y1, x2, y2) { + if (quad.point !== node) { + var dx = quad.cx - node.x, dy = quad.cy - node.y, dn = 1 / Math.sqrt(dx * dx + dy * dy); + if ((x2 - x1) * dn < theta) { + var k = quad.charge * dn * dn; + node.px -= dx * k; + node.py -= dy * k; + return true; + } + if (quad.point && isFinite(dn)) { + var k = quad.pointCharge * dn * dn; + node.px -= dx * k; + node.py -= dy * k; + } + } + return !quad.charge; + }; + } + function dragmove(d) { + d.px = d3.event.x; + d.py = d3.event.y; + force.resume(); + } + var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, gravity = .1, theta = .8, interval, nodes = [], links = [], distances, strengths, charges; + force.tick = function() { + if ((alpha *= .99) < .005) { + event.end({ + type: "end", + alpha: alpha = 0 + }); + return true; + } + var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y; + for (i = 0; i < m; ++i) { + o = links[i]; + s = o.source; + t = o.target; + x = t.x - s.x; + y = t.y - s.y; + if (l = x * x + y * y) { + l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; + x *= l; + y *= l; + t.x -= x * (k = s.weight / (t.weight + s.weight)); + t.y -= y * k; + s.x += x * (k = 1 - k); + s.y += y * k; + } + } + if (k = alpha * gravity) { + x = size[0] / 2; + y = size[1] / 2; + i = -1; + if (k) while (++i < n) { + o = nodes[i]; + o.x += (x - o.x) * k; + o.y += (y - o.y) * k; + } + } + if (charge) { + d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); + i = -1; + while (++i < n) { + if (!(o = nodes[i]).fixed) { + q.visit(repulse(o)); + } + } + } + i = -1; + while (++i < n) { + o = nodes[i]; + if (o.fixed) { + o.x = o.px; + o.y = o.py; + } else { + o.x -= (o.px - (o.px = o.x)) * friction; + o.y -= (o.py - (o.py = o.y)) * friction; + } + } + event.tick({ + type: "tick", + alpha: alpha + }); + }; + force.nodes = function(x) { + if (!arguments.length) return nodes; + nodes = x; + return force; + }; + force.links = function(x) { + if (!arguments.length) return links; + links = x; + return force; + }; + force.size = function(x) { + if (!arguments.length) return size; + size = x; + return force; + }; + force.linkDistance = function(x) { + if (!arguments.length) return linkDistance; + linkDistance = d3_functor(x); + return force; + }; + force.distance = force.linkDistance; + force.linkStrength = function(x) { + if (!arguments.length) return linkStrength; + linkStrength = d3_functor(x); + return force; + }; + force.friction = function(x) { + if (!arguments.length) return friction; + friction = x; + return force; + }; + force.charge = function(x) { + if (!arguments.length) return charge; + charge = typeof x === "function" ? x : +x; + return force; + }; + force.gravity = function(x) { + if (!arguments.length) return gravity; + gravity = x; + return force; + }; + force.theta = function(x) { + if (!arguments.length) return theta; + theta = x; + return force; + }; + force.alpha = function(x) { + if (!arguments.length) return alpha; + if (alpha) { + if (x > 0) alpha = x; else alpha = 0; + } else if (x > 0) { + event.start({ + type: "start", + alpha: alpha = x + }); + d3.timer(force.tick); + } + return force; + }; + force.start = function() { + function position(dimension, size) { + var neighbors = neighbor(i), j = -1, m = neighbors.length, x; + while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x; + return Math.random() * size; + } + function neighbor() { + if (!neighbors) { + neighbors = []; + for (j = 0; j < n; ++j) { + neighbors[j] = []; + } + for (j = 0; j < m; ++j) { + var o = links[j]; + neighbors[o.source.index].push(o.target); + neighbors[o.target.index].push(o.source); + } + } + return neighbors[i]; + } + var i, j, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o; + for (i = 0; i < n; ++i) { + (o = nodes[i]).index = i; + o.weight = 0; + } + distances = []; + strengths = []; + for (i = 0; i < m; ++i) { + o = links[i]; + if (typeof o.source == "number") o.source = nodes[o.source]; + if (typeof o.target == "number") o.target = nodes[o.target]; + distances[i] = linkDistance.call(this, o, i); + strengths[i] = linkStrength.call(this, o, i); + ++o.source.weight; + ++o.target.weight; + } + for (i = 0; i < n; ++i) { + o = nodes[i]; + if (isNaN(o.x)) o.x = position("x", w); + if (isNaN(o.y)) o.y = position("y", h); + if (isNaN(o.px)) o.px = o.x; + if (isNaN(o.py)) o.py = o.y; + } + charges = []; + if (typeof charge === "function") { + for (i = 0; i < n; ++i) { + charges[i] = +charge.call(this, nodes[i], i); + } + } else { + for (i = 0; i < n; ++i) { + charges[i] = charge; + } + } + return force.resume(); + }; + force.resume = function() { + return force.alpha(.1); + }; + force.stop = function() { + return force.alpha(0); + }; + force.drag = function() { + if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart", d3_layout_forceDragstart).on("drag", dragmove).on("dragend", d3_layout_forceDragend); + this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag); + }; + return d3.rebind(force, event, "on"); + }; + d3.layout.partition = function() { + function position(node, x, dx, dy) { + var children = node.children; + node.x = x; + node.y = node.depth * dy; + node.dx = dx; + node.dy = dy; + if (children && (n = children.length)) { + var i = -1, n, c, d; + dx = node.value ? dx / node.value : 0; + while (++i < n) { + position(c = children[i], x, d = c.value * dx, dy); + x += d; + } + } + } + function depth(node) { + var children = node.children, d = 0; + if (children && (n = children.length)) { + var i = -1, n; + while (++i < n) d = Math.max(d, depth(children[i])); + } + return 1 + d; + } + function partition(d, i) { + var nodes = hierarchy.call(this, d, i); + position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); + return nodes; + } + var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ]; + partition.size = function(x) { + if (!arguments.length) return size; + size = x; + return partition; + }; + return d3_layout_hierarchyRebind(partition, hierarchy); + }; + d3.layout.pie = function() { + function pie(data, i) { + var values = data.map(function(d, i) { + return +value.call(pie, d, i); + }); + var a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle); + var k = ((typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - startAngle) / d3.sum(values); + var index = d3.range(data.length); + if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) { + return values[j] - values[i]; + } : function(i, j) { + return sort(data[i], data[j]); + }); + var arcs = []; + index.forEach(function(i) { + var d; + arcs[i] = { + data: data[i], + value: d = values[i], + startAngle: a, + endAngle: a += d * k + }; + }); + return arcs; + } + var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = 2 * Math.PI; + pie.value = function(x) { + if (!arguments.length) return value; + value = x; + return pie; + }; + pie.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return pie; + }; + pie.startAngle = function(x) { + if (!arguments.length) return startAngle; + startAngle = x; + return pie; + }; + pie.endAngle = function(x) { + if (!arguments.length) return endAngle; + endAngle = x; + return pie; + }; + return pie; + }; + var d3_layout_pieSortByValue = {}; + d3.layout.stack = function() { + function stack(data, index) { + var series = data.map(function(d, i) { + return values.call(stack, d, i); + }); + var points = series.map(function(d, i) { + return d.map(function(v, i) { + return [ x.call(stack, v, i), y.call(stack, v, i) ]; + }); + }); + var orders = order.call(stack, points, index); + series = d3.permute(series, orders); + points = d3.permute(points, orders); + var offsets = offset.call(stack, points, index); + var n = series.length, m = series[0].length, i, j, o; + for (j = 0; j < m; ++j) { + out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); + for (i = 1; i < n; ++i) { + out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); + } + } + return data; + } + var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; + stack.values = function(x) { + if (!arguments.length) return values; + values = x; + return stack; + }; + stack.order = function(x) { + if (!arguments.length) return order; + order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; + return stack; + }; + stack.offset = function(x) { + if (!arguments.length) return offset; + offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; + return stack; + }; + stack.x = function(z) { + if (!arguments.length) return x; + x = z; + return stack; + }; + stack.y = function(z) { + if (!arguments.length) return y; + y = z; + return stack; + }; + stack.out = function(z) { + if (!arguments.length) return out; + out = z; + return stack; + }; + return stack; + }; + var d3_layout_stackOrders = d3.map({ + "inside-out": function(data) { + var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) { + return max[a] - max[b]; + }), top = 0, bottom = 0, tops = [], bottoms = []; + for (i = 0; i < n; ++i) { + j = index[i]; + if (top < bottom) { + top += sums[j]; + tops.push(j); + } else { + bottom += sums[j]; + bottoms.push(j); + } + } + return bottoms.reverse().concat(tops); + }, + reverse: function(data) { + return d3.range(data.length).reverse(); + }, + "default": d3_layout_stackOrderDefault + }); + var d3_layout_stackOffsets = d3.map({ + silhouette: function(data) { + var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o > max) max = o; + sums.push(o); + } + for (j = 0; j < m; ++j) { + y0[j] = (max - sums[j]) / 2; + } + return y0; + }, + wiggle: function(data) { + var n = data.length, x = data[0], m = x.length, max = 0, i, j, k, s1, s2, s3, dx, o, o0, y0 = []; + y0[0] = o = o0 = 0; + for (j = 1; j < m; ++j) { + for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; + for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { + for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { + s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; + } + s2 += s3 * data[i][j][1]; + } + y0[j] = o -= s1 ? s2 / s1 * dx : 0; + if (o < o0) o0 = o; + } + for (j = 0; j < m; ++j) y0[j] -= o0; + return y0; + }, + expand: function(data) { + var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k; + } + for (j = 0; j < m; ++j) y0[j] = 0; + return y0; + }, + zero: d3_layout_stackOffsetZero + }); + d3.layout.histogram = function() { + function histogram(data, i) { + var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x; + while (++i < m) { + bin = bins[i] = []; + bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); + bin.y = 0; + } + if (m > 0) { + i = -1; + while (++i < n) { + x = values[i]; + if (x >= range[0] && x <= range[1]) { + bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; + bin.y += k; + bin.push(data[i]); + } + } + } + return bins; + } + var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges; + histogram.value = function(x) { + if (!arguments.length) return valuer; + valuer = x; + return histogram; + }; + histogram.range = function(x) { + if (!arguments.length) return ranger; + ranger = d3_functor(x); + return histogram; + }; + histogram.bins = function(x) { + if (!arguments.length) return binner; + binner = typeof x === "number" ? function(range) { + return d3_layout_histogramBinFixed(range, x); + } : d3_functor(x); + return histogram; + }; + histogram.frequency = function(x) { + if (!arguments.length) return frequency; + frequency = !!x; + return histogram; + }; + return histogram; + }; + d3.layout.hierarchy = function() { + function recurse(data, depth, nodes) { + var childs = children.call(hierarchy, data, depth), node = d3_layout_hierarchyInline ? data : { + data: data + }; + node.depth = depth; + nodes.push(node); + if (childs && (n = childs.length)) { + var i = -1, n, c = node.children = [], v = 0, j = depth + 1, d; + while (++i < n) { + d = recurse(childs[i], j, nodes); + d.parent = node; + c.push(d); + v += d.value; + } + if (sort) c.sort(sort); + if (value) node.value = v; + } else if (value) { + node.value = +value.call(hierarchy, data, depth) || 0; + } + return node; + } + function revalue(node, depth) { + var children = node.children, v = 0; + if (children && (n = children.length)) { + var i = -1, n, j = depth + 1; + while (++i < n) v += revalue(children[i], j); + } else if (value) { + v = +value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth) || 0; + } + if (value) node.value = v; + return v; + } + function hierarchy(d) { + var nodes = []; + recurse(d, 0, nodes); + return nodes; + } + var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; + hierarchy.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return hierarchy; + }; + hierarchy.children = function(x) { + if (!arguments.length) return children; + children = x; + return hierarchy; + }; + hierarchy.value = function(x) { + if (!arguments.length) return value; + value = x; + return hierarchy; + }; + hierarchy.revalue = function(root) { + revalue(root, 0); + return root; + }; + return hierarchy; + }; + var d3_layout_hierarchyInline = false; + d3.layout.pack = function() { + function pack(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0]; + root.x = 0; + root.y = 0; + d3_layout_treeVisitAfter(root, function(d) { + d.r = Math.sqrt(d.value); + }); + d3_layout_treeVisitAfter(root, d3_layout_packSiblings); + var w = size[0], h = size[1], k = Math.max(2 * root.r / w, 2 * root.r / h); + if (padding > 0) { + var dr = padding * k / 2; + d3_layout_treeVisitAfter(root, function(d) { + d.r += dr; + }); + d3_layout_treeVisitAfter(root, d3_layout_packSiblings); + d3_layout_treeVisitAfter(root, function(d) { + d.r -= dr; + }); + k = Math.max(2 * root.r / w, 2 * root.r / h); + } + d3_layout_packTransform(root, w / 2, h / 2, 1 / k); + return nodes; + } + var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ]; + pack.size = function(x) { + if (!arguments.length) return size; + size = x; + return pack; + }; + pack.padding = function(_) { + if (!arguments.length) return padding; + padding = +_; + return pack; + }; + return d3_layout_hierarchyRebind(pack, hierarchy); + }; + d3.layout.cluster = function() { + function cluster(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0, kx, ky; + d3_layout_treeVisitAfter(root, function(node) { + var children = node.children; + if (children && children.length) { + node.x = d3_layout_clusterX(children); + node.y = d3_layout_clusterY(children); + } else { + node.x = previousNode ? x += separation(node, previousNode) : 0; + node.y = 0; + previousNode = node; + } + }); + var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2; + d3_layout_treeVisitAfter(root, function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; + }); + return nodes; + } + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ]; + cluster.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return cluster; + }; + cluster.size = function(x) { + if (!arguments.length) return size; + size = x; + return cluster; + }; + return d3_layout_hierarchyRebind(cluster, hierarchy); + }; + d3.layout.tree = function() { + function tree(d, i) { + function firstWalk(node, previousSibling) { + var children = node.children, layout = node._tree; + if (children && (n = children.length)) { + var n, firstChild = children[0], previousChild, ancestor = firstChild, child, i = -1; + while (++i < n) { + child = children[i]; + firstWalk(child, previousChild); + ancestor = apportion(child, previousChild, ancestor); + previousChild = child; + } + d3_layout_treeShift(node); + var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim); + if (previousSibling) { + layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); + layout.mod = layout.prelim - midpoint; + } else { + layout.prelim = midpoint; + } + } else { + if (previousSibling) { + layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); + } + } + } + function secondWalk(node, x) { + node.x = node._tree.prelim + x; + var children = node.children; + if (children && (n = children.length)) { + var i = -1, n; + x += node._tree.mod; + while (++i < n) { + secondWalk(children[i], x); + } + } + } + function apportion(node, previousSibling, ancestor) { + if (previousSibling) { + var vip = node, vop = node, vim = previousSibling, vom = node.parent.children[0], sip = vip._tree.mod, sop = vop._tree.mod, sim = vim._tree.mod, som = vom._tree.mod, shift; + while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { + vom = d3_layout_treeLeft(vom); + vop = d3_layout_treeRight(vop); + vop._tree.ancestor = node; + shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip); + if (shift > 0) { + d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift); + sip += shift; + sop += shift; + } + sim += vim._tree.mod; + sip += vip._tree.mod; + som += vom._tree.mod; + sop += vop._tree.mod; + } + if (vim && !d3_layout_treeRight(vop)) { + vop._tree.thread = vim; + vop._tree.mod += sim - sop; + } + if (vip && !d3_layout_treeLeft(vom)) { + vom._tree.thread = vip; + vom._tree.mod += sip - som; + ancestor = node; + } + } + return ancestor; + } + var nodes = hierarchy.call(this, d, i), root = nodes[0]; + d3_layout_treeVisitAfter(root, function(node, previousSibling) { + node._tree = { + ancestor: node, + prelim: 0, + mod: 0, + change: 0, + shift: 0, + number: previousSibling ? previousSibling._tree.number + 1 : 0 + }; + }); + firstWalk(root); + secondWalk(root, -root._tree.prelim); + var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), right = d3_layout_treeSearch(root, d3_layout_treeRightmost), deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2, y1 = deep.depth || 1; + d3_layout_treeVisitAfter(root, function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = node.depth / y1 * size[1]; + delete node._tree; + }); + return nodes; + } + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ]; + tree.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return tree; + }; + tree.size = function(x) { + if (!arguments.length) return size; + size = x; + return tree; + }; + return d3_layout_hierarchyRebind(tree, hierarchy); + }; + d3.layout.treemap = function() { + function scale(children, k) { + var i = -1, n = children.length, child, area; + while (++i < n) { + area = (child = children[i]).value * (k < 0 ? 0 : k); + child.area = isNaN(area) || area <= 0 ? 0 : area; + } + } + function squarify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = Math.min(rect.dx, rect.dy), n; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while ((n = remaining.length) > 0) { + row.push(child = remaining[n - 1]); + row.area += child.area; + if ((score = worst(row, u)) <= best) { + remaining.pop(); + best = score; + } else { + row.area -= row.pop().area; + position(row, u, rect, false); + u = Math.min(rect.dx, rect.dy); + row.length = row.area = 0; + best = Infinity; + } + } + if (row.length) { + position(row, u, rect, true); + row.length = row.area = 0; + } + children.forEach(squarify); + } + } + function stickify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), remaining = children.slice(), child, row = []; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while (child = remaining.pop()) { + row.push(child); + row.area += child.area; + if (child.z != null) { + position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); + row.length = row.area = 0; + } + } + children.forEach(stickify); + } + } + function worst(row, u) { + var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length; + while (++i < n) { + if (!(r = row[i].area)) continue; + if (r < rmin) rmin = r; + if (r > rmax) rmax = r; + } + s *= s; + u *= u; + return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity; + } + function position(row, u, rect, flush) { + var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o; + if (u == rect.dx) { + if (flush || v > rect.dy) v = rect.dy; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dy = v; + x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0); + } + o.z = true; + o.dx += rect.x + rect.dx - x; + rect.y += v; + rect.dy -= v; + } else { + if (flush || v > rect.dx) v = rect.dx; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dx = v; + y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0); + } + o.z = false; + o.dy += rect.y + rect.dy - y; + rect.x += v; + rect.dx -= v; + } + } + function treemap(d) { + var nodes = stickies || hierarchy(d), root = nodes[0]; + root.x = 0; + root.y = 0; + root.dx = size[0]; + root.dy = size[1]; + if (stickies) hierarchy.revalue(root); + scale([ root ], root.dx * root.dy / root.value); + (stickies ? stickify : squarify)(root); + if (sticky) stickies = nodes; + return nodes; + } + var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, ratio = .5 * (1 + Math.sqrt(5)); + treemap.size = function(x) { + if (!arguments.length) return size; + size = x; + return treemap; + }; + treemap.padding = function(x) { + function padFunction(node) { + var p = x.call(treemap, node, node.depth); + return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p); + } + function padConstant(node) { + return d3_layout_treemapPad(node, x); + } + if (!arguments.length) return padding; + var type; + pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], padConstant) : padConstant; + return treemap; + }; + treemap.round = function(x) { + if (!arguments.length) return round != Number; + round = x ? Math.round : Number; + return treemap; + }; + treemap.sticky = function(x) { + if (!arguments.length) return sticky; + sticky = x; + stickies = null; + return treemap; + }; + treemap.ratio = function(x) { + if (!arguments.length) return ratio; + ratio = x; + return treemap; + }; + return d3_layout_hierarchyRebind(treemap, hierarchy); + }; + d3.csv = d3_dsv(",", "text/csv"); + d3.tsv = d3_dsv(" ", "text/tab-separated-values"); + d3.geo = {}; + var d3_geo_radians = Math.PI / 180; + d3.geo.azimuthal = function() { + function azimuthal(coordinates) { + var x1 = coordinates[0] * d3_geo_radians - x0, y1 = coordinates[1] * d3_geo_radians, cx1 = Math.cos(x1), sx1 = Math.sin(x1), cy1 = Math.cos(y1), sy1 = Math.sin(y1), cc = mode !== "orthographic" ? sy0 * sy1 + cy0 * cy1 * cx1 : null, c, k = mode === "stereographic" ? 1 / (1 + cc) : mode === "gnomonic" ? 1 / cc : mode === "equidistant" ? (c = Math.acos(cc), c ? c / Math.sin(c) : 0) : mode === "equalarea" ? Math.sqrt(2 / (1 + cc)) : 1, x = k * cy1 * sx1, y = k * (sy0 * cy1 * cx1 - cy0 * sy1); + return [ scale * x + translate[0], scale * y + translate[1] ]; + } + var mode = "orthographic", origin, scale = 200, translate = [ 480, 250 ], x0, y0, cy0, sy0; + azimuthal.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale, p = Math.sqrt(x * x + y * y), c = mode === "stereographic" ? 2 * Math.atan(p) : mode === "gnomonic" ? Math.atan(p) : mode === "equidistant" ? p : mode === "equalarea" ? 2 * Math.asin(.5 * p) : Math.asin(p), sc = Math.sin(c), cc = Math.cos(c); + return [ (x0 + Math.atan2(x * sc, p * cy0 * cc + y * sy0 * sc)) / d3_geo_radians, Math.asin(cc * sy0 - (p ? y * sc * cy0 / p : 0)) / d3_geo_radians ]; + }; + azimuthal.mode = function(x) { + if (!arguments.length) return mode; + mode = x + ""; + return azimuthal; + }; + azimuthal.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + x0 = origin[0] * d3_geo_radians; + y0 = origin[1] * d3_geo_radians; + cy0 = Math.cos(y0); + sy0 = Math.sin(y0); + return azimuthal; + }; + azimuthal.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return azimuthal; + }; + azimuthal.translate = function(x) { + if (!arguments.length) return translate; + translate = [ +x[0], +x[1] ]; + return azimuthal; + }; + return azimuthal.origin([ 0, 0 ]); + }; + d3.geo.albers = function() { + function albers(coordinates) { + var t = n * (d3_geo_radians * coordinates[0] - lng0), p = Math.sqrt(C - 2 * n * Math.sin(d3_geo_radians * coordinates[1])) / n; + return [ scale * p * Math.sin(t) + translate[0], scale * (p * Math.cos(t) - p0) + translate[1] ]; + } + function reload() { + var phi1 = d3_geo_radians * parallels[0], phi2 = d3_geo_radians * parallels[1], lat0 = d3_geo_radians * origin[1], s = Math.sin(phi1), c = Math.cos(phi1); + lng0 = d3_geo_radians * origin[0]; + n = .5 * (s + Math.sin(phi2)); + C = c * c + 2 * n * s; + p0 = Math.sqrt(C - 2 * n * Math.sin(lat0)) / n; + return albers; + } + var origin = [ -98, 38 ], parallels = [ 29.5, 45.5 ], scale = 1e3, translate = [ 480, 250 ], lng0, n, C, p0; + albers.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale, p0y = p0 + y, t = Math.atan2(x, p0y), p = Math.sqrt(x * x + p0y * p0y); + return [ (lng0 + t / n) / d3_geo_radians, Math.asin((C - p * p * n * n) / (2 * n)) / d3_geo_radians ]; + }; + albers.origin = function(x) { + if (!arguments.length) return origin; + origin = [ +x[0], +x[1] ]; + return reload(); + }; + albers.parallels = function(x) { + if (!arguments.length) return parallels; + parallels = [ +x[0], +x[1] ]; + return reload(); + }; + albers.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return albers; + }; + albers.translate = function(x) { + if (!arguments.length) return translate; + translate = [ +x[0], +x[1] ]; + return albers; + }; + return reload(); + }; + d3.geo.albersUsa = function() { + function albersUsa(coordinates) { + var lon = coordinates[0], lat = coordinates[1]; + return (lat > 50 ? alaska : lon < -140 ? hawaii : lat < 21 ? puertoRico : lower48)(coordinates); + } + var lower48 = d3.geo.albers(); + var alaska = d3.geo.albers().origin([ -160, 60 ]).parallels([ 55, 65 ]); + var hawaii = d3.geo.albers().origin([ -160, 20 ]).parallels([ 8, 18 ]); + var puertoRico = d3.geo.albers().origin([ -60, 10 ]).parallels([ 8, 18 ]); + albersUsa.scale = function(x) { + if (!arguments.length) return lower48.scale(); + lower48.scale(x); + alaska.scale(x * .6); + hawaii.scale(x); + puertoRico.scale(x * 1.5); + return albersUsa.translate(lower48.translate()); + }; + albersUsa.translate = function(x) { + if (!arguments.length) return lower48.translate(); + var dz = lower48.scale() / 1e3, dx = x[0], dy = x[1]; + lower48.translate(x); + alaska.translate([ dx - 400 * dz, dy + 170 * dz ]); + hawaii.translate([ dx - 190 * dz, dy + 200 * dz ]); + puertoRico.translate([ dx + 580 * dz, dy + 430 * dz ]); + return albersUsa; + }; + return albersUsa.scale(lower48.scale()); + }; + d3.geo.bonne = function() { + function bonne(coordinates) { + var x = coordinates[0] * d3_geo_radians - x0, y = coordinates[1] * d3_geo_radians - y0; + if (y1) { + var p = c1 + y1 - y, E = x * Math.cos(y) / p; + x = p * Math.sin(E); + y = p * Math.cos(E) - c1; + } else { + x *= Math.cos(y); + y *= -1; + } + return [ scale * x + translate[0], scale * y + translate[1] ]; + } + var scale = 200, translate = [ 480, 250 ], x0, y0, y1, c1; + bonne.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale; + if (y1) { + var c = c1 + y, p = Math.sqrt(x * x + c * c); + y = c1 + y1 - p; + x = x0 + p * Math.atan2(x, c) / Math.cos(y); + } else { + y *= -1; + x /= Math.cos(y); + } + return [ x / d3_geo_radians, y / d3_geo_radians ]; + }; + bonne.parallel = function(x) { + if (!arguments.length) return y1 / d3_geo_radians; + c1 = 1 / Math.tan(y1 = x * d3_geo_radians); + return bonne; + }; + bonne.origin = function(x) { + if (!arguments.length) return [ x0 / d3_geo_radians, y0 / d3_geo_radians ]; + x0 = x[0] * d3_geo_radians; + y0 = x[1] * d3_geo_radians; + return bonne; + }; + bonne.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return bonne; + }; + bonne.translate = function(x) { + if (!arguments.length) return translate; + translate = [ +x[0], +x[1] ]; + return bonne; + }; + return bonne.origin([ 0, 0 ]).parallel(45); + }; + d3.geo.equirectangular = function() { + function equirectangular(coordinates) { + var x = coordinates[0] / 360, y = -coordinates[1] / 360; + return [ scale * x + translate[0], scale * y + translate[1] ]; + } + var scale = 500, translate = [ 480, 250 ]; + equirectangular.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale; + return [ 360 * x, -360 * y ]; + }; + equirectangular.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return equirectangular; + }; + equirectangular.translate = function(x) { + if (!arguments.length) return translate; + translate = [ +x[0], +x[1] ]; + return equirectangular; + }; + return equirectangular; + }; + d3.geo.mercator = function() { + function mercator(coordinates) { + var x = coordinates[0] / 360, y = -(Math.log(Math.tan(Math.PI / 4 + coordinates[1] * d3_geo_radians / 2)) / d3_geo_radians) / 360; + return [ scale * x + translate[0], scale * Math.max(-.5, Math.min(.5, y)) + translate[1] ]; + } + var scale = 500, translate = [ 480, 250 ]; + mercator.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale; + return [ 360 * x, 2 * Math.atan(Math.exp(-360 * y * d3_geo_radians)) / d3_geo_radians - 90 ]; + }; + mercator.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return mercator; + }; + mercator.translate = function(x) { + if (!arguments.length) return translate; + translate = [ +x[0], +x[1] ]; + return mercator; + }; + return mercator; + }; + d3.geo.path = function() { + function path(d, i) { + if (typeof pointRadius === "function") pointCircle = d3_path_circle(pointRadius.apply(this, arguments)); + pathType(d); + var result = buffer.length ? buffer.join("") : null; + buffer = []; + return result; + } + function project(coordinates) { + return projection(coordinates).join(","); + } + function polygonArea(coordinates) { + var sum = area(coordinates[0]), i = 0, n = coordinates.length; + while (++i < n) sum -= area(coordinates[i]); + return sum; + } + function polygonCentroid(coordinates) { + var polygon = d3.geom.polygon(coordinates[0].map(projection)), area = polygon.area(), centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1), x = centroid[0], y = centroid[1], z = area, i = 0, n = coordinates.length; + while (++i < n) { + polygon = d3.geom.polygon(coordinates[i].map(projection)); + area = polygon.area(); + centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1); + x -= centroid[0]; + y -= centroid[1]; + z -= area; + } + return [ x, y, 6 * z ]; + } + function area(coordinates) { + return Math.abs(d3.geom.polygon(coordinates.map(projection)).area()); + } + var pointRadius = 4.5, pointCircle = d3_path_circle(pointRadius), projection = d3.geo.albersUsa(), buffer = []; + var pathType = d3_geo_type({ + FeatureCollection: function(o) { + var features = o.features, i = -1, n = features.length; + while (++i < n) buffer.push(pathType(features[i].geometry)); + }, + Feature: function(o) { + pathType(o.geometry); + }, + Point: function(o) { + buffer.push("M", project(o.coordinates), pointCircle); + }, + MultiPoint: function(o) { + var coordinates = o.coordinates, i = -1, n = coordinates.length; + while (++i < n) buffer.push("M", project(coordinates[i]), pointCircle); + }, + LineString: function(o) { + var coordinates = o.coordinates, i = -1, n = coordinates.length; + buffer.push("M"); + while (++i < n) buffer.push(project(coordinates[i]), "L"); + buffer.pop(); + }, + MultiLineString: function(o) { + var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m; + while (++i < n) { + subcoordinates = coordinates[i]; + j = -1; + m = subcoordinates.length; + buffer.push("M"); + while (++j < m) buffer.push(project(subcoordinates[j]), "L"); + buffer.pop(); + } + }, + Polygon: function(o) { + var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m; + while (++i < n) { + subcoordinates = coordinates[i]; + j = -1; + if ((m = subcoordinates.length - 1) > 0) { + buffer.push("M"); + while (++j < m) buffer.push(project(subcoordinates[j]), "L"); + buffer[buffer.length - 1] = "Z"; + } + } + }, + MultiPolygon: function(o) { + var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m, subsubcoordinates, k, p; + while (++i < n) { + subcoordinates = coordinates[i]; + j = -1; + m = subcoordinates.length; + while (++j < m) { + subsubcoordinates = subcoordinates[j]; + k = -1; + if ((p = subsubcoordinates.length - 1) > 0) { + buffer.push("M"); + while (++k < p) buffer.push(project(subsubcoordinates[k]), "L"); + buffer[buffer.length - 1] = "Z"; + } + } + } + }, + GeometryCollection: function(o) { + var geometries = o.geometries, i = -1, n = geometries.length; + while (++i < n) buffer.push(pathType(geometries[i])); + } + }); + var areaType = path.area = d3_geo_type({ + FeatureCollection: function(o) { + var area = 0, features = o.features, i = -1, n = features.length; + while (++i < n) area += areaType(features[i]); + return area; + }, + Feature: function(o) { + return areaType(o.geometry); + }, + Polygon: function(o) { + return polygonArea(o.coordinates); + }, + MultiPolygon: function(o) { + var sum = 0, coordinates = o.coordinates, i = -1, n = coordinates.length; + while (++i < n) sum += polygonArea(coordinates[i]); + return sum; + }, + GeometryCollection: function(o) { + var sum = 0, geometries = o.geometries, i = -1, n = geometries.length; + while (++i < n) sum += areaType(geometries[i]); + return sum; + } + }, 0); + var centroidType = path.centroid = d3_geo_type({ + Feature: function(o) { + return centroidType(o.geometry); + }, + Polygon: function(o) { + var centroid = polygonCentroid(o.coordinates); + return [ centroid[0] / centroid[2], centroid[1] / centroid[2] ]; + }, + MultiPolygon: function(o) { + var area = 0, coordinates = o.coordinates, centroid, x = 0, y = 0, z = 0, i = -1, n = coordinates.length; + while (++i < n) { + centroid = polygonCentroid(coordinates[i]); + x += centroid[0]; + y += centroid[1]; + z += centroid[2]; + } + return [ x / z, y / z ]; + } + }); + path.projection = function(x) { + projection = x; + return path; + }; + path.pointRadius = function(x) { + if (typeof x === "function") pointRadius = x; else { + pointRadius = +x; + pointCircle = d3_path_circle(pointRadius); + } + return path; + }; + return path; + }; + d3.geo.bounds = function(feature) { + var left = Infinity, bottom = Infinity, right = -Infinity, top = -Infinity; + d3_geo_bounds(feature, function(x, y) { + if (x < left) left = x; + if (x > right) right = x; + if (y < bottom) bottom = y; + if (y > top) top = y; + }); + return [ [ left, bottom ], [ right, top ] ]; + }; + var d3_geo_boundsTypes = { + Feature: d3_geo_boundsFeature, + FeatureCollection: d3_geo_boundsFeatureCollection, + GeometryCollection: d3_geo_boundsGeometryCollection, + LineString: d3_geo_boundsLineString, + MultiLineString: d3_geo_boundsMultiLineString, + MultiPoint: d3_geo_boundsLineString, + MultiPolygon: d3_geo_boundsMultiPolygon, + Point: d3_geo_boundsPoint, + Polygon: d3_geo_boundsPolygon + }; + d3.geo.circle = function() { + function circle() {} + function visible(point) { + return arc.distance(point) < radians; + } + function clip(coordinates) { + var i = -1, n = coordinates.length, clipped = [], p0, p1, p2, d0, d1; + while (++i < n) { + d1 = arc.distance(p2 = coordinates[i]); + if (d1 < radians) { + if (p1) clipped.push(d3_geo_greatArcInterpolate(p1, p2)((d0 - radians) / (d0 - d1))); + clipped.push(p2); + p0 = p1 = null; + } else { + p1 = p2; + if (!p0 && clipped.length) { + clipped.push(d3_geo_greatArcInterpolate(clipped[clipped.length - 1], p1)((radians - d0) / (d1 - d0))); + p0 = p1; + } + } + d0 = d1; + } + p0 = coordinates[0]; + p1 = clipped[0]; + if (p1 && p2[0] === p0[0] && p2[1] === p0[1] && !(p2[0] === p1[0] && p2[1] === p1[1])) { + clipped.push(p1); + } + return resample(clipped); + } + function resample(coordinates) { + var i = 0, n = coordinates.length, j, m, resampled = n ? [ coordinates[0] ] : coordinates, resamples, origin = arc.source(); + while (++i < n) { + resamples = arc.source(coordinates[i - 1])(coordinates[i]).coordinates; + for (j = 0, m = resamples.length; ++j < m; ) resampled.push(resamples[j]); + } + arc.source(origin); + return resampled; + } + var origin = [ 0, 0 ], degrees = 90 - .01, radians = degrees * d3_geo_radians, arc = d3.geo.greatArc().source(origin).target(d3_identity); + circle.clip = function(d) { + if (typeof origin === "function") arc.source(origin.apply(this, arguments)); + return clipType(d) || null; + }; + var clipType = d3_geo_type({ + FeatureCollection: function(o) { + var features = o.features.map(clipType).filter(d3_identity); + return features && (o = Object.create(o), o.features = features, o); + }, + Feature: function(o) { + var geometry = clipType(o.geometry); + return geometry && (o = Object.create(o), o.geometry = geometry, o); + }, + Point: function(o) { + return visible(o.coordinates) && o; + }, + MultiPoint: function(o) { + var coordinates = o.coordinates.filter(visible); + return coordinates.length && { + type: o.type, + coordinates: coordinates + }; + }, + LineString: function(o) { + var coordinates = clip(o.coordinates); + return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); + }, + MultiLineString: function(o) { + var coordinates = o.coordinates.map(clip).filter(function(d) { + return d.length; + }); + return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); + }, + Polygon: function(o) { + var coordinates = o.coordinates.map(clip); + return coordinates[0].length && (o = Object.create(o), o.coordinates = coordinates, o); + }, + MultiPolygon: function(o) { + var coordinates = o.coordinates.map(function(d) { + return d.map(clip); + }).filter(function(d) { + return d[0].length; + }); + return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); + }, + GeometryCollection: function(o) { + var geometries = o.geometries.map(clipType).filter(d3_identity); + return geometries.length && (o = Object.create(o), o.geometries = geometries, o); + } + }); + circle.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + if (typeof origin !== "function") arc.source(origin); + return circle; + }; + circle.angle = function(x) { + if (!arguments.length) return degrees; + radians = (degrees = +x) * d3_geo_radians; + return circle; + }; + return d3.rebind(circle, arc, "precision"); + }; + d3.geo.greatArc = function() { + function greatArc() { + var d = greatArc.distance.apply(this, arguments), t = 0, dt = precision / d, coordinates = [ p0 ]; + while ((t += dt) < 1) coordinates.push(interpolate(t)); + coordinates.push(p1); + return { + type: "LineString", + coordinates: coordinates + }; + } + var source = d3_geo_greatArcSource, p0, target = d3_geo_greatArcTarget, p1, precision = 6 * d3_geo_radians, interpolate = d3_geo_greatArcInterpolator(); + greatArc.distance = function() { + if (typeof source === "function") interpolate.source(p0 = source.apply(this, arguments)); + if (typeof target === "function") interpolate.target(p1 = target.apply(this, arguments)); + return interpolate.distance(); + }; + greatArc.source = function(_) { + if (!arguments.length) return source; + source = _; + if (typeof source !== "function") interpolate.source(p0 = source); + return greatArc; + }; + greatArc.target = function(_) { + if (!arguments.length) return target; + target = _; + if (typeof target !== "function") interpolate.target(p1 = target); + return greatArc; + }; + greatArc.precision = function(_) { + if (!arguments.length) return precision / d3_geo_radians; + precision = _ * d3_geo_radians; + return greatArc; + }; + return greatArc; + }; + d3.geo.greatCircle = d3.geo.circle; + d3.geom = {}; + d3.geom.contour = function(grid, start) { + var s = start || d3_geom_contourStart(grid), c = [], x = s[0], y = s[1], dx = 0, dy = 0, pdx = NaN, pdy = NaN, i = 0; + do { + i = 0; + if (grid(x - 1, y - 1)) i += 1; + if (grid(x, y - 1)) i += 2; + if (grid(x - 1, y)) i += 4; + if (grid(x, y)) i += 8; + if (i === 6) { + dx = pdy === -1 ? -1 : 1; + dy = 0; + } else if (i === 9) { + dx = 0; + dy = pdx === 1 ? -1 : 1; + } else { + dx = d3_geom_contourDx[i]; + dy = d3_geom_contourDy[i]; + } + if (dx != pdx && dy != pdy) { + c.push([ x, y ]); + pdx = dx; + pdy = dy; + } + x += dx; + y += dy; + } while (s[0] != x || s[1] != y); + return c; + }; + var d3_geom_contourDx = [ 1, 0, 1, 1, -1, 0, -1, 1, 0, 0, 0, 0, -1, 0, -1, NaN ], d3_geom_contourDy = [ 0, -1, 0, 0, 0, -1, 0, 0, 1, -1, 1, 1, 0, -1, 0, NaN ]; + d3.geom.hull = function(vertices) { + if (vertices.length < 3) return []; + var len = vertices.length, plen = len - 1, points = [], stack = [], i, j, h = 0, x1, y1, x2, y2, u, v, a, sp; + for (i = 1; i < len; ++i) { + if (vertices[i][1] < vertices[h][1]) { + h = i; + } else if (vertices[i][1] == vertices[h][1]) { + h = vertices[i][0] < vertices[h][0] ? i : h; + } + } + for (i = 0; i < len; ++i) { + if (i === h) continue; + y1 = vertices[i][1] - vertices[h][1]; + x1 = vertices[i][0] - vertices[h][0]; + points.push({ + angle: Math.atan2(y1, x1), + index: i + }); + } + points.sort(function(a, b) { + return a.angle - b.angle; + }); + a = points[0].angle; + v = points[0].index; + u = 0; + for (i = 1; i < plen; ++i) { + j = points[i].index; + if (a == points[i].angle) { + x1 = vertices[v][0] - vertices[h][0]; + y1 = vertices[v][1] - vertices[h][1]; + x2 = vertices[j][0] - vertices[h][0]; + y2 = vertices[j][1] - vertices[h][1]; + if (x1 * x1 + y1 * y1 >= x2 * x2 + y2 * y2) { + points[i].index = -1; + } else { + points[u].index = -1; + a = points[i].angle; + u = i; + v = j; + } + } else { + a = points[i].angle; + u = i; + v = j; + } + } + stack.push(h); + for (i = 0, j = 0; i < 2; ++j) { + if (points[j].index !== -1) { + stack.push(points[j].index); + i++; + } + } + sp = stack.length; + for (; j < plen; ++j) { + if (points[j].index === -1) continue; + while (!d3_geom_hullCCW(stack[sp - 2], stack[sp - 1], points[j].index, vertices)) { + --sp; + } + stack[sp++] = points[j].index; + } + var poly = []; + for (i = 0; i < sp; ++i) { + poly.push(vertices[stack[i]]); + } + return poly; + }; + d3.geom.polygon = function(coordinates) { + coordinates.area = function() { + var i = 0, n = coordinates.length, a = coordinates[n - 1][0] * coordinates[0][1], b = coordinates[n - 1][1] * coordinates[0][0]; + while (++i < n) { + a += coordinates[i - 1][0] * coordinates[i][1]; + b += coordinates[i - 1][1] * coordinates[i][0]; + } + return (b - a) * .5; + }; + coordinates.centroid = function(k) { + var i = -1, n = coordinates.length, x = 0, y = 0, a, b = coordinates[n - 1], c; + if (!arguments.length) k = -1 / (6 * coordinates.area()); + while (++i < n) { + a = b; + b = coordinates[i]; + c = a[0] * b[1] - b[0] * a[1]; + x += (a[0] + b[0]) * c; + y += (a[1] + b[1]) * c; + } + return [ x * k, y * k ]; + }; + coordinates.clip = function(subject) { + var input, i = -1, n = coordinates.length, j, m, a = coordinates[n - 1], b, c, d; + while (++i < n) { + input = subject.slice(); + subject.length = 0; + b = coordinates[i]; + c = input[(m = input.length) - 1]; + j = -1; + while (++j < m) { + d = input[j]; + if (d3_geom_polygonInside(d, a, b)) { + if (!d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + subject.push(d); + } else if (d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + c = d; + } + a = b; + } + return subject; + }; + return coordinates; + }; + d3.geom.voronoi = function(vertices) { + var polygons = vertices.map(function() { + return []; + }); + d3_voronoi_tessellate(vertices, function(e) { + var s1, s2, x1, x2, y1, y2; + if (e.a === 1 && e.b >= 0) { + s1 = e.ep.r; + s2 = e.ep.l; + } else { + s1 = e.ep.l; + s2 = e.ep.r; + } + if (e.a === 1) { + y1 = s1 ? s1.y : -1e6; + x1 = e.c - e.b * y1; + y2 = s2 ? s2.y : 1e6; + x2 = e.c - e.b * y2; + } else { + x1 = s1 ? s1.x : -1e6; + y1 = e.c - e.a * x1; + x2 = s2 ? s2.x : 1e6; + y2 = e.c - e.a * x2; + } + var v1 = [ x1, y1 ], v2 = [ x2, y2 ]; + polygons[e.region.l.index].push(v1, v2); + polygons[e.region.r.index].push(v1, v2); + }); + return polygons.map(function(polygon, i) { + var cx = vertices[i][0], cy = vertices[i][1]; + polygon.forEach(function(v) { + v.angle = Math.atan2(v[0] - cx, v[1] - cy); + }); + return polygon.sort(function(a, b) { + return a.angle - b.angle; + }).filter(function(d, i) { + return !i || d.angle - polygon[i - 1].angle > 1e-10; + }); + }); + }; + var d3_voronoi_opposite = { + l: "r", + r: "l" + }; + d3.geom.delaunay = function(vertices) { + var edges = vertices.map(function() { + return []; + }), triangles = []; + d3_voronoi_tessellate(vertices, function(e) { + edges[e.region.l.index].push(vertices[e.region.r.index]); + }); + edges.forEach(function(edge, i) { + var v = vertices[i], cx = v[0], cy = v[1]; + edge.forEach(function(v) { + v.angle = Math.atan2(v[0] - cx, v[1] - cy); + }); + edge.sort(function(a, b) { + return a.angle - b.angle; + }); + for (var j = 0, m = edge.length - 1; j < m; j++) { + triangles.push([ v, edge[j], edge[j + 1] ]); + } + }); + return triangles; + }; + d3.geom.quadtree = function(points, x1, y1, x2, y2) { + function insert(n, p, x1, y1, x2, y2) { + if (isNaN(p.x) || isNaN(p.y)) return; + if (n.leaf) { + var v = n.point; + if (v) { + if (Math.abs(v.x - p.x) + Math.abs(v.y - p.y) < .01) { + insertChild(n, p, x1, y1, x2, y2); + } else { + n.point = null; + insertChild(n, v, x1, y1, x2, y2); + insertChild(n, p, x1, y1, x2, y2); + } + } else { + n.point = p; + } + } else { + insertChild(n, p, x1, y1, x2, y2); + } + } + function insertChild(n, p, x1, y1, x2, y2) { + var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, right = p.x >= sx, bottom = p.y >= sy, i = (bottom << 1) + right; + n.leaf = false; + n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); + if (right) x1 = sx; else x2 = sx; + if (bottom) y1 = sy; else y2 = sy; + insert(n, p, x1, y1, x2, y2); + } + var p, i = -1, n = points.length; + if (n && isNaN(points[0].x)) points = points.map(d3_geom_quadtreePoint); + if (arguments.length < 5) { + if (arguments.length === 3) { + y2 = x2 = y1; + y1 = x1; + } else { + x1 = y1 = Infinity; + x2 = y2 = -Infinity; + while (++i < n) { + p = points[i]; + if (p.x < x1) x1 = p.x; + if (p.y < y1) y1 = p.y; + if (p.x > x2) x2 = p.x; + if (p.y > y2) y2 = p.y; + } + var dx = x2 - x1, dy = y2 - y1; + if (dx > dy) y2 = y1 + dx; else x2 = x1 + dy; + } + } + var root = d3_geom_quadtreeNode(); + root.add = function(p) { + insert(root, p, x1, y1, x2, y2); + }; + root.visit = function(f) { + d3_geom_quadtreeVisit(f, root, x1, y1, x2, y2); + }; + points.forEach(root.add); + return root; + }; + d3.time = {}; + var d3_time = Date, d3_time_daySymbols = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]; + d3_time_utc.prototype = { + getDate: function() { + return this._.getUTCDate(); + }, + getDay: function() { + return this._.getUTCDay(); + }, + getFullYear: function() { + return this._.getUTCFullYear(); + }, + getHours: function() { + return this._.getUTCHours(); + }, + getMilliseconds: function() { + return this._.getUTCMilliseconds(); + }, + getMinutes: function() { + return this._.getUTCMinutes(); + }, + getMonth: function() { + return this._.getUTCMonth(); + }, + getSeconds: function() { + return this._.getUTCSeconds(); + }, + getTime: function() { + return this._.getTime(); + }, + getTimezoneOffset: function() { + return 0; + }, + valueOf: function() { + return this._.valueOf(); + }, + setDate: function() { + d3_time_prototype.setUTCDate.apply(this._, arguments); + }, + setDay: function() { + d3_time_prototype.setUTCDay.apply(this._, arguments); + }, + setFullYear: function() { + d3_time_prototype.setUTCFullYear.apply(this._, arguments); + }, + setHours: function() { + d3_time_prototype.setUTCHours.apply(this._, arguments); + }, + setMilliseconds: function() { + d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); + }, + setMinutes: function() { + d3_time_prototype.setUTCMinutes.apply(this._, arguments); + }, + setMonth: function() { + d3_time_prototype.setUTCMonth.apply(this._, arguments); + }, + setSeconds: function() { + d3_time_prototype.setUTCSeconds.apply(this._, arguments); + }, + setTime: function() { + d3_time_prototype.setTime.apply(this._, arguments); + } + }; + var d3_time_prototype = Date.prototype; + var d3_time_formatDateTime = "%a %b %e %H:%M:%S %Y", d3_time_formatDate = "%m/%d/%y", d3_time_formatTime = "%H:%M:%S"; + var d3_time_days = d3_time_daySymbols, d3_time_dayAbbreviations = d3_time_days.map(d3_time_formatAbbreviate), d3_time_months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], d3_time_monthAbbreviations = d3_time_months.map(d3_time_formatAbbreviate); + d3.time.format = function(template) { + function format(date) { + var string = [], i = -1, j = 0, c, f; + while (++i < n) { + if (template.charCodeAt(i) == 37) { + string.push(template.substring(j, i), (f = d3_time_formats[c = template.charAt(++i)]) ? f(date) : c); + j = i + 1; + } + } + string.push(template.substring(j, i)); + return string.join(""); + } + var n = template.length; + format.parse = function(string) { + var d = { + y: 1900, + m: 0, + d: 1, + H: 0, + M: 0, + S: 0, + L: 0 + }, i = d3_time_parse(d, template, string, 0); + if (i != string.length) return null; + if ("p" in d) d.H = d.H % 12 + d.p * 12; + var date = new d3_time; + date.setFullYear(d.y, d.m, d.d); + date.setHours(d.H, d.M, d.S, d.L); + return date; + }; + format.toString = function() { + return template; + }; + return format; + }; + var d3_time_zfill2 = d3.format("02d"), d3_time_zfill3 = d3.format("03d"), d3_time_zfill4 = d3.format("04d"), d3_time_sfill2 = d3.format("2d"); + var d3_time_dayRe = d3_time_formatRe(d3_time_days), d3_time_dayAbbrevRe = d3_time_formatRe(d3_time_dayAbbreviations), d3_time_monthRe = d3_time_formatRe(d3_time_months), d3_time_monthLookup = d3_time_formatLookup(d3_time_months), d3_time_monthAbbrevRe = d3_time_formatRe(d3_time_monthAbbreviations), d3_time_monthAbbrevLookup = d3_time_formatLookup(d3_time_monthAbbreviations); + var d3_time_formats = { + a: function(d) { + return d3_time_dayAbbreviations[d.getDay()]; + }, + A: function(d) { + return d3_time_days[d.getDay()]; + }, + b: function(d) { + return d3_time_monthAbbreviations[d.getMonth()]; + }, + B: function(d) { + return d3_time_months[d.getMonth()]; + }, + c: d3.time.format(d3_time_formatDateTime), + d: function(d) { + return d3_time_zfill2(d.getDate()); + }, + e: function(d) { + return d3_time_sfill2(d.getDate()); + }, + H: function(d) { + return d3_time_zfill2(d.getHours()); + }, + I: function(d) { + return d3_time_zfill2(d.getHours() % 12 || 12); + }, + j: function(d) { + return d3_time_zfill3(1 + d3.time.dayOfYear(d)); + }, + L: function(d) { + return d3_time_zfill3(d.getMilliseconds()); + }, + m: function(d) { + return d3_time_zfill2(d.getMonth() + 1); + }, + M: function(d) { + return d3_time_zfill2(d.getMinutes()); + }, + p: function(d) { + return d.getHours() >= 12 ? "PM" : "AM"; + }, + S: function(d) { + return d3_time_zfill2(d.getSeconds()); + }, + U: function(d) { + return d3_time_zfill2(d3.time.sundayOfYear(d)); + }, + w: function(d) { + return d.getDay(); + }, + W: function(d) { + return d3_time_zfill2(d3.time.mondayOfYear(d)); + }, + x: d3.time.format(d3_time_formatDate), + X: d3.time.format(d3_time_formatTime), + y: function(d) { + return d3_time_zfill2(d.getFullYear() % 100); + }, + Y: function(d) { + return d3_time_zfill4(d.getFullYear() % 1e4); + }, + Z: d3_time_zone, + "%": function(d) { + return "%"; + } + }; + var d3_time_parsers = { + a: d3_time_parseWeekdayAbbrev, + A: d3_time_parseWeekday, + b: d3_time_parseMonthAbbrev, + B: d3_time_parseMonth, + c: d3_time_parseLocaleFull, + d: d3_time_parseDay, + e: d3_time_parseDay, + H: d3_time_parseHour24, + I: d3_time_parseHour24, + L: d3_time_parseMilliseconds, + m: d3_time_parseMonthNumber, + M: d3_time_parseMinutes, + p: d3_time_parseAmPm, + S: d3_time_parseSeconds, + x: d3_time_parseLocaleDate, + X: d3_time_parseLocaleTime, + y: d3_time_parseYear, + Y: d3_time_parseFullYear + }; + var d3_time_numberRe = /^\s*\d+/; + var d3_time_amPmLookup = d3.map({ + am: 0, + pm: 1 + }); + d3.time.format.utc = function(template) { + function format(date) { + try { + d3_time = d3_time_utc; + var utc = new d3_time; + utc._ = date; + return local(utc); + } finally { + d3_time = Date; + } + } + var local = d3.time.format(template); + format.parse = function(string) { + try { + d3_time = d3_time_utc; + var date = local.parse(string); + return date && date._; + } finally { + d3_time = Date; + } + }; + format.toString = local.toString; + return format; + }; + var d3_time_formatIso = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ"); + d3.time.format.iso = Date.prototype.toISOString ? d3_time_formatIsoNative : d3_time_formatIso; + d3_time_formatIsoNative.parse = function(string) { + var date = new Date(string); + return isNaN(date) ? null : date; + }; + d3_time_formatIsoNative.toString = d3_time_formatIso.toString; + d3.time.second = d3_time_interval(function(date) { + return new d3_time(Math.floor(date / 1e3) * 1e3); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 1e3); + }, function(date) { + return date.getSeconds(); + }); + d3.time.seconds = d3.time.second.range; + d3.time.seconds.utc = d3.time.second.utc.range; + d3.time.minute = d3_time_interval(function(date) { + return new d3_time(Math.floor(date / 6e4) * 6e4); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 6e4); + }, function(date) { + return date.getMinutes(); + }); + d3.time.minutes = d3.time.minute.range; + d3.time.minutes.utc = d3.time.minute.utc.range; + d3.time.hour = d3_time_interval(function(date) { + var timezone = date.getTimezoneOffset() / 60; + return new d3_time((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 36e5); + }, function(date) { + return date.getHours(); + }); + d3.time.hours = d3.time.hour.range; + d3.time.hours.utc = d3.time.hour.utc.range; + d3.time.day = d3_time_interval(function(date) { + var day = new d3_time(1970, 0); + day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + return day; + }, function(date, offset) { + date.setDate(date.getDate() + offset); + }, function(date) { + return date.getDate() - 1; + }); + d3.time.days = d3.time.day.range; + d3.time.days.utc = d3.time.day.utc.range; + d3.time.dayOfYear = function(date) { + var year = d3.time.year(date); + return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5); + }; + d3_time_daySymbols.forEach(function(day, i) { + day = day.toLowerCase(); + i = 7 - i; + var interval = d3.time[day] = d3_time_interval(function(date) { + (date = d3.time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); + return date; + }, function(date, offset) { + date.setDate(date.getDate() + Math.floor(offset) * 7); + }, function(date) { + var day = d3.time.year(date).getDay(); + return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); + }); + d3.time[day + "s"] = interval.range; + d3.time[day + "s"].utc = interval.utc.range; + d3.time[day + "OfYear"] = function(date) { + var day = d3.time.year(date).getDay(); + return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7); + }; + }); + d3.time.week = d3.time.sunday; + d3.time.weeks = d3.time.sunday.range; + d3.time.weeks.utc = d3.time.sunday.utc.range; + d3.time.weekOfYear = d3.time.sundayOfYear; + d3.time.month = d3_time_interval(function(date) { + date = d3.time.day(date); + date.setDate(1); + return date; + }, function(date, offset) { + date.setMonth(date.getMonth() + offset); + }, function(date) { + return date.getMonth(); + }); + d3.time.months = d3.time.month.range; + d3.time.months.utc = d3.time.month.utc.range; + d3.time.year = d3_time_interval(function(date) { + date = d3.time.day(date); + date.setMonth(0, 1); + return date; + }, function(date, offset) { + date.setFullYear(date.getFullYear() + offset); + }, function(date) { + return date.getFullYear(); + }); + d3.time.years = d3.time.year.range; + d3.time.years.utc = d3.time.year.utc.range; + var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ]; + var d3_time_scaleLocalMethods = [ [ d3.time.second, 1 ], [ d3.time.second, 5 ], [ d3.time.second, 15 ], [ d3.time.second, 30 ], [ d3.time.minute, 1 ], [ d3.time.minute, 5 ], [ d3.time.minute, 15 ], [ d3.time.minute, 30 ], [ d3.time.hour, 1 ], [ d3.time.hour, 3 ], [ d3.time.hour, 6 ], [ d3.time.hour, 12 ], [ d3.time.day, 1 ], [ d3.time.day, 2 ], [ d3.time.week, 1 ], [ d3.time.month, 1 ], [ d3.time.month, 3 ], [ d3.time.year, 1 ] ]; + var d3_time_scaleLocalFormats = [ [ d3.time.format("%Y"), function(d) { + return true; + } ], [ d3.time.format("%B"), function(d) { + return d.getMonth(); + } ], [ d3.time.format("%b %d"), function(d) { + return d.getDate() != 1; + } ], [ d3.time.format("%a %d"), function(d) { + return d.getDay() && d.getDate() != 1; + } ], [ d3.time.format("%I %p"), function(d) { + return d.getHours(); + } ], [ d3.time.format("%I:%M"), function(d) { + return d.getMinutes(); + } ], [ d3.time.format(":%S"), function(d) { + return d.getSeconds(); + } ], [ d3.time.format(".%L"), function(d) { + return d.getMilliseconds(); + } ] ]; + var d3_time_scaleLinear = d3.scale.linear(), d3_time_scaleLocalFormat = d3_time_scaleFormat(d3_time_scaleLocalFormats); + d3_time_scaleLocalMethods.year = function(extent, m) { + return d3_time_scaleLinear.domain(extent.map(d3_time_scaleGetYear)).ticks(m).map(d3_time_scaleSetYear); + }; + d3.time.scale = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); + }; + var d3_time_scaleUTCMethods = d3_time_scaleLocalMethods.map(function(m) { + return [ m[0].utc, m[1] ]; + }); + var d3_time_scaleUTCFormats = [ [ d3.time.format.utc("%Y"), function(d) { + return true; + } ], [ d3.time.format.utc("%B"), function(d) { + return d.getUTCMonth(); + } ], [ d3.time.format.utc("%b %d"), function(d) { + return d.getUTCDate() != 1; + } ], [ d3.time.format.utc("%a %d"), function(d) { + return d.getUTCDay() && d.getUTCDate() != 1; + } ], [ d3.time.format.utc("%I %p"), function(d) { + return d.getUTCHours(); + } ], [ d3.time.format.utc("%I:%M"), function(d) { + return d.getUTCMinutes(); + } ], [ d3.time.format.utc(":%S"), function(d) { + return d.getUTCSeconds(); + } ], [ d3.time.format.utc(".%L"), function(d) { + return d.getUTCMilliseconds(); + } ] ]; + var d3_time_scaleUTCFormat = d3_time_scaleFormat(d3_time_scaleUTCFormats); + d3_time_scaleUTCMethods.year = function(extent, m) { + return d3_time_scaleLinear.domain(extent.map(d3_time_scaleUTCGetYear)).ticks(m).map(d3_time_scaleUTCSetYear); + }; + d3.time.scale.utc = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleUTCMethods, d3_time_scaleUTCFormat); + }; +})(); \ No newline at end of file diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/lib/d3.v2.min.js b/docdoku-web-front/app/js/lib/charts/nv3d/lib/d3.v2.min.js new file mode 100644 index 0000000000..cc47f1ea0c --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/lib/d3.v2.min.js @@ -0,0 +1,4 @@ +(function(){function e(e,t){try{for(var n in t)Object.defineProperty(e.prototype,n,{value:t[n],enumerable:!1})}catch(r){e.prototype=t}}function t(e){var t=-1,n=e.length,r=[];while(++t=0?e.substring(t):(t=e.length,""),r=[];while(t>0)r.push(e.substring(t-=3,t+3));return r.reverse().join(",")+n}function b(e,t){var n=Math.pow(10,Math.abs(8-t)*3);return{scale:t>8?function(e){return e/n}:function(e){return e*n},symbol:e}}function w(e){return function(t){return t<=0?0:t>=1?1:e(t)}}function E(e){return function(t){return 1-e(1-t)}}function S(e){return function(t){return.5*(t<.5?e(2*t):2-e(2-2*t))}}function x(e){return e}function T(e){return function(t){return Math.pow(t,e)}}function N(e){return 1-Math.cos(e*Math.PI/2)}function C(e){return Math.pow(2,10*(e-1))}function k(e){return 1-Math.sqrt(1-e*e)}function L(e,t){var n;return arguments.length<2&&(t=.45),arguments.length<1?(e=1,n=t/4):n=t/(2*Math.PI)*Math.asin(1/e),function(r){return 1+e*Math.pow(2,10*-r)*Math.sin((r-n)*2*Math.PI/t)}}function A(e){return e||(e=1.70158),function(t){return t*t*((e+1)*t-e)}}function O(e){return e<1/2.75?7.5625*e*e:e<2/2.75?7.5625*(e-=1.5/2.75)*e+.75:e<2.5/2.75?7.5625*(e-=2.25/2.75)*e+.9375:7.5625*(e-=2.625/2.75)*e+.984375}function M(){d3.event.stopPropagation(),d3.event.preventDefault()}function _(){var e=d3.event,t;while(t=e.sourceEvent)e=t;return e}function D(e){var t=new d,n=0,r=arguments.length;while(++n360?e-=360:e<0&&(e+=360),e<60?s+(o-s)*e/60:e<180?o:e<240?s+(o-s)*(240-e)/60:s}function i(e){return Math.round(r(e)*255)}var s,o;return e%=360,e<0&&(e+=360),t=t<0?0:t>1?1:t,n=n<0?0:n>1?1:n,o=n<=.5?n*(1+t):n+t-n*t,s=2*n-o,R(i(e+120),i(e),i(e-120))}function Y(e,t,n){return new Z(e,t,n)}function Z(e,t,n){this.h=e,this.c=t,this.l=n}function et(e,t,n){return tt(n,Math.cos(e*=Math.PI/180)*t,Math.sin(e)*t)}function tt(e,t,n){return new nt(e,t,n)}function nt(e,t,n){this.l=e,this.a=t,this.b=n}function rt(e,t,n){var r=(e+16)/116,i=r+t/500,s=r-n/200;return i=st(i)*ds,r=st(r)*vs,s=st(s)*ms,R(ut(3.2404542*i-1.5371385*r-.4985314*s),ut(-0.969266*i+1.8760108*r+.041556*s),ut(.0556434*i-.2040259*r+1.0572252*s))}function it(e,t,n){return Y(Math.atan2(n,t)/Math.PI*180,Math.sqrt(t*t+n*n),e)}function st(e){return e>.206893034?e*e*e:(e-4/29)/7.787037}function ot(e){return e>.008856?Math.pow(e,1/3):7.787037*e+4/29}function ut(e){return Math.round(255*(e<=.00304?12.92*e:1.055*Math.pow(e,1/2.4)-.055))}function at(e){return Ki(e,Ss),e}function ft(e){return function(){return gs(e,this)}}function lt(e){return function(){return ys(e,this)}}function ct(e,t){function n(){this.removeAttribute(e)}function r(){this.removeAttributeNS(e.space,e.local)}function i(){this.setAttribute(e,t)}function s(){this.setAttributeNS(e.space,e.local,t)}function o(){var n=t.apply(this,arguments);n==null?this.removeAttribute(e):this.setAttribute(e,n)}function u(){var n=t.apply(this,arguments);n==null?this.removeAttributeNS(e.space,e.local):this.setAttributeNS(e.space,e.local,n)}return e=d3.ns.qualify(e),t==null?e.local?r:n:typeof t=="function"?e.local?u:o:e.local?s:i}function ht(e){return new RegExp("(?:^|\\s+)"+d3.requote(e)+"(?:\\s+|$)","g")}function pt(e,t){function n(){var n=-1;while(++n0&&(e=e.substring(0,o)),t?i:r}function Et(e,t){for(var n=0,r=e.length;nt?c():(v.active=t,i.forEach(function(t,n){(n=n.call(e,m,u))&&h.push(n)}),s.start.call(e,m,u),l(r)||d3.timer(l,0,n),1)}function l(n){if(v.active!==t)return c();var r=(n-p)/d,i=o(r),a=h.length;while(a>0)h[--a].call(e,i);if(r>=1)return c(),ks=t,s.end.call(e,m,u),ks=0,1}function c(){return--v.count||delete e.__transition__,1}var h=[],p=e.delay,d=e.duration,v=(e=e.node).__transition__||(e.__transition__={active:0,count:0}),m=e.__data__;++v.count,p<=r?f(r):d3.timer(f,p,n)})},0,n),e}function Tt(e){var t=ks,n=Ds,r=Ms,i=_s;return ks=this.id,Ds=this.ease(),Et(this,function(t,n,r){Ms=t.delay,_s=t.duration,e.call(t=t.node,t.__data__,n,r)}),ks=t,Ds=n,Ms=r,_s=i,this}function Nt(e,t,n){return n!=""&&Ps}function Ct(e,t){return d3.tween(e,F(t))}function kt(){var e,t=Date.now(),n=Hs;while(n)e=t-n.then,e>=n.delay&&(n.flush=n.callback(e)),n=n.next;var r=Lt()-t;r>24?(isFinite(r)&&(clearTimeout(js),js=setTimeout(kt,r)),Bs=0):(Bs=1,Fs(kt))}function Lt(){var e=null,t=Hs,n=Infinity;while(t)t.flush?t=e?e.next=t.next:Hs=t.next:(n=Math.min(n,t.then+t.delay),t=(e=t).next);return n}function At(e,t){var n=e.ownerSVGElement||e;if(n.createSVGPoint){var r=n.createSVGPoint();if(Is<0&&(window.scrollX||window.scrollY)){n=d3.select(document.body).append("svg").style("position","absolute").style("top",0).style("left",0);var i=n[0][0].getScreenCTM();Is=!i.f&&!i.e,n.remove()}return Is?(r.x=t.pageX,r.y=t.pageY):(r.x=t.clientX,r.y=t.clientY),r=r.matrixTransform(e.getScreenCTM().inverse()),[r.x,r.y]}var s=e.getBoundingClientRect();return[t.clientX-s.left-e.clientLeft,t.clientY-s.top-e.clientTop]}function Ot(){}function Mt(e){var t=e[0],n=e[e.length-1];return t2?Ut:Rt,a=r?q:I;return o=i(e,t,a,n),u=i(t,e,a,d3.interpolate),s}function s(e){return o(e)}var o,u;return s.invert=function(e){return u(e)},s.domain=function(t){return arguments.length?(e=t.map(Number),i()):e},s.range=function(e){return arguments.length?(t=e,i()):t},s.rangeRound=function(e){return s.range(e).interpolate(d3.interpolateRound)},s.clamp=function(e){return arguments.length?(r=e,i()):r},s.interpolate=function(e){return arguments.length?(n=e,i()):n},s.ticks=function(t){return It(e,t)},s.tickFormat=function(t){return qt(e,t)},s.nice=function(){return Dt(e,jt),i()},s.copy=function(){return Ht(e,t,n,r)},i()}function Bt(e,t){return d3.rebind(e,t,"range","rangeRound","interpolate","clamp")}function jt(e){return e=Math.pow(10,Math.round(Math.log(e)/Math.LN10)-1),e&&{floor:function(t){return Math.floor(t/e)*e},ceil:function(t){return Math.ceil(t/e)*e}}}function Ft(e,t){var n=Mt(e),r=n[1]-n[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),s=t/r*i;return s<=.15?i*=10:s<=.35?i*=5:s<=.75&&(i*=2),n[0]=Math.ceil(n[0]/i)*i,n[1]=Math.floor(n[1]/i)*i+i*.5,n[2]=i,n}function It(e,t){return d3.range.apply(d3,Ft(e,t))}function qt(e,t){return d3.format(",."+Math.max(0,-Math.floor(Math.log(Ft(e,t)[2])/Math.LN10+.01))+"f")}function Rt(e,t,n,r){var i=n(e[0],e[1]),s=r(t[0],t[1]);return function(e){return s(i(e))}}function Ut(e,t,n,r){var i=[],s=[],o=0,u=Math.min(e.length,t.length)-1;e[u]0;f--)i.push(r(s)*f)}else{for(;sa;o--);i=i.slice(s,o)}return i},n.tickFormat=function(e,i){arguments.length<2&&(i=qs);if(arguments.length<1)return i;var s=Math.max(.1,e/n.ticks().length),o=t===Xt?(u=-1e-12,Math.floor):(u=1e-12,Math.ceil),u;return function(e){return e/r(o(t(e)+u))<=s?i(e):""}},n.copy=function(){return zt(e.copy(),t)},Bt(n,e)}function Wt(e){return Math.log(e<0?0:e)/Math.LN10}function Xt(e){return-Math.log(e>0?0:-e)/Math.LN10}function Vt(e,t){function n(t){return e(r(t))}var r=$t(t),i=$t(1/t);return n.invert=function(t){return i(e.invert(t))},n.domain=function(t){return arguments.length?(e.domain(t.map(r)),n):e.domain().map(i)},n.ticks=function(e){return It(n.domain(),e)},n.tickFormat=function(e){return qt(n.domain(),e)},n.nice=function(){return n.domain(Dt(n.domain(),jt))},n.exponent=function(e){if(!arguments.length)return t;var s=n.domain();return r=$t(t=e),i=$t(1/t),n.domain(s)},n.copy=function(){return Vt(e.copy(),t)},Bt(n,e)}function $t(e){return function(t){return t<0?-Math.pow(-t,e):Math.pow(t,e)}}function Jt(e,t){function n(t){return o[((s.get(t)||s.set(t,e.push(t)))-1)%o.length]}function i(t,n){return d3.range(e.length).map(function(e){return t+n*e})}var s,o,u;return n.domain=function(i){if(!arguments.length)return e;e=[],s=new r;var o=-1,u=i.length,a;while(++o1){u=t[1],s=e[a],a++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(s[0]-u[0])+","+(s[1]-u[1])+","+s[0]+","+s[1];for(var f=2;f9&&(s=n*3/Math.sqrt(s),o[u]=s*r,o[u+1]=s*i));u=-1;while(++u<=a)s=(e[Math.min(a,u+1)][0]-e[Math.max(0,u-1)][0])/(6*(1+o[u]*o[u])),t.push([s||0,o[u]*s||0]);return t}function Nn(e){return e.length<3?un(e):e[0]+dn(e,Tn(e))}function Cn(e){var t,n=-1,r=e.length,i,s;while(++n1){var r=Mt(e.domain()),i,s=-1,o=t.length,u=(t[1]-t[0])/++n,a,f;while(++s0;)(f=+t[s]-a*u)>=r[0]&&i.push(f);for(--s,a=0;++ar&&(n=t,r=i);return n}function ir(e){return e.reduce(sr,0)}function sr(e,t){return e+t[1]}function or(e,t){return ur(e,Math.ceil(Math.log(t.length)/Math.LN2+1))}function ur(e,t){var n=-1,r=+e[0],i=(e[1]-r)/t,s=[];while(++n<=t)s[n]=i*n+r;return s}function ar(e){return[d3.min(e),d3.max(e)]}function fr(e,t){return d3.rebind(e,t,"sort","children","value"),e.links=pr,e.nodes=function(t){return uo=!0,(e.nodes=e)(t)},e}function lr(e){return e.children}function cr(e){return e.value}function hr(e,t){return t.value-e.value}function pr(e){return d3.merge(e.map(function(e){return(e.children||[]).map(function(t){return{source:e,target:t}})}))}function dr(e,t){return e.value-t.value}function vr(e,t){var n=e._pack_next;e._pack_next=t,t._pack_prev=e,t._pack_next=n,n._pack_prev=t}function mr(e,t){e._pack_next=t,t._pack_prev=e}function gr(e,t){var n=t.x-e.x,r=t.y-e.y,i=e.r+t.r;return i*i-n*n-r*r>.001}function yr(e){function t(e){r=Math.min(e.x-e.r,r),i=Math.max(e.x+e.r,i),s=Math.min(e.y-e.r,s),o=Math.max(e.y+e.r,o)}if(!(n=e.children)||!(p=n.length))return;var n,r=Infinity,i=-Infinity,s=Infinity,o=-Infinity,u,a,f,l,c,h,p;n.forEach(br),u=n[0],u.x=-u.r,u.y=0,t(u);if(p>1){a=n[1],a.x=a.r,a.y=0,t(a);if(p>2){f=n[2],Sr(u,a,f),t(f),vr(u,f),u._pack_prev=f,vr(f,a),a=u._pack_next;for(l=3;l0&&(e=r)}return e}function Mr(e,t){return e.x-t.x}function _r(e,t){return t.x-e.x}function Dr(e,t){return e.depth-t.depth}function Pr(e,t){function n(e,r){var i=e.children;if(i&&(a=i.length)){var s,o=null,u=-1,a;while(++u=0)s=r[i]._tree,s.prelim+=t,s.mod+=t,t+=s.shift+(n+=s.change)}function Br(e,t,n){e=e._tree,t=t._tree;var r=n/(t.number-e.number);e.change+=r,t.change-=r,t.shift+=n,t.prelim+=n,t.mod+=n}function jr(e,t,n){return e._tree.ancestor.parent==t.parent?e._tree.ancestor:n}function Fr(e){return{x:e.x,y:e.y,dx:e.dx,dy:e.dy}}function Ir(e,t){var n=e.x+t[3],r=e.y+t[0],i=e.dx-t[1]-t[3],s=e.dy-t[0]-t[2];return i<0&&(n+=i/2,i=0),s<0&&(r+=s/2,s=0),{x:n,y:r,dx:i,dy:s}}function qr(e,t){function n(e,r){d3.text(e,t,function(e){r(e&&n.parse(e))})}function r(t){return t.map(i).join(e)}function i(e){return o.test(e)?'"'+e.replace(/\"/g,'""')+'"':e}var s=new RegExp("\r\n|["+e+"\r\n]","g"),o=new RegExp('["'+e+"\n]"),u=e.charCodeAt(0);return n.parse=function(e){var t;return n.parseRows(e,function(e,n){if(n){var r={},i=-1,s=t.length;while(++i=e.length)return i;if(l)return l=!1,r;var t=s.lastIndex;if(e.charCodeAt(t)===34){var n=t;while(n++0}function ii(e,t,n){return(n[0]-t[0])*(e[1]-t[1])<(n[1]-t[1])*(e[0]-t[0])}function si(e,t,n,r){var i=e[0],s=t[0],o=n[0],u=r[0],a=e[1],f=t[1],l=n[1],c=r[1],h=i-o,p=s-i,d=u-o,v=a-l,m=f-a,g=c-l,y=(d*v-g*h)/(g*p-d*m);return[i+y*p,a+y*m]}function oi(e,t){var n={list:e.map(function(e,t){return{index:t,x:e[0],y:e[1]}}).sort(function(e,t){return e.yt.y?1:e.xt.x?1:0}),bottomSite:null},r={list:[],leftEnd:null,rightEnd:null,init:function(){r.leftEnd=r.createHalfEdge(null,"l"),r.rightEnd=r.createHalfEdge(null,"l"),r.leftEnd.r=r.rightEnd,r.rightEnd.l=r.leftEnd,r.list.unshift(r.leftEnd,r.rightEnd)},createHalfEdge:function(e,t){return{edge:e,side:t,vertex:null,l:null,r:null}},insert:function(e,t){t.l=e,t.r=e.r,e.r.l=t,e.r=t},leftBound:function(e){var t=r.leftEnd;do t=t.r;while(t!=r.rightEnd&&i.rightOf(t,e));return t=t.l,t},del:function(e){e.l.r=e.r,e.r.l=e.l,e.edge=null},right:function(e){return e.r},left:function(e){return e.l},leftRegion:function(e){return e.edge==null?n.bottomSite:e.edge.region[e.side]},rightRegion:function(e){return e.edge==null?n.bottomSite:e.edge.region[ho[e.side]]}},i={bisect:function(e,t){var n={region:{l:e,r:t},ep:{l:null,r:null}},r=t.x-e.x,i=t.y-e.y,s=r>0?r:-r,o=i>0?i:-i;return n.c=e.x*r+e.y*i+(r*r+i*i)*.5,s>o?(n.a=1,n.b=i/r,n.c/=r):(n.b=1,n.a=r/i,n.c/=i),n},intersect:function(e,t){var n=e.edge,r=t.edge;if(!n||!r||n.region.r==r.region.r)return null;var i=n.a*r.b-n.b*r.a;if(Math.abs(i)<1e-10)return null;var s=(n.c*r.b-r.c*n.b)/i,o=(r.c*n.a-n.c*r.a)/i,u=n.region.r,a=r.region.r,f,l;u.y=l.region.r.x;return c&&f.side==="l"||!c&&f.side==="r"?null:{x:s,y:o}},rightOf:function(e,t){var n=e.edge,r=n.region.r,i=t.x>r.x;if(i&&e.side==="l")return 1;if(!i&&e.side==="r")return 0;if(n.a===1){var s=t.y-r.y,o=t.x-r.x,u=0,a=0;!i&&n.b<0||i&&n.b>=0?a=u=s>=n.b*o:(a=t.x+t.y*n.b>n.c,n.b<0&&(a=!a),a||(u=1));if(!u){var f=r.x-n.region.l.x;a=n.b*(o*o-s*s)h*h+p*p}return e.side==="l"?a:!a},endPoint:function(e,n,r){e.ep[n]=r;if(!e.ep[ho[n]])return;t(e)},distance:function(e,t){var n=e.x-t.x,r=e.y-t.y;return Math.sqrt(n*n+r*r)}},s={list:[],insert:function(e,t,n){e.vertex=t,e.ystar=t.y+n;for(var r=0,i=s.list,o=i.length;ru.ystar||e.ystar==u.ystar&&t.x>u.vertex.x)continue;break}i.splice(r,0,e)},del:function(e){for(var t=0,n=s.list,r=n.length;td.y&&(v=p,p=d,d=v,b="r"),y=i.bisect(p,d),h=r.createHalfEdge(y,b),r.insert(l,h),i.endPoint(y,ho[b],g),m=i.intersect(l,h),m&&(s.del(l),s.insert(l,m,i.distance(m,p))),m=i.intersect(h,c),m&&s.insert(h,m,i.distance(m,p))}}for(a=r.right(r.leftEnd);a!=r.rightEnd;a=r.right(a))t(a.edge)}function ui(){return{leaf:!0,nodes:[],point:null}}function ai(e,t,n,r,i,s){if(!e(t,n,r,i,s)){var o=(n+i)*.5,u=(r+s)*.5,a=t.nodes;a[0]&&ai(e,a[0],n,r,o,u),a[1]&&ai(e,a[1],o,r,i,u),a[2]&&ai(e,a[2],n,u,o,s),a[3]&&ai(e,a[3],o,u,i,s)}}function fi(e){return{x:e[0],y:e[1]}}function li(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function ci(e){return e.substring(0,3)}function hi(e,t,n,r){var i,s,o=0,u=t.length,a=n.length;while(o=a)return-1;i=t.charCodeAt(o++);if(i==37){s=Ho[t.charAt(o++)];if(!s||(r=s(e,n,r))<0)return-1}else if(i!=n.charCodeAt(r++))return-1}return r}function pi(e){return new RegExp("^(?:"+e.map(d3.requote).join("|")+")","i")}function di(e){var t=new r,n=-1,i=e.length;while(++n68?1900:2e3)}function Ni(e,t,n){Bo.lastIndex=0;var r=Bo.exec(t.substring(n,n+2));return r?(e.m=r[0]-1,n+=r[0].length):-1}function Ci(e,t,n){Bo.lastIndex=0;var r=Bo.exec(t.substring(n,n+2));return r?(e.d=+r[0],n+=r[0].length):-1}function ki(e,t,n){Bo.lastIndex=0;var r=Bo.exec(t.substring(n,n+2));return r?(e.H=+r[0],n+=r[0].length):-1}function Li(e,t,n){Bo.lastIndex=0;var r=Bo.exec(t.substring(n,n+2));return r?(e.M=+r[0],n+=r[0].length):-1}function Ai(e,t,n){Bo.lastIndex=0;var r=Bo.exec(t.substring(n,n+2));return r?(e.S=+r[0],n+=r[0].length):-1}function Oi(e,t,n){Bo.lastIndex=0;var r=Bo.exec(t.substring(n,n+3));return r?(e.L=+r[0],n+=r[0].length):-1}function Mi(e,t,n){var r=jo.get(t.substring(n,n+=2).toLowerCase());return r==null?-1:(e.p=r,n)}function _i(e){var t=e.getTimezoneOffset(),n=t>0?"-":"+",r=~~(Math.abs(t)/60),i=Math.abs(t)%60;return n+To(r)+To(i)}function Di(e){return e.toISOString()}function Pi(e,t,n){function r(t){var n=e(t),r=s(n,1);return t-n1)while(ot?1:e>=t?0:NaN},d3.descending=function(e,t){return te?1:t>=e?0:NaN},d3.mean=function(e,t){var n=e.length,r,i=0,s=-1,o=0;if(arguments.length===1)while(++s1&&(e=e.map(t)),e=e.filter(f),e.length?d3.quantile(e.sort(d3.ascending),.5):undefined},d3.min=function(e,t){var n=-1,r=e.length,i,s;if(arguments.length===1){while(++ns&&(i=s)}else{while(++ns&&(i=s)}return i},d3.max=function(e,t){var n=-1,r=e.length,i,s;if(arguments.length===1){while(++ni&&(i=s)}else{while(++ni&&(i=s)}return i},d3.extent=function(e,t){var n=-1,r=e.length,i,s,o;if(arguments.length===1){while(++ns&&(i=s),os&&(i=s),o1);return e+t*n*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(e,t){var n=arguments.length;n<2&&(t=1),n<1&&(e=0);var r=d3.random.normal();return function(){return Math.exp(e+t*r())}},irwinHall:function(e){return function(){for(var t=0,n=0;n>>1;e.call(t,t[s],s)>>1;n0&&(i=s);return i},d3.last=function(e,t){var n=0,r=e.length,i=e[0],s;arguments.length===1&&(t=d3.ascending);while(++n=i.length)return u?u.call(n,t):o?t.sort(o):t;var a=-1,f=t.length,l=i[s++],c,h,p=new r,d,v={};while(++a=i.length)return e;var r=[],o=s[n++],u;for(u in e)r.push({key:u,values:t(e[u],n)});return o&&r.sort(function(e,t){return o(e.key,t.key)}),r}var n={},i=[],s=[],o,u;return n.map=function(t){return e(t,0)},n.entries=function(n){return t(e(n,0),0)},n.key=function(e){return i.push(e),n},n.sortKeys=function(e){return s[i.length-1]=e,n},n.sortValues=function(e){return o=e,n},n.rollup=function(e){return u=e,n},n},d3.keys=function(e){var t=[];for(var n in e)t.push(n);return t},d3.values=function(e){var t=[];for(var n in e)t.push(e[n]);return t},d3.entries=function(e){var t=[];for(var n in e)t.push({key:n,value:e[n]});return t},d3.permute=function(e,t){var n=[],r=-1,i=t.length;while(++rt)r.push(o/i);else while((o=e+n*++s)=200&&e<300||e===304?r:null)}},r.send(null)},d3.text=function(e,t,n){function r(e){n(e&&e.responseText)}arguments.length<3&&(n=t,t=null),d3.xhr(e,t,r)},d3.json=function(e,t){d3.text(e,"application/json",function(e){t(e?JSON.parse(e):null)})},d3.html=function(e,t){d3.text(e,"text/html",function(e){if(e!=null){var n=document.createRange();n.selectNode(document.body),e=n.createContextualFragment(e)}t(e)})},d3.xml=function(e,t,n){function r(e){n(e&&e.responseXML)}arguments.length<3&&(n=t,t=null),d3.xhr(e,t,r)};var es={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};d3.ns={prefix:es,qualify:function(e){var t=e.indexOf(":"),n=e;return t>=0&&(n=e.substring(0,t),e=e.substring(t+1)),es.hasOwnProperty(n)?{space:es[n],local:e}:e}},d3.dispatch=function(){var e=new d,t=-1,n=arguments.length;while(++t0&&(r=e.substring(n+1),e=e.substring(0,n)),arguments.length<2?this[e].on(r):this[e].on(r,t)},d3.format=function(e){var t=ts.exec(e),n=t[1]||" ",r=t[3]||"",i=t[5],s=+t[6],o=t[7],u=t[8],a=t[9],f=1,l="",c=!1;u&&(u=+u.substring(1)),i&&(n="0",o&&(s-=Math.floor((s-1)/4)));switch(a){case"n":o=!0,a="g";break;case"%":f=100,l="%",a="f";break;case"p":f=100,l="%",a="r";break;case"d":c=!0,u=0;break;case"s":f=-1,a="r"}return a=="r"&&!u&&(a="g"),a=ns.get(a)||g,function(e){if(c&&e%1)return"";var t=e<0&&(e=-e)?"-":r;if(f<0){var h=d3.formatPrefix(e,u);e=h.scale(e),l=h.symbol}else e*=f;e=a(e,u);if(i){var p=e.length+t.length;p=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/,ns=d3.map({g:function(e,t){return e.toPrecision(t)},e:function(e,t){return e.toExponential(t)},f:function(e,t){return e.toFixed(t)},r:function(e,t){return d3.round(e,t=m(e,t)).toFixed(Math.max(0,Math.min(20,t)))}}),rs=["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(b);d3.formatPrefix=function(e,t){var n=0;return e&&(e<0&&(e*=-1),t&&(e=d3.round(e,m(e,t))),n=1+Math.floor(1e-12+Math.log(e)/Math.LN10),n=Math.max(-24,Math.min(24,Math.floor((n<=0?n+1:n-1)/3)*3))),rs[8+n/3]};var is=T(2),ss=T(3),os=function(){return x},us=d3.map({linear:os,poly:T,quad:function(){return is},cubic:function(){return ss},sin:function(){return N},exp:function(){return C},circle:function(){return k},elastic:L,back:A,bounce:function(){return O}}),as=d3.map({"in":x,out:E,"in-out":S,"out-in":function(e){return S(E(e))}});d3.ease=function(e){var t=e.indexOf("-"),n=t>=0?e.substring(0,t):e,r=t>=0?e.substring(t+1):"in";return n=us.get(n)||os,r=as.get(r)||x,w(r(n.apply(null,Array.prototype.slice.call(arguments,1))))},d3.event=null,d3.transform=function(e){var t=document.createElementNS(d3.ns.prefix.svg,"g");return(d3.transform=function(e){t.setAttribute("transform",e);var n=t.transform.baseVal.consolidate();return new P(n?n.matrix:ls)})(e)},P.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var fs=180/Math.PI,ls={a:1,b:0,c:0,d:1,e:0,f:0};d3.interpolate=function(e,t){var n=d3.interpolators.length,r;while(--n>=0&&!(r=d3.interpolators[n](e,t)));return r},d3.interpolateNumber=function(e,t){return t-=e,function(n){return e+t*n}},d3.interpolateRound=function(e,t){return t-=e,function(n){return Math.round(e+t*n)}},d3.interpolateString=function(e,t){var n,r,i,s=0,o=0,u=[],a=[],f,l;cs.lastIndex=0;for(r=0;n=cs.exec(t);++r)n.index&&u.push(t.substring(s,o=n.index)),a.push({i:u.length,x:n[0]}),u.push(null),s=cs.lastIndex;s180?l+=360:l-f>180&&(f+=360),r.push({i:n.push(n.pop()+"rotate(",null,")")-2,x:d3.interpolateNumber(f,l)})):l&&n.push(n.pop()+"rotate("+l+")"),c!=h?r.push({i:n.push(n.pop()+"skewX(",null,")")-2,x:d3.interpolateNumber(c,h)}):h&&n.push(n.pop()+"skewX("+h+")"),p[0]!=d[0]||p[1]!=d[1]?(i=n.push(n.pop()+"scale(",null,",",null,")"),r.push({i:i-4,x:d3.interpolateNumber(p[0],d[0])},{i:i-2,x:d3.interpolateNumber(p[1],d[1])})):(d[0]!=1||d[1]!=1)&&n.push(n.pop()+"scale("+d+")"),i=r.length,function(e){var t=-1,s;while(++t180?s-=360:s<-180&&(s+=360),function(e){return G(n+s*e,r+o*e,i+u*e)+""}},d3.interpolateLab=function(e,t){e=d3.lab(e),t=d3.lab(t);var n=e.l,r=e.a,i=e.b,s=t.l-n,o=t.a-r,u=t.b-i;return function(e){return rt(n+s*e,r+o*e,i+u*e)+""}},d3.interpolateHcl=function(e,t){e=d3.hcl(e),t=d3.hcl(t);var n=e.h,r=e.c,i=e.l,s=t.h-n,o=t.c-r,u=t.l-i;return s>180?s-=360:s<-180&&(s+=360),function(e){return et(n+s*e,r+o*e,i+u*e)+""}},d3.interpolateArray=function(e,t){var n=[],r=[],i=e.length,s=t.length,o=Math.min(e.length,t.length),u;for(u=0;u=0;)if(s=n[r])i&&i!==s.nextSibling&&i.parentNode.insertBefore(s,i),i=s;return this},Ss.sort=function(e){e=bt.apply(this,arguments);for(var t=-1,n=this.length;++t=Vs?e?"M0,"+s+"A"+s+","+s+" 0 1,1 0,"+ -s+"A"+s+","+s+" 0 1,1 0,"+s+"M0,"+e+"A"+e+","+e+" 0 1,0 0,"+ -e+"A"+e+","+e+" 0 1,0 0,"+e+"Z":"M0,"+s+"A"+s+","+s+" 0 1,1 0,"+ -s+"A"+s+","+s+" 0 1,1 0,"+s+"Z":e?"M"+s*l+","+s*c+"A"+s+","+s+" 0 "+f+",1 "+s*h+","+s*p+"L"+e*h+","+e*p+"A"+e+","+e+" 0 "+f+",0 "+e*l+","+e*c+"Z":"M"+s*l+","+s*c+"A"+s+","+s+" 0 "+f+",1 "+s*h+","+s*p+"L0,0"+"Z"}var t=Zt,n=en,r=tn,i=nn;return e.innerRadius=function(n){return arguments.length?(t=u(n),e):t},e.outerRadius=function(t){return arguments.length?(n=u(t),e):n},e.startAngle=function(t){return arguments.length?(r=u(t),e):r},e.endAngle=function(t){return arguments.length?(i=u(t),e):i},e.centroid=function(){var e=(t.apply(this,arguments)+n.apply(this,arguments))/2,s=(r.apply(this,arguments)+i.apply(this,arguments))/2+Xs;return[Math.cos(s)*e,Math.sin(s)*e]},e};var Xs=-Math.PI/2,Vs=2*Math.PI-1e-6;d3.svg.line=function(){return rn(i)};var $s=d3.map({linear:un,"linear-closed":an,"step-before":fn,"step-after":ln,basis:mn,"basis-open":gn,"basis-closed":yn,bundle:bn,cardinal:pn,"cardinal-open":cn,"cardinal-closed":hn,monotone:Nn});$s.forEach(function(e,t){t.key=e,t.closed=/-closed$/.test(e)});var Js=[0,2/3,1/3,0],Ks=[0,1/3,2/3,0],Qs=[0,1/6,2/3,1/6];d3.svg.line.radial=function(){var e=rn(Cn);return e.radius=e.x,delete e.x,e.angle=e.y,delete e.y,e},fn.reverse=ln,ln.reverse=fn,d3.svg.area=function(){return kn(i)},d3.svg.area.radial=function(){var e=kn(Cn);return e.radius=e.x,delete e.x,e.innerRadius=e.x0,delete e.x0,e.outerRadius=e.x1,delete e.x1,e.angle=e.y,delete e.y,e.startAngle=e.y0,delete e.y0,e.endAngle=e.y1,delete e.y1,e},d3.svg.chord=function(){function e(e,u){var a=t(this,s,e,u),f=t(this,o,e,u);return"M"+a.p0+r(a.r,a.p1,a.a1-a.a0)+(n(a,f)?i(a.r,a.p1,a.r,a.p0):i(a.r,a.p1,f.r,f.p0)+r(f.r,f.p1,f.a1-f.a0)+i(f.r,f.p1,a.r,a.p0))+"Z"}function t(e,t,n,r){var i=t.call(e,n,r),s=a.call(e,i,r),o=f.call(e,i,r)+Xs,u=l.call(e,i,r)+Xs;return{r:s,a0:o,a1:u,p0:[s*Math.cos(o),s*Math.sin(o)],p1:[s*Math.cos(u),s*Math.sin(u)]}}function n(e,t){return e.a0==t.a0&&e.a1==t.a1}function r(e,t,n){return"A"+e+","+e+" 0 "+ +(n>Math.PI)+",1 "+t}function i(e,t,n,r){return"Q 0,0 "+r}var s=Ln,o=An,a=On,f=tn,l=nn;return e.radius=function(t){return arguments.length?(a=u(t),e):a},e.source=function(t){return arguments.length?(s=u(t),e):s},e.target=function(t){return arguments.length?(o=u(t),e):o},e.startAngle=function(t){return arguments.length?(f=u(t),e):f},e.endAngle=function(t){return arguments.length?(l=u(t),e):l},e},d3.svg.diagonal=function(){function e(e,i){var s=t.call(this,e,i),o=n.call(this,e,i),u=(s.y+o.y)/2,a=[s,{x:s.x,y:u},{x:o.x,y:u},o];return a=a.map(r),"M"+a[0]+"C"+a[1]+" "+a[2]+" "+a[3]}var t=Ln,n=An,r=Dn;return e.source=function(n){return arguments.length?(t=u(n),e):t},e.target=function(t){return arguments.length?(n=u(t),e):n},e.projection=function(t){return arguments.length?(r=t,e):r},e},d3.svg.diagonal.radial=function(){var e=d3.svg.diagonal(),t=Dn,n=e.projection;return e.projection=function(e){return arguments.length?n(Pn(t=e)):t},e},d3.svg.mouse=d3.mouse,d3.svg.touches=d3.touches,d3.svg.symbol=function(){function e(e,r){return(Gs.get(t.call(this,e,r))||jn)(n.call(this,e,r))}var t=Bn,n=Hn;return e.type=function(n){return arguments.length?(t=u(n),e):t},e.size=function(t){return arguments.length?(n=u(t),e):n},e};var Gs=d3.map({circle:jn,cross:function(e){var t=Math.sqrt(e/5)/2;return"M"+ -3*t+","+ -t+"H"+ -t+"V"+ -3*t+"H"+t+"V"+ -t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+ -t+"V"+t+"H"+ -3*t+"Z"},diamond:function(e){var t=Math.sqrt(e/(2*Zs)),n=t*Zs;return"M0,"+ -t+"L"+n+",0"+" 0,"+t+" "+ -n+",0"+"Z"},square:function(e){var t=Math.sqrt(e)/2;return"M"+ -t+","+ -t+"L"+t+","+ -t+" "+t+","+t+" "+ -t+","+t+"Z"},"triangle-down":function(e){var t=Math.sqrt(e/Ys),n=t*Ys/2;return"M0,"+n+"L"+t+","+ -n+" "+ -t+","+ -n+"Z"},"triangle-up":function(e){var t=Math.sqrt(e/Ys),n=t*Ys/2;return"M0,"+ -n+"L"+t+","+n+" "+ -t+","+n+"Z"}});d3.svg.symbolTypes=Gs.keys();var Ys=Math.sqrt(3),Zs=Math.tan(30*Math.PI/180);d3.svg.axis=function(){function e(e){e.each(function(){var e=d3.select(this),c=a==null?t.ticks?t.ticks.apply(t,u):t.domain():a,h=f==null?t.tickFormat?t.tickFormat.apply(t,u):String:f,p=qn(t,c,l),d=e.selectAll(".minor").data(p,String),v=d.enter().insert("line","g").attr("class","tick minor").style("opacity",1e-6),m=d3.transition(d.exit()).style("opacity",1e-6).remove(),g=d3.transition(d).style("opacity",1),y=e.selectAll("g").data(c,String),b=y.enter().insert("g","path").style("opacity",1e-6),w=d3.transition(y.exit()).style("opacity",1e-6).remove(),E=d3.transition(y).style("opacity",1),S,x=_t(t),T=e.selectAll(".domain").data([0]),N=T.enter().append("path").attr("class","domain"),C=d3.transition(T),k=t.copy(),L=this.__chart__||k;this.__chart__=k,b.append("line").attr("class","tick"),b.append("text");var A=b.select("line"),O=E.select("line"),M=y.select("text").text(h),_=b.select("text"),D=E.select("text");switch(n){case"bottom":S=Fn,v.attr("y2",i),g.attr("x2",0).attr("y2",i),A.attr("y2",r),_.attr("y",Math.max(r,0)+o),O.attr("x2",0).attr("y2",r),D.attr("x",0).attr("y",Math.max(r,0)+o),M.attr("dy",".71em").attr("text-anchor","middle"),C.attr("d","M"+x[0]+","+s+"V0H"+x[1]+"V"+s);break;case"top":S=Fn,v.attr("y2",-i),g.attr("x2",0).attr("y2",-i),A.attr("y2",-r),_.attr("y",-(Math.max(r,0)+o)),O.attr("x2",0).attr("y2",-r),D.attr("x",0).attr("y",-(Math.max(r,0)+o)),M.attr("dy","0em").attr("text-anchor","middle"),C.attr("d","M"+x[0]+","+ -s+"V0H"+x[1]+"V"+ -s);break;case"left":S=In,v.attr("x2",-i),g.attr("x2",-i).attr("y2",0),A.attr("x2",-r),_.attr("x",-(Math.max(r,0)+o)),O.attr("x2",-r).attr("y2",0),D.attr("x",-(Math.max(r,0)+o)).attr("y",0),M.attr("dy",".32em").attr("text-anchor","end"),C.attr("d","M"+ -s+","+x[0]+"H0V"+x[1]+"H"+ -s);break;case"right":S=In,v.attr("x2",i),g.attr("x2",i).attr("y2",0),A.attr("x2",r),_.attr("x",Math.max(r,0)+o),O.attr("x2",r).attr("y2",0),D.attr("x",Math.max(r,0)+o).attr("y",0),M.attr("dy",".32em").attr("text-anchor","start"),C.attr("d","M"+s+","+x[0]+"H0V"+x[1]+"H"+s)}if(t.ticks)b.call(S,L),E.call(S,k),w.call(S,k),v.call(S,L),g.call(S,k),m.call(S,k);else{var P=k.rangeBand()/2,H=function(e){return k(e)+P};b.call(S,H),E.call(S,H)}})}var t=d3.scale.linear(),n="bottom",r=6,i=6,s=6,o=3,u=[10],a=null,f,l=0;return e.scale=function(n){return arguments.length?(t=n,e):t},e.orient=function(t){return arguments.length?(n=t,e):n},e.ticks=function(){return arguments.length?(u=arguments,e):u},e.tickValues=function(t){return arguments.length?(a=t,e):a},e.tickFormat=function(t){return arguments.length?(f=t,e):f},e.tickSize=function(t,n,o){if(!arguments.length)return r;var u=arguments.length-1;return r=+t,i=u>1?+n:r,s=u>0?+arguments[u]:r,e},e.tickPadding=function(t){return arguments.length?(o=+t,e):o},e.tickSubdivide=function(t){return arguments.length?(l=+t,e):l},e},d3.svg.brush=function(){function e(s){s.each(function(){var s=d3.select(this),f=s.selectAll(".background").data([0]),l=s.selectAll(".extent").data([0]),c=s.selectAll(".resize").data(a,String),h;s.style("pointer-events","all").on("mousedown.brush",i).on("touchstart.brush",i),f.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),l.enter().append("rect").attr("class","extent").style("cursor","move"),c.enter().append("g").attr("class",function(e){return"resize "+e}).style("cursor",function(e){return eo[e]}).append("rect").attr("x",function(e){return/[ew]$/.test(e)?-3:null}).attr("y",function(e){return/^[ns]/.test(e)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),c.style("display",e.empty()?"none":null),c.exit().remove(),o&&(h=_t(o),f.attr("x",h[0]).attr("width",h[1]-h[0]),n(s)),u&&(h=_t(u),f.attr("y",h[0]).attr("height",h[1]-h[0]),r(s)),t(s)})}function t(e){e.selectAll(".resize").attr("transform",function(e){return"translate("+f[+/e$/.test(e)][0]+","+f[+/^s/.test(e)][1]+")"})}function n(e){e.select(".extent").attr("x",f[0][0]),e.selectAll(".extent,.n>rect,.s>rect").attr("width",f[1][0]-f[0][0])}function r(e){e.select(".extent").attr("y",f[0][1]),e.selectAll(".extent,.e>rect,.w>rect").attr("height",f[1][1]-f[0][1])}function i(){function i(){var e=d3.event.changedTouches;return e?d3.touches(v,e)[0]:d3.mouse(v)}function a(){d3.event.keyCode==32&&(S||(x=null,T[0]-=f[1][0],T[1]-=f[1][1],S=2),M())}function c(){d3.event.keyCode==32&&S==2&&(T[0]+=f[1][0],T[1]+=f[1][1],S=0,M())}function h(){var e=i(),s=!1;N&&(e[0]+=N[0],e[1]+=N[1]),S||(d3.event.altKey?(x||(x=[(f[0][0]+f[1][0])/2,(f[0][1]+f[1][1])/2]),T[0]=f[+(e[0]0?a=e:a=0:e>0&&(r.start({type:"start",alpha:a=e}),d3.timer(n.tick)),n):a},n.start=function(){function e(e,n){var i=t(r),s=-1,o=i.length,u;while(++si&&(i=u),r.push(u)}for(o=0;o0){s=-1;while(++s=a[0]&&d<=a[1]&&(l=o[d3.bisect(f,d,1,h)-1],l.y+=p,l.push(e[s]))}return o}var t=!0,n=Number,r=ar,i=or;return e.value=function(t){return arguments.length?(n=t,e):n},e.range=function(t){return arguments.length?(r=u(t),e):r},e.bins=function(t){return arguments.length?(i=typeof t=="number"?function(e){return ur(e,t)}:u(t),e):i},e.frequency=function(n){return arguments.length?(t=!!n,e):t},e},d3.layout.hierarchy=function(){function e(t,o,u){var a=i.call(n,t,o),f=uo?t:{data:t};f.depth=o,u.push(f);if(a&&(c=a.length)){var l=-1,c,h=f.children=[],p=0,d=o+1,v;while(++l0){var l=n*f/2;Pr(o,function(e){e.r+=l}),Pr(o,yr),Pr(o,function(e){e.r-=l}),f=Math.max(2*o.r/u,2*o.r/a)}return Er(o,u/2,a/2,1/f),s}var t=d3.layout.hierarchy().sort(dr),n=0,r=[1,1];return e.size=function(t){return arguments.length?(r=t,e):r},e.padding=function(t){return arguments.length?(n=+t,e):n},fr(e,t)},d3.layout.cluster=function(){function e(e,i){var s=t.call(this,e,i),o=s[0],u,a=0,f,l;Pr(o,function(e){var t=e.children;t&&t.length?(e.x=Tr(t),e.y=xr(t)):(e.x=u?a+=n(e,u):0,e.y=0,u=e)});var c=Nr(o),h=Cr(o),p=c.x-n(c,h)/2,d=h.x+n(h,c)/2;return Pr(o,function(e){e.x=(e.x-p)/(d-p)*r[0],e.y=(1-(o.y?e.y/o.y:1))*r[1]}),s}var t=d3.layout.hierarchy().sort(null).value(null),n=kr,r=[1,1];return e.separation=function(t){return arguments.length?(n=t,e):n},e.size=function(t){return arguments.length?(r=t,e):r},fr(e,t)},d3.layout.tree=function(){function e(e,i){function s(e,t){var r=e.children,i=e._tree;if(r&&(o=r.length)){var o,a=r[0],f,l=a,c,h=-1;while(++h0&&(Br(jr(o,e,r),e,h),a+=h,f+=h),l+=o._tree.mod,a+=i._tree.mod,c+=u._tree.mod,f+=s._tree.mod;o&&!Ar(s)&&(s._tree.thread=o,s._tree.mod+=l-f),i&&!Lr(u)&&(u._tree.thread=i,u._tree.mod+=a-c,r=e)}return r}var a=t.call(this,e,i),f=a[0];Pr(f,function(e,t){e._tree={ancestor:e,prelim:0,mod:0,change:0,shift:0,number:t?t._tree.number+1:0}}),s(f),o(f,-f._tree.prelim);var l=Or(f,_r),c=Or(f,Mr),h=Or(f,Dr),p=l.x-n(l,c)/2,d=c.x+n(c,l)/2,v=h.depth||1;return Pr(f,function(e){e.x=(e.x-p)/(d-p)*r[0],e.y=e.depth/v*r[1],delete e._tree}),a}var t=d3.layout.hierarchy().sort(null).value(null),n=kr,r=[1,1];return e.separation=function(t){return arguments.length?(n=t,e):n},e.size=function(t){return arguments.length?(r=t,e):r},fr(e,t)},d3.layout.treemap=function(){function e(e,t){var n=-1,r=e.length,i,s;while(++n0)u.push(f=a[d-1]),u.area+=f.area,(h=r(u,p))<=c?(a.pop(),c=h):(u.area-=u.pop().area,i(u,p,o,!1),p=Math.min(o.dx,o.dy),u.length=u.area=0,c=Infinity);u.length&&(i(u,p,o,!0),u.length=u.area=0),s.forEach(t)}}function n(t){var r=t.children;if(r&&r.length){var s=l(t),o=r.slice(),u,a=[];e(o,s.dx*s.dy/t.value),a.area=0;while(u=o.pop())a.push(u),a.area+=u.area,u.z!=null&&(i(a,u.z?s.dx:s.dy,s,!o.length),a.length=a.area=0);r.forEach(n)}}function r(e,t){var n=e.area,r,i=0,s=Infinity,o=-1,u=e.length;while(++oi&&(i=r)}return n*=n,t*=t,n?Math.max(t*i*p/n,n/(t*s*p)):Infinity}function i(e,t,n,r){var i=-1,s=e.length,o=n.x,a=n.y,f=t?u(e.area/t):0,l;if(t==n.dx){if(r||f>n.dy)f=n.dy;while(++in.dx)f=n.dx;while(++i50?n:s<-140?r:o<21?i:t)(e)}var t=d3.geo.albers(),n=d3.geo.albers().origin([-160,60]).parallels([55,65]),r=d3.geo.albers().origin([-160,20]).parallels([8,18]),i=d3.geo.albers().origin([-60,10]).parallels([8,18]);return e.scale=function(s){return arguments.length?(t.scale(s),n.scale(s*.6),r.scale(s),i.scale(s*1.5),e.translate(t.translate())):t.scale()},e.translate=function(s){if(!arguments.length)return t.translate();var o=t.scale()/1e3,u=s[0],a=s[1];return t.translate(s),n.translate([u-400*o,a+170*o]),r.translate([u-190*o,a+200*o]),i.translate([u+580*o,a+430*o]),e},e.scale(t.scale())},d3.geo.bonne=function(){function e(e){var u=e[0]*ao-r,a=e[1]*ao-i;if(s){var f=o+s-a,l=u*Math.cos(a)/f;u=f*Math.sin(l),a=f*Math.cos(l)-o}else u*=Math.cos(a),a*=-1;return[t*u+n[0],t*a+n[1]]}var t=200,n=[480,250],r,i,s,o;return e.invert=function(e){var i=(e[0]-n[0])/t,u=(e[1]-n[1])/t;if(s){var a=o+u,f=Math.sqrt(i*i+a*a);u=o+s-f,i=r+f*Math.atan2(i,a)/Math.cos(u)}else u*=-1,i/=Math.cos(u);return[i/ao,u/ao]},e.parallel=function(t){return arguments.length?(o=1/Math.tan +(s=t*ao),e):s/ao},e.origin=function(t){return arguments.length?(r=t[0]*ao,i=t[1]*ao,e):[r/ao,i/ao]},e.scale=function(n){return arguments.length?(t=+n,e):t},e.translate=function(t){return arguments.length?(n=[+t[0],+t[1]],e):n},e.origin([0,0]).parallel(45)},d3.geo.equirectangular=function(){function e(e){var r=e[0]/360,i=-e[1]/360;return[t*r+n[0],t*i+n[1]]}var t=500,n=[480,250];return e.invert=function(e){var r=(e[0]-n[0])/t,i=(e[1]-n[1])/t;return[360*r,-360*i]},e.scale=function(n){return arguments.length?(t=+n,e):t},e.translate=function(t){return arguments.length?(n=[+t[0],+t[1]],e):n},e},d3.geo.mercator=function(){function e(e){var r=e[0]/360,i=-(Math.log(Math.tan(Math.PI/4+e[1]*ao/2))/ao)/360;return[t*r+n[0],t*Math.max(-0.5,Math.min(.5,i))+n[1]]}var t=500,n=[480,250];return e.invert=function(e){var r=(e[0]-n[0])/t,i=(e[1]-n[1])/t;return[360*r,2*Math.atan(Math.exp(-360*i*ao))/ao-90]},e.scale=function(n){return arguments.length?(t=+n,e):t},e.translate=function(t){return arguments.length?(n=[+t[0],+t[1]],e):n},e},d3.geo.path=function(){function e(e,t){typeof s=="function"&&(o=Ur(s.apply(this,arguments))),f(e);var n=a.length?a.join(""):null;return a=[],n}function t(e){return u(e).join(",")}function n(e){var t=i(e[0]),n=0,r=e.length;while(++n0){a.push("M");while(++o0){a.push("M");while(++lr&&(r=e),si&&(i=s)}),[[t,n],[r,i]]};var fo={Feature:Wr,FeatureCollection:Xr,GeometryCollection:Vr,LineString:$r,MultiLineString:Jr,MultiPoint:$r,MultiPolygon:Kr,Point:Qr,Polygon:Gr};d3.geo.circle=function(){function e(){}function t(e){return a.distance(e)=l*l+c*c?r[s].index=-1:(r[h].index=-1,d=r[s].angle,h=s,p=o)):(d=r[s].angle,h=s,p=o);i.push(u);for(s=0,o=0;s<2;++o)r[o].index!==-1&&(i.push(r[o].index),s++);v=i.length;for(;o=0?(n=e.ep.r,r=e.ep.l):(n=e.ep.l,r=e.ep.r),e.a===1?(o=n?n.y:-1e6,i=e.c-e.b*o,u=r?r.y:1e6,s=e.c-e.b*u):(i=n?n.x:-1e6,o=e.c-e.a*i,s=r?r.x:1e6,u=e.c-e.a*s);var a=[i,o],f=[s,u];t[e.region.l.index].push(a,f),t[e.region.r.index].push(a,f)}),t.map(function(t,n){var r=e[n][0],i=e[n][1];return t.forEach(function(e){e.angle=Math.atan2(e[0]-r,e[1]-i)}),t.sort(function(e,t){return e.angle-t.angle}).filter(function(e,n){return!n||e.angle-t[n-1].angle>1e-10})})};var ho={l:"r",r:"l"};d3.geom.delaunay=function(e){var t=e.map(function(){return[]}),n=[];return oi(e,function(n){t[n.region.l.index].push(e[n.region.r.index])}),t.forEach(function(t,r){var i=e[r],s=i[0],o=i[1];t.forEach(function(e){e.angle=Math.atan2(e[0]-s,e[1]-o)}),t.sort(function(e,t){return e.angle-t.angle});for(var u=0,a=t.length-1;u=u,l=t.y>=a,c=(l<<1)+f;e.leaf=!1,e=e.nodes[c]||(e.nodes[c]=ui()),f?n=u:i=u,l?r=a:o=a,s(e,t,n,r,i,o)}var u,a=-1,f=e.length;f&&isNaN(e[0].x)&&(e=e.map(fi));if(arguments.length<5)if(arguments.length===3)i=r=n,n=t;else{t=n=Infinity,r=i=-Infinity;while(++ar&&(r=u.x),u.y>i&&(i=u.y);var l=r-t,c=i-n;l>c?i=n+l:r=t+c}var h=ui();return h.add=function(e){s(h,e,t,n,r,i)},h.visit=function(e){ai(e,h,t,n,r,i)},e.forEach(h.add),h},d3.time={};var po=Date,vo=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];li.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){mo.setUTCDate.apply(this._,arguments)},setDay:function(){mo.setUTCDay.apply(this._,arguments)},setFullYear:function(){mo.setUTCFullYear.apply(this._,arguments)},setHours:function(){mo.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){mo.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){mo.setUTCMinutes.apply(this._,arguments)},setMonth:function(){mo.setUTCMonth.apply(this._,arguments)},setSeconds:function(){mo.setUTCSeconds.apply(this._,arguments)},setTime:function(){mo.setTime.apply(this._,arguments)}};var mo=Date.prototype,go="%a %b %e %H:%M:%S %Y",yo="%m/%d/%y",bo="%H:%M:%S",wo=vo,Eo=wo.map(ci),So=["January","February","March","April","May","June","July","August","September","October","November","December"],xo=So.map(ci);d3.time.format=function(e){function t(t){var r=[],i=-1,s=0,o,u;while(++i=12?"PM":"AM"},S:function(e){return To(e.getSeconds())},U:function(e){return To(d3.time.sundayOfYear(e))},w:function(e){return e.getDay()},W:function(e){return To(d3.time.mondayOfYear(e))},x:d3.time.format(yo),X:d3.time.format(bo),y:function(e){return To(e.getFullYear()%100)},Y:function(e){return Co(e.getFullYear()%1e4)},Z:_i,"%":function(e){return"%"}},Ho={a:vi,A:mi,b:gi,B:yi,c:bi,d:Ci,e:Ci,H:ki,I:ki,L:Oi,m:Ni,M:Li,p:Mi,S:Ai,x:wi,X:Ei,y:xi,Y:Si},Bo=/^\s*\d+/,jo=d3.map({am:0,pm:1});d3.time.format.utc=function(e){function t(e){try{po=li;var t=new po;return t._=e,n(t)}finally{po=Date}}var n=d3.time.format(e);return t.parse=function(e){try{po=li;var t=n.parse(e);return t&&t._}finally{po=Date}},t.toString=n.toString,t};var Fo=d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");d3.time.format.iso=Date.prototype.toISOString?Di:Fo,Di.parse=function(e){var t=new Date(e);return isNaN(t)?null:t},Di.toString=Fo.toString,d3.time.second=Pi(function(e){return new po(Math.floor(e/1e3)*1e3)},function(e,t){e.setTime(e.getTime()+Math.floor(t)*1e3)},function(e){return e.getSeconds()}),d3.time.seconds=d3.time.second.range,d3.time.seconds.utc=d3.time.second.utc.range,d3.time.minute=Pi(function(e){return new po(Math.floor(e/6e4)*6e4)},function(e,t){e.setTime(e.getTime()+Math.floor(t)*6e4)},function(e){return e.getMinutes()}),d3.time.minutes=d3.time.minute.range,d3.time.minutes.utc=d3.time.minute.utc.range,d3.time.hour=Pi(function(e){var t=e.getTimezoneOffset()/60;return new po((Math.floor(e/36e5-t)+t)*36e5)},function(e,t){e.setTime(e.getTime()+Math.floor(t)*36e5)},function(e){return e.getHours()}),d3.time.hours=d3.time.hour.range,d3.time.hours.utc=d3.time.hour.utc.range,d3.time.day=Pi(function(e){var t=new po(1970,0);return t.setFullYear(e.getFullYear(),e.getMonth(),e.getDate()),t},function(e,t){e.setDate(e.getDate()+t)},function(e){return e.getDate()-1}),d3.time.days=d3.time.day.range,d3.time.days.utc=d3.time.day.utc.range,d3.time.dayOfYear=function(e){var t=d3.time.year(e);return Math.floor((e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*6e4)/864e5)},vo.forEach(function(e,t){e=e.toLowerCase(),t=7-t;var n=d3.time[e]=Pi(function(e){return(e=d3.time.day(e)).setDate(e.getDate()-(e.getDay()+t)%7),e},function(e,t){e.setDate(e.getDate()+Math.floor(t)*7)},function(e){var n=d3.time.year(e).getDay();return Math.floor((d3.time.dayOfYear(e)+(n+t)%7)/7)-(n!==t)});d3.time[e+"s"]=n.range,d3.time[e+"s"].utc=n.utc.range,d3.time[e+"OfYear"]=function(e){var n=d3.time.year(e).getDay();return Math.floor((d3.time.dayOfYear(e)+(n+t)%7)/7)}}),d3.time.week=d3.time.sunday,d3.time.weeks=d3.time.sunday.range,d3.time.weeks.utc=d3.time.sunday.utc.range,d3.time.weekOfYear=d3.time.sundayOfYear,d3.time.month=Pi(function(e){return e=d3.time.day(e),e.setDate(1),e},function(e,t){e.setMonth(e.getMonth()+t)},function(e){return e.getMonth()}),d3.time.months=d3.time.month.range,d3.time.months.utc=d3.time.month.utc.range,d3.time.year=Pi(function(e){return e=d3.time.day(e),e.setMonth(0,1),e},function(e,t){e.setFullYear(e.getFullYear()+t)},function(e){return e.getFullYear()}),d3.time.years=d3.time.year.range,d3.time.years.utc=d3.time.year.utc.range;var Io=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],qo=[[d3.time.second,1],[d3.time.second,5],[d3.time.second,15],[d3.time.second,30],[d3.time.minute,1],[d3.time.minute,5],[d3.time.minute,15],[d3.time.minute,30],[d3.time.hour,1],[d3.time.hour,3],[d3.time.hour,6],[d3.time.hour,12],[d3.time.day,1],[d3.time.day,2],[d3.time.week,1],[d3.time.month,1],[d3.time.month,3],[d3.time.year,1]],Ro=[[d3.time.format("%Y"),function(e){return!0}],[d3.time.format("%B"),function(e){return e.getMonth()}],[d3.time.format("%b %d"),function(e){return e.getDate()!=1}],[d3.time.format("%a %d"),function(e){return e.getDay()&&e.getDate()!=1}],[d3.time.format("%I %p"),function(e){return e.getHours()}],[d3.time.format("%I:%M"),function(e){return e.getMinutes()}],[d3.time.format(":%S"),function(e){return e.getSeconds()}],[d3.time.format(".%L"),function(e){return e.getMilliseconds()}]],Uo=d3.scale.linear(),zo=Ii(Ro);qo.year=function(e,t){return Uo.domain(e.map(Ri)).ticks(t).map(qi)},d3.time.scale=function(){return Bi(d3.scale.linear(),qo,zo)};var Wo=qo.map(function(e){return[e[0].utc,e[1]]}),Xo=[[d3.time.format.utc("%Y"),function(e){return!0}],[d3.time.format.utc("%B"),function(e){return e.getUTCMonth()}],[d3.time.format.utc("%b %d"),function(e){return e.getUTCDate()!=1}],[d3.time.format.utc("%a %d"),function(e){return e.getUTCDay()&&e.getUTCDate()!=1}],[d3.time.format.utc("%I %p"),function(e){return e.getUTCHours()}],[d3.time.format.utc("%I:%M"),function(e){return e.getUTCMinutes()}],[d3.time.format.utc(":%S"),function(e){return e.getUTCSeconds()}],[d3.time.format.utc(".%L"),function(e){return e.getUTCMilliseconds()}]],Vo=Ii(Xo);Wo.year=function(e,t){return Uo.domain(e.map(zi)).ticks(t).map(Ui)},d3.time.scale.utc=function(){return Bi(d3.scale.linear(),Wo,Vo)}})(); \ No newline at end of file diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/lib/fisheye.js b/docdoku-web-front/app/js/lib/charts/nv3d/lib/fisheye.js new file mode 100644 index 0000000000..e1addd7b8b --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/lib/fisheye.js @@ -0,0 +1,86 @@ +(function() { + d3.fisheye = { + scale: function(scaleType) { + return d3_fisheye_scale(scaleType(), 3, 0); + }, + circular: function() { + var radius = 200, + distortion = 2, + k0, + k1, + focus = [0, 0]; + + function fisheye(d) { + var dx = d.x - focus[0], + dy = d.y - focus[1], + dd = Math.sqrt(dx * dx + dy * dy); + if (!dd || dd >= radius) return {x: d.x, y: d.y, z: 1}; + var k = k0 * (1 - Math.exp(-dd * k1)) / dd * .75 + .25; + return {x: focus[0] + dx * k, y: focus[1] + dy * k, z: Math.min(k, 10)}; + } + + function rescale() { + k0 = Math.exp(distortion); + k0 = k0 / (k0 - 1) * radius; + k1 = distortion / radius; + return fisheye; + } + + fisheye.radius = function(_) { + if (!arguments.length) return radius; + radius = +_; + return rescale(); + }; + + fisheye.distortion = function(_) { + if (!arguments.length) return distortion; + distortion = +_; + return rescale(); + }; + + fisheye.focus = function(_) { + if (!arguments.length) return focus; + focus = _; + return fisheye; + }; + + return rescale(); + } + }; + + function d3_fisheye_scale(scale, d, a) { + + function fisheye(_) { + var x = scale(_), + left = x < a, + v, + range = d3.extent(scale.range()), + min = range[0], + max = range[1], + m = left ? a - min : max - a; + if (m == 0) m = max - min; + return (left ? -1 : 1) * m * (d + 1) / (d + (m / Math.abs(x - a))) + a; + } + + fisheye.distortion = function(_) { + if (!arguments.length) return d; + d = +_; + return fisheye; + }; + + fisheye.focus = function(_) { + if (!arguments.length) return a; + a = +_; + return fisheye; + }; + + fisheye.copy = function() { + return d3_fisheye_scale(scale.copy(), d, a); + }; + + fisheye.nice = scale.nice; + fisheye.ticks = scale.ticks; + fisheye.tickFormat = scale.tickFormat; + return d3.rebind(fisheye, scale, "domain", "range"); + } +})(); diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/lib/hive.js b/docdoku-web-front/app/js/lib/charts/nv3d/lib/hive.js new file mode 100644 index 0000000000..06e53aed47 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/lib/hive.js @@ -0,0 +1,80 @@ +d3.hive = {}; + +d3.hive.link = function() { + var source = function(d) { return d.source; }, + target = function(d) { return d.target; }, + angle = function(d) { return d.angle; }, + startRadius = function(d) { return d.radius; }, + endRadius = startRadius, + arcOffset = -Math.PI / 2; + + function link(d, i) { + var s = node(source, this, d, i), + t = node(target, this, d, i), + x; + if (t.a < s.a) x = t, t = s, s = x; + if (t.a - s.a > Math.PI) s.a += 2 * Math.PI; + var a1 = s.a + (t.a - s.a) / 3, + a2 = t.a - (t.a - s.a) / 3; + return s.r0 - s.r1 || t.r0 - t.r1 + ? "M" + Math.cos(s.a) * s.r0 + "," + Math.sin(s.a) * s.r0 + + "L" + Math.cos(s.a) * s.r1 + "," + Math.sin(s.a) * s.r1 + + "C" + Math.cos(a1) * s.r1 + "," + Math.sin(a1) * s.r1 + + " " + Math.cos(a2) * t.r1 + "," + Math.sin(a2) * t.r1 + + " " + Math.cos(t.a) * t.r1 + "," + Math.sin(t.a) * t.r1 + + "L" + Math.cos(t.a) * t.r0 + "," + Math.sin(t.a) * t.r0 + + "C" + Math.cos(a2) * t.r0 + "," + Math.sin(a2) * t.r0 + + " " + Math.cos(a1) * s.r0 + "," + Math.sin(a1) * s.r0 + + " " + Math.cos(s.a) * s.r0 + "," + Math.sin(s.a) * s.r0 + : "M" + Math.cos(s.a) * s.r0 + "," + Math.sin(s.a) * s.r0 + + "C" + Math.cos(a1) * s.r1 + "," + Math.sin(a1) * s.r1 + + " " + Math.cos(a2) * t.r1 + "," + Math.sin(a2) * t.r1 + + " " + Math.cos(t.a) * t.r1 + "," + Math.sin(t.a) * t.r1; + } + + function node(method, thiz, d, i) { + var node = method.call(thiz, d, i), + a = +(typeof angle === "function" ? angle.call(thiz, node, i) : angle) + arcOffset, + r0 = +(typeof startRadius === "function" ? startRadius.call(thiz, node, i) : startRadius), + r1 = (startRadius === endRadius ? r0 : +(typeof endRadius === "function" ? endRadius.call(thiz, node, i) : endRadius)); + return {r0: r0, r1: r1, a: a}; + } + + link.source = function(_) { + if (!arguments.length) return source; + source = _; + return link; + }; + + link.target = function(_) { + if (!arguments.length) return target; + target = _; + return link; + }; + + link.angle = function(_) { + if (!arguments.length) return angle; + angle = _; + return link; + }; + + link.radius = function(_) { + if (!arguments.length) return startRadius; + startRadius = endRadius = _; + return link; + }; + + link.startRadius = function(_) { + if (!arguments.length) return startRadius; + startRadius = _; + return link; + }; + + link.endRadius = function(_) { + if (!arguments.length) return endRadius; + endRadius = _; + return link; + }; + + return link; +}; diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/lib/horizon.js b/docdoku-web-front/app/js/lib/charts/nv3d/lib/horizon.js new file mode 100644 index 0000000000..d84c65679d --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/lib/horizon.js @@ -0,0 +1,192 @@ +(function() { + d3.horizon = function() { + var bands = 1, // between 1 and 5, typically + mode = "offset", // or mirror + interpolate = "linear", // or basis, monotone, step-before, etc. + x = d3_horizonX, + y = d3_horizonY, + w = 960, + h = 40, + duration = 0; + + var color = d3.scale.linear() + .domain([-1, 0, 1]) + .range(["#d62728", "#fff", "#1f77b4"]); + + // For each small multiple… + function horizon(g) { + g.each(function(d, i) { + var g = d3.select(this), + n = 2 * bands + 1, + xMin = Infinity, + xMax = -Infinity, + yMax = -Infinity, + x0, // old x-scale + y0, // old y-scale + id; // unique id for paths + + // Compute x- and y-values along with extents. + var data = d.map(function(d, i) { + var xv = x.call(this, d, i), + yv = y.call(this, d, i); + if (xv < xMin) xMin = xv; + if (xv > xMax) xMax = xv; + if (-yv > yMax) yMax = -yv; + if (yv > yMax) yMax = yv; + return [xv, yv]; + }); + + // Compute the new x- and y-scales, and transform. + var x1 = d3.scale.linear().domain([xMin, xMax]).range([0, w]), + y1 = d3.scale.linear().domain([0, yMax]).range([0, h * bands]), + t1 = d3_horizonTransform(bands, h, mode); + + // Retrieve the old scales, if this is an update. + if (this.__chart__) { + x0 = this.__chart__.x; + y0 = this.__chart__.y; + t0 = this.__chart__.t; + id = this.__chart__.id; + } else { + x0 = x1.copy(); + y0 = y1.copy(); + t0 = t1; + id = ++d3_horizonId; + } + + // We'll use a defs to store the area path and the clip path. + var defs = g.selectAll("defs") + .data([null]); + + // The clip path is a simple rect. + defs.enter().append("defs").append("clipPath") + .attr("id", "d3_horizon_clip" + id) + .append("rect") + .attr("width", w) + .attr("height", h); + + defs.select("rect").transition() + .duration(duration) + .attr("width", w) + .attr("height", h); + + // We'll use a container to clip all horizon layers at once. + g.selectAll("g") + .data([null]) + .enter().append("g") + .attr("clip-path", "url(#d3_horizon_clip" + id + ")"); + + // Instantiate each copy of the path with different transforms. + var path = g.select("g").selectAll("path") + .data(d3.range(-1, -bands - 1, -1).concat(d3.range(1, bands + 1)), Number); + + var d0 = d3_horizonArea + .interpolate(interpolate) + .x(function(d) { return x0(d[0]); }) + .y0(h * bands) + .y1(function(d) { return h * bands - y0(d[1]); }) + (data); + + var d1 = d3_horizonArea + .x(function(d) { return x1(d[0]); }) + .y1(function(d) { return h * bands - y1(d[1]); }) + (data); + + path.enter().append("path") + .style("fill", color) + .attr("transform", t0) + .attr("d", d0); + + path.transition() + .duration(duration) + .style("fill", color) + .attr("transform", t1) + .attr("d", d1); + + path.exit().transition() + .duration(duration) + .attr("transform", t1) + .attr("d", d1) + .remove(); + + // Stash the new scales. + this.__chart__ = {x: x1, y: y1, t: t1, id: id}; + }); + d3.timer.flush(); + } + + horizon.duration = function(x) { + if (!arguments.length) return duration; + duration = +x; + return horizon; + }; + + horizon.bands = function(x) { + if (!arguments.length) return bands; + bands = +x; + color.domain([-bands, 0, bands]); + return horizon; + }; + + horizon.mode = function(x) { + if (!arguments.length) return mode; + mode = x + ""; + return horizon; + }; + + horizon.colors = function(x) { + if (!arguments.length) return color.range(); + color.range(x); + return horizon; + }; + + horizon.interpolate = function(x) { + if (!arguments.length) return interpolate; + interpolate = x + ""; + return horizon; + }; + + horizon.x = function(z) { + if (!arguments.length) return x; + x = z; + return horizon; + }; + + horizon.y = function(z) { + if (!arguments.length) return y; + y = z; + return horizon; + }; + + horizon.width = function(x) { + if (!arguments.length) return w; + w = +x; + return horizon; + }; + + horizon.height = function(x) { + if (!arguments.length) return h; + h = +x; + return horizon; + }; + + return horizon; + }; + + var d3_horizonArea = d3.svg.area(), + d3_horizonId = 0; + + function d3_horizonX(d) { + return d[0]; + } + + function d3_horizonY(d) { + return d[1]; + } + + function d3_horizonTransform(bands, h, mode) { + return mode == "offset" + ? function(d) { return "translate(0," + (d + (d < 0) - bands) * h + ")"; } + : function(d) { return (d < 0 ? "scale(1,-1)" : "") + "translate(0," + (d - bands) * h + ")"; }; + } +})(); diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/lib/sankey.js b/docdoku-web-front/app/js/lib/charts/nv3d/lib/sankey.js new file mode 100644 index 0000000000..c3bc59fbcc --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/lib/sankey.js @@ -0,0 +1,292 @@ +d3.sankey = function() { + var sankey = {}, + nodeWidth = 24, + nodePadding = 8, + size = [1, 1], + nodes = [], + links = []; + + sankey.nodeWidth = function(_) { + if (!arguments.length) return nodeWidth; + nodeWidth = +_; + return sankey; + }; + + sankey.nodePadding = function(_) { + if (!arguments.length) return nodePadding; + nodePadding = +_; + return sankey; + }; + + sankey.nodes = function(_) { + if (!arguments.length) return nodes; + nodes = _; + return sankey; + }; + + sankey.links = function(_) { + if (!arguments.length) return links; + links = _; + return sankey; + }; + + sankey.size = function(_) { + if (!arguments.length) return size; + size = _; + return sankey; + }; + + sankey.layout = function(iterations) { + computeNodeLinks(); + computeNodeValues(); + computeNodeBreadths(); + computeNodeDepths(iterations); + computeLinkDepths(); + return sankey; + }; + + sankey.relayout = function() { + computeLinkDepths(); + return sankey; + }; + + sankey.link = function() { + var curvature = .5; + + function link(d) { + var x0 = d.source.x + d.source.dx, + x1 = d.target.x, + xi = d3.interpolateNumber(x0, x1), + x2 = xi(curvature), + x3 = xi(1 - curvature), + y0 = d.source.y + d.sy + d.dy / 2, + y1 = d.target.y + d.ty + d.dy / 2; + return "M" + x0 + "," + y0 + + "C" + x2 + "," + y0 + + " " + x3 + "," + y1 + + " " + x1 + "," + y1; + } + + link.curvature = function(_) { + if (!arguments.length) return curvature; + curvature = +_; + return link; + }; + + return link; + }; + + // Populate the sourceLinks and targetLinks for each node. + // Also, if the source and target are not objects, assume they are indices. + function computeNodeLinks() { + nodes.forEach(function(node) { + node.sourceLinks = []; + node.targetLinks = []; + }); + links.forEach(function(link) { + var source = link.source, + target = link.target; + if (typeof source === "number") source = link.source = nodes[link.source]; + if (typeof target === "number") target = link.target = nodes[link.target]; + source.sourceLinks.push(link); + target.targetLinks.push(link); + }); + } + + // Compute the value (size) of each node by summing the associated links. + function computeNodeValues() { + nodes.forEach(function(node) { + node.value = Math.max( + d3.sum(node.sourceLinks, value), + d3.sum(node.targetLinks, value) + ); + }); + } + + // Iteratively assign the breadth (x-position) for each node. + // Nodes are assigned the maximum breadth of incoming neighbors plus one; + // nodes with no incoming links are assigned breadth zero, while + // nodes with no outgoing links are assigned the maximum breadth. + function computeNodeBreadths() { + var remainingNodes = nodes, + nextNodes, + x = 0; + + while (remainingNodes.length) { + nextNodes = []; + remainingNodes.forEach(function(node) { + node.x = x; + node.dx = nodeWidth; + node.sourceLinks.forEach(function(link) { + nextNodes.push(link.target); + }); + }); + remainingNodes = nextNodes; + ++x; + } + + // + moveSinksRight(x); + scaleNodeBreadths((size[0] - nodeWidth) / (x - 1)); + } + + function moveSourcesRight() { + nodes.forEach(function(node) { + if (!node.targetLinks.length) { + node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1; + } + }); + } + + function moveSinksRight(x) { + nodes.forEach(function(node) { + if (!node.sourceLinks.length) { + node.x = x - 1; + } + }); + } + + function scaleNodeBreadths(kx) { + nodes.forEach(function(node) { + node.x *= kx; + }); + } + + function computeNodeDepths(iterations) { + var nodesByBreadth = d3.nest() + .key(function(d) { return d.x; }) + .sortKeys(d3.ascending) + .entries(nodes) + .map(function(d) { return d.values; }); + + // + initializeNodeDepth(); + resolveCollisions(); + for (var alpha = 1; iterations > 0; --iterations) { + relaxRightToLeft(alpha *= .99); + resolveCollisions(); + relaxLeftToRight(alpha); + resolveCollisions(); + } + + function initializeNodeDepth() { + var ky = d3.min(nodesByBreadth, function(nodes) { + return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value); + }); + + nodesByBreadth.forEach(function(nodes) { + nodes.forEach(function(node, i) { + node.y = i; + node.dy = node.value * ky; + }); + }); + + links.forEach(function(link) { + link.dy = link.value * ky; + }); + } + + function relaxLeftToRight(alpha) { + nodesByBreadth.forEach(function(nodes, breadth) { + nodes.forEach(function(node) { + if (node.targetLinks.length) { + var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value); + node.y += (y - center(node)) * alpha; + } + }); + }); + + function weightedSource(link) { + return center(link.source) * link.value; + } + } + + function relaxRightToLeft(alpha) { + nodesByBreadth.slice().reverse().forEach(function(nodes) { + nodes.forEach(function(node) { + if (node.sourceLinks.length) { + var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value); + node.y += (y - center(node)) * alpha; + } + }); + }); + + function weightedTarget(link) { + return center(link.target) * link.value; + } + } + + function resolveCollisions() { + nodesByBreadth.forEach(function(nodes) { + var node, + dy, + y0 = 0, + n = nodes.length, + i; + + // Push any overlapping nodes down. + nodes.sort(ascendingDepth); + for (i = 0; i < n; ++i) { + node = nodes[i]; + dy = y0 - node.y; + if (dy > 0) node.y += dy; + y0 = node.y + node.dy + nodePadding; + } + + // If the bottommost node goes outside the bounds, push it back up. + dy = y0 - nodePadding - size[1]; + if (dy > 0) { + y0 = node.y -= dy; + + // Push any overlapping nodes back up. + for (i = n - 2; i >= 0; --i) { + node = nodes[i]; + dy = node.y + node.dy + nodePadding - y0; + if (dy > 0) node.y -= dy; + y0 = node.y; + } + } + }); + } + + function ascendingDepth(a, b) { + return a.y - b.y; + } + } + + function computeLinkDepths() { + nodes.forEach(function(node) { + node.sourceLinks.sort(ascendingTargetDepth); + node.targetLinks.sort(ascendingSourceDepth); + }); + nodes.forEach(function(node) { + var sy = 0, ty = 0; + node.sourceLinks.forEach(function(link) { + link.sy = sy; + sy += link.dy; + }); + node.targetLinks.forEach(function(link) { + link.ty = ty; + ty += link.dy; + }); + }); + + function ascendingSourceDepth(a, b) { + return a.source.y - b.source.y; + } + + function ascendingTargetDepth(a, b) { + return a.target.y - b.target.y; + } + } + + function center(node) { + return node.y + node.dy / 2; + } + + function value(link) { + return link.value; + } + + return sankey; +}; diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/nv.d3.js b/docdoku-web-front/app/js/lib/charts/nv3d/nv.d3.js new file mode 100644 index 0000000000..e695f3aa4f --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/nv.d3.js @@ -0,0 +1,11502 @@ +(function(){ + +var nv = window.nv || {}; + +nv.version = '0.0.1a'; +nv.dev = true //set false when in production + +window.nv = nv; + +nv.tooltip = {}; // For the tooltip system +nv.utils = {}; // Utility subsystem +nv.models = {}; //stores all the possible models/components +nv.charts = {}; //stores all the ready to use charts +nv.graphs = []; //stores all the graphs currently on the page +nv.logs = {}; //stores some statistics and potential error messages + +nv.dispatch = d3.dispatch('render_start', 'render_end'); + +// ************************************************************************* +// Development render timers - disabled if dev = false + +if (nv.dev) { + nv.dispatch.on('render_start', function(e) { + nv.logs.startTime = +new Date(); + }); + + nv.dispatch.on('render_end', function(e) { + nv.logs.endTime = +new Date(); + nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime; + nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times + }); +} + +// ******************************************** +// Public Core NV functions + +// Logs all arguments, and returns the last so you can test things in place +nv.log = function() { + if (nv.dev && console.log && console.log.apply) + console.log.apply(console, arguments) + else if (nv.dev && console.log && Function.prototype.bind) { + var log = Function.prototype.bind.call(console.log, console); + log.apply(console, arguments); + } + return arguments[arguments.length - 1]; +}; + + +nv.render = function render(step) { + step = step || 1; // number of graphs to generate in each timeout loop + + nv.render.active = true; + nv.dispatch.render_start(); + + setTimeout(function() { + var chart, graph; + + for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) { + chart = graph.generate(); + if (typeof graph.callback == typeof(Function)) graph.callback(chart); + nv.graphs.push(chart); + } + + nv.render.queue.splice(0, i); + + if (nv.render.queue.length) setTimeout(arguments.callee, 0); + else { nv.render.active = false; nv.dispatch.render_end(); } + }, 0); +}; + +nv.render.active = false; +nv.render.queue = []; + +nv.addGraph = function(obj) { + if (typeof arguments[0] === typeof(Function)) + obj = {generate: arguments[0], callback: arguments[1]}; + + nv.render.queue.push(obj); + + if (!nv.render.active) nv.render(); +}; + +nv.identity = function(d) { return d; }; + +nv.strip = function(s) { return s.replace(/(\s|&)/g,''); }; + +function daysInMonth(month,year) { + return (new Date(year, month+1, 0)).getDate(); +} + +function d3_time_range(floor, step, number) { + return function(t0, t1, dt) { + var time = floor(t0), times = []; + if (time < t0) step(time); + if (dt > 1) { + while (time < t1) { + var date = new Date(+time); + if ((number(date) % dt === 0)) times.push(date); + step(time); + } + } else { + while (time < t1) { times.push(new Date(+time)); step(time); } + } + return times; + }; +} + +d3.time.monthEnd = function(date) { + return new Date(date.getFullYear(), date.getMonth(), 0); +}; + +d3.time.monthEnds = d3_time_range(d3.time.monthEnd, function(date) { + date.setUTCDate(date.getUTCDate() + 1); + date.setDate(daysInMonth(date.getMonth() + 1, date.getFullYear())); + }, function(date) { + return date.getMonth(); + } +); + + +/***** + * A no-frills tooltip implementation. + *****/ + + +(function() { + + var nvtooltip = window.nv.tooltip = {}; + + nvtooltip.show = function(pos, content, gravity, dist, parentContainer, classes) { + + var container = document.createElement('div'); + container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip'); + + gravity = gravity || 's'; + dist = dist || 20; + + var body = parentContainer ? parentContainer : document.getElementsByTagName('body')[0]; + + container.innerHTML = content; + container.style.left = 0; + container.style.top = 0; + container.style.opacity = 0; + + body.appendChild(container); + + var height = parseInt(container.offsetHeight), + width = parseInt(container.offsetWidth), + windowWidth = nv.utils.windowSize().width, + windowHeight = nv.utils.windowSize().height, + scrollTop = window.scrollY, + scrollLeft = window.scrollX, + left, top; + + windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16; + windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16; + + var tooltipTop = function ( Elem ) { + var offsetTop = top; + do { + if( !isNaN( Elem.offsetTop ) ) { + offsetTop += (Elem.offsetTop); + } + } while( Elem = Elem.offsetParent ); + return offsetTop; + } + + var tooltipLeft = function ( Elem ) { + var offsetLeft = left; + do { + if( !isNaN( Elem.offsetLeft ) ) { + offsetLeft += (Elem.offsetLeft); + } + } while( Elem = Elem.offsetParent ); + return offsetLeft; + } + + switch (gravity) { + case 'e': + left = pos[0] - width - dist; + top = pos[1] - (height / 2); + var tLeft = tooltipLeft(container); + var tTop = tooltipTop(container); + if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left; + if (tTop < scrollTop) top = scrollTop - tTop + top; + if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; + break; + case 'w': + left = pos[0] + dist; + top = pos[1] - (height / 2); + if (tLeft + width > windowWidth) left = pos[0] - width - dist; + if (tTop < scrollTop) top = scrollTop + 5; + if (tTop + height > scrollTop + windowHeight) top = scrollTop - height - 5; + break; + case 'n': + left = pos[0] - (width / 2) - 5; + top = pos[1] + dist; + var tLeft = tooltipLeft(container); + var tTop = tooltipTop(container); + if (tLeft < scrollLeft) left = scrollLeft + 5; + if (tLeft + width > windowWidth) left = left - width/2 + 5; + if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; + break; + case 's': + left = pos[0] - (width / 2); + top = pos[1] - height - dist; + var tLeft = tooltipLeft(container); + var tTop = tooltipTop(container); + if (tLeft < scrollLeft) left = scrollLeft + 5; + if (tLeft + width > windowWidth) left = left - width/2 + 5; + if (scrollTop > tTop) top = scrollTop; + break; + } + + + container.style.left = left+'px'; + container.style.top = top+'px'; + container.style.opacity = 1; + container.style.position = 'absolute'; //fix scroll bar issue + container.style.pointerEvents = 'none'; //fix scroll bar issue + + return container; + }; + + nvtooltip.cleanup = function() { + + // Find the tooltips, mark them for removal by this class (so others cleanups won't find it) + var tooltips = document.getElementsByClassName('nvtooltip'); + var purging = []; + while(tooltips.length) { + purging.push(tooltips[0]); + tooltips[0].style.transitionDelay = '0 !important'; + tooltips[0].style.opacity = 0; + tooltips[0].className = 'nvtooltip-pending-removal'; + } + + + setTimeout(function() { + + while (purging.length) { + var removeMe = purging.pop(); + removeMe.parentNode.removeChild(removeMe); + } + }, 500); + }; + + +})(); + +nv.utils.windowSize = function() { + // Sane defaults + var size = {width: 640, height: 480}; + + // Earlier IE uses Doc.body + if (document.body && document.body.offsetWidth) { + size.width = document.body.offsetWidth; + size.height = document.body.offsetHeight; + } + + // IE can use depending on mode it is in + if (document.compatMode=='CSS1Compat' && + document.documentElement && + document.documentElement.offsetWidth ) { + size.width = document.documentElement.offsetWidth; + size.height = document.documentElement.offsetHeight; + } + + // Most recent browsers use + if (window.innerWidth && window.innerHeight) { + size.width = window.innerWidth; + size.height = window.innerHeight; + } + return (size); +}; + + + +// Easy way to bind multiple functions to window.onresize +// TODO: give a way to remove a function after its bound, other than removing all of them +nv.utils.windowResize = function(fun){ + var oldresize = window.onresize; + + window.onresize = function(e) { + if (typeof oldresize == 'function') oldresize(e); + fun(e); + } +} + +// Backwards compatible way to implement more d3-like coloring of graphs. +// If passed an array, wrap it in a function which implements the old default +// behavior +nv.utils.getColor = function(color) { + if (!arguments.length) return nv.utils.defaultColor(); //if you pass in nothing, get default colors back + + if( Object.prototype.toString.call( color ) === '[object Array]' ) + return function(d, i) { return d.color || color[i % color.length]; }; + else + return color; + //can't really help it if someone passes rubbish as color +} + +// Default color chooser uses the index of an object as before. +nv.utils.defaultColor = function() { + var colors = d3.scale.category20().range(); + return function(d, i) { return d.color || colors[i % colors.length] }; +} + + +// Returns a color function that takes the result of 'getKey' for each series and +// looks for a corresponding color from the dictionary, +nv.utils.customTheme = function(dictionary, getKey, defaultColors) { + getKey = getKey || function(series) { return series.key }; // use default series.key if getKey is undefined + defaultColors = defaultColors || d3.scale.category20().range(); //default color function + + var defIndex = defaultColors.length; //current default color (going in reverse) + + return function(series, index) { + var key = getKey(series); + + if (!defIndex) defIndex = defaultColors.length; //used all the default colors, start over + + if (typeof dictionary[key] !== "undefined") + return (typeof dictionary[key] === "function") ? dictionary[key]() : dictionary[key]; + else + return defaultColors[--defIndex]; // no match in dictionary, use default color + } +} + + + +// From the PJAX example on d3js.org, while this is not really directly needed +// it's a very cool method for doing pjax, I may expand upon it a little bit, +// open to suggestions on anything that may be useful +nv.utils.pjax = function(links, content) { + d3.selectAll(links).on("click", function() { + history.pushState(this.href, this.textContent, this.href); + load(this.href); + d3.event.preventDefault(); + }); + + function load(href) { + d3.html(href, function(fragment) { + var target = d3.select(content).node(); + target.parentNode.replaceChild(d3.select(fragment).select(content).node(), target); + nv.utils.pjax(links, content); + }); + } + + d3.select(window).on("popstate", function() { + if (d3.event.state) load(d3.event.state); + }); +} + +nv.models.axis = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var axis = d3.svg.axis() + ; + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 75 //only used for tickLabel currently + , height = 60 //only used for tickLabel currently + , scale = d3.scale.linear() + , axisLabelText = null + , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes + , highlightZero = true + , rotateLabels = 0 + , rotateYLabel = true + , staggerLabels = false + , isOrdinal = false + , ticks = null + ; + + axis + .scale(scale) + .orient('bottom') + .tickFormat(function(d) { return d }) + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var scale0; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + //------------------------------------------------------------ + + + if (ticks !== null) + axis.ticks(ticks); + else if (axis.orient() == 'top' || axis.orient() == 'bottom') + axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100); + + + //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component + + + d3.transition(g) + .call(axis); + + scale0 = scale0 || axis.scale(); + + var fmt = axis.tickFormat(); + if (fmt == null) { + fmt = scale0.tickFormat(); + } + + var axisLabel = g.selectAll('text.nv-axislabel') + .data([axisLabelText || null]); + axisLabel.exit().remove(); + switch (axis.orient()) { + case 'top': + axisLabel.enter().append('text').attr('class', 'nv-axislabel') + .attr('text-anchor', 'middle') + .attr('y', 0); + var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0])); + axisLabel + .attr('x', w/2); + if (showMaxMin) { + var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + .data(scale.domain()); + axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text'); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(' + scale(d) + ',0)' + }) + .select('text') + .attr('dy', '0em') + .attr('y', -axis.tickPadding()) + .attr('text-anchor', 'middle') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + d3.transition(axisMaxMin) + .attr('transform', function(d,i) { + return 'translate(' + scale.range()[i] + ',0)' + }); + } + break; + case 'bottom': + var xLabelMargin = 36; + var maxTextWidth = 30; + var xTicks = g.selectAll('g').select("text"); + if (rotateLabels%360) { + //Calculate the longest xTick width + xTicks.each(function(d,i){ + var width = this.getBBox().width; + if(width > maxTextWidth) maxTextWidth = width; + }); + //Convert to radians before calculating sin. Add 30 to margin for healthy padding. + var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180)); + var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30; + //Rotate all xTicks + xTicks + .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' }) + .attr('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end'); + } + axisLabel.enter().append('text').attr('class', 'nv-axislabel') + .attr('text-anchor', 'middle') + .attr('y', xLabelMargin); + var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0])); + axisLabel + .attr('x', w/2); + if (showMaxMin) { + //if (showMaxMin && !isOrdinal) { + var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + //.data(scale.domain()) + .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]); + axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text'); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)' + }) + .select('text') + .attr('dy', '.71em') + .attr('y', axis.tickPadding()) + .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' }) + .attr('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + d3.transition(axisMaxMin) + .attr('transform', function(d,i) { + //return 'translate(' + scale.range()[i] + ',0)' + //return 'translate(' + scale(d) + ',0)' + return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)' + }); + } + if (staggerLabels) + xTicks + .attr('transform', function(d,i) { return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' }); + + break; + case 'right': + axisLabel.enter().append('text').attr('class', 'nv-axislabel') + .attr('text-anchor', rotateYLabel ? 'middle' : 'begin') + .attr('transform', rotateYLabel ? 'rotate(90)' : '') + .attr('y', rotateYLabel ? (-Math.max(margin.right,width) + 12) : -10); //TODO: consider calculating this based on largest tick width... OR at least expose this on chart + axisLabel + .attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding()); + if (showMaxMin) { + var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + .data(scale.domain()); + axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text') + .style('opacity', 0); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(0,' + scale(d) + ')' + }) + .select('text') + .attr('dy', '.32em') + .attr('y', 0) + .attr('x', axis.tickPadding()) + .attr('text-anchor', 'start') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + d3.transition(axisMaxMin) + .attr('transform', function(d,i) { + return 'translate(0,' + scale.range()[i] + ')' + }) + .select('text') + .style('opacity', 1); + } + break; + case 'left': + /* + //For dynamically placing the label. Can be used with dynamically-sized chart axis margins + var yTicks = g.selectAll('g').select("text"); + yTicks.each(function(d,i){ + var labelPadding = this.getBBox().width + axis.tickPadding() + 16; + if(labelPadding > width) width = labelPadding; + }); + */ + axisLabel.enter().append('text').attr('class', 'nv-axislabel') + .attr('text-anchor', rotateYLabel ? 'middle' : 'end') + .attr('transform', rotateYLabel ? 'rotate(-90)' : '') + .attr('y', rotateYLabel ? (-Math.max(margin.left,width) + 12) : -10); //TODO: consider calculating this based on largest tick width... OR at least expose this on chart + axisLabel + .attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding()); + if (showMaxMin) { + var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + .data(scale.domain()); + axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text') + .style('opacity', 0); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(0,' + scale0(d) + ')' + }) + .select('text') + .attr('dy', '.32em') + .attr('y', 0) + .attr('x', -axis.tickPadding()) + .attr('text-anchor', 'end') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + d3.transition(axisMaxMin) + .attr('transform', function(d,i) { + return 'translate(0,' + scale.range()[i] + ')' + }) + .select('text') + .style('opacity', 1); + } + break; + } + axisLabel + .text(function(d) { return d }); + + + if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) { + //check if max and min overlap other values, if so, hide the values that overlap + g.selectAll('g') // the g's wrapping each tick + .each(function(d,i) { + d3.select(this).select('text').attr('opacity', 1); + if (scale(d) < scale.range()[1] + 10 || scale(d) > scale.range()[0] - 10) { // 10 is assuming text height is 16... if d is 0, leave it! + if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL + d3.select(this).attr('opacity', 0); + + d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!! + } + }); + + //if Max and Min = 0 only show min, Issue #281 + if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) + wrap.selectAll('g.nv-axisMaxMin') + .style('opacity', function(d,i) { return !i ? 1 : 0 }); + + } + + if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) { + var maxMinRange = []; + wrap.selectAll('g.nv-axisMaxMin') + .each(function(d,i) { + try { + if (i) // i== 1, max position + maxMinRange.push(scale(d) - this.getBBox().width - 4) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case) + else // i==0, min position + maxMinRange.push(scale(d) + this.getBBox().width + 4) + }catch (err) { + if (i) // i== 1, max position + maxMinRange.push(scale(d) - 4) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case) + else // i==0, min position + maxMinRange.push(scale(d) + 4) + } + }); + g.selectAll('g') // the g's wrapping each tick + .each(function(d,i) { + if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) { + if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL + d3.select(this).remove(); + else + d3.select(this).select('text').remove(); // Don't remove the ZERO line!! + } + }); + } + + + //highlight zero line ... Maybe should not be an option and should just be in CSS? + if (highlightZero) + g.selectAll('line.tick') + .filter(function(d) { return !parseFloat(Math.round(d*100000)/1000000) }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique + .classed('zero', true); + + //store old scales for use in transitions on update + scale0 = scale.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.axis = axis; + + d3.rebind(chart, axis, 'orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat'); + d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); //these are also accessible by chart.scale(), but added common ones directly for ease of use + + chart.margin = function(_) { + if(!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + } + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.ticks = function(_) { + if (!arguments.length) return ticks; + ticks = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.axisLabel = function(_) { + if (!arguments.length) return axisLabelText; + axisLabelText = _; + return chart; + } + + chart.showMaxMin = function(_) { + if (!arguments.length) return showMaxMin; + showMaxMin = _; + return chart; + } + + chart.highlightZero = function(_) { + if (!arguments.length) return highlightZero; + highlightZero = _; + return chart; + } + + chart.scale = function(_) { + if (!arguments.length) return scale; + scale = _; + axis.scale(scale); + isOrdinal = typeof scale.rangeBands === 'function'; + d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); + return chart; + } + + chart.rotateYLabel = function(_) { + if(!arguments.length) return rotateYLabel; + rotateYLabel = _; + return chart; + } + + chart.rotateLabels = function(_) { + if(!arguments.length) return rotateLabels; + rotateLabels = _; + return chart; + } + + chart.staggerLabels = function(_) { + if (!arguments.length) return staggerLabels; + staggerLabels = _; + return chart; + }; + + + //============================================================ + + + return chart; +} +//TODO: consider deprecating and using multibar with single series for this +nv.models.historicalBar = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceX = [] + , forceY = [0] + , padData = false + , clipEdge = true + , color = nv.utils.defaultColor() + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup Scales + + x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )) + + if (padData) + x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range([0, availableWidth]); + + y .domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) )) + .range([availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-bar').data([data[0].values]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-bars'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + + defsEnter.append('clipPath') + .attr('id', 'nv-chart-clip-path-' + id) + .append('rect'); + + wrap.select('#nv-chart-clip-path-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + + + + var bars = wrap.select('.nv-bars').selectAll('.nv-bar') + .data(function(d) { return d }); + + bars.exit().remove(); + + + var barsEnter = bars.enter().append('rect') + //.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) + .attr('x', 0 ) + .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }) + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + point: d, + series: data[0], + pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + point: d, + series: data[0], + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }); + + bars + .attr('fill', function(d,i) { return color(d, i); }) + .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) + .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) //TODO: better width calculations that don't assume always uniform data spacing;w + .attr('width', (availableWidth / data[0].values.length) * .9 ) + + + d3.transition(bars) + //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + .attr('y', function(d,i) { + return getY(d,i) < 0 ? + y(0) : + y(0) - y(getY(d,i)) < 1 ? + y(0) - 1 : + y(getY(d,i)) + }) + .attr('height', function(d,i) { return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) }); + //.order(); // not sure if this makes any sense for this model + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.padData = function(_) { + if (!arguments.length) return padData; + padData = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +// Chart design based on the recommendations of Stephen Few. Implementation +// based on the work of Clint Ivy, Jamie Love, and Jason Davies. +// http://projects.instantcognition.com/protovis/bulletchart/ + +nv.models.bullet = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , orient = 'left' // TODO top & bottom + , reverse = false + , ranges = function(d) { return d.ranges } + , markers = function(d) { return d.markers } + , measures = function(d) { return d.measures } + , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) + , width = 380 + , height = 30 + , tickFormat = null + , color = nv.utils.getColor(['#1f77b4']) + , dispatch = d3.dispatch('elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(d, i) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + var rangez = ranges.call(this, d, i).slice().sort(d3.descending), + markerz = markers.call(this, d, i).slice().sort(d3.descending), + measurez = measures.call(this, d, i).slice().sort(d3.descending); + + + //------------------------------------------------------------ + // Setup Scales + + // Compute the new x-scale. + var x1 = d3.scale.linear() + .domain( d3.extent(d3.merge([forceX, rangez])) ) + .range(reverse ? [availableWidth, 0] : [0, availableWidth]); + + // Retrieve the old x-scale, if this is an update. + var x0 = this.__chart__ || d3.scale.linear() + .domain([0, Infinity]) + .range(x1.range()); + + // Stash the new scale. + this.__chart__ = x1; + + + var rangeMin = d3.min(rangez), //rangez[2] + rangeMax = d3.max(rangez), //rangez[0] + rangeAvg = rangez[1]; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('rect').attr('class', 'nv-range nv-rangeMax'); + gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg'); + gEnter.append('rect').attr('class', 'nv-range nv-rangeMin'); + gEnter.append('rect').attr('class', 'nv-measure'); + gEnter.append('path').attr('class', 'nv-markerTriangle'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) + w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; + var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) }, + xp1 = function(d) { return d < 0 ? x1(d) : x1(0) }; + + + g.select('rect.nv-rangeMax') + .attr('height', availableHeight) + .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin)) + .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin)) + .datum(rangeMax > 0 ? rangeMax : rangeMin) + /* + .attr('x', rangeMin < 0 ? + rangeMax > 0 ? + x1(rangeMin) + : x1(rangeMax) + : x1(0)) + */ + + g.select('rect.nv-rangeAvg') + .attr('height', availableHeight) + .attr('width', w1(rangeAvg)) + .attr('x', xp1(rangeAvg)) + .datum(rangeAvg) + /* + .attr('width', rangeMax <= 0 ? + x1(rangeMax) - x1(rangeAvg) + : x1(rangeAvg) - x1(rangeMin)) + .attr('x', rangeMax <= 0 ? + x1(rangeAvg) + : x1(rangeMin)) + */ + + g.select('rect.nv-rangeMin') + .attr('height', availableHeight) + .attr('width', w1(rangeMax)) + .attr('x', xp1(rangeMax)) + .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax)) + .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax)) + .datum(rangeMax > 0 ? rangeMin : rangeMax) + /* + .attr('width', rangeMax <= 0 ? + x1(rangeAvg) - x1(rangeMin) + : x1(rangeMax) - x1(rangeAvg)) + .attr('x', rangeMax <= 0 ? + x1(rangeMin) + : x1(rangeAvg)) + */ + + g.select('rect.nv-measure') + .style('fill', color) + .attr('height', availableHeight / 3) + .attr('y', availableHeight / 3) + .attr('width', measurez < 0 ? + x1(0) - x1(measurez[0]) + : x1(measurez[0]) - x1(0)) + .attr('x', xp1(measurez)) + .on('mouseover', function() { + dispatch.elementMouseover({ + value: measurez[0], + label: 'Current', + pos: [x1(measurez[0]), availableHeight/2] + }) + }) + .on('mouseout', function() { + dispatch.elementMouseout({ + value: measurez[0], + label: 'Current' + }) + }) + + var h3 = availableHeight / 6; + if (markerz[0]) { + g.selectAll('path.nv-markerTriangle') + .attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' }) + .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z') + .on('mouseover', function() { + dispatch.elementMouseover({ + value: markerz[0], + label: 'Previous', + pos: [x1(markerz[0]), availableHeight/2] + }) + }) + .on('mouseout', function() { + dispatch.elementMouseout({ + value: markerz[0], + label: 'Previous' + }) + }); + } else { + g.selectAll('path.nv-markerTriangle').remove(); + } + + + wrap.selectAll('.nv-range') + .on('mouseover', function(d,i) { + var label = !i ? "Maximum" : i == 1 ? "Mean" : "Minimum"; + + dispatch.elementMouseover({ + value: d, + label: label, + pos: [x1(d), availableHeight/2] + }) + }) + .on('mouseout', function(d,i) { + var label = !i ? "Maximum" : i == 1 ? "Mean" : "Minimum"; + + dispatch.elementMouseout({ + value: d, + label: label + }) + }) + +/* // THIS IS THE PREVIOUS BULLET IMPLEMENTATION, WILL REMOVE SHORTLY + // Update the range rects. + var range = g.selectAll('rect.nv-range') + .data(rangez); + + range.enter().append('rect') + .attr('class', function(d, i) { return 'nv-range nv-s' + i; }) + .attr('width', w0) + .attr('height', availableHeight) + .attr('x', reverse ? x0 : 0) + .on('mouseover', function(d,i) { + dispatch.elementMouseover({ + value: d, + label: (i <= 0) ? 'Maximum' : (i > 1) ? 'Minimum' : 'Mean', //TODO: make these labels a variable + pos: [x1(d), availableHeight/2] + }) + }) + .on('mouseout', function(d,i) { + dispatch.elementMouseout({ + value: d, + label: (i <= 0) ? 'Minimum' : (i >=1) ? 'Maximum' : 'Mean' //TODO: make these labels a variable + }) + }) + + d3.transition(range) + .attr('x', reverse ? x1 : 0) + .attr('width', w1) + .attr('height', availableHeight); + + + // Update the measure rects. + var measure = g.selectAll('rect.nv-measure') + .data(measurez); + + measure.enter().append('rect') + .attr('class', function(d, i) { return 'nv-measure nv-s' + i; }) + .style('fill', function(d,i) { return color(d,i ) }) + .attr('width', w0) + .attr('height', availableHeight / 3) + .attr('x', reverse ? x0 : 0) + .attr('y', availableHeight / 3) + .on('mouseover', function(d) { + dispatch.elementMouseover({ + value: d, + label: 'Current', //TODO: make these labels a variable + pos: [x1(d), availableHeight/2] + }) + }) + .on('mouseout', function(d) { + dispatch.elementMouseout({ + value: d, + label: 'Current' //TODO: make these labels a variable + }) + }) + + d3.transition(measure) + .attr('width', w1) + .attr('height', availableHeight / 3) + .attr('x', reverse ? x1 : 0) + .attr('y', availableHeight / 3); + + + + // Update the marker lines. + var marker = g.selectAll('path.nv-markerTriangle') + .data(markerz); + + var h3 = availableHeight / 6; + marker.enter().append('path') + .attr('class', 'nv-markerTriangle') + .attr('transform', function(d) { return 'translate(' + x0(d) + ',' + (availableHeight / 2) + ')' }) + .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z') + .on('mouseover', function(d,i) { + dispatch.elementMouseover({ + value: d, + label: 'Previous', + pos: [x1(d), availableHeight/2] + }) + }) + .on('mouseout', function(d,i) { + dispatch.elementMouseout({ + value: d, + label: 'Previous' + }) + }); + + d3.transition(marker) + .attr('transform', function(d) { return 'translate(' + (x1(d) - x1(0)) + ',' + (availableHeight / 2) + ')' }); + + marker.exit().remove(); +*/ + + }); + + // d3.timer.flush(); // Not needed? + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + // left, right, top, bottom + chart.orient = function(_) { + if (!arguments.length) return orient; + orient = _; + reverse = orient == 'right' || orient == 'bottom'; + return chart; + }; + + // ranges (bad, satisfactory, good) + chart.ranges = function(_) { + if (!arguments.length) return ranges; + ranges = _; + return chart; + }; + + // markers (previous, goal) + chart.markers = function(_) { + if (!arguments.length) return markers; + markers = _; + return chart; + }; + + // measures (actual, forecast) + chart.measures = function(_) { + if (!arguments.length) return measures; + measures = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.tickFormat = function(_) { + if (!arguments.length) return tickFormat; + tickFormat = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + //============================================================ + + + return chart; +}; + + + +// Chart design based on the recommendations of Stephen Few. Implementation +// based on the work of Clint Ivy, Jamie Love, and Jason Davies. +// http://projects.instantcognition.com/protovis/bulletchart/ +nv.models.bulletChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var bullet = nv.models.bullet() + ; + + var orient = 'left' // TODO top & bottom + , reverse = false + , margin = {top: 5, right: 40, bottom: 20, left: 120} + , ranges = function(d) { return d.ranges } + , markers = function(d) { return d.markers } + , measures = function(d) { return d.measures } + , width = null + , height = 55 + , tickFormat = null + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + x + '

                              ' + + '

                              ' + y + '

                              ' + } + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left, + top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top, + content = tooltip(e.key, e.label, e.value, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(d, i) { + var container = d3.select(this); + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + that = this; + + + chart.update = function() { chart(selection) }; + chart.container = this; + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!d || !ranges.call(this, d, i)) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', 18 + margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + + var rangez = ranges.call(this, d, i).slice().sort(d3.descending), + markerz = markers.call(this, d, i).slice().sort(d3.descending), + measurez = measures.call(this, d, i).slice().sort(d3.descending); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-bulletWrap'); + gEnter.append('g').attr('class', 'nv-titles'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + // Compute the new x-scale. + var x1 = d3.scale.linear() + .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain + .range(reverse ? [availableWidth, 0] : [0, availableWidth]); + + // Retrieve the old x-scale, if this is an update. + var x0 = this.__chart__ || d3.scale.linear() + .domain([0, Infinity]) + .range(x1.range()); + + // Stash the new scale. + this.__chart__ = x1; + + /* + // Derive width-scales from the x-scales. + var w0 = bulletWidth(x0), + w1 = bulletWidth(x1); + + function bulletWidth(x) { + var x0 = x(0); + return function(d) { + return Math.abs(x(d) - x(0)); + }; + } + + function bulletTranslate(x) { + return function(d) { + return 'translate(' + x(d) + ',0)'; + }; + } + */ + + var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) + w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; + + + var title = gEnter.select('.nv-titles').append('g') + .attr('text-anchor', 'end') + .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')'); + title.append('text') + .attr('class', 'nv-title') + .text(function(d) { return d.title; }); + + title.append('text') + .attr('class', 'nv-subtitle') + .attr('dy', '1em') + .text(function(d) { return d.subtitle; }); + + + + bullet + .width(availableWidth) + .height(availableHeight) + + var bulletWrap = g.select('.nv-bulletWrap'); + + d3.transition(bulletWrap).call(bullet); + + + + // Compute the tick format. + var format = tickFormat || x1.tickFormat( availableWidth / 100 ); + + // Update the tick groups. + var tick = g.selectAll('g.nv-tick') + .data(x1.ticks( availableWidth / 50 ), function(d) { + return this.textContent || format(d); + }); + + // Initialize the ticks with the old scale, x0. + var tickEnter = tick.enter().append('g') + .attr('class', 'nv-tick') + .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' }) + .style('opacity', 1e-6); + + tickEnter.append('line') + .attr('y1', availableHeight) + .attr('y2', availableHeight * 7 / 6); + + tickEnter.append('text') + .attr('text-anchor', 'middle') + .attr('dy', '1em') + .attr('y', availableHeight * 7 / 6) + .text(format); + + + // Transition the updating ticks to the new scale, x1. + var tickUpdate = d3.transition(tick) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1); + + tickUpdate.select('line') + .attr('y1', availableHeight) + .attr('y2', availableHeight * 7 / 6); + + tickUpdate.select('text') + .attr('y', availableHeight * 7 / 6); + + // Transition the exiting ticks to the new scale, x1. + d3.transition(tick.exit()) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1e-6) + .remove(); + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + dispatch.on('tooltipShow', function(e) { + e.key = data[0].title; + if (tooltips) showTooltip(e, that.parentNode); + }); + + //============================================================ + + }); + + d3.timer.flush(); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + bullet.dispatch.on('elementMouseover.tooltip', function(e) { + dispatch.tooltipShow(e); + }); + + bullet.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.bullet = bullet; + + d3.rebind(chart, bullet, 'color'); + + // left, right, top, bottom + chart.orient = function(x) { + if (!arguments.length) return orient; + orient = x; + reverse = orient == 'right' || orient == 'bottom'; + return chart; + }; + + // ranges (bad, satisfactory, good) + chart.ranges = function(x) { + if (!arguments.length) return ranges; + ranges = x; + return chart; + }; + + // markers (previous, goal) + chart.markers = function(x) { + if (!arguments.length) return markers; + markers = x; + return chart; + }; + + // measures (actual, forecast) + chart.measures = function(x) { + if (!arguments.length) return measures; + measures = x; + return chart; + }; + + chart.width = function(x) { + if (!arguments.length) return width; + width = x; + return chart; + }; + + chart.height = function(x) { + if (!arguments.length) return height; + height = x; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.tickFormat = function(x) { + if (!arguments.length) return tickFormat; + tickFormat = x; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +}; + + + +nv.models.cumulativeLineChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + ; + + var margin = {top: 30, right: 30, bottom: 50, left: 60} + , color = nv.utils.defaultColor() + , width = null + , height = null + , showLegend = true + , tooltips = true + , showControls = true + , rescaleY = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' at ' + x + '

                              ' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , id = lines.id() + , state = { index: 0, rescaleY: rescaleY } + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + ; + + xAxis + .orient('bottom') + .tickPadding(7) + ; + yAxis + .orient('left') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var dx = d3.scale.linear() + , index = {i: 0, x: 0} + ; + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, null, null, offsetElement); + }; + +/* + //Moved to see if we can get better behavior to fix issue #315 + var indexDrag = d3.behavior.drag() + .on('dragstart', dragStart) + .on('drag', dragMove) + .on('dragend', dragEnd); + + function dragStart(d,i) { + d3.select(chart.container) + .style('cursor', 'ew-resize'); + } + + function dragMove(d,i) { + d.x += d3.event.dx; + d.i = Math.round(dx.invert(d.x)); + + d3.select(this).attr('transform', 'translate(' + dx(d.i) + ',0)'); + chart.update(); + } + + function dragEnd(d,i) { + d3.select(chart.container) + .style('cursor', 'auto'); + chart.update(); + } +*/ + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this).classed('nv-chart-' + id, true), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + + chart.update = function() { chart(selection) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + var indexDrag = d3.behavior.drag() + .on('dragstart', dragStart) + .on('drag', dragMove) + .on('dragend', dragEnd); + + + function dragStart(d,i) { + d3.select(chart.container) + .style('cursor', 'ew-resize'); + } + + function dragMove(d,i) { + index.x = d3.event.x; + index.i = Math.round(dx.invert(index.x)); + updateZero(); + } + + function dragEnd(d,i) { + d3.select(chart.container) + .style('cursor', 'auto'); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + } + + + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = lines.xScale(); + y = lines.yScale(); + + + if (!rescaleY) { + var seriesDomains = data + .filter(function(series) { return !series.disabled }) + .map(function(series,i) { + var initialDomain = d3.extent(series.values, lines.y()); + + //account for series being disabled when losing 95% or more + if (initialDomain[0] < -.95) initialDomain[0] = -.95; + + return [ + (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]), + (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0]) + ]; + }); + + var completeDomain = [ + d3.min(seriesDomains, function(d) { return d[0] }), + d3.max(seriesDomains, function(d) { return d[1] }) + ] + + lines.yDomain(completeDomain); + } else { + lines.yDomain(null); + } + + + dx .domain([0, data[0].values.length - 1]) //Assumes all series have same length + .range([0, availableWidth]) + .clamp(true); + + //------------------------------------------------------------ + + + var data = indexify(index.i, data); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-background'); + gEnter.append('g').attr('class', 'nv-linesWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + var controlsData = [ + { key: 'Re-scale y-axis', disabled: !rescaleY } + ]; + + controls.width(140).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + // Show error if series goes below 100% + var tempDisabled = data.filter(function(d) { return d.tempDisabled }); + + wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates + if (tempDisabled.length) { + wrap.append('text').attr('class', 'tempDisabled') + .attr('x', availableWidth / 2) + .attr('y', '-.71em') + .style('text-anchor', 'end') + .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.'); + } + + + + //------------------------------------------------------------ + // Main Chart Component(s) + + gEnter.select('.nv-background') + .append('rect'); + + g.select('.nv-background rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + lines + //.x(function(d) { return d.x }) + .y(function(d) { return d.display.y }) + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled })); + + + + var linesWrap = g.select('.nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled })); + + //d3.transition(linesWrap).call(lines); + linesWrap.call(lines); + + + var indexLine = linesWrap.selectAll('.nv-indexLine') + .data([index]); + indexLine.enter().append('rect').attr('class', 'nv-indexLine') + .attr('width', 3) + .attr('x', -2) + .attr('fill', 'red') + .attr('fill-opacity', .5) + .call(indexDrag) + + indexLine + .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' }) + .attr('height', availableHeight) + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + //Suggest how many ticks based on the chart width and D3 should listen (70 is the optimal number for MM/DD/YY dates) + .ticks( Math.min(data[0].values.length,availableWidth/70) ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + + function updateZero() { + indexLine + .data([index]); + + chart.update(); + } + + g.select('.nv-background rect') + .on('click', function() { + index.x = d3.mouse(this)[0]; + index.i = Math.round(dx.invert(index.x)); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + + updateZero(); + }); + + lines.dispatch.on('elementClick', function(e) { + index.i = e.pointIndex; + index.x = dx(index.i); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + + updateZero(); + }); + + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + rescaleY = !d.disabled; + + state.rescaleY = rescaleY; + dispatch.stateChange(state); + + //selection.transition().call(chart); + selection.call(chart); + }); + + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + //selection.transition().call(chart); + selection.call(chart); + }); + +/* + // + legend.dispatch.on('legendMouseover', function(d, i) { + d.hover = true; + selection.transition().call(chart) + }); + + legend.dispatch.on('legendMouseout', function(d, i) { + d.hover = false; + selection.transition().call(chart) + }); +*/ + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + + if (typeof e.index !== 'undefined') { + index.i = e.index; + index.x = dx(index.i); + + state.index = e.index; + + indexLine + .data([index]); + } + + + if (typeof e.rescaleY !== 'undefined') { + rescaleY = e.rescaleY; + } + + selection.call(chart); + }); + + //============================================================ + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.lines = lines; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.rescaleY = function(_) { + if (!arguments.length) return rescaleY; + rescaleY = _ + return rescaleY; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + //============================================================ + // Functions + //------------------------------------------------------------ + + /* Normalize the data according to an index point. */ + function indexify(idx, data) { + return data.map(function(line, i) { + var v = lines.y()(line.values[idx], idx); + + //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue + if (v < -.95) { + //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100) + line.tempDisabled = true; + return line; + } + + line.tempDisabled = false; + + line.values = line.values.map(function(point, pointIndex) { + point.display = {'y': (lines.y()(point, pointIndex) - v) / (1 + v) }; + return point; + }) + + return line; + }) + } + + //============================================================ + + + return chart; +} +//TODO: consider deprecating by adding necessary features to multiBar model +nv.models.discreteBar = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , x = d3.scale.ordinal() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , color = nv.utils.defaultColor() + , showValues = false + , valueFormat = d3.format(',.2f') + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + , rectClass = 'discreteBar' + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + //add series index to each data point for reference + data = data.map(function(series, i) { + series.values = series.values.map(function(point) { + point.series = i; + return point; + }); + return series; + }); + + + //------------------------------------------------------------ + // Setup Scales + + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0 } + }) + }); + + x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands([0, availableWidth], .1); + + y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY))); + + + // If showValues, pad the Y axis range to account for label height + if (showValues) y.range([availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]); + else y.range([availableHeight, 0]); + + //store old scales if they exist + x0 = x0 || x; + y0 = y0 || y.copy().range([y(0),y(0)]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-groups'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }); + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .75); + + + var bars = groups.selectAll('g.nv-bar') + .data(function(d) { return d.values }); + + bars.exit().remove(); + + + var barsEnter = bars.enter().append('g') + .attr('transform', function(d,i,j) { + return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')' + }) + .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + value: getY(d,i), + point: d, + series: data[d.series], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }); + + barsEnter.append('rect') + .attr('height', 0) + .attr('width', x.rangeBand() * .9 / data.length ) + + if (showValues) { + barsEnter.append('text') + .attr('text-anchor', 'middle') + bars.select('text') + .attr('x', x.rangeBand() * .9 / 2) + .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 }) + .text(function(d,i) { return valueFormat(getY(d,i)) }); + } else { + bars.selectAll('text').remove(); + } + + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' }) + .style('fill', function(d,i) { return d.color || color(d,i) }) + .style('stroke', function(d,i) { return d.color || color(d,i) }) + .select('rect') + .attr('class', rectClass) + .attr('width', x.rangeBand() * .9 / data.length); + d3.transition(bars) + //.delay(function(d,i) { return i * 1200 / data[0].values.length }) + .attr('transform', function(d,i) { + var left = x(getX(d,i)) + x.rangeBand() * .05, + top = getY(d,i) < 0 ? + y(0) : + y(0) - y(getY(d,i)) < 1 ? + y(0) - 1 : //make 1 px positive bars show up above y=0 + y(getY(d,i)); + + return 'translate(' + left + ', ' + top + ')' + }) + .select('rect') + .attr('height', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y(0)) || 1) + }); + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.showValues = function(_) { + if (!arguments.length) return showValues; + showValues = _; + return chart; + }; + + chart.valueFormat= function(_) { + if (!arguments.length) return valueFormat; + valueFormat = _; + return chart; + }; + + chart.rectClass= function(_) { + if (!arguments.length) return rectClass; + rectClass = _; + return chart; + } + //============================================================ + + + return chart; +} + +nv.models.discreteBarChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var discretebar = nv.models.discreteBar() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + ; + + var margin = {top: 15, right: 10, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.getColor() + , staggerLabels = false + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + x + '

                              ' + + '

                              ' + y + '

                              ' + } + , x + , y + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate') + ; + + xAxis + .orient('bottom') + .highlightZero(false) + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient('left') + .tickFormat(d3.format(',.1f')) + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + + chart.update = function() { dispatch.beforeUpdate(); selection.transition().call(chart); }; + chart.container = this; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = discretebar.xScale(); + y = discretebar.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g'); + var defsEnter = gEnter.append('defs'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Main Chart Component(s) + + discretebar + .width(availableWidth) + .height(availableHeight); + + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(barsWrap).call(discretebar); + + //------------------------------------------------------------ + + + + defsEnter.append('clipPath') + .attr('id', 'nv-x-label-clip-' + discretebar.id()) + .append('rect'); + + g.select('#nv-x-label-clip-' + discretebar.id() + ' rect') + .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1)) + .attr('height', 16) + .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 )); + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')'); + //d3.transition(g.select('.nv-x.nv-axis')) + g.select('.nv-x.nv-axis').transition().duration(0) + .call(xAxis); + + + var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); + + if (staggerLabels) { + xTicks + .selectAll('text') + .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' }) + } + + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + //============================================================ + + + }); + + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + discretebar.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + discretebar.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.discretebar = discretebar; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, discretebar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'id', 'showValues', 'valueFormat'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + discretebar.color(color); + return chart; + }; + + chart.staggerLabels = function(_) { + if (!arguments.length) return staggerLabels; + staggerLabels = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.distribution = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 400 //technically width or height depending on x or y.... + , size = 8 + , axis = 'x' // 'x' or 'y'... horizontal or vertical + , getData = function(d) { return d[axis] } // defaults d.x or d.y + , color = nv.utils.defaultColor() + , scale = d3.scale.linear() + , domain + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var scale0; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom), + naxis = axis == 'x' ? 'y' : 'x', + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup Scales + + scale0 = scale0 || scale; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-distribution').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + + //------------------------------------------------------------ + + + var distWrap = g.selectAll('g.nv-dist') + .data(function(d) { return d }, function(d) { return d.key }); + + distWrap.enter().append('g'); + distWrap + .attr('class', function(d,i) { return 'nv-dist nv-series-' + i }) + .style('stroke', function(d,i) { return color(d, i) }); + + var dist = distWrap.selectAll('line.nv-dist' + axis) + .data(function(d) { return d.values }) + dist.enter().append('line') + .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) }) + d3.transition(distWrap.exit().selectAll('line.nv-dist' + axis)) + .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) + .style('stroke-opacity', 0) + .remove(); + dist + .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i }) + .attr(naxis + '1', 0) + .attr(naxis + '2', size); + d3.transition(dist) + .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) + + + scale0 = scale.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.axis = function(_) { + if (!arguments.length) return axis; + axis = _; + return chart; + }; + + chart.size = function(_) { + if (!arguments.length) return size; + size = _; + return chart; + }; + + chart.getData = function(_) { + if (!arguments.length) return getData; + getData = d3.functor(_); + return chart; + }; + + chart.scale = function(_) { + if (!arguments.length) return scale; + scale = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + //============================================================ + + + return chart; +} +nv.models.indentedTree = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} //TODO: implement, maybe as margin on the containing div + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() + , id = Math.floor(Math.random() * 10000) + , header = true + , filterZero = false + , noData = "No Data Available." + , childIndent = 20 + , columns = [{key:'key', label: 'Name', type:'text'}] //TODO: consider functions like chart.addColumn, chart.removeColumn, instead of a block like this + , tableClass = null + , iconOpen = 'images/grey-plus.png' //TODO: consider removing this and replacing with a '+' or '-' unless user defines images + , iconClose = 'images/grey-minus.png' + , dispatch = d3.dispatch('elementClick', 'elementDblclick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var i = 0, + depth = 1; + + var tree = d3.layout.tree() + .children(function(d) { return d.values }) + .size([height, childIndent]); //Not sure if this is needed now that the result is HTML + + chart.update = function() { selection.transition().call(chart) }; + chart.container = this; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + if (!data[0]) data[0] = {key: noData}; + + //------------------------------------------------------------ + + + var nodes = tree.nodes(data[0]); + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = d3.select(this).selectAll('div').data([[nodes]]); + var wrapEnter = wrap.enter().append('div').attr('class', 'nvd3 nv-wrap nv-indentedtree'); + var tableEnter = wrapEnter.append('table'); + var table = wrap.select('table').attr('width', '100%').attr('class', tableClass); + + //------------------------------------------------------------ + + + if (header) { + var thead = tableEnter.append('thead'); + + var theadRow1 = thead.append('tr'); + + columns.forEach(function(column) { + theadRow1 + .append('th') + .attr('width', column.width ? column.width : '10%') + .style('text-align', column.type == 'numeric' ? 'right' : 'left') + .append('span') + .text(column.label); + }); + } + + + var tbody = table.selectAll('tbody') + .data(function(d) { return d }); + tbody.enter().append('tbody'); + + + + //compute max generations + depth = d3.max(nodes, function(node) { return node.depth }); + tree.size([height, depth * childIndent]); //TODO: see if this is necessary at all + + + // Update the nodes… + var node = tbody.selectAll('tr') + .data(function(d) { return d.filter(function(d) { return (filterZero && !d.children) ? filterZero(d) : true; }) }, function(d) { return d.id || (d.id == ++i)}); + //.style('display', 'table-row'); //TODO: see if this does anything + + node.exit().remove(); + + + node.select('img.nv-treeicon') + .attr('src', icon) + .classed('folded', folded); + + var nodeEnter = node.enter().append('tr'); + + + columns.forEach(function(column, index) { + + var nodeName = nodeEnter.append('td') + .style('padding-left', function(d) { return (index ? 0 : d.depth * childIndent + 12 + (icon(d) ? 0 : 16)) + 'px' }, 'important') //TODO: check why I did the ternary here + .style('text-align', column.type == 'numeric' ? 'right' : 'left'); + + + if (index == 0) { + nodeName.append('img') + .classed('nv-treeicon', true) + .classed('nv-folded', folded) + .attr('src', icon) + .style('width', '14px') + .style('height', '14px') + .style('padding', '0 1px') + .style('display', function(d) { return icon(d) ? 'inline-block' : 'none'; }) + .on('click', click); + } + + + nodeName.append('span') + .attr('class', d3.functor(column.classes) ) + .text(function(d) { return column.format ? column.format(d) : + (d[column.key] || '-') }); + + if (column.showCount) { + nodeName.append('span') + .attr('class', 'nv-childrenCount'); + + node.selectAll('span.nv-childrenCount').text(function(d) { + return ((d.values && d.values.length) || (d._values && d._values.length)) ? //If this is a parent + '(' + ((d.values && (d.values.filter(function(d) { return filterZero ? filterZero(d) : true; }).length)) //If children are in values check its children and filter + || (d._values && d._values.filter(function(d) { return filterZero ? filterZero(d) : true; }).length) //Otherwise, do the same, but with the other name, _values... + || 0) + ')' //This is the catch-all in case there are no children after a filter + : '' //If this is not a parent, just give an empty string + }); + } + + if (column.click) + nodeName.select('span').on('click', column.click); + + }); + + + node + .order() + .on('click', function(d) { + dispatch.elementClick({ + row: this, //TODO: decide whether or not this should be consistent with scatter/line events or should be an html link (a href) + data: d, + pos: [d.x, d.y] + }); + }) + .on('dblclick', function(d) { + dispatch.elementDblclick({ + row: this, + data: d, + pos: [d.x, d.y] + }); + }) + .on('mouseover', function(d) { + dispatch.elementMouseover({ + row: this, + data: d, + pos: [d.x, d.y] + }); + }) + .on('mouseout', function(d) { + dispatch.elementMouseout({ + row: this, + data: d, + pos: [d.x, d.y] + }); + }); + + + + + // Toggle children on click. + function click(d, _, unshift) { + d3.event.stopPropagation(); + + if(d3.event.shiftKey && !unshift) { + //If you shift-click, it'll toggle fold all the children, instead of itself + d3.event.shiftKey = false; + d.values && d.values.forEach(function(node){ + if (node.values || node._values) { + click(node, 0, true); + } + }); + return true; + } + if(!hasChildren(d)) { + //download file + //window.location.href = d.url; + return true; + } + if (d.values) { + d._values = d.values; + d.values = null; + } else { + d.values = d._values; + d._values = null; + } + chart.update(); + } + + + function icon(d) { + return (d._values && d._values.length) ? iconOpen : (d.values && d.values.length) ? iconClose : ''; + } + + function folded(d) { + return (d._values && d._values.length); + } + + function hasChildren(d) { + var values = d.values || d._values; + + return (values && values.length); + } + + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + scatter.color(color); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.header = function(_) { + if (!arguments.length) return header; + header = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + chart.filterZero = function(_) { + if (!arguments.length) return filterZero; + filterZero = _; + return chart; + }; + + chart.columns = function(_) { + if (!arguments.length) return columns; + columns = _; + return chart; + }; + + chart.tableClass = function(_) { + if (!arguments.length) return tableClass; + tableClass = _; + return chart; + }; + + chart.iconOpen = function(_){ + if (!arguments.length) return iconOpen; + iconOpen = _; + return chart; + } + + chart.iconClose = function(_){ + if (!arguments.length) return iconClose; + iconClose = _; + return chart; + } + + //============================================================ + + + return chart; +};nv.models.legend = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 5, right: 0, bottom: 5, left: 0} + , width = 400 + , height = 20 + , getKey = function(d) { return d.key } + , color = nv.utils.defaultColor() + , align = true + , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-legend').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + var series = g.selectAll('.nv-series') + .data(function(d) { return d }); + var seriesEnter = series.enter().append('g').attr('class', 'nv-series') + .on('mouseover', function(d,i) { + dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects + }) + .on('mouseout', function(d,i) { + dispatch.legendMouseout(d,i); + }) + .on('click', function(d,i) { + dispatch.legendClick(d,i); + }) + .on('dblclick', function(d,i) { + dispatch.legendDblclick(d,i); + }); + seriesEnter.append('circle') + .style('stroke-width', 2) + .attr('r', 5); + seriesEnter.append('text') + .attr('text-anchor', 'start') + .attr('dy', '.32em') + .attr('dx', '8'); + series.classed('disabled', function(d) { return d.disabled }); + series.exit().remove(); + series.select('circle') + .style('fill', function(d,i) { return d.color || color(d,i)}) + .style('stroke', function(d,i) { return d.color || color(d, i) }); + series.select('text').text(getKey); + + + //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option) + + // NEW ALIGNING CODE, TODO: clean up + if (align) { + var seriesWidths = []; + series.each(function(d,i) { + seriesWidths.push(d3.select(this).select('text').node().getComputedTextLength() + 28); // 28 is ~ the width of the circle plus some padding + }); + + //nv.log('Series Widths: ', JSON.stringify(seriesWidths)); + + var seriesPerRow = 0; + var legendWidth = 0; + var columnWidths = []; + + while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) { + columnWidths[seriesPerRow] = seriesWidths[seriesPerRow]; + legendWidth += seriesWidths[seriesPerRow++]; + } + + + while ( legendWidth > availableWidth && seriesPerRow > 1 ) { + columnWidths = []; + seriesPerRow--; + + for (k = 0; k < seriesWidths.length; k++) { + if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) ) + columnWidths[k % seriesPerRow] = seriesWidths[k]; + } + + legendWidth = columnWidths.reduce(function(prev, cur, index, array) { + return prev + cur; + }); + } + //console.log(columnWidths, legendWidth, seriesPerRow); + + var xPositions = []; + for (var i = 0, curX = 0; i < seriesPerRow; i++) { + xPositions[i] = curX; + curX += columnWidths[i]; + } + + series + .attr('transform', function(d, i) { + return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')'; + }); + + //position legend as far right as possible within the total width + g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')'); + + height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20); + + } else { + + var ypos = 5, + newxpos = 5, + maxwidth = 0, + xpos; + series + .attr('transform', function(d, i) { + var length = d3.select(this).select('text').node().getComputedTextLength() + 28; + xpos = newxpos; + + if (width < margin.left + margin.right + xpos + length) { + newxpos = xpos = 5; + ypos += 20; + } + + newxpos += length; + if (newxpos > maxwidth) maxwidth = newxpos; + + return 'translate(' + xpos + ',' + ypos + ')'; + }); + + //position legend as far right as possible within the total width + g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')'); + + height = margin.top + margin.bottom + ypos + 15; + + } + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.key = function(_) { + if (!arguments.length) return getKey; + getKey = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.align = function(_) { + if (!arguments.length) return align; + align = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.line = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var scatter = nv.models.scatter() + ; + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() // a function that returns a color + , getX = function(d) { return d.x } // accessor to get the x value from a data point + , getY = function(d) { return d.y } // accessor to get the y value from a data point + , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continuous when it is not defined + , isArea = function(d) { return d.area } // decides if a line is an area or just a line + , clipEdge = false // if true, masks lines within x and y scale + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , interpolate = "linear" // controls the line interpolation + ; + + scatter + .size(16) // default size + .sizeDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0 //used to store previous scales + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + //------------------------------------------------------------ + // Setup Scales + + x = scatter.xScale(); + y = scatter.yScale(); + + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + gEnter.append('g').attr('class', 'nv-groups'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + + scatter + .width(availableWidth) + .height(availableHeight) + + var scatterWrap = wrap.select('.nv-scatterWrap'); + //.datum(data); // Data automatically trickles down from the wrap + + d3.transition(scatterWrap).call(scatter); + + + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + scatter.id()) + .append('rect'); + + wrap.select('#nv-edge-clip-' + scatter.id() + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); + scatterWrap + .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); + + + + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i)}); + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .5); + + + + var areaPaths = groups.selectAll('path.nv-area') + .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area + areaPaths.enter().append('path') + .attr('class', 'nv-area') + .attr('d', function(d) { + return d3.svg.area() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x0(getX(d,i)) }) + .y0(function(d,i) { return y0(getY(d,i)) }) + .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) + //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this + .apply(this, [d.values]) + }); + d3.transition(groups.exit().selectAll('path.nv-area')) + .attr('d', function(d) { + return d3.svg.area() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x0(getX(d,i)) }) + .y0(function(d,i) { return y0(getY(d,i)) }) + .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) + //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this + .apply(this, [d.values]) + }); + d3.transition(areaPaths) + .attr('d', function(d) { + return d3.svg.area() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x0(getX(d,i)) }) + .y0(function(d,i) { return y0(getY(d,i)) }) + .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) + //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this + .apply(this, [d.values]) + }); + + + + var linePaths = groups.selectAll('path.nv-line') + .data(function(d) { return [d.values] }); + linePaths.enter().append('path') + .attr('class', 'nv-line') + .attr('d', + d3.svg.line() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x0(getX(d,i)) }) + .y(function(d,i) { return y0(getY(d,i)) }) + ); + d3.transition(groups.exit().selectAll('path.nv-line')) + .attr('d', + d3.svg.line() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ); + d3.transition(linePaths) + .attr('d', + d3.svg.line() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ); + + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = scatter.dispatch; + chart.scatter = scatter; + + d3.rebind(chart, scatter, 'id', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'padData'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + scatter.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + scatter.y(_); + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + scatter.color(color); + return chart; + }; + + chart.interpolate = function(_) { + if (!arguments.length) return interpolate; + interpolate = _; + return chart; + }; + + chart.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return chart; + }; + + chart.isArea = function(_) { + if (!arguments.length) return isArea; + isArea = d3.functor(_); + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.lineChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + ; + +//set margin.right to 23 to fit dates on the x-axis within the chart + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , color = nv.utils.defaultColor() + , width = null + , height = null + , showLegend = true + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' at ' + x + '

                              ' + } + , x + , y + , state = {} + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + ; + + xAxis + .orient('bottom') + .tickPadding(7) + ; + yAxis + .orient('left') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + + // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else + if (offsetElement) { + var svg = d3.select(offsetElement).select('svg'); + var viewBox = svg.attr('viewBox'); + if (viewBox) { + viewBox = viewBox.split(' '); + var ratio = parseInt(svg.style('width')) / viewBox[2]; + e.pos[0] = e.pos[0] * ratio; + e.pos[1] = e.pos[1] * ratio; + } + } + + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, null, null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + + chart.update = function() { chart(selection) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = lines.xScale(); + y = lines.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-linesWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + + //------------------------------------------------------------ + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + lines + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + + + var linesWrap = g.select('.nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(linesWrap).call(lines); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + +/* + legend.dispatch.on('legendMouseover', function(d, i) { + d.hover = true; + selection.transition().call(chart) + }); + + legend.dispatch.on('legendMouseout', function(d, i) { + d.hover = false; + selection.transition().call(chart) + }); +*/ + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + selection.call(chart); + }); + + //============================================================ + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.lines = lines; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.linePlusBarChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , bars = nv.models.historicalBar() + , xAxis = nv.models.axis() + , y1Axis = nv.models.axis() + , y2Axis = nv.models.axis() + , legend = nv.models.legend() + ; + + var margin = {top: 30, right: 60, bottom: 50, left: 60} + , width = null + , height = null + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , color = nv.utils.defaultColor() + , showLegend = true + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' at ' + x + '

                              '; + } + , x + , y1 + , y2 + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + ; + + bars + .padData(true) + ; + lines + .clipEdge(false) + .padData(true) + ; + xAxis + .orient('bottom') + .tickPadding(7) + .highlightZero(false) + ; + y1Axis + .orient('left') + ; + y2Axis + .orient('right') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var state = {}, + showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + } + ; + + //------------------------------------------------------------ + + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { chart(selection) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + var dataBars = data.filter(function(d) { return !d.disabled && d.bar }); + var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240 + + //x = xAxis.scale(); + x = dataLines.filter(function(d) { return !d.disabled; }).length && dataLines.filter(function(d) { return !d.disabled; })[0].values.length ? lines.xScale() : bars.xScale(); + //x = dataLines.filter(function(d) { return !d.disabled; }).length ? lines.xScale() : bars.xScale(); //old code before change above + y1 = bars.yScale(); + y2 = lines.yScale(); + + //------------------------------------------------------------ + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-linePlusBar').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y1 nv-axis'); + gEnter.append('g').attr('class', 'nv-y2 nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-linesWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width( availableWidth / 2 ); + + g.select('.nv-legendWrap') + .datum(data.map(function(series) { + series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; + series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)'); + return series; + })) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + + lines + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].bar })) + + bars + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && data[i].bar })) + + + + var barsWrap = g.select('.nv-barsWrap') + .datum(dataBars.length ? dataBars : [{values:[]}]) + + var linesWrap = g.select('.nv-linesWrap') + .datum(!dataLines[0].disabled ? dataLines : [{values:[]}] ); + //.datum(!dataLines[0].disabled ? dataLines : [{values:dataLines[0].values.map(function(d) { return [d[0], null] }) }] ); + + d3.transition(barsWrap).call(bars); + d3.transition(linesWrap).call(lines); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y1.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + + y1Axis + .scale(y1) + .ticks( availableHeight / 36 ) + .tickSize(-availableWidth, 0); + + d3.transition(g.select('.nv-y1.nv-axis')) + .style('opacity', dataBars.length ? 1 : 0) + .call(y1Axis); + + + y2Axis + .scale(y2) + .ticks( availableHeight / 36 ) + .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none + + g.select('.nv-y2.nv-axis') + .style('opacity', dataLines.length ? 1 : 0) + .attr('transform', 'translate(' + availableWidth + ',0)'); + //.attr('transform', 'translate(' + x.range()[1] + ',0)'); + + d3.transition(g.select('.nv-y2.nv-axis')) + .call(y2Axis); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + selection.call(chart); + }); + + //============================================================ + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + bars.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.legend = legend; + chart.lines = lines; + chart.bars = bars; + chart.xAxis = xAxis; + chart.y1Axis = y1Axis; + chart.y2Axis = y2Axis; + + d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate'); + //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc. + //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + lines.x(_); + bars.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + lines.y(_); + bars.y(_); + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.lineWithFocusChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , lines2 = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , x2Axis = nv.models.axis() + , y2Axis = nv.models.axis() + , legend = nv.models.legend() + , brush = d3.svg.brush() + ; + + var margin = {top: 30, right: 30, bottom: 30, left: 60} + , margin2 = {top: 0, right: 30, bottom: 20, left: 60} + , color = nv.utils.defaultColor() + , width = null + , height = null + , height2 = 100 + , x + , y + , x2 + , y2 + , showLegend = true + , brushExtent = null + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' at ' + x + '

                              ' + } + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush') + ; + + lines + .clipEdge(true) + ; + lines2 + .interactive(false) + ; + xAxis + .orient('bottom') + .tickPadding(5) + ; + yAxis + .orient('left') + ; + x2Axis + .orient('bottom') + .tickPadding(5) + ; + y2Axis + .orient('left') + ; + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, null, null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight1 = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom - height2, + availableHeight2 = height2 - margin2.top - margin2.bottom; + + chart.update = function() { chart(selection) }; + chart.container = this; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight1 / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = lines.xScale(); + y = lines.yScale(); + x2 = lines2.xScale(); + y2 = lines2.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-legendWrap'); + + var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); + focusEnter.append('g').attr('class', 'nv-x nv-axis'); + focusEnter.append('g').attr('class', 'nv-y nv-axis'); + focusEnter.append('g').attr('class', 'nv-linesWrap'); + + var contextEnter = gEnter.append('g').attr('class', 'nv-context'); + contextEnter.append('g').attr('class', 'nv-x nv-axis'); + contextEnter.append('g').attr('class', 'nv-y nv-axis'); + contextEnter.append('g').attr('class', 'nv-linesWrap'); + contextEnter.append('g').attr('class', 'nv-brushBackground'); + contextEnter.append('g').attr('class', 'nv-x nv-brush'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight1 = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom - height2; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + lines + .width(availableWidth) + .height(availableHeight1) + .color( + data + .map(function(d,i) { + return d.color || color(d, i); + }) + .filter(function(d,i) { + return !data[i].disabled; + }) + ); + + lines2 + .defined(lines.defined()) + .width(availableWidth) + .height(availableHeight2) + .color( + data + .map(function(d,i) { + return d.color || color(d, i); + }) + .filter(function(d,i) { + return !data[i].disabled; + }) + ); + + g.select('.nv-context') + .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')') + + var contextLinesWrap = g.select('.nv-context .nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(contextLinesWrap).call(lines2); + + //------------------------------------------------------------ + + + /* + var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(focusLinesWrap).call(lines); + */ + + + //------------------------------------------------------------ + // Setup Main (Focus) Axes + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight1, 0); + + yAxis + .scale(y) + .ticks( availableHeight1 / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-focus .nv-x.nv-axis') + .attr('transform', 'translate(0,' + availableHeight1 + ')'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Brush + + brush + .x(x2) + .on('brush', onBrush); + + if (brushExtent) brush.extent(brushExtent); + + var brushBG = g.select('.nv-brushBackground').selectAll('g') + .data([brushExtent || brush.extent()]) + + var brushBGenter = brushBG.enter() + .append('g'); + + brushBGenter.append('rect') + .attr('class', 'left') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight2); + + brushBGenter.append('rect') + .attr('class', 'right') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight2); + + gBrush = g.select('.nv-x.nv-brush') + .call(brush); + gBrush.selectAll('rect') + //.attr('y', -5) + .attr('height', availableHeight2); + gBrush.selectAll('.resize').append('path').attr('d', resizePath); + + onBrush(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Secondary (Context) Axes + + x2Axis + .scale(x2) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight2, 0); + + g.select('.nv-context .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y2.range()[0] + ')'); + d3.transition(g.select('.nv-context .nv-x.nv-axis')) + .call(x2Axis); + + + y2Axis + .scale(y2) + .ticks( availableHeight2 / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-context .nv-y.nv-axis')) + .call(y2Axis); + + g.select('.nv-context .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y2.range()[0] + ')'); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + selection.transition().call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + //============================================================ + + + //============================================================ + // Functions + //------------------------------------------------------------ + + // Taken from crossfilter (http://square.github.com/crossfilter/) + function resizePath(d) { + var e = +(d == 'e'), + x = e ? 1 : -1, + y = availableHeight2 / 3; + return 'M' + (.5 * x) + ',' + y + + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) + + 'V' + (2 * y - 6) + + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y) + + 'Z' + + 'M' + (2.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8) + + 'M' + (4.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8); + } + + + function updateBrushBG() { + if (!brush.empty()) brush.extent(brushExtent); + brushBG + .data([brush.empty() ? x2.domain() : brushExtent]) + .each(function(d,i) { + var leftWidth = x2(d[0]) - x.range()[0], + rightWidth = x.range()[1] - x2(d[1]); + d3.select(this).select('.left') + .attr('width', leftWidth < 0 ? 0 : leftWidth); + + d3.select(this).select('.right') + .attr('x', x2(d[1])) + .attr('width', rightWidth < 0 ? 0 : rightWidth); + }); + } + + + function onBrush() { + brushExtent = brush.empty() ? null : brush.extent(); + extent = brush.empty() ? x2.domain() : brush.extent(); + + + dispatch.brush({extent: extent, brush: brush}); + + + updateBrushBG(); + + // Update Main (Focus) + var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') + .datum( + data + .filter(function(d) { return !d.disabled }) + .map(function(d,i) { + return { + key: d.key, + values: d.values.filter(function(d,i) { + return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; + }) + } + }) + ); + d3.transition(focusLinesWrap).call(lines); + + + // Update Main (Focus) Axes + d3.transition(g.select('.nv-focus .nv-x.nv-axis')) + .call(xAxis); + d3.transition(g.select('.nv-focus .nv-y.nv-axis')) + .call(yAxis); + } + + //============================================================ + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.legend = legend; + chart.lines = lines; + chart.lines2 = lines2; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.x2Axis = x2Axis; + chart.y2Axis = y2Axis; + + d3.rebind(chart, lines, 'defined', 'isArea', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); + + chart.x = function(_) { + if (!arguments.length) return lines.x; + lines.x(_); + lines2.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return lines.y; + lines.y(_); + lines2.y(_); + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.margin2 = function(_) { + if (!arguments.length) return margin2; + margin2 = _; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.height2 = function(_) { + if (!arguments.length) return height2; + height2 = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color =nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.interpolate = function(_) { + if (!arguments.length) return lines.interpolate(); + lines.interpolate(_); + lines2.interpolate(_); + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + // Chart has multiple similar Axes, to prevent code duplication, probably need to link all axis functions manually like below + chart.xTickFormat = function(_) { + if (!arguments.length) return xAxis.tickFormat(); + xAxis.tickFormat(_); + x2Axis.tickFormat(_); + return chart; + }; + + chart.yTickFormat = function(_) { + if (!arguments.length) return yAxis.tickFormat(); + yAxis.tickFormat(_); + y2Axis.tickFormat(_); + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.multiBar = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , x = d3.scale.ordinal() + , y = d3.scale.linear() + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , clipEdge = true + , stacked = false + , color = nv.utils.defaultColor() + , barColor = null // adding the ability to set the color for each rather than the whole group + , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled + , delay = 1200 + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0 //used to store previous scales + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + if (stacked) + data = d3.layout.stack() + .offset('zero') + .values(function(d){ return d.values }) + .y(getY) + (data); + + + //add series index to each data point for reference + data = data.map(function(series, i) { + series.values = series.values.map(function(point) { + point.series = i; + return point; + }); + return series; + }); + + + //------------------------------------------------------------ + // HACK for negative value stacking + if (stacked) + data[0].values.map(function(d,i) { + var posBase = 0, negBase = 0; + data.map(function(d) { + var f = d.values[i] + f.size = Math.abs(f.y); + if (f.y<0) { + f.y1 = negBase; + negBase = negBase - f.size; + } else + { + f.y1 = f.size + posBase; + posBase = posBase + f.size; + } + }); + }); + + //------------------------------------------------------------ + // Setup Scales + + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 } + }) + }); + + x .domain(d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands([0, availableWidth], .1); + + //y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y1 : 0) }).concat(forceY))) + y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 : d.y1 + d.y ) : d.y }).concat(forceY))) + .range([availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + gEnter.append('g').attr('class', 'nv-groups'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + //.style('stroke-opacity', 1e-6) + //.style('fill-opacity', 1e-6) + .selectAll('rect.nv-bar') + .delay(function(d,i) { return i * delay/ data[0].values.length }) + .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) }) + .attr('height', 0) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i) }); + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .75); + + + var bars = groups.selectAll('rect.nv-bar') + .data(function(d) { return d.values }); + + bars.exit().remove(); + + + var barsEnter = bars.enter().append('rect') + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + .attr('x', function(d,i,j) { + return stacked ? 0 : (j * x.rangeBand() / data.length ) + }) + .attr('y', function(d) { return y0(stacked ? d.y0 : 0) }) + .attr('height', 0) + .attr('width', x.rangeBand() / (stacked ? 1 : data.length) ); + bars + .style('fill', function(d,i,j){ return color(d, j, i); }) + .style('stroke', function(d,i,j){ return color(d, j, i); }) + .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + value: getY(d,i), + point: d, + series: data[d.series], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }); + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; }) + + if (barColor) { + if (!disabled) disabled = data.map(function() { return true }); + bars + //.style('fill', barColor) + //.style('stroke', barColor) + //.style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(j).toString(); }) + //.style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(j).toString(); }) + .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }) + .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }); + } + + + if (stacked) + d3.transition(bars) + .delay(function(d,i) { return i * delay / data[0].values.length }) + .attr('y', function(d,i) { + + return y((stacked ? d.y1 : 0)); + }) + .attr('height', function(d,i) { + return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1); + }) + .each('end', function() { + d3.transition(d3.select(this)) + .attr('x', function(d,i) { + return stacked ? 0 : (d.series * x.rangeBand() / data.length ) + }) + .attr('width', x.rangeBand() / (stacked ? 1 : data.length) ); + }) + else + d3.transition(bars) + .delay(function(d,i) { return i * delay/ data[0].values.length }) + .attr('x', function(d,i) { + return d.series * x.rangeBand() / data.length + }) + .attr('width', x.rangeBand() / data.length) + .each('end', function() { + d3.transition(d3.select(this)) + .attr('y', function(d,i) { + return getY(d,i) < 0 ? + y(0) : + y(0) - y(getY(d,i)) < 1 ? + y(0) - 1 : + y(getY(d,i)) || 0; + }) + .attr('height', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0; + }); + }) + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.stacked = function(_) { + if (!arguments.length) return stacked; + stacked = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.barColor = function(_) { + if (!arguments.length) return barColor; + barColor = nv.utils.getColor(_); + return chart; + }; + + chart.disabled = function(_) { + if (!arguments.length) return disabled; + disabled = _; + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.delay = function(_) { + if (!arguments.length) return delay; + delay = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.multiBarChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var multibar = nv.models.multiBar() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + ; + + var margin = {top: 30, right: 20, bottom: 30, left: 60} + , width = null + , height = null + , color = nv.utils.defaultColor() + , showControls = true + , showLegend = true + , reduceXTicks = true // if false a tick will show for every data point + , rotateLabels = 0 + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' on ' + x + '

                              ' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , state = { stacked: false } + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , controlWidth = function() { return showControls ? 180 : 0 } + ; + + multibar + .stacked(false) + ; + xAxis + .orient('bottom') + .tickPadding(7) + .highlightZero(false) + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient('left') + .tickFormat(d3.format(',.1f')) + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { selection.transition().call(chart) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = multibar.xScale(); + y = multibar.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth - controlWidth()); + + if (multibar.barColor()) + data.forEach(function(series,i) { + series.color = d3.rgb('#ccc').darker(i * 1.5).toString(); + }) + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + var controlsData = [ + { key: 'Grouped', disabled: multibar.stacked() }, + { key: 'Stacked', disabled: !multibar.stacked() } + ]; + + controls.width(controlWidth()).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + multibar + .disabled(data.map(function(series) { return series.disabled })) + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) + + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(barsWrap).call(multibar); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g'); + + xTicks + .selectAll('line, text') + .style('opacity', 1) + + if (reduceXTicks) + xTicks + .filter(function(d,i) { + return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0; + }) + .selectAll('text, line') + .style('opacity', 0); + + if(rotateLabels) + xTicks + .selectAll('text') + .attr('transform', function(d,i,j) { return 'rotate('+rotateLabels+' 0,0)' }) + .attr('text-transform', rotateLabels > 0 ? 'start' : 'end'); + + g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text') + .style('opacity', 1); + + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + //------------------------------------------------------------ + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + switch (d.key) { + case 'Grouped': + multibar.stacked(false); + break; + case 'Stacked': + multibar.stacked(true); + break; + } + + state.stacked = multibar.stacked(); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode) + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + if (typeof e.stacked !== 'undefined') { + multibar.stacked(e.stacked); + state.stacked = e.stacked; + } + + selection.call(chart); + }); + + //============================================================ + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + multibar.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + multibar.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.multibar = multibar; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id', 'stacked', 'delay', 'barColor'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.reduceXTicks= function(_) { + if (!arguments.length) return reduceXTicks; + reduceXTicks = _; + return chart; + }; + + chart.rotateLabels = function(_) { + if (!arguments.length) return rotateLabels; + rotateLabels = _; + return chart; + } + + chart.tooltip = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.multiBarHorizontal = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , x = d3.scale.ordinal() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , color = nv.utils.defaultColor() + , barColor = null // adding the ability to set the color for each rather than the whole group + , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled + , stacked = false + , showValues = false + , valuePadding = 60 + , valueFormat = d3.format(',.2f') + , delay = 1200 + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0 //used to store previous scales + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + if (stacked) + data = d3.layout.stack() + .offset('zero') + .values(function(d){ return d.values }) + .y(getY) + (data); + + + //add series index to each data point for reference + data = data.map(function(series, i) { + series.values = series.values.map(function(point) { + point.series = i; + return point; + }); + return series; + }); + + + + //------------------------------------------------------------ + // HACK for negative value stacking + if (stacked) + data[0].values.map(function(d,i) { + var posBase = 0, negBase = 0; + data.map(function(d) { + var f = d.values[i] + f.size = Math.abs(f.y); + if (f.y<0) { + f.y1 = negBase - f.size; + negBase = negBase - f.size; + } else + { + f.y1 = posBase; + posBase = posBase + f.size; + } + }); + }); + + + + //------------------------------------------------------------ + // Setup Scales + + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 } + }) + }); + + x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands([0, availableHeight], .1); + + //y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY))) + y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 + d.y : d.y1 ) : d.y }).concat(forceY))) + + if (showValues && !stacked) + y.range([(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]); + else + y.range([0, availableWidth]); + + x0 = x0 || x; + y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-groups'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i) }); + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .75); + + + var bars = groups.selectAll('g.nv-bar') + .data(function(d) { return d.values }); + + bars.exit().remove(); + + + var barsEnter = bars.enter().append('g') + .attr('transform', function(d,i,j) { + return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')' + }); + + barsEnter.append('rect') + .attr('width', 0) + .attr('height', x.rangeBand() / (stacked ? 1 : data.length) ) + + bars + .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + value: getY(d,i), + point: d, + series: data[d.series], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }); + + + barsEnter.append('text'); + + if (showValues && !stacked) { + bars.select('text') + .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' }) + .attr('y', x.rangeBand() / (data.length * 2)) + .attr('dy', '.32em') + .text(function(d,i) { return valueFormat(getY(d,i)) }) + d3.transition(bars) + //.delay(function(d,i) { return i * delay / data[0].values.length }) + .select('text') + .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 }) + } else { + //bars.selectAll('text').remove(); + bars.selectAll('text').text(''); + } + + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + + if (barColor) { + if (!disabled) disabled = data.map(function() { return true }); + bars + //.style('fill', barColor) + //.style('stroke', barColor) + //.style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(j).toString(); }) + //.style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(j).toString(); }) + .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }) + .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }); + } + + if (stacked) + d3.transition(bars) + //.delay(function(d,i) { return i * delay / data[0].values.length }) + .attr('transform', function(d,i) { + //return 'translate(' + y(d.y0) + ',0)' + //return 'translate(' + y(d.y0) + ',' + x(getX(d,i)) + ')' + return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')' + }) + .select('rect') + .attr('width', function(d,i) { + return Math.abs(y(getY(d,i) + d.y0) - y(d.y0)) + }) + .attr('height', x.rangeBand() ); + else + d3.transition(bars) + //.delay(function(d,i) { return i * delay / data[0].values.length }) + .attr('transform', function(d,i) { + //TODO: stacked must be all positive or all negative, not both? + return 'translate(' + + (getY(d,i) < 0 ? y(getY(d,i)) : y(0)) + + ',' + + (d.series * x.rangeBand() / data.length + + + x(getX(d,i)) ) + + ')' + }) + .select('rect') + .attr('height', x.rangeBand() / data.length ) + .attr('width', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) + }); + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.stacked = function(_) { + if (!arguments.length) return stacked; + stacked = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.barColor = function(_) { + if (!arguments.length) return barColor; + barColor = nv.utils.getColor(_); + return chart; + }; + + chart.disabled = function(_) { + if (!arguments.length) return disabled; + disabled = _; + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.delay = function(_) { + if (!arguments.length) return delay; + delay = _; + return chart; + }; + + chart.showValues = function(_) { + if (!arguments.length) return showValues; + showValues = _; + return chart; + }; + + chart.valueFormat= function(_) { + if (!arguments.length) return valueFormat; + valueFormat = _; + return chart; + }; + + chart.valuePadding = function(_) { + if (!arguments.length) return valuePadding; + valuePadding = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.multiBarHorizontalChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var multibar = nv.models.multiBarHorizontal() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend().height(30) + , controls = nv.models.legend().height(30) + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.defaultColor() + , showControls = true + , showLegend = true + , stacked = false + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + ' - ' + x + '

                              ' + + '

                              ' + y + '

                              ' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , state = { stacked: stacked } + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , controlWidth = function() { return showControls ? 180 : 0 } + ; + + multibar + .stacked(stacked) + ; + xAxis + .orient('left') + .tickPadding(5) + .highlightZero(false) + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient('bottom') + .tickFormat(d3.format(',.1f')) + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { selection.transition().call(chart) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = multibar.xScale(); + y = multibar.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth - controlWidth()); + + if (multibar.barColor()) + data.forEach(function(series,i) { + series.color = d3.rgb('#ccc').darker(i * 1.5).toString(); + }) + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + var controlsData = [ + { key: 'Grouped', disabled: multibar.stacked() }, + { key: 'Stacked', disabled: !multibar.stacked() } + ]; + + controls.width(controlWidth()).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + multibar + .disabled(data.map(function(series) { return series.disabled })) + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) + + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(barsWrap).call(multibar); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( availableHeight / 24 ) + .tickSize(-availableWidth, 0); + + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); + + xTicks + .selectAll('line, text') + .style('opacity', 1) + + + yAxis + .scale(y) + .ticks( availableWidth / 100 ) + .tickSize( -availableHeight, 0); + + g.select('.nv-y.nv-axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + //------------------------------------------------------------ + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + switch (d.key) { + case 'Grouped': + multibar.stacked(false); + break; + case 'Stacked': + multibar.stacked(true); + break; + } + + state.stacked = multibar.stacked(); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + if (typeof e.stacked !== 'undefined') { + multibar.stacked(e.stacked); + state.stacked = e.stacked; + } + + selection.call(chart); + }); + //============================================================ + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + multibar.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + multibar.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.multibar = multibar; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id', 'delay', 'showValues', 'valueFormat', 'stacked', 'barColor'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltip = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} +nv.models.multiChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 30, right: 20, bottom: 50, left: 60}, + color = d3.scale.category20().range(), + width = null, + height = null, + showLegend = true, + tooltips = true, + tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' at ' + x + '

                              ' + }, + x, y; //can be accessed via chart.lines.[x/y]Scale() + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x = d3.scale.linear(), + yScale1 = d3.scale.linear(), + yScale2 = d3.scale.linear(), + + lines1 = nv.models.line().yScale(yScale1), + lines2 = nv.models.line().yScale(yScale2), + + bars1 = nv.models.multiBar().stacked(false).yScale(yScale1), + bars2 = nv.models.multiBar().stacked(false).yScale(yScale2), + + stack1 = nv.models.stackedArea().yScale(yScale1), + stack2 = nv.models.stackedArea().yScale(yScale2), + + xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5), + yAxis1 = nv.models.axis().scale(yScale1).orient('left'), + yAxis2 = nv.models.axis().scale(yScale2).orient('right'), + + legend = nv.models.legend().height(30), + dispatch = d3.dispatch('tooltipShow', 'tooltipHide'); + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)), + y = ((e.series.yAxis == 2) ? yAxis2 : yAxis1).tickFormat()(lines1.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent); + }; + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + var dataLines1 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 1}) + var dataLines2 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 2}) + var dataBars1 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 1}) + var dataBars2 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 2}) + var dataStack1 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 1}) + var dataStack2 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 2}) + + var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1}) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: d.x, y: d.y } + }) + }) + + var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2}) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: d.x, y: d.y } + }) + }) + + x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } )) + .range([0, availableWidth]); + + var wrap = container.selectAll('g.wrap.multiChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g'); + + gEnter.append('g').attr('class', 'x axis'); + gEnter.append('g').attr('class', 'y1 axis'); + gEnter.append('g').attr('class', 'y2 axis'); + gEnter.append('g').attr('class', 'lines1Wrap'); + gEnter.append('g').attr('class', 'lines2Wrap'); + gEnter.append('g').attr('class', 'bars1Wrap'); + gEnter.append('g').attr('class', 'bars2Wrap'); + gEnter.append('g').attr('class', 'stack1Wrap'); + gEnter.append('g').attr('class', 'stack2Wrap'); + gEnter.append('g').attr('class', 'legendWrap'); + + var g = wrap.select('g'); + + if (showLegend) { + legend.width( availableWidth / 2 ); + + g.select('.legendWrap') + .datum(data.map(function(series) { + series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; + series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)'); + return series; + })) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.legendWrap') + .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')'); + } + + + lines1 + .width(availableWidth) + .height(availableHeight) + .interpolate("monotone") + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'})); + + lines2 + .width(availableWidth) + .height(availableHeight) + .interpolate("monotone") + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'})); + + bars1 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'})); + + bars2 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'})); + + stack1 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'})); + + stack2 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'})); + + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + var lines1Wrap = g.select('.lines1Wrap') + .datum(dataLines1) + var bars1Wrap = g.select('.bars1Wrap') + .datum(dataBars1) + var stack1Wrap = g.select('.stack1Wrap') + .datum(dataStack1) + + var lines2Wrap = g.select('.lines2Wrap') + .datum(dataLines2) + var bars2Wrap = g.select('.bars2Wrap') + .datum(dataBars2) + var stack2Wrap = g.select('.stack2Wrap') + .datum(dataStack2) + + var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){ + return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) + }).concat([{x:0, y:0}]) : [] + var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){ + return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) + }).concat([{x:0, y:0}]) : [] + + yScale1 .domain(d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } )) + .range([0, availableHeight]) + + yScale2 .domain(d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } )) + .range([0, availableHeight]) + + lines1.yDomain(yScale1.domain()) + bars1.yDomain(yScale1.domain()) + stack1.yDomain(yScale1.domain()) + + lines2.yDomain(yScale2.domain()) + bars2.yDomain(yScale2.domain()) + stack2.yDomain(yScale2.domain()) + + if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);} + if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);} + + if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);} + if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);} + + if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);} + if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);} + + + + xAxis + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.x.axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + d3.transition(g.select('.x.axis')) + .call(xAxis); + + yAxis1 + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + + d3.transition(g.select('.y1.axis')) + .call(yAxis1); + + yAxis2 + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.y2.axis')) + .call(yAxis2); + + g.select('.y2.axis') + .style('opacity', series2.length ? 1 : 0) + .attr('transform', 'translate(' + x.range()[1] + ',0)'); + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.series').classed('disabled', false); + return d; + }); + } + selection.transition().call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + }); + + chart.update = function() { chart(selection) }; + chart.container = this; + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines1.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines1.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + lines2.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines2.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + bars1.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars1.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + bars2.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars2.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + stack1.dispatch.on('tooltipShow', function(e) { + //disable tooltips when value ~= 0 + //// TODO: consider removing points from voronoi that have 0 value instead of this hack + if (!Math.round(stack1.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range + setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); + return false; + } + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); + + stack1.dispatch.on('tooltipHide', function(e) { + dispatch.tooltipHide(e); + }); + + stack2.dispatch.on('tooltipShow', function(e) { + //disable tooltips when value ~= 0 + //// TODO: consider removing points from voronoi that have 0 value instead of this hack + if (!Math.round(stack2.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range + setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); + return false; + } + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); + + stack2.dispatch.on('tooltipHide', function(e) { + dispatch.tooltipHide(e); + }); + + lines1.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines1.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + lines2.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines2.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + + + //============================================================ + // Global getters and setters + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.lines1 = lines1; + chart.lines2 = lines2; + chart.bars1 = bars1; + chart.bars2 = bars2; + chart.stack1 = stack1; + chart.stack2 = stack2; + chart.xAxis = xAxis; + chart.yAxis1 = yAxis1; + chart.yAxis2 = yAxis2; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + lines1.x(_); + bars1.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + lines1.y(_); + bars1.y(_); + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin = _; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = _; + legend.color(_); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + return chart; +} + + +nv.models.ohlcBar = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , getOpen = function(d) { return d.open } + , getClose = function(d) { return d.close } + , getHigh = function(d) { return d.high } + , getLow = function(d) { return d.low } + , forceX = [] + , forceY = [] + , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart + , clipEdge = true + , color = nv.utils.defaultColor() + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + //TODO: store old scales for transitions + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup Scales + + x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); + + if (padData) + x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range([0, availableWidth]); + + y .domain(yDomain || [ + d3.min(data[0].values.map(getLow).concat(forceY)), + d3.max(data[0].values.map(getHigh).concat(forceY)) + ]) + .range([availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-ticks'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + + defsEnter.append('clipPath') + .attr('id', 'nv-chart-clip-path-' + id) + .append('rect'); + + wrap.select('#nv-chart-clip-path-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + + + + var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') + .data(function(d) { return d }); + + ticks.exit().remove(); + + + var ticksEnter = ticks.enter().append('path') + .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) + .attr('d', function(d,i) { + var w = (availableWidth / data[0].values.length) * .9; + return 'm0,0l0,' + + (y(getOpen(d,i)) + - y(getHigh(d,i))) + + 'l' + + (-w/2) + + ',0l' + + (w/2) + + ',0l0,' + + (y(getLow(d,i)) - y(getOpen(d,i))) + + 'l0,' + + (y(getClose(d,i)) + - y(getLow(d,i))) + + 'l' + + (w/2) + + ',0l' + + (-w/2) + + ',0z'; + }) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) + //.attr('fill', function(d,i) { return color[0]; }) + //.attr('stroke', function(d,i) { return color[0]; }) + //.attr('x', 0 ) + //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }) + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + point: d, + series: data[0], + pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + point: d, + series: data[0], + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }); + + ticks + .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) + d3.transition(ticks) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) + .attr('d', function(d,i) { + var w = (availableWidth / data[0].values.length) * .9; + return 'm0,0l0,' + + (y(getOpen(d,i)) + - y(getHigh(d,i))) + + 'l' + + (-w/2) + + ',0l' + + (w/2) + + ',0l0,' + + (y(getLow(d,i)) + - y(getOpen(d,i))) + + 'l0,' + + (y(getClose(d,i)) + - y(getLow(d,i))) + + 'l' + + (w/2) + + ',0l' + + (-w/2) + + ',0z'; + }) + //.attr('width', (availableWidth / data[0].values.length) * .9 ) + + + //d3.transition(ticks) + //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }); + //.order(); // not sure if this makes any sense for this model + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.open = function(_) { + if (!arguments.length) return getOpen; + getOpen = _; + return chart; + }; + + chart.close = function(_) { + if (!arguments.length) return getClose; + getClose = _; + return chart; + }; + + chart.high = function(_) { + if (!arguments.length) return getHigh; + getHigh = _; + return chart; + }; + + chart.low = function(_) { + if (!arguments.length) return getLow; + getLow = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.padData = function(_) { + if (!arguments.length) return padData; + padData = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.pie = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 500 + , height = 500 + , getValues = function(d) { return d.values } + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , getDescription = function(d) { return d.description } + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , color = nv.utils.defaultColor() + , valueFormat = d3.format(',.2f') + , showLabels = true + , pieLabelsOutside = true + , donutLabelsOutside = false + , labelThreshold = .02 //if slice percentage is under this, don't show label + , donut = false + , labelSunbeamLayout = false + , startAngle = false + , endAngle = false + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + radius = Math.min(availableWidth, availableHeight) / 2, + arcRadius = radius-(radius / 5), + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + //var wrap = container.selectAll('.nv-wrap.nv-pie').data([data]); + var wrap = container.selectAll('.nv-wrap.nv-pie').data([getValues(data[0])]); + var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-pie'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); + + //------------------------------------------------------------ + + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + + var arc = d3.svg.arc() + .outerRadius(arcRadius); + + if (startAngle) arc.startAngle(startAngle) + if (endAngle) arc.endAngle(endAngle); + if (donut) arc.innerRadius(radius / 2); + + + // Setup the Pie chart and choose the data element + var pie = d3.layout.pie() + .sort(null) + .value(function(d) { return d.disabled ? 0 : getY(d) }); + + var slices = wrap.select('.nv-pie').selectAll('.nv-slice') + .data(pie); + + slices.exit().remove(); + + var ae = slices.enter().append('g') + .attr('class', 'nv-slice') + .on('mouseover', function(d,i){ + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + pointIndex: i, + pos: [d3.event.pageX, d3.event.pageY], + id: id + }); + }) + .on('mouseout', function(d,i){ + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + index: i, + id: id + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + index: i, + pos: d3.event, + id: id + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + index: i, + pos: d3.event, + id: id + }); + d3.event.stopPropagation(); + }); + + slices + .attr('fill', function(d,i) { return color(d, i); }) + .attr('stroke', function(d,i) { return color(d, i); }); + + var paths = ae.append('path') + .each(function(d) { this._current = d; }); + //.attr('d', arc); + + d3.transition(slices.select('path')) + .attr('d', arc) + .attrTween('d', arcTween); + + if (showLabels) { + // This does the normal label + var labelsArc = d3.svg.arc().innerRadius(0); + + if (pieLabelsOutside){ labelsArc = arc; } + + if (donutLabelsOutside) { labelsArc = d3.svg.arc().outerRadius(arc.outerRadius()); } + + ae.append("g").classed("nv-label", true) + .each(function(d, i) { + var group = d3.select(this); + + group + .attr('transform', function(d) { + if (labelSunbeamLayout) { + d.outerRadius = arcRadius + 10; // Set Outer Coordinate + d.innerRadius = arcRadius + 15; // Set Inner Coordinate + var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI); + if ((d.startAngle+d.endAngle)/2 < Math.PI) { + rotateAngle -= 90; + } else { + rotateAngle += 90; + } + return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')'; + } else { + d.outerRadius = radius + 10; // Set Outer Coordinate + d.innerRadius = radius + 15; // Set Inner Coordinate + return 'translate(' + labelsArc.centroid(d) + ')' + } + }); + + group.append('rect') + .style('stroke', '#fff') + .style('fill', '#fff') + .attr("rx", 3) + .attr("ry", 3); + + group.append('text') + .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned + .style('fill', '#000') + + + }); + + slices.select(".nv-label").transition() + .attr('transform', function(d) { + if (labelSunbeamLayout) { + d.outerRadius = arcRadius + 10; // Set Outer Coordinate + d.innerRadius = arcRadius + 15; // Set Inner Coordinate + var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI); + if ((d.startAngle+d.endAngle)/2 < Math.PI) { + rotateAngle -= 90; + } else { + rotateAngle += 90; + } + return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')'; + } else { + d.outerRadius = radius + 10; // Set Outer Coordinate + d.innerRadius = radius + 15; // Set Inner Coordinate + return 'translate(' + labelsArc.centroid(d) + ')' + } + }); + + slices.each(function(d, i) { + var slice = d3.select(this); + + slice + .select(".nv-label text") + .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned + .text(function(d, i) { + var percent = (d.endAngle - d.startAngle) / (2 * Math.PI); + return (d.value && percent > labelThreshold) ? getX(d.data) : ''; + }); + + var textBox = slice.select('text').node().getBBox(); + slice.select(".nv-label rect") + .attr("width", textBox.width + 10) + .attr("height", textBox.height + 10) + .attr("transform", function() { + return "translate(" + [textBox.x - 5, textBox.y - 5] + ")"; + }); + }); + } + + + // Computes the angle of an arc, converting from radians to degrees. + function angle(d) { + var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90; + return a > 90 ? a - 180 : a; + } + + function arcTween(a) { + if (!donut) a.innerRadius = 0; + var i = d3.interpolate(this._current, a); + this._current = i(0); + return function(t) { + return arc(i(t)); + }; + } + + function tweenPie(b) { + b.innerRadius = 0; + var i = d3.interpolate({startAngle: 0, endAngle: 0}, b); + return function(t) { + return arc(i(t)); + }; + } + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.values = function(_) { + if (!arguments.length) return getValues; + getValues = _; + return chart; + }; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + }; + + chart.description = function(_) { + if (!arguments.length) return getDescription; + getDescription = _; + return chart; + }; + + chart.showLabels = function(_) { + if (!arguments.length) return showLabels; + showLabels = _; + return chart; + }; + + chart.labelSunbeamLayout = function(_) { + if (!arguments.length) return labelSunbeamLayout; + labelSunbeamLayout = _; + return chart; + }; + + chart.donutLabelsOutside = function(_) { + if (!arguments.length) return donutLabelsOutside; + donutLabelsOutside = _; + return chart; + }; + + chart.pieLabelsOutside = function(_) { + if (!arguments.length) return pieLabelsOutside; + pieLabelsOutside = _; + return chart; + }; + + chart.donut = function(_) { + if (!arguments.length) return donut; + donut = _; + return chart; + }; + + chart.startAngle = function(_) { + if (!arguments.length) return startAngle; + startAngle = _; + return chart; + }; + + chart.endAngle = function(_) { + if (!arguments.length) return endAngle; + endAngle = _; + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.valueFormat = function(_) { + if (!arguments.length) return valueFormat; + valueFormat = _; + return chart; + }; + + chart.labelThreshold = function(_) { + if (!arguments.length) return labelThreshold; + labelThreshold = _; + return chart; + }; + //============================================================ + + + return chart; +} + +nv.models.pieChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var pie = nv.models.pie() + , legend = nv.models.legend() + ; + + var margin = {top: 30, right: 20, bottom: 20, left: 20} + , width = null + , height = null + , showLegend = true + , color = nv.utils.defaultColor() + , tooltips = true + , tooltip = function(key, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + '

                              ' + } + , state = {} + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var tooltipLabel = pie.description()(e.point) || pie.x()(e.point) + var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ), + top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0), + y = pie.valueFormat()(pie.y()(e.point)), + content = tooltip(tooltipLabel, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { chart(selection); }; + chart.container = this; + + //set state.disabled + state.disabled = data[0].map(function(d) { return !!d.disabled }); + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-pieWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend + .width( availableWidth ) + .key(pie.x()); + + wrap.select('.nv-legendWrap') + .datum(pie.values()(data[0])) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + pie + .width(availableWidth) + .height(availableHeight); + + + var pieWrap = g.select('.nv-pieWrap') + .datum(data); + + d3.transition(pieWrap).call(pie); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i, that) { + d.disabled = !d.disabled; + + if (!pie.values()(data[0]).filter(function(d) { return !d.disabled }).length) { + pie.values()(data[0]).map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data[0].map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart) + }); + + pie.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data[0].forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + selection.call(chart); + }); + + //============================================================ + + + }); + + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + pie.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.legend = legend; + chart.dispatch = dispatch; + chart.pie = pie; + + d3.rebind(chart, pie, 'valueFormat', 'values', 'x', 'y', 'description', 'id', 'showLabels', 'donutLabelsOutside', 'pieLabelsOutside', 'donut', 'labelThreshold'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + pie.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.scatter = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() // chooses color + , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one + , x = d3.scale.linear() + , y = d3.scale.linear() + , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area + , getX = function(d) { return d.x } // accessor to get the x value + , getY = function(d) { return d.y } // accessor to get the y value + , getSize = function(d) { return d.size || 1} // accessor to get the point size + , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape + , onlyCircles = true // Set to false to use shapes + , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) + , forceY = [] // List of numbers to Force into the Y scale + , forceSize = [] // List of numbers to Force into the Size scale + , interactive = true // If true, plots a voronoi overlay for advanced point intersection + , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out + , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart + , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding + , clipEdge = false // if true, masks points within x and y scale + , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance + , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips + , xDomain = null // Override x domain (skips the calculation from data) + , yDomain = null // Override y domain + , sizeDomain = null // Override point size domain + , sizeRange = null + , singlePoint = false + , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout') + , useVoronoi = true + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0, z0 // used to store previous scales + , timeoutID + , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + //add series index to each data point for reference + data = data.map(function(series, i) { + series.values = series.values.map(function(point) { + point.series = i; + return point; + }); + return series; + }); + + //------------------------------------------------------------ + // Setup Scales + + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain && sizeDomain) ? [] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance + d3.merge( + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) } + }) + }) + ); + + x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x }).concat(forceX))) + + if (padData && data[0]) + x.range([(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]); + //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range([0, availableWidth]); + + y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY))) + .range([availableHeight, 0]); + + z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize))) + .range(sizeRange || [16, 256]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + + x0 = x0 || x; + y0 = y0 || y; + z0 = z0 || z; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id + (singlePoint ? ' nv-single-point' : '')); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-groups'); + gEnter.append('g').attr('class', 'nv-point-paths'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + + function updateInteractiveLayer() { + + if (!interactive) return false; + + var eventElements; + + var vertices = d3.merge(data.map(function(group, groupIndex) { + return group.values + .map(function(point, pointIndex) { + // *Adding noise to make duplicates very unlikely + // **Injecting series and point index for reference + return [x(getX(point,pointIndex)) * (Math.random() / 1e12 + 1) , y(getY(point,pointIndex)) * (Math.random() / 1e12 + 1), groupIndex, pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates + }) + .filter(function(pointArray, pointIndex) { + return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct! + }) + }) + ); + + + + //inject series and point index for reference into voronoi + if (useVoronoi === true) { + + if (clipVoronoi) { + var pointClipsEnter = wrap.select('defs').selectAll('.nv-point-clips') + .data([id]) + .enter(); + + pointClipsEnter.append('clipPath') + .attr('class', 'nv-point-clips') + .attr('id', 'nv-points-clip-' + id); + + var pointClips = wrap.select('#nv-points-clip-' + id).selectAll('circle') + .data(vertices); + pointClips.enter().append('circle') + .attr('r', clipRadius); + pointClips.exit().remove(); + pointClips + .attr('cx', function(d) { return d[0] }) + .attr('cy', function(d) { return d[1] }); + + wrap.select('.nv-point-paths') + .attr('clip-path', 'url(#nv-points-clip-' + id + ')'); + } + + + // if(vertices.length < 3) { + // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work + vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]); + vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]); + vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]); + vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]); + // } + + var bounds = d3.geom.polygon([ + [-10,-10], + [-10,height + 10], + [width + 10,height + 10], + [width + 10,-10] + ]); + + var voronoi = d3.geom.voronoi(vertices).map(function(d, i) { + return { + 'data': bounds.clip(d), + 'series': vertices[i][2], + 'point': vertices[i][3] + } + }); + + + + var pointPaths = wrap.select('.nv-point-paths').selectAll('path') + .data(voronoi); + pointPaths.enter().append('path') + .attr('class', function(d,i) { return 'nv-path-'+i; }); + pointPaths.exit().remove(); + pointPaths + .attr('d', function(d) { return 'M' + d.data.join('L') + 'Z'; }); + + pointPaths + .on('click', function(d) { + if (needsUpdate) return 0; + var series = data[d.series], + point = series.values[d.point]; + + dispatch.elementClick({ + point: point, + series: series, + pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top], + seriesIndex: d.series, + pointIndex: d.point + }); + }) + .on('mouseover', function(d) { + if (needsUpdate) return 0; + var series = data[d.series], + point = series.values[d.point]; + + dispatch.elementMouseover({ + point: point, + series: series, + pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top], + seriesIndex: d.series, + pointIndex: d.point + }); + }) + .on('mouseout', function(d, i) { + if (needsUpdate) return 0; + var series = data[d.series], + point = series.values[d.point]; + + dispatch.elementMouseout({ + point: point, + series: series, + seriesIndex: d.series, + pointIndex: d.point + }); + }); + + + } else { + /* + // bring data in form needed for click handlers + var dataWithPoints = vertices.map(function(d, i) { + return { + 'data': d, + 'series': vertices[i][2], + 'point': vertices[i][3] + } + }); + */ + + // add event handlers to points instead voronoi paths + wrap.select('.nv-groups').selectAll('.nv-group') + .selectAll('.nv-point') + //.data(dataWithPoints) + //.style('pointer-events', 'auto') // recativate events, disabled by css + .on('click', function(d,i) { + //nv.log('test', d, i); + if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point + var series = data[d.series], + point = series.values[i]; + + dispatch.elementClick({ + point: point, + series: series, + pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], + seriesIndex: d.series, + pointIndex: i + }); + }) + .on('mouseover', function(d,i) { + if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point + var series = data[d.series], + point = series.values[i]; + + dispatch.elementMouseover({ + point: point, + series: series, + pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], + seriesIndex: d.series, + pointIndex: i + }); + }) + .on('mouseout', function(d,i) { + if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point + var series = data[d.series], + point = series.values[i]; + + dispatch.elementMouseout({ + point: point, + series: series, + seriesIndex: d.series, + pointIndex: i + }); + }); + } + + needsUpdate = false; + } + + needsUpdate = true; + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }); + d3.transition(groups) + .style('fill', function(d,i) { return color(d, i) }) + .style('stroke', function(d,i) { return color(d, i) }) + .style('stroke-opacity', 1) + .style('fill-opacity', .5); + + + if (onlyCircles) { + + var points = groups.selectAll('circle.nv-point') + .data(function(d) { return d.values }); + points.enter().append('circle') + .attr('cx', function(d,i) { return x0(getX(d,i)) }) + .attr('cy', function(d,i) { return y0(getY(d,i)) }) + .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) }); + points.exit().remove(); + d3.transition(groups.exit().selectAll('path.nv-point')) + .attr('cx', function(d,i) { return x(getX(d,i)) }) + .attr('cy', function(d,i) { return y(getY(d,i)) }) + .remove(); + points.attr('class', function(d,i) { return 'nv-point nv-point-' + i }); + d3.transition(points) + .attr('cx', function(d,i) { return x(getX(d,i)) }) + .attr('cy', function(d,i) { return y(getY(d,i)) }) + .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) }); + + } else { + + var points = groups.selectAll('path.nv-point') + .data(function(d) { return d.values }); + points.enter().append('path') + .attr('transform', function(d,i) { + return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')' + }) + .attr('d', + d3.svg.symbol() + .type(getShape) + .size(function(d,i) { return z(getSize(d,i)) }) + ); + points.exit().remove(); + d3.transition(groups.exit().selectAll('path.nv-point')) + .attr('transform', function(d,i) { + return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')' + }) + .remove(); + points.attr('class', function(d,i) { return 'nv-point nv-point-' + i }); + d3.transition(points) + .attr('transform', function(d,i) { + //nv.log(d,i,getX(d,i), x(getX(d,i))); + return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')' + }) + .attr('d', + d3.svg.symbol() + .type(getShape) + .size(function(d,i) { return z(getSize(d,i)) }) + ); + } + + + // Delay updating the invisible interactive layer for smoother animation + clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer + timeoutID = setTimeout(updateInteractiveLayer, 300); + //updateInteractiveLayer(); + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + z0 = z.copy(); + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + dispatch.on('elementMouseover.point', function(d) { + if (interactive) + d3.select('.nv-chart-' + id + ' .nv-series-' + d.seriesIndex + ' .nv-point-' + d.pointIndex) + .classed('hover', true); + }); + + dispatch.on('elementMouseout.point', function(d) { + if (interactive) + d3.select('.nv-chart-' + id + ' .nv-series-' + d.seriesIndex + ' .nv-point-' + d.pointIndex) + .classed('hover', false); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = d3.functor(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + }; + + chart.size = function(_) { + if (!arguments.length) return getSize; + getSize = d3.functor(_); + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.zScale = function(_) { + if (!arguments.length) return z; + z = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.sizeDomain = function(_) { + if (!arguments.length) return sizeDomain; + sizeDomain = _; + return chart; + }; + + chart.sizeRange = function(_) { + if (!arguments.length) return sizeRange; + sizeRange = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.forceSize = function(_) { + if (!arguments.length) return forceSize; + forceSize = _; + return chart; + }; + + chart.interactive = function(_) { + if (!arguments.length) return interactive; + interactive = _; + return chart; + }; + + chart.pointActive = function(_) { + if (!arguments.length) return pointActive; + pointActive = _; + return chart; + }; + + chart.padData = function(_) { + if (!arguments.length) return padData; + padData = _; + return chart; + }; + + chart.padDataOuter = function(_) { + if (!arguments.length) return padDataOuter; + padDataOuter = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.clipVoronoi= function(_) { + if (!arguments.length) return clipVoronoi; + clipVoronoi = _; + return chart; + }; + + chart.useVoronoi= function(_) { + if (!arguments.length) return useVoronoi; + useVoronoi = _; + if (useVoronoi === false) { + clipVoronoi = false; + } + return chart; + }; + + chart.clipRadius = function(_) { + if (!arguments.length) return clipRadius; + clipRadius = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.shape = function(_) { + if (!arguments.length) return getShape; + getShape = _; + return chart; + }; + + chart.onlyCircles = function(_) { + if (!arguments.length) return onlyCircles; + onlyCircles = _; + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.singlePoint = function(_) { + if (!arguments.length) return singlePoint; + singlePoint = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.scatterChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var scatter = nv.models.scatter() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + , distX = nv.models.distribution() + , distY = nv.models.distribution() + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 75} + , width = null + , height = null + , color = nv.utils.defaultColor() + , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale() + , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale() + , xPadding = 0 + , yPadding = 0 + , showDistX = false + , showDistY = false + , showLegend = true + , showControls = !!d3.fisheye + , fisheye = 0 + , pauseFisheye = false + , tooltips = true + , tooltipX = function(key, x, y) { return '' + x + '' } + , tooltipY = function(key, x, y) { return '' + y + '' } + //, tooltip = function(key, x, y) { return '

                              ' + key + '

                              ' } + , tooltip = null + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , noData = "No Data Available." + ; + + scatter + .xScale(x) + .yScale(y) + ; + xAxis + .orient('bottom') + .tickPadding(10) + ; + yAxis + .orient('left') + .tickPadding(10) + ; + distX + .axis('x') + ; + distY + .axis('y') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var state = {}, + x0, y0; + + var showTooltip = function(e, offsetElement) { + //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?) + + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0), + leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ), + topY = e.pos[1] + ( offsetElement.offsetTop || 0), + xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)), + yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex)); + + if( tooltipX != null ) + nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip'); + if( tooltipY != null ) + nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip'); + if( tooltip != null ) + nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + var controlsData = [ + { key: 'Magnify', disabled: true } + ]; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { chart(selection) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id()); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + // background for pointer events + gEnter.append('rect').attr('class', 'nvd3 nv-background') + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + gEnter.append('g').attr('class', 'nv-distWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width( availableWidth / 2 ); + + wrap.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + controls.width(180).color(['#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + scatter + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) + + wrap.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + .call(scatter); + + + //Adjust for x and y padding + if (xPadding) { + var xRange = x.domain()[1] - x.domain()[0]; + x.domain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]); + } + + if (yPadding) { + var yRange = y.domain()[1] - y.domain()[0]; + y.domain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : availableWidth / 100 ) + .tickSize( -availableHeight , 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .call(xAxis); + + + yAxis + .scale(y) + .ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis') + .call(yAxis); + + + if (showDistX) { + distX + .getData(scatter.x()) + .scale(x) + .width(availableWidth) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionX'); + g.select('.nv-distributionX') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + } + + if (showDistY) { + distY + .getData(scatter.y()) + .scale(y) + .width(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionY'); + g.select('.nv-distributionY') + .attr('transform', 'translate(-' + distY.size() + ',0)') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } + + //------------------------------------------------------------ + + + + + if (d3.fisheye) { + g.select('.nv-background') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g.select('.nv-background').on('mousemove', updateFisheye); + g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;}); + scatter.dispatch.on('elementClick.freezeFisheye', function() { + pauseFisheye = !pauseFisheye; + }); + } + + + function updateFisheye() { + if (pauseFisheye) { + g.select('.nv-point-paths').style('pointer-events', 'all'); + return false; + } + + g.select('.nv-point-paths').style('pointer-events', 'none' ); + + var mouse = d3.mouse(this); + x.distortion(fisheye).focus(mouse[0]); + y.distortion(fisheye).focus(mouse[1]); + + g.select('.nv-scatterWrap') + .call(scatter); + + g.select('.nv-x.nv-axis').call(xAxis); + g.select('.nv-y.nv-axis').call(yAxis); + g.select('.nv-distributionX') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + g.select('.nv-distributionY') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + fisheye = d.disabled ? 0 : 2.5; + g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all'); + g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' ); + + if (d.disabled) { + x.distortion(fisheye).focus(0); + y.distortion(fisheye).focus(0); + + g.select('.nv-scatterWrap').call(scatter); + g.select('.nv-x.nv-axis').call(xAxis); + g.select('.nv-y.nv-axis').call(yAxis); + } else { + pauseFisheye = false; + } + + chart(selection); + }); + + legend.dispatch.on('legendClick', function(d,i, that) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + chart(selection); + }); + + /* + legend.dispatch.on('legendMouseover', function(d, i) { + d.hover = true; + chart(selection); + }); + + legend.dispatch.on('legendMouseout', function(d, i) { + d.hover = false; + chart(selection); + }); + */ + + scatter.dispatch.on('elementMouseover.tooltip', function(e) { + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) + .attr('y1', e.pos[1] - availableHeight); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) + .attr('x2', e.pos[0] + distX.size()); + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + selection.call(chart); + }); + + //============================================================ + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + scatter.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) + .attr('y1', 0); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) + .attr('x2', distY.size()); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.scatter = scatter; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.distX = distX; + chart.distY = distY; + + d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + distX.color(color); + distY.color(color); + return chart; + }; + + chart.showDistX = function(_) { + if (!arguments.length) return showDistX; + showDistX = _; + return chart; + }; + + chart.showDistY = function(_) { + if (!arguments.length) return showDistY; + showDistY = _; + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.fisheye = function(_) { + if (!arguments.length) return fisheye; + fisheye = _; + return chart; + }; + + chart.xPadding = function(_) { + if (!arguments.length) return xPadding; + xPadding = _; + return chart; + }; + + chart.yPadding = function(_) { + if (!arguments.length) return yPadding; + yPadding = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltipXContent = function(_) { + if (!arguments.length) return tooltipX; + tooltipX = _; + return chart; + }; + + chart.tooltipYContent = function(_) { + if (!arguments.length) return tooltipY; + tooltipY = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.scatterPlusLineChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var scatter = nv.models.scatter() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + , distX = nv.models.distribution() + , distY = nv.models.distribution() + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 75} + , width = null + , height = null + , color = nv.utils.defaultColor() + , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale() + , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale() + , showDistX = false + , showDistY = false + , showLegend = true + , showControls = !!d3.fisheye + , fisheye = 0 + , pauseFisheye = false + , tooltips = true + , tooltipX = function(key, x, y) { return '' + x + '' } + , tooltipY = function(key, x, y) { return '' + y + '' } + , tooltip = function(key, x, y, date) { return '

                              ' + key + '

                              ' + + '

                              ' + date + '

                              ' } + //, tooltip = null + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , noData = "No Data Available." + ; + + scatter + .xScale(x) + .yScale(y) + ; + xAxis + .orient('bottom') + .tickPadding(10) + ; + yAxis + .orient('left') + .tickPadding(10) + ; + distX + .axis('x') + ; + distY + .axis('y') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var state = {}, + x0, y0; + + var showTooltip = function(e, offsetElement) { + //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?) + + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0), + leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ), + topY = e.pos[1] + ( offsetElement.offsetTop || 0), + xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)), + yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex)); + + if( tooltipX != null ) + nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip'); + if( tooltipY != null ) + nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip'); + if( tooltip != null ) + nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e.point.tooltip, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + var controlsData = [ + { key: 'Magnify', disabled: true } + ]; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { chart(selection) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = scatter.xScale(); + y = scatter.yScale(); + + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id()); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + // background for pointer events + gEnter.append('rect').attr('class', 'nvd3 nv-background') + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + gEnter.append('g').attr('class', 'nv-regressionLinesWrap'); + gEnter.append('g').attr('class', 'nv-distWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width( availableWidth / 2 ); + + wrap.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + controls.width(180).color(['#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Main Chart Component(s) + + scatter + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) + + wrap.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + .call(scatter); + + + wrap.select('.nv-regressionLinesWrap') + .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')'); + + var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines') + .data(function(d) { return d }); + + var reglines = regWrap.enter() + .append('g').attr('class', 'nv-regLines') + .append('line').attr('class', 'nv-regLine') + .style('stroke-opacity', 0); + + //d3.transition(regWrap.selectAll('.nv-regLines line')) + regWrap.selectAll('.nv-regLines line') + .attr('x1', x.range()[0]) + .attr('x2', x.range()[1]) + .attr('y1', function(d,i) { return y(x.domain()[0] * d.slope + d.intercept) }) + .attr('y2', function(d,i) { return y(x.domain()[1] * d.slope + d.intercept) }) + .style('stroke', function(d,i,j) { return color(d,j) }) + .style('stroke-opacity', function(d,i) { + return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1 + }); + + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( xAxis.ticks() ? xAxis.ticks() : availableWidth / 100 ) + .tickSize( -availableHeight , 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .call(xAxis); + + + yAxis + .scale(y) + .ticks( yAxis.ticks() ? yAxis.ticks() : availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis') + .call(yAxis); + + + if (showDistX) { + distX + .getData(scatter.x()) + .scale(x) + .width(availableWidth) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionX'); + g.select('.nv-distributionX') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + } + + if (showDistY) { + distY + .getData(scatter.y()) + .scale(y) + .width(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionY'); + g.select('.nv-distributionY') + .attr('transform', 'translate(-' + distY.size() + ',0)') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } + + //------------------------------------------------------------ + + + + + if (d3.fisheye) { + g.select('.nv-background') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g.select('.nv-background').on('mousemove', updateFisheye); + g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;}); + scatter.dispatch.on('elementClick.freezeFisheye', function() { + pauseFisheye = !pauseFisheye; + }); + } + + + function updateFisheye() { + if (pauseFisheye) { + g.select('.nv-point-paths').style('pointer-events', 'all'); + return false; + } + + g.select('.nv-point-paths').style('pointer-events', 'none' ); + + var mouse = d3.mouse(this); + x.distortion(fisheye).focus(mouse[0]); + y.distortion(fisheye).focus(mouse[1]); + + g.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + .call(scatter); + g.select('.nv-x.nv-axis').call(xAxis); + g.select('.nv-y.nv-axis').call(yAxis); + g.select('.nv-distributionX') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + g.select('.nv-distributionY') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + fisheye = d.disabled ? 0 : 2.5; + g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all'); + g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' ); + + if (d.disabled) { + x.distortion(fisheye).focus(0); + y.distortion(fisheye).focus(0); + + g.select('.nv-scatterWrap').call(scatter); + g.select('.nv-x.nv-axis').call(xAxis); + g.select('.nv-y.nv-axis').call(yAxis); + } else { + pauseFisheye = false; + } + + chart(selection); + }); + + legend.dispatch.on('legendClick', function(d,i, that) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + chart(selection); + }); + + /* + legend.dispatch.on('legendMouseover', function(d, i) { + d.hover = true; + chart(selection); + }); + + legend.dispatch.on('legendMouseout', function(d, i) { + d.hover = false; + chart(selection); + }); + */ + + scatter.dispatch.on('elementMouseover.tooltip', function(e) { + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) + .attr('y1', e.pos[1] - availableHeight); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) + .attr('x2', e.pos[0] + distX.size()); + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + selection.call(chart); + }); + + //============================================================ + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + scatter.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) + .attr('y1', 0); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) + .attr('x2', distY.size()); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.scatter = scatter; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.distX = distX; + chart.distY = distY; + + d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + distX.color(color); + distY.color(color); + return chart; + }; + + chart.showDistX = function(_) { + if (!arguments.length) return showDistX; + showDistX = _; + return chart; + }; + + chart.showDistY = function(_) { + if (!arguments.length) return showDistY; + showDistY = _; + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.fisheye = function(_) { + if (!arguments.length) return fisheye; + fisheye = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltipXContent = function(_) { + if (!arguments.length) return tooltipX; + tooltipX = _; + return chart; + }; + + chart.tooltipYContent = function(_) { + if (!arguments.length) return tooltipY; + tooltipY = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.sparkline = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 2, right: 0, bottom: 2, left: 0} + , width = 400 + , height = 32 + , animate = true + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , color = nv.utils.getColor(['#000']) + , xDomain + , yDomain + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup Scales + + x .domain(xDomain || d3.extent(data, getX )) + .range([0, availableWidth]); + + y .domain(yDomain || d3.extent(data, getY )) + .range([availableHeight, 0]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + + //------------------------------------------------------------ + + + var paths = wrap.selectAll('path') + .data(function(d) { return [d] }); + paths.enter().append('path'); + paths.exit().remove(); + paths + .style('stroke', function(d,i) { return d.color || color(d, i) }) + .attr('d', d3.svg.line() + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ); + + + // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent) + var points = wrap.selectAll('circle.nv-point') + .data(function(data) { + var yValues = data.map(function(d, i) { return getY(d,i); }); + function pointIndex(index) { + if (index != -1) { + var result = data[index]; + result.pointIndex = index; + return result; + } else { + return null; + } + } + var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])), + minPoint = pointIndex(yValues.indexOf(y.domain()[0])), + currentPoint = pointIndex(yValues.length - 1); + return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;}); + }); + points.enter().append('circle'); + points.exit().remove(); + points + .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) }) + .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) }) + .attr('r', 2) + .attr('class', function(d,i) { + return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' : + getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue' + }); + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = d3.functor(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.animate = function(_) { + if (!arguments.length) return animate; + animate = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.sparklinePlus = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var sparkline = nv.models.sparkline(); + + var margin = {top: 15, right: 100, bottom: 10, left: 50} + , width = null + , height = null + , x + , y + , index = [] + , paused = false + , xTickFormat = d3.format(',r') + , yTickFormat = d3.format(',.2f') + , showValue = true + , alignValue = true + , rightAlignValue = false + , noData = "No Data Available." + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this); + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + var currentValue = sparkline.y()(data[data.length-1], data.length-1); + + chart.update = function() { chart(selection) }; + chart.container = this; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + + //------------------------------------------------------------ + // Setup Scales + + x = sparkline.xScale(); + y = sparkline.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-sparklineWrap'); + gEnter.append('g').attr('class', 'nv-valueWrap'); + gEnter.append('g').attr('class', 'nv-hoverArea'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Main Chart Component(s) + + var sparklineWrap = g.select('.nv-sparklineWrap'); + + sparkline + .width(availableWidth) + .height(availableHeight); + + sparklineWrap + .call(sparkline); + + //------------------------------------------------------------ + + + var valueWrap = g.select('.nv-valueWrap'); + + var value = valueWrap.selectAll('.nv-currentValue') + .data([currentValue]); + + value.enter().append('text').attr('class', 'nv-currentValue') + .attr('dx', rightAlignValue ? -8 : 8) + .attr('dy', '.9em') + .style('text-anchor', rightAlignValue ? 'end' : 'start'); + + value + .attr('x', availableWidth + (rightAlignValue ? margin.right : 0)) + .attr('y', alignValue ? function(d) { return y(d) } : 0) + .style('fill', sparkline.color()(data[data.length-1], data.length-1)) + .text(yTickFormat(currentValue)); + + + + gEnter.select('.nv-hoverArea').append('rect') + .on('mousemove', sparklineHover) + .on('click', function() { paused = !paused }) + .on('mouseout', function() { index = []; updateValueLine(); }); + //.on('mouseout', function() { index = null; updateValueLine(); }); + + g.select('.nv-hoverArea rect') + .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' }) + .attr('width', availableWidth + margin.left + margin.right) + .attr('height', availableHeight + margin.top); + + + + function updateValueLine() { //index is currently global (within the chart), may or may not keep it that way + if (paused) return; + + var hoverValue = g.selectAll('.nv-hoverValue').data(index) + + var hoverEnter = hoverValue.enter() + .append('g').attr('class', 'nv-hoverValue') + .style('stroke-opacity', 0) + .style('fill-opacity', 0); + + hoverValue.exit() + .transition().duration(250) + .style('stroke-opacity', 0) + .style('fill-opacity', 0) + .remove(); + + hoverValue + .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' }) + .transition().duration(250) + .style('stroke-opacity', 1) + .style('fill-opacity', 1); + + if (!index.length) return; + + hoverEnter.append('line') + .attr('x1', 0) + .attr('y1', -margin.top) + .attr('x2', 0) + .attr('y2', availableHeight); + + + hoverEnter.append('text').attr('class', 'nv-xValue') + .attr('x', -6) + .attr('y', -margin.top) + .attr('text-anchor', 'end') + .attr('dy', '.9em') + + + g.select('.nv-hoverValue .nv-xValue') + .text(xTickFormat(sparkline.x()(data[index[0]], index[0]))); + + hoverEnter.append('text').attr('class', 'nv-yValue') + .attr('x', 6) + .attr('y', -margin.top) + .attr('text-anchor', 'start') + .attr('dy', '.9em') + + g.select('.nv-hoverValue .nv-yValue') + .text(yTickFormat(sparkline.y()(data[index[0]], index[0]))); + + } + + + function sparklineHover() { + if (paused) return; + + var pos = d3.mouse(this)[0] - margin.left; + + function getClosestIndex(data, x) { + var distance = Math.abs(sparkline.x()(data[0], 0) - x); + var closestIndex = 0; + for (var i = 0; i < data.length; i++){ + if (Math.abs(sparkline.x()(data[i], i) - x) < distance) { + distance = Math.abs(sparkline.x()(data[i], i) - x); + closestIndex = i; + } + } + return closestIndex; + } + + index = [getClosestIndex(data, Math.round(x.invert(pos)))]; + + updateValueLine(); + } + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.sparkline = sparkline; + + d3.rebind(chart, sparkline, 'x', 'y', 'xScale', 'yScale', 'color'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xTickFormat = function(_) { + if (!arguments.length) return xTickFormat; + xTickFormat = _; + return chart; + }; + + chart.yTickFormat = function(_) { + if (!arguments.length) return yTickFormat; + yTickFormat = _; + return chart; + }; + + chart.showValue = function(_) { + if (!arguments.length) return showValue; + showValue = _; + return chart; + }; + + chart.alignValue = function(_) { + if (!arguments.length) return alignValue; + alignValue = _; + return chart; + }; + + chart.rightAlignValue = function(_) { + if (!arguments.length) return rightAlignValue; + rightAlignValue = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.stackedArea = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() // a function that computes the color + , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one + , getX = function(d) { return d.x } // accessor to get the x value from a data point + , getY = function(d) { return d.y } // accessor to get the y value from a data point + , style = 'stack' + , offset = 'zero' + , order = 'default' + , interpolate = 'linear' // controls the line interpolation + , clipEdge = false // if true, masks lines within x and y scale + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , scatter = nv.models.scatter() + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout') + ; + + scatter + .size(2.2) // default size + .sizeDomain([2.2]) // all the same size by default + ; + + /************************************ + * offset: + * 'wiggle' (stream) + * 'zero' (stacked) + * 'expand' (normalize to 100%) + * 'silhouette' (simple centered) + * + * order: + * 'inside-out' (stream) + * 'default' (input order) + ************************************/ + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + //------------------------------------------------------------ + // Setup Scales + + x = scatter.xScale(); + y = scatter.yScale(); + + //------------------------------------------------------------ + + + // Injecting point index into each point because d3.layout.stack().out does not give index + // ***Also storing getY(d,i) as stackedY so that it can be set to 0 if series is disabled + data = data.map(function(aseries, i) { + aseries.values = aseries.values.map(function(d, j) { + d.index = j; + d.stackedY = aseries.disabled ? 0 : getY(d,j); + return d; + }) + return aseries; + }); + + + data = d3.layout.stack() + .order(order) + .offset(offset) + .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion + .x(getX) + .y(function(d) { return d.stackedY }) + .out(function(d, y0, y) { + d.display = { + y: y, + y0: y0 + }; + }) + (data); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-areaWrap'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + scatter + .width(availableWidth) + .height(availableHeight) + .x(getX) + .y(function(d) { return d.display.y + d.display.y0 }) + .forceY([0]) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + + + var scatterWrap = g.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + //d3.transition(scatterWrap).call(scatter); + scatterWrap.call(scatter); + + + + + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + + + + var area = d3.svg.area() + .x(function(d,i) { return x(getX(d,i)) }) + .y0(function(d) { return y(d.display.y0) }) + .y1(function(d) { return y(d.display.y + d.display.y0) }) + .interpolate(interpolate); + + var zeroArea = d3.svg.area() + .x(function(d,i) { return x(getX(d,i)) }) + .y0(function(d) { return y(d.display.y0) }) + .y1(function(d) { return y(d.display.y0) }); + + + var path = g.select('.nv-areaWrap').selectAll('path.nv-area') + .data(function(d) { return d }); + //.data(function(d) { return d }, function(d) { return d.key }); + path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i }) + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.areaMouseover({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: i + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.areaMouseout({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: i + }); + }) + .on('click', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.areaClick({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: i + }); + }) + //d3.transition(path.exit()) + path.exit() + .attr('d', function(d,i) { return zeroArea(d.values,i) }) + .remove(); + path + .style('fill', function(d,i){ return d.color || color(d, i) }) + .style('stroke', function(d,i){ return d.color || color(d, i) }); + //d3.transition(path) + path + .attr('d', function(d,i) { return area(d.values,i) }) + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + scatter.dispatch.on('elementMouseover.area', function(e) { + g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true); + }); + scatter.dispatch.on('elementMouseout.area', function(e) { + g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false); + }); + + //============================================================ + + }); + + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + scatter.dispatch.on('elementClick.area', function(e) { + dispatch.areaClick(e); + }) + scatter.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); + scatter.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + //============================================================ + + + //============================================================ + // Global getters and setters + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.scatter = scatter; + + d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius'); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = d3.functor(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + } + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.offset = function(_) { + if (!arguments.length) return offset; + offset = _; + return chart; + }; + + chart.order = function(_) { + if (!arguments.length) return order; + order = _; + return chart; + }; + + //shortcut for offset + order + chart.style = function(_) { + if (!arguments.length) return style; + style = _; + + switch (style) { + case 'stack': + chart.offset('zero'); + chart.order('default'); + break; + case 'stream': + chart.offset('wiggle'); + chart.order('inside-out'); + break; + case 'stream-center': + chart.offset('silhouette'); + chart.order('inside-out'); + break; + case 'expand': + chart.offset('expand'); + chart.order('default'); + break; + } + + return chart; + }; + + chart.interpolate = function(_) { + if (!arguments.length) return interpolate; + interpolate = _; + return interpolate; + + }; + + //============================================================ + + + return chart; +} + +nv.models.stackedAreaChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var stacked = nv.models.stackedArea() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + ; + + var margin = {top: 30, right: 25, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.defaultColor() // a function that takes in d, i and returns color + , showControls = true + , showLegend = true + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' on ' + x + '

                              ' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , yAxisTickFormat = d3.format(',.2f') + , state = { style: stacked.style() } + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , controlWidth = 250 + ; + + xAxis + .orient('bottom') + .tickPadding(7) + ; + yAxis + .orient('left') + ; + stacked.scatter + .pointActive(function(d) { + //console.log(stacked.y()(d), !!Math.round(stacked.y()(d) * 100)); + return !!Math.round(stacked.y()(d) * 100); + }) + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { chart(selection) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = stacked.xScale(); + y = stacked.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-stackedWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend + .width( availableWidth - controlWidth ); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + controlWidth + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + var controlsData = [ + { key: 'Stacked', disabled: stacked.offset() != 'zero' }, + { key: 'Stream', disabled: stacked.offset() != 'wiggle' }, + { key: 'Expanded', disabled: stacked.offset() != 'expand' } + ]; + + controls + .width( controlWidth ) + .color(['#444', '#444', '#444']); + + g.select('.nv-controlsWrap') + .datum(controlsData) + .call(controls); + + + if ( margin.top != Math.max(controls.height(), legend.height()) ) { + margin.top = Math.max(controls.height(), legend.height()); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + + g.select('.nv-controlsWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + stacked + .width(availableWidth) + .height(availableHeight) + + var stackedWrap = g.select('.nv-stackedWrap') + .datum(data); + //d3.transition(stackedWrap).call(stacked); + stackedWrap.call(stacked); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize( -availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + //d3.transition(g.select('.nv-x.nv-axis')) + g.select('.nv-x.nv-axis') + .transition().duration(0) + .call(xAxis); + + yAxis + .scale(y) + .ticks(stacked.offset() == 'wiggle' ? 0 : availableHeight / 36) + .tickSize(-availableWidth, 0) + .setTickFormat(stacked.offset() == 'expand' ? d3.format('%') : yAxisTickFormat); + + //d3.transition(g.select('.nv-y.nv-axis')) + g.select('.nv-y.nv-axis') + .transition().duration(0) + .call(yAxis); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + stacked.dispatch.on('areaClick.toggle', function(e) { + if (data.filter(function(d) { return !d.disabled }).length === 1) + data = data.map(function(d) { + d.disabled = false; + return d + }); + else + data = data.map(function(d,i) { + d.disabled = (i != e.seriesIndex); + return d + }); + + //selection.transition().call(chart); + chart(selection); + }); + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + //selection.transition().call(chart); + chart(selection); + }); + + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + switch (d.key) { + case 'Stacked': + stacked.style('stack'); + break; + case 'Stream': + stacked.style('stream'); + break; + case 'Expanded': + stacked.style('expand'); + break; + } + + state.style = stacked.style(); + dispatch.stateChange(state); + + //selection.transition().call(chart); + chart(selection); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + if (typeof e.style !== 'undefined') { + stacked.style(e.style); + } + + selection.call(chart); + }); + + }); + + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + stacked.dispatch.on('tooltipShow', function(e) { + //disable tooltips when value ~= 0 + //// TODO: consider removing points from voronoi that have 0 value instead of this hack + /* + if (!Math.round(stacked.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range + setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); + return false; + } + */ + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); + + stacked.dispatch.on('tooltipHide', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.stacked = stacked; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'sizeDomain', 'interactive', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return getWidth; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return getHeight; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + stacked.color(color); + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltip = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + yAxis.setTickFormat = yAxis.tickFormat; + yAxis.tickFormat = function(_) { + if (!arguments.length) return yAxisTickFormat; + yAxisTickFormat = _; + return yAxis; + }; + + //============================================================ + + return chart; +} +})(); \ No newline at end of file diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/nv.d3.min.js b/docdoku-web-front/app/js/lib/charts/nv3d/nv.d3.min.js new file mode 100644 index 0000000000..1461db2456 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/nv.d3.min.js @@ -0,0 +1,5 @@ +(function(){function t(e,t){return(new Date(t,e+1,0)).getDate()}function n(e,t,n){return function(r,i,s){var o=e(r),u=[];o1)while(o=document.body.scrollWidth?h:h-16,c=window.innerHeight>=document.body.scrollHeight?c:c-16;var g=function(e){var t=m;do isNaN(e.offsetTop)||(t+=e.offsetTop);while(e=e.offsetParent);return t},y=function(e){var t=v;do isNaN(e.offsetLeft)||(t+=e.offsetLeft);while(e=e.offsetParent);return t};switch(r){case"e":v=t[0]-l-i,m=t[1]-f/2;var b=y(u),w=g(u);bd?t[0]+i:d-b+v),wp+h&&(m=p+h-w+m-f);break;case"w":v=t[0]+i,m=t[1]-f/2,b+l>c&&(v=t[0]-l-i),wp+h&&(m=p-f-5);break;case"n":v=t[0]-l/2-5,m=t[1]+i;var b=y(u),w=g(u);bc&&(v=v-l/2+5),w+f>p+h&&(m=p+h-w+m-f);break;case"s":v=t[0]-l/2,m=t[1]-f-i;var b=y(u),w=g(u);bc&&(v=v-l/2+5),p>w&&(m=p)}return u.style.left=v+"px",u.style.top=m+"px",u.style.opacity=1,u.style.position="absolute",u.style.pointerEvents="none",u},t.cleanup=function(){var e=document.getElementsByClassName("nvtooltip"),t=[];while(e.length)t.push(e[0]),e[0].style.transitionDelay="0 !important",e[0].style.opacity=0,e[0].className="nvtooltip-pending-removal";setTimeout(function(){while(t.length){var e=t.pop();e.parentNode.removeChild(e)}},500)}}(),e.utils.windowSize=function(){var e={width:640,height:480};return document.body&&document.body.offsetWidth&&(e.width=document.body.offsetWidth,e.height=document.body.offsetHeight),document.compatMode=="CSS1Compat"&&document.documentElement&&document.documentElement.offsetWidth&&(e.width=document.documentElement.offsetWidth,e.height=document.documentElement.offsetHeight),window.innerWidth&&window.innerHeight&&(e.width=window.innerWidth,e.height=window.innerHeight),e},e.utils.windowResize=function(e){var t=window.onresize;window.onresize=function(n){typeof t=="function"&&t(n),e(n)}},e.utils.getColor=function(t){return arguments.length?Object.prototype.toString.call(t)==="[object Array]"?function(e,n){return e.color||t[n%t.length]}:t:e.utils.defaultColor()},e.utils.defaultColor=function(){var e=d3.scale.category20().range();return function(t,n){return t.color||e[n%e.length]}},e.utils.customTheme=function(e,t,n){t=t||function(e){return e.key},n=n||d3.scale.category20().range();var r=n.length;return function(i,s){var o=t(i);return r||(r=n.length),typeof e[o]!="undefined"?typeof e[o]=="function"?e[o]():e[o]:n[--r]}},e.utils.pjax=function(t,n){function r(r){d3.html(r,function(r){var i=d3.select(n).node();i.parentNode.replaceChild(d3.select(r).select(n).node(),i),e.utils.pjax(t,n)})}d3.selectAll(t).on("click",function(){history.pushState(this.href,this.textContent,this.href),r(this.href),d3.event.preventDefault()}),d3.select(window).on("popstate",function(){d3.event.state&&r(d3.event.state)})},e.models.axis=function(){function d(r){return r.each(function(r){var d=d3.select(this),v=d.selectAll("g.nv-wrap.nv-axis").data([r]),m=v.enter().append("g").attr("class","nvd3 nv-wrap nv-axis"),g=m.append("g"),y=v.select("g");h!==null?e.ticks(h):(e.orient()=="top"||e.orient()=="bottom")&&e.ticks(Math.abs(i.range()[1]-i.range()[0])/100),d3.transition(y).call(e),p=p||e.scale();var b=e.tickFormat();b==null&&(b=p.tickFormat());var w=y.selectAll("text.nv-axislabel").data([s||null]);w.exit().remove();switch(e.orient()){case"top":w.enter().append("text").attr("class","nv-axislabel").attr("text-anchor","middle").attr("y",0);var E=i.range().length==2?i.range()[1]:i.range()[i.range().length-1]+(i.range()[1]-i.range()[0]);w.attr("x",E/2);if(o){var S=v.selectAll("g.nv-axisMaxMin").data(i.domain());S.enter().append("g").attr("class","nv-axisMaxMin").append("text"),S.exit().remove(),S.attr("transform",function(e,t){return"translate("+i(e)+",0)"}).select("text").attr("dy","0em").attr("y",-e.tickPadding()).attr("text-anchor","middle").text(function(e,t){var n=b(e);return(""+n).match("NaN")?"":n}),d3.transition(S).attr("transform",function(e,t){return"translate("+i.range()[t]+",0)"})}break;case"bottom":var x=36,T=30,N=y.selectAll("g").select("text");if(a%360){N.each(function(e,t){var n=this.getBBox().width;n>T&&(T=n)});var C=Math.abs(Math.sin(a*Math.PI/180)),x=(C?C*T:T)+30;N.attr("transform",function(e,t,n){return"rotate("+a+" 0,0)"}).attr("text-anchor",a%360>0?"start":"end")}w.enter().append("text").attr("class","nv-axislabel").attr("text-anchor","middle").attr("y",x);var E=i.range().length==2?i.range()[1]:i.range()[i.range().length-1]+(i.range()[1]-i.range()[0]);w.attr("x",E/2);if(o){var S=v.selectAll("g.nv-axisMaxMin").data([i.domain()[0],i.domain()[i.domain().length-1]]);S.enter().append("g").attr("class","nv-axisMaxMin").append("text"),S.exit().remove(),S.attr("transform",function(e,t){return"translate("+(i(e)+(c?i.rangeBand()/2:0))+",0)"}).select("text").attr("dy",".71em").attr("y",e.tickPadding()).attr("transform",function(e,t,n){return"rotate("+a+" 0,0)"}).attr("text-anchor",a?a%360>0?"start":"end":"middle").text(function(e,t){var n=b(e);return(""+n).match("NaN")?"":n}),d3.transition(S).attr("transform",function(e,t){return"translate("+(i(e)+(c?i.rangeBand()/2:0))+",0)"})}l&&N.attr("transform",function(e,t){return"translate(0,"+(t%2==0?"0":"12")+")"});break;case"right":w.enter().append("text").attr("class","nv-axislabel").attr("text-anchor",f?"middle":"begin").attr("transform",f?"rotate(90)":"").attr("y",f?-Math.max(t.right,n)+12:-10),w.attr("x",f?i.range()[0]/2:e.tickPadding());if(o){var S=v.selectAll("g.nv-axisMaxMin").data(i.domain());S.enter().append("g").attr("class","nv-axisMaxMin").append("text").style("opacity",0),S.exit().remove(),S.attr("transform",function(e,t){return"translate(0,"+i(e)+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",e.tickPadding()).attr("text-anchor","start").text(function(e,t){var n=b(e);return(""+n).match("NaN")?"":n}),d3.transition(S).attr("transform",function(e,t){return"translate(0,"+i.range()[t]+")"}).select("text").style("opacity",1)}break;case"left":w.enter().append("text").attr("class","nv-axislabel").attr("text-anchor",f?"middle":"end").attr("transform",f?"rotate(-90)":"").attr("y",f?-Math.max(t.left,n)+12:-10),w.attr("x",f?-i.range()[0]/2:-e.tickPadding());if(o){var S=v.selectAll("g.nv-axisMaxMin").data(i.domain());S.enter().append("g").attr("class","nv-axisMaxMin").append("text").style("opacity",0),S.exit().remove(),S.attr("transform",function(e,t){return"translate(0,"+p(e)+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",-e.tickPadding()).attr("text-anchor","end").text(function(e,t){var n=b(e);return(""+n).match("NaN")?"":n}),d3.transition(S).attr("transform",function(e,t){return"translate(0,"+i.range()[t]+")"}).select("text").style("opacity",1)}}w.text(function(e){return e}),o&&(e.orient()==="left"||e.orient()==="right")&&(y.selectAll("g").each(function(e,t){d3.select(this).select("text").attr("opacity",1);if(i(e)i.range()[0]-10)(e>1e-10||e<-1e-10)&&d3.select(this).attr("opacity",0),d3.select(this).select("text").attr("opacity",0)}),i.domain()[0]==i.domain()[1]&&i.domain()[0]==0&&v.selectAll("g.nv-axisMaxMin").style("opacity",function(e,t){return t?0:1}));if(o&&(e.orient()==="top"||e.orient()==="bottom")){var k=[];v.selectAll("g.nv-axisMaxMin").each(function(e,t){try{t?k.push(i(e)-this.getBBox().width-4):k.push(i(e)+this.getBBox().width+4)}catch(n){t?k.push(i(e)-4):k.push(i(e)+4)}}),y.selectAll("g").each(function(e,t){if(i(e)k[1])e>1e-10||e<-1e-10?d3.select(this).remove():d3.select(this).select("text").remove()})}u&&y.selectAll("line.tick").filter(function(e){return!parseFloat(Math.round(e*1e5)/1e6)}).classed("zero",!0),p=i.copy()}),d}var e=d3.svg.axis(),t={top:0,right:0,bottom:0,left:0},n=75,r=60,i=d3.scale.linear(),s=null,o=!0,u=!0,a=0,f=!0,l=!1,c=!1,h=null;e.scale(i).orient("bottom").tickFormat(function(e){return e});var p;return d.axis=e,d3.rebind(d,e,"orient","tickValues","tickSubdivide","tickSize","tickPadding","tickFormat"),d3.rebind(d,i,"domain","range","rangeBand","rangeBands"),d.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,d):t},d.width=function(e){return arguments.length?(n=e,d):n},d.ticks=function(e){return arguments.length?(h=e,d):h},d.height=function(e){return arguments.length?(r=e,d):r},d.axisLabel=function(e){return arguments.length?(s=e,d):s},d.showMaxMin=function(e){return arguments.length?(o=e,d):o},d.highlightZero=function(e){return arguments.length?(u=e,d):u},d.scale=function(t){return arguments.length?(i=t,e.scale(i),c=typeof i.rangeBands=="function",d3.rebind(d,i,"domain","range","rangeBand","rangeBands"),d):i},d.rotateYLabel=function(e){return arguments.length?(f=e,d):f},d.rotateLabels=function(e){return arguments.length?(a=e,d):a},d.staggerLabels=function(e){return arguments.length?(l=e,d):l},d},e.models.historicalBar=function(){function g(e){return e.each(function(e){var g=n-t.left-t.right,b=r-t.top-t.bottom,w=d3.select(this);s.domain(d||d3.extent(e[0].values.map(u).concat(f))),c?s.range([g*.5/e[0].values.length,g*(e[0].values.length-.5)/e[0].values.length]):s.range([0,g]),o.domain(v||d3.extent(e[0].values.map(a).concat(l))).range([b,0]);if(s.domain()[0]===s.domain()[1]||o.domain()[0]===o.domain()[1])singlePoint=!0;s.domain()[0]===s.domain()[1]&&(s.domain()[0]?s.domain([s.domain()[0]-s.domain()[0]*.01,s.domain()[1]+s.domain()[1]*.01]):s.domain([-1,1])),o.domain()[0]===o.domain()[1]&&(o.domain()[0]?o.domain([o.domain()[0]+o.domain()[0]*.01,o.domain()[1]-o.domain()[1]*.01]):o.domain([-1,1]));var E=w.selectAll("g.nv-wrap.nv-bar").data([e[0].values]),S=E.enter().append("g").attr("class","nvd3 nv-wrap nv-bar"),T=S.append("defs"),N=S.append("g"),C=E.select("g");N.append("g").attr("class","nv-bars"),E.attr("transform","translate("+t.left+","+t.top+")"),w.on("click",function(e,t){m.chartClick({data:e,index:t,pos:d3.event,id:i})}),T.append("clipPath").attr("id","nv-chart-clip-path-"+i).append("rect"),E.select("#nv-chart-clip-path-"+i+" rect").attr("width",g).attr("height",b),C.attr("clip-path",h?"url(#nv-chart-clip-path-"+i+")":"");var k=E.select(".nv-bars").selectAll(".nv-bar").data(function(e){return e});k.exit().remove();var L=k.enter().append("rect").attr("x",0).attr("y",function(e,t){return o(Math.max(0,a(e,t)))}).attr("height",function(e,t){return Math.abs(o(a(e,t))-o(0))}).on("mouseover",function(t,n){d3.select(this).classed("hover",!0),m.elementMouseover({point:t,series:e[0],pos:[s(u(t,n)),o(a(t,n))],pointIndex:n,seriesIndex:0,e:d3.event})}).on("mouseout",function(t,n){d3.select(this).classed("hover",!1),m.elementMouseout({point:t,series:e[0],pointIndex:n,seriesIndex:0,e:d3.event})}).on("click",function(e,t){m.elementClick({value:a(e,t),data:e,index:t,pos:[s(u(e,t)),o(a(e,t))],e:d3.event,id:i}),d3.event.stopPropagation()}).on("dblclick",function(e,t){m.elementDblClick({value:a(e,t),data:e,index:t,pos:[s(u(e,t)),o(a(e,t))],e:d3.event,id:i}),d3.event.stopPropagation()});k.attr("fill",function(e,t){return p(e,t)}).attr("class",function(e,t,n){return(a(e,t)<0?"nv-bar negative":"nv-bar positive")+" nv-bar-"+n+"-"+t}).attr("transform",function(t,n){return"translate("+(s(u(t,n))-g/e[0].values.length*.45)+",0)"}).attr("width",g/e[0].values.length*.9),d3.transition(k).attr("y",function(e,t){return a(e,t)<0?o(0):o(0)-o(a(e,t))<1?o(0)-1:o(a(e,t))}).attr("height",function(e,t){return Math.max(Math.abs(o(a(e,t))-o(0)),1)})}),g}var t={top:0,right:0,bottom:0,left:0},n=960,r=500,i=Math.floor(Math.random()*1e4),s=d3.scale.linear(),o=d3.scale.linear(),u=function(e){return e.x},a=function(e){return e.y},f=[],l=[0],c=!1,h=!0,p=e.utils.defaultColor(),d,v,m=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout");return g.dispatch=m,g.x=function(e){return arguments.length?(u=e,g):u},g.y=function(e){return arguments.length?(a=e,g):a},g.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,g):t},g.width=function(e){return arguments.length?(n=e,g):n},g.height=function(e){return arguments.length?(r=e,g):r},g.xScale=function(e){return arguments.length?(s=e,g):s},g.yScale=function(e){return arguments.length?(o=e,g):o},g.xDomain=function(e){return arguments.length?(d=e,g):d},g.yDomain=function(e){return arguments.length?(v=e,g):v},g.forceX=function(e){return arguments.length?(f=e,g):f},g.forceY=function(e){return arguments.length?(l=e,g):l},g.padData=function(e){return arguments.length?(c=e,g):c},g.clipEdge=function(e){return arguments.length?(h=e,g):h},g.color=function(t){return arguments.length?(p=e.utils.getColor(t),g):p},g.id=function(e){return arguments.length?(i=e,g):i},g},e.models.bullet=function(){function p(e){return e.each(function(e,n){var l=a-t.left-t.right,p=f-t.top-t.bottom,d=d3.select(this),v=i.call(this,e,n).slice().sort(d3.descending),m=s.call(this,e,n).slice().sort(d3.descending),g=o.call(this,e,n).slice().sort(d3.descending),y=d3.scale.linear().domain(d3.extent(d3.merge([u,v]))).range(r?[l,0]:[0,l]),b=this.__chart__||d3.scale.linear().domain([0,Infinity]).range(y.range());this.__chart__=y;var w=d3.min(v),E=d3.max(v),S=v[1],x=d.selectAll("g.nv-wrap.nv-bullet").data([e]),T=x.enter().append("g").attr("class","nvd3 nv-wrap nv-bullet"),N=T.append("g"),C=x.select("g");N.append("rect").attr("class","nv-range nv-rangeMax"),N.append("rect").attr("class","nv-range nv-rangeAvg"),N.append("rect").attr("class","nv-range nv-rangeMin"),N.append("rect").attr("class","nv-measure"),N.append("path").attr("class","nv-markerTriangle"),x.attr("transform","translate("+t.left+","+t.top+")");var k=function(e){return Math.abs(b(e)-b(0))},L=function(e){return Math.abs(y(e)-y(0))},A=function(e){return e<0?b(e):b(0)},O=function(e){return e<0?y(e):y(0)};C.select("rect.nv-rangeMax").attr("height",p).attr("width",L(E>0?E:w)).attr("x",O(E>0?E:w)).datum(E>0?E:w),C.select("rect.nv-rangeAvg").attr("height",p).attr("width",L(S)).attr("x",O(S)).datum(S),C.select("rect.nv-rangeMin").attr("height",p).attr("width",L(E)).attr("x",O(E)).attr("width",L(E>0?w:E)).attr("x",O(E>0?w:E)).datum(E>0?w:E),C.select("rect.nv-measure").style("fill",c).attr("height",p/3).attr("y",p/3).attr("width",g<0?y(0)-y(g[0]):y(g[0])-y(0)).attr("x",O(g)).on("mouseover",function(){h.elementMouseover({value:g[0],label:"Current",pos:[y(g[0]),p/2]})}).on("mouseout",function(){h.elementMouseout({value:g[0],label:"Current"})});var M=p/6;m[0]?C.selectAll("path.nv-markerTriangle").attr("transform",function(e){return"translate("+y(m[0])+","+p/2+")"}).attr("d","M0,"+M+"L"+M+","+ -M+" "+ -M+","+ -M+"Z").on("mouseover",function(){h.elementMouseover({value:m[0],label:"Previous",pos:[y(m[0]),p/2]})}).on("mouseout",function(){h.elementMouseout({value:m[0],label:"Previous"})}):C.selectAll("path.nv-markerTriangle").remove(),x.selectAll(".nv-range").on("mouseover",function(e,t){var n=t?t==1?"Mean":"Minimum":"Maximum";h.elementMouseover({value:e,label:n,pos:[y(e),p/2]})}).on("mouseout",function(e,t){var n=t?t==1?"Mean":"Minimum":"Maximum";h.elementMouseout({value:e,label:n})})}),p}var t={top:0,right:0,bottom:0,left:0},n="left",r=!1,i=function(e){return e.ranges},s=function(e){return e.markers},o=function(e){return e.measures},u=[0],a=380,f=30,l=null,c=e.utils.getColor(["#1f77b4"]),h=d3.dispatch("elementMouseover","elementMouseout");return p.dispatch=h,p.orient=function(e){return arguments.length?(n=e,r=n=="right"||n=="bottom",p):n},p.ranges=function(e){return arguments.length?(i=e,p):i},p.markers=function(e){return arguments.length?(s=e,p):s},p.measures=function(e){return arguments.length?(o=e,p):o},p.forceX=function(e){return arguments.length?(u=e,p):u},p.width=function(e){return arguments.length?(a=e,p):a},p.height=function(e){return arguments.length?(f=e,p):f},p.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,p):t},p.tickFormat=function(e){return arguments.length?(l=e,p):l},p.color=function(t){return arguments.length?(c=e.utils.getColor(t),p):c},p},e.models.bulletChart=function(){function m(e){return e.each(function(n,h){var g=d3.select(this),y=(a||parseInt(g.style("width"))||960)-i.left-i.right,b=f-i.top-i.bottom,w=this;m.update=function(){m(e)},m.container=this;if(!n||!s.call(this,n,h)){var E=g.selectAll(".nv-noData").data([p]);return E.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),E.attr("x",i.left+y/2).attr("y",18+i.top+b/2).text(function(e){return e}),m}g.selectAll(".nv-noData").remove();var S=s.call(this,n,h).slice().sort(d3.descending),x=o.call(this,n,h).slice().sort(d3.descending),T=u.call(this,n,h).slice().sort(d3.descending),N=g.selectAll("g.nv-wrap.nv-bulletChart").data([n]),C=N.enter().append("g").attr("class","nvd3 nv-wrap nv-bulletChart"),k=C.append("g"),L=N.select("g");k.append("g").attr("class","nv-bulletWrap"),k.append("g").attr("class","nv-titles"),N.attr("transform","translate("+i.left+","+i.top+")");var A=d3.scale.linear().domain([0,Math.max(S[0],x[0],T[0])]).range(r?[y,0]:[0,y]),O=this.__chart__||d3.scale.linear().domain([0,Infinity]).range(A.range());this.__chart__=A;var M=function(e){return Math.abs(O(e)-O(0))},_=function(e){return Math.abs(A(e)-A(0))},D=k.select(".nv-titles").append("g").attr("text-anchor","end").attr("transform","translate(-6,"+(f-i.top-i.bottom)/2+")");D.append("text").attr("class","nv-title").text(function(e){return e.title}),D.append("text").attr("class","nv-subtitle").attr("dy","1em").text(function(e){return e.subtitle}),t.width(y).height(b);var P=L.select(".nv-bulletWrap");d3.transition(P).call(t);var H=l||A.tickFormat(y/100),B=L.selectAll("g.nv-tick").data(A.ticks(y/50),function(e){return this.textContent||H(e)}),j=B.enter().append("g").attr("class","nv-tick").attr("transform",function(e){return"translate("+O(e)+",0)"}).style("opacity",1e-6);j.append("line").attr("y1",b).attr("y2",b*7/6),j.append("text").attr("text-anchor","middle").attr("dy","1em").attr("y",b*7/6).text(H);var F=d3.transition(B).attr("transform",function(e){return"translate("+A(e)+",0)"}).style("opacity",1);F.select("line").attr("y1",b).attr("y2",b*7/6),F.select("text").attr("y",b*7/6),d3.transition(B.exit()).attr("transform",function(e){return"translate("+A(e)+",0)"}).style("opacity",1e-6).remove(),d.on("tooltipShow",function(e){e.key=data[0].title,c&&v(e,w.parentNode)})}),d3.timer.flush(),m}var t=e.models.bullet(),n="left",r=!1,i={top:5,right:40,bottom:20,left:120},s=function(e){return e.ranges},o=function(e){return e.markers},u=function(e){return e.measures},a=null,f=55,l=null,c=!0,h=function(e,t,n,r,i){return"

                              "+t+"

                              "+"

                              "+n+"

                              "},p="No Data Available.",d=d3.dispatch("tooltipShow","tooltipHide"),v=function(t,n){var r=t.pos[0]+(n.offsetLeft||0)+i.left,s=t.pos[1]+(n.offsetTop||0)+i.top,o=h(t.key,t.label,t.value,t,m);e.tooltip.show([r,s],o,t.value<0?"e":"w",null,n)};return t.dispatch.on("elementMouseover.tooltip",function(e){d.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){d.tooltipHide(e)}),d.on("tooltipHide",function(){c&&e.tooltip.cleanup()}),m.dispatch=d,m.bullet=t,d3.rebind(m,t,"color"),m.orient=function(e){return arguments.length?(n=e,r=n=="right"||n=="bottom",m):n},m.ranges=function(e){return arguments.length?(s=e,m):s},m.markers=function(e){return arguments.length?(o=e,m):o},m.measures=function(e){return arguments.length?(u=e,m):u},m.width=function(e){return arguments.length?(a=e,m):a},m.height=function(e){return arguments.length?(f=e,m):f},m.margin=function(e){return arguments.length?(i.top=typeof e.top!="undefined"?e.top:i.top,i.right=typeof e.right!="undefined"?e.right:i.right,i.bottom=typeof e.bottom!="undefined"?e.bottom:i.bottom,i.left=typeof e.left!="undefined"?e.left:i.left,m):i},m.tickFormat=function(e){return arguments.length?(l=e,m):l},m.tooltips=function(e){return arguments.length?(c=e,m):c},m.tooltipContent=function(e){return arguments.length?(h=e,m):h},m.noData=function(e){return arguments.length?(p=e,m):p},m},e.models.cumulativeLineChart=function(){function T(e){return e.each(function(d){function M(e,t){d3.select(T.container).style("cursor","ew-resize")}function _(e,t){S.x=d3.event.x,S.i=Math.round(E.invert(S.x)),W()}function D(e,t){d3.select(T.container).style("cursor","auto"),y.index=S.i,w.stateChange(y)}function W(){z.data([S]),T.update()}var C=d3.select(this).classed("nv-chart-"+g,!0),k=this,L=(a||parseInt(C.style("width"))||960)-o.left-o.right,A=(f||parseInt(C.style("height"))||400)-o.top-o.bottom;T.update=function(){T(e)},T.container=this,y.disabled=d.map(function(e){return!!e.disabled});var O=d3.behavior.drag().on("dragstart",M).on("drag",_).on("dragend",D);if(!d||!d.length||!d.filter(function(e){return e.values.length}).length){var P=C.selectAll(".nv-noData").data([b]);return P.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),P.attr("x",o.left+L/2).attr("y",o.top+A/2).text(function(e){return e}),T}C.selectAll(".nv-noData").remove(),v=t.xScale(),m=t.yScale();if(!p){var H=d.filter(function(e){return!e.disabled}).map(function(e,n){var r=d3.extent(e.values,t.y());return r[0]<-0.95&&(r[0]=-0.95),[(r[0]-r[1])/(1+r[1]),(r[1]-r[0])/(1+r[0])]}),B=[d3.min(H,function(e){return e[0]}),d3.max(H,function(e){return e[1]})];t.yDomain(B)}else t.yDomain(null);E.domain([0,d[0].values.length-1]).range([0,L]).clamp(!0);var d=N(S.i,d),j=C.selectAll("g.nv-wrap.nv-cumulativeLine").data([d]),F=j.enter().append("g").attr("class","nvd3 nv-wrap nv-cumulativeLine").append("g"),I=j.select("g");F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-background"),F.append("g").attr("class","nv-linesWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-controlsWrap"),l&&(i.width(L),I.select(".nv-legendWrap").datum(d).call(i),o.top!=i.height()&&(o.top=i.height(),A=(f||parseInt(C.style("height"))||400)-o.top-o.bottom),I.select(".nv-legendWrap").attr("transform","translate(0,"+ -o.top+")"));if(h){var q=[{key:"Re-scale y-axis",disabled:!p}];s.width(140).color(["#444","#444","#444"]),I.select(".nv-controlsWrap").datum(q).attr("transform","translate(0,"+ -o.top+")").call(s)}j.attr("transform","translate("+o.left+","+o.top+")");var R=d.filter(function(e){return e.tempDisabled});j.select(".tempDisabled").remove(),R.length&&j.append("text").attr("class","tempDisabled").attr("x",L/2).attr("y","-.71em").style("text-anchor","end").text(R.map(function(e){return e.key}).join(", ")+" values cannot be calculated for this time period."),F.select(".nv-background").append("rect"),I.select(".nv-background rect").attr("width",L).attr("height",A),t.y(function(e){return e.display.y}).width(L).height(A).color(d.map(function(e,t){return e.color||u(e,t)}).filter(function(e,t){return!d[t].disabled&&!d[t].tempDisabled}));var U=I.select(".nv-linesWrap").datum(d.filter(function(e){return!e.disabled&&!e.tempDisabled}));U.call(t);var z=U.selectAll(".nv-indexLine").data([S]);z.enter().append("rect").attr("class","nv-indexLine").attr("width",3).attr("x",-2).attr("fill","red").attr("fill-opacity",.5).call(O),z.attr("transform",function(e){return"translate("+E(e.i)+",0)"}).attr("height",A),n.scale(v).ticks(Math.min(d[0].values.length,L/70)).tickSize(-A,0),I.select(".nv-x.nv-axis").attr("transform","translate(0,"+m.range()[0]+")"),d3.transition(I.select(".nv-x.nv-axis")).call(n),r.scale(m).ticks(A/36).tickSize(-L,0),d3.transition(I.select(".nv-y.nv-axis")).call(r),I.select(".nv-background rect").on("click",function(){S.x=d3.mouse(this)[0],S.i=Math.round(E.invert(S.x)),y.index=S.i,w.stateChange(y),W()}),t.dispatch.on("elementClick",function(e){S.i=e.pointIndex,S.x=E(S.i),y.index=S.i,w.stateChange(y),W()}),s.dispatch.on("legendClick",function(t,n){t.disabled=!t.disabled,p=!t.disabled,y.rescaleY=p,w.stateChange(y),e.call(T)}),i.dispatch.on("legendClick",function(t,n){t.disabled=!t.disabled,d.filter(function(e){return!e.disabled}).length||d.map(function(e){return e.disabled=!1,j.selectAll(".nv-series").classed("disabled",!1),e}),y.disabled=d.map(function(e){return!!e.disabled}),w.stateChange(y),e.call(T)}),w.on("tooltipShow",function(e){c&&x(e,k.parentNode)}),w.on("changeState",function(t){typeof t.disabled!="undefined"&&(d.forEach(function(e,n){e.disabled=t.disabled[n]}),y.disabled=t.disabled),typeof t.index!="undefined"&&(S.i=t.index,S.x=E(S.i),y.index=t.index,z.data([S])),typeof t.rescaleY!="undefined"&&(p=t.rescaleY),e.call(T)})}),T}function N(e,n){return n.map(function(n,r){var i=t.y()(n.values[e],e);return i<-0.95?(n.tempDisabled=!0,n):(n.tempDisabled=!1,n.values=n.values.map(function(e,n){return e.display={y:(t.y()(e,n)-i)/(1+i)},e}),n)})}var t=e.models.line(),n=e.models.axis(),r=e.models.axis(),i=e.models.legend(),s=e.models.legend(),o={top:30,right:30,bottom:50,left:60},u=e.utils.defaultColor(),a=null,f=null,l=!0,c=!0,h=!0,p=!0,d=function(e,t,n,r,i){return"

                              "+e+"

                              "+"

                              "+n+" at "+t+"

                              "},v,m,g=t.id(),y={index:0,rescaleY:p},b="No Data Available.",w=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState");n.orient("bottom").tickPadding(7),r.orient("left");var E=d3.scale.linear(),S={i:0,x:0},x=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),a=n.tickFormat()(t.x()(i.point,i.pointIndex)),f=r.tickFormat()(t.y()(i.point,i.pointIndex)),l=d(i.series.key,a,f,i,T);e.tooltip.show([o,u],l,null,null,s)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+o.left,e.pos[1]+o.top],w.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){w.tooltipHide(e)}),w.on("tooltipHide",function(){c&&e.tooltip.cleanup()}),T.dispatch=w,T.lines=t,T.legend=i,T.xAxis=n,T.yAxis=r,d3.rebind(T,t,"defined","isArea","x","y","size","xDomain","yDomain","forceX","forceY","interactive","clipEdge","clipVoronoi","id"),T.margin=function(e){return arguments.length?(o.top=typeof e.top!="undefined"?e.top:o.top,o.right=typeof e.right!="undefined"?e.right:o.right,o.bottom=typeof e.bottom!="undefined"?e.bottom:o.bottom,o.left=typeof e.left!="undefined"?e.left:o.left,T):o},T.width=function(e){return arguments.length?(a=e,T):a},T.height=function(e){return arguments.length?(f=e,T):f},T.color=function(t){return arguments.length?(u=e.utils.getColor(t),i.color(u),T):u},T.rescaleY=function(e){return arguments.length?(p=e,p):p},T.showControls=function(e){return arguments.length?(h=e,T):h},T.showLegend=function(e){return arguments.length?(l=e,T):l},T.tooltips=function(e){return arguments.length?(c=e,T):c},T.tooltipContent=function(e){return arguments.length?(d=e,T):d},T.state=function(e){return arguments.length?(y=e,T):y},T.noData=function(e){return arguments.length?(b=e,T):b},T},e.models.discreteBar=function(){function b(e){return e.each(function(e){var i=n-t.left-t.right,b=r-t.top-t.bottom,w=d3.select(this);e=e.map(function(e,t){return e.values=e.values.map(function(e){return e.series=t,e}),e});var E=p&&d?[]:e.map(function(e){return e.values.map(function(e,t){return{x:u(e,t),y:a(e,t),y0:e.y0}})});s.domain(p||d3.merge(E).map(function(e){return e.x})).rangeBands([0,i],.1),o.domain(d||d3.extent(d3.merge(E).map(function(e){return e.y}).concat(f))),c?o.range([b-(o.domain()[0]<0?12:0),o.domain()[1]>0?12:0]):o.range([b,0]),g=g||s,y=y||o.copy().range([o(0),o(0)]);var S=w.selectAll("g.nv-wrap.nv-discretebar").data([e]),T=S.enter().append("g").attr("class","nvd3 nv-wrap nv-discretebar"),N=T.append("g"),C=S.select("g");N.append("g").attr("class","nv-groups"),S.attr("transform","translate("+t.left+","+t.top+")");var k=S.select(".nv-groups").selectAll(".nv-group").data(function(e){return e},function(e){return e.key});k.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),d3.transition(k.exit()).style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),k.attr("class",function(e,t){return"nv-group nv-series-"+t}).classed("hover",function(e){return e.hover}),d3.transition(k).style("stroke-opacity",1).style("fill-opacity",.75);var L=k.selectAll("g.nv-bar").data(function(e){return e.values});L.exit().remove();var A=L.enter().append("g").attr("transform",function(e,t,n){return"translate("+(s(u(e,t))+s.rangeBand()*.05)+", "+o(0)+")"}).on("mouseover",function(t,n){d3.select(this).classed("hover",!0),v.elementMouseover({value:a(t,n),point:t,series:e[t.series],pos:[s(u(t,n))+s.rangeBand()*(t.series+.5)/e.length,o(a(t,n))],pointIndex:n,seriesIndex:t.series,e:d3.event})}).on("mouseout",function(t,n){d3.select(this).classed("hover",!1),v.elementMouseout({value:a(t,n),point:t,series:e[t.series],pointIndex:n,seriesIndex:t.series,e:d3.event})}).on("click",function(t,n){v.elementClick({value:a(t,n),point:t,series:e[t.series],pos:[s(u(t,n))+s.rangeBand()*(t.series+.5)/e.length,o(a(t,n))],pointIndex:n,seriesIndex:t.series,e:d3.event}),d3.event.stopPropagation()}).on("dblclick",function(t,n){v.elementDblClick({value:a(t,n),point:t,series:e[t.series],pos:[s(u(t,n))+s.rangeBand()*(t.series+.5)/e.length,o(a(t,n))],pointIndex:n,seriesIndex:t.series,e:d3.event}),d3.event.stopPropagation()});A.append("rect").attr("height",0).attr("width",s.rangeBand()*.9/e.length),c?(A.append("text").attr("text-anchor","middle"),L.select("text").attr("x",s.rangeBand()*.9/2).attr("y",function(e,t){return a(e,t)<0?o(a(e,t))-o(0)+12:-4}).text(function(e,t){return h(a(e,t))})):L.selectAll("text").remove(),L.attr("class",function(e,t){return a(e,t)<0?"nv-bar negative":"nv-bar positive"}).style("fill",function(e,t){return e.color||l(e,t)}).style("stroke",function(e,t){return e.color||l(e,t)}).select("rect").attr("class",m).attr("width",s.rangeBand()*.9/e.length),d3.transition(L).attr("transform",function(e,t){var n=s(u(e,t))+s.rangeBand()*.05,r=a(e,t)<0?o(0):o(0)-o(a(e,t))<1?o(0)-1:o(a(e,t));return"translate("+n+", "+r+")"}).select("rect").attr("height",function(e,t){return Math.max(Math.abs(o(a(e,t))-o(0))||1)}),g=s.copy(),y=o.copy()}),b}var t={top:0,right:0,bottom:0,left:0},n=960,r=500,i=Math.floor(Math.random()*1e4),s=d3.scale.ordinal(),o=d3.scale.linear(),u=function(e){return e.x},a=function(e){return e.y},f=[0],l=e.utils.defaultColor(),c=!1,h=d3.format(",.2f"),p,d,v=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout"),m="discreteBar",g,y;return b.dispatch=v,b.x=function(e){return arguments.length?(u=e,b):u},b.y=function(e) +{return arguments.length?(a=e,b):a},b.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,b):t},b.width=function(e){return arguments.length?(n=e,b):n},b.height=function(e){return arguments.length?(r=e,b):r},b.xScale=function(e){return arguments.length?(s=e,b):s},b.yScale=function(e){return arguments.length?(o=e,b):o},b.xDomain=function(e){return arguments.length?(p=e,b):p},b.yDomain=function(e){return arguments.length?(d=e,b):d},b.forceY=function(e){return arguments.length?(f=e,b):f},b.color=function(t){return arguments.length?(l=e.utils.getColor(t),b):l},b.id=function(e){return arguments.length?(i=e,b):i},b.showValues=function(e){return arguments.length?(c=e,b):c},b.valueFormat=function(e){return arguments.length?(h=e,b):h},b.rectClass=function(e){return arguments.length?(m=e,b):m},b},e.models.discreteBarChart=function(){function m(e){return e.each(function(u){var l=d3.select(this),g=this,b=(s||parseInt(l.style("width"))||960)-i.left-i.right,w=(o||parseInt(l.style("height"))||400)-i.top-i.bottom;m.update=function(){d.beforeUpdate(),e.transition().call(m)},m.container=this;if(!u||!u.length||!u.filter(function(e){return e.values.length}).length){var E=l.selectAll(".nv-noData").data([p]);return E.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),E.attr("x",i.left+b/2).attr("y",i.top+w/2).text(function(e){return e}),m}l.selectAll(".nv-noData").remove(),c=t.xScale(),h=t.yScale();var S=l.selectAll("g.nv-wrap.nv-discreteBarWithAxes").data([u]),T=S.enter().append("g").attr("class","nvd3 nv-wrap nv-discreteBarWithAxes").append("g"),N=T.append("defs"),C=S.select("g");T.append("g").attr("class","nv-x nv-axis"),T.append("g").attr("class","nv-y nv-axis"),T.append("g").attr("class","nv-barsWrap"),C.attr("transform","translate("+i.left+","+i.top+")"),t.width(b).height(w);var k=C.select(".nv-barsWrap").datum(u.filter(function(e){return!e.disabled}));d3.transition(k).call(t),N.append("clipPath").attr("id","nv-x-label-clip-"+t.id()).append("rect"),C.select("#nv-x-label-clip-"+t.id()+" rect").attr("width",c.rangeBand()*(a?2:1)).attr("height",16).attr("x",-c.rangeBand()/(a?1:2)),n.scale(c).ticks(b/100).tickSize(-w,0),C.select(".nv-x.nv-axis").attr("transform","translate(0,"+(h.range()[0]+(t.showValues()&&h.domain()[0]<0?16:0))+")"),C.select(".nv-x.nv-axis").transition().duration(0).call(n);var L=C.select(".nv-x.nv-axis").selectAll("g");a&&L.selectAll("text").attr("transform",function(e,t,n){return"translate(0,"+(n%2==0?"5":"17")+")"}),r.scale(h).ticks(w/36).tickSize(-b,0),d3.transition(C.select(".nv-y.nv-axis")).call(r),d.on("tooltipShow",function(e){f&&v(e,g.parentNode)})}),m}var t=e.models.discreteBar(),n=e.models.axis(),r=e.models.axis(),i={top:15,right:10,bottom:50,left:60},s=null,o=null,u=e.utils.getColor(),a=!1,f=!0,l=function(e,t,n,r,i){return"

                              "+t+"

                              "+"

                              "+n+"

                              "},c,h,p="No Data Available.",d=d3.dispatch("tooltipShow","tooltipHide","beforeUpdate");n.orient("bottom").highlightZero(!1).showMaxMin(!1).tickFormat(function(e){return e}),r.orient("left").tickFormat(d3.format(",.1f"));var v=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),a=n.tickFormat()(t.x()(i.point,i.pointIndex)),f=r.tickFormat()(t.y()(i.point,i.pointIndex)),c=l(i.series.key,a,f,i,m);e.tooltip.show([o,u],c,i.value<0?"n":"s",null,s)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+i.left,e.pos[1]+i.top],d.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){d.tooltipHide(e)}),d.on("tooltipHide",function(){f&&e.tooltip.cleanup()}),m.dispatch=d,m.discretebar=t,m.xAxis=n,m.yAxis=r,d3.rebind(m,t,"x","y","xDomain","yDomain","forceX","forceY","id","showValues","valueFormat"),m.margin=function(e){return arguments.length?(i.top=typeof e.top!="undefined"?e.top:i.top,i.right=typeof e.right!="undefined"?e.right:i.right,i.bottom=typeof e.bottom!="undefined"?e.bottom:i.bottom,i.left=typeof e.left!="undefined"?e.left:i.left,m):i},m.width=function(e){return arguments.length?(s=e,m):s},m.height=function(e){return arguments.length?(o=e,m):o},m.color=function(n){return arguments.length?(u=e.utils.getColor(n),t.color(u),m):u},m.staggerLabels=function(e){return arguments.length?(a=e,m):a},m.tooltips=function(e){return arguments.length?(f=e,m):f},m.tooltipContent=function(e){return arguments.length?(l=e,m):l},m.noData=function(e){return arguments.length?(p=e,m):p},m},e.models.distribution=function(){function l(e){return e.each(function(e){var a=n-(i==="x"?t.left+t.right:t.top+t.bottom),l=i=="x"?"y":"x",c=d3.select(this);f=f||u;var h=c.selectAll("g.nv-distribution").data([e]),p=h.enter().append("g").attr("class","nvd3 nv-distribution"),d=p.append("g"),v=h.select("g");h.attr("transform","translate("+t.left+","+t.top+")");var m=v.selectAll("g.nv-dist").data(function(e){return e},function(e){return e.key});m.enter().append("g"),m.attr("class",function(e,t){return"nv-dist nv-series-"+t}).style("stroke",function(e,t){return o(e,t)});var g=m.selectAll("line.nv-dist"+i).data(function(e){return e.values});g.enter().append("line").attr(i+"1",function(e,t){return f(s(e,t))}).attr(i+"2",function(e,t){return f(s(e,t))}),d3.transition(m.exit().selectAll("line.nv-dist"+i)).attr(i+"1",function(e,t){return u(s(e,t))}).attr(i+"2",function(e,t){return u(s(e,t))}).style("stroke-opacity",0).remove(),g.attr("class",function(e,t){return"nv-dist"+i+" nv-dist"+i+"-"+t}).attr(l+"1",0).attr(l+"2",r),d3.transition(g).attr(i+"1",function(e,t){return u(s(e,t))}).attr(i+"2",function(e,t){return u(s(e,t))}),f=u.copy()}),l}var t={top:0,right:0,bottom:0,left:0},n=400,r=8,i="x",s=function(e){return e[i]},o=e.utils.defaultColor(),u=d3.scale.linear(),a,f;return l.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,l):t},l.width=function(e){return arguments.length?(n=e,l):n},l.axis=function(e){return arguments.length?(i=e,l):i},l.size=function(e){return arguments.length?(r=e,l):r},l.getData=function(e){return arguments.length?(s=d3.functor(e),l):s},l.scale=function(e){return arguments.length?(u=e,l):u},l.color=function(t){return arguments.length?(o=e.utils.getColor(t),l):o},l},e.models.indentedTree=function(){function v(e){return e.each(function(t){function C(e,t,n){d3.event.stopPropagation();if(d3.event.shiftKey&&!n)return d3.event.shiftKey=!1,e.values&&e.values.forEach(function(e){(e.values||e._values)&&C(e,0,!0)}),!0;if(!A(e))return!0;e.values?(e._values=e.values,e.values=null):(e.values=e._values,e._values=null),v.update()}function k(e){return e._values&&e._values.length?h:e.values&&e.values.length?p:""}function L(e){return e._values&&e._values.length}function A(e){var t=e.values||e._values;return t&&t.length}var n=0,i=1,s=d3.layout.tree().children(function(e){return e.values}).size([r,f]);v.update=function(){e.transition().call(v)},v.container=this,t[0]||(t[0]={key:a});var m=s.nodes(t[0]),g=d3.select(this).selectAll("div").data([[m]]),y=g.enter().append("div").attr("class","nvd3 nv-wrap nv-indentedtree"),b=y.append("table"),w=g.select("table").attr("width","100%").attr("class",c);if(o){var E=b.append("thead"),S=E.append("tr");l.forEach(function(e){S.append("th").attr("width",e.width?e.width:"10%").style("text-align",e.type=="numeric"?"right":"left").append("span").text(e.label)})}var x=w.selectAll("tbody").data(function(e){return e});x.enter().append("tbody"),i=d3.max(m,function(e){return e.depth}),s.size([r,i*f]);var T=x.selectAll("tr").data(function(e){return e.filter(function(e){return u&&!e.children?u(e):!0})},function(e){return e.id||e.id==++n});T.exit().remove(),T.select("img.nv-treeicon").attr("src",k).classed("folded",L);var N=T.enter().append("tr");l.forEach(function(e,t){var n=N.append("td").style("padding-left",function(e){return(t?0:e.depth*f+12+(k(e)?0:16))+"px"},"important").style("text-align",e.type=="numeric"?"right":"left");t==0&&n.append("img").classed("nv-treeicon",!0).classed("nv-folded",L).attr("src",k).style("width","14px").style("height","14px").style("padding","0 1px").style("display",function(e){return k(e)?"inline-block":"none"}).on("click",C),n.append("span").attr("class",d3.functor(e.classes)).text(function(t){return e.format?e.format(t):t[e.key]||"-"}),e.showCount&&(n.append("span").attr("class","nv-childrenCount"),T.selectAll("span.nv-childrenCount").text(function(e){return e.values&&e.values.length||e._values&&e._values.length?"("+(e.values&&e.values.filter(function(e){return u?u(e):!0}).length||e._values&&e._values.filter(function(e){return u?u(e):!0}).length||0)+")":""})),e.click&&n.select("span").on("click",e.click)}),T.order().on("click",function(e){d.elementClick({row:this,data:e,pos:[e.x,e.y]})}).on("dblclick",function(e){d.elementDblclick({row:this,data:e,pos:[e.x,e.y]})}).on("mouseover",function(e){d.elementMouseover({row:this,data:e,pos:[e.x,e.y]})}).on("mouseout",function(e){d.elementMouseout({row:this,data:e,pos:[e.x,e.y]})})}),v}var t={top:0,right:0,bottom:0,left:0},n=960,r=500,i=e.utils.defaultColor(),s=Math.floor(Math.random()*1e4),o=!0,u=!1,a="No Data Available.",f=20,l=[{key:"key",label:"Name",type:"text"}],c=null,h="images/grey-plus.png",p="images/grey-minus.png",d=d3.dispatch("elementClick","elementDblclick","elementMouseover","elementMouseout");return v.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,v):t},v.width=function(e){return arguments.length?(n=e,v):n},v.height=function(e){return arguments.length?(r=e,v):r},v.color=function(t){return arguments.length?(i=e.utils.getColor(t),scatter.color(i),v):i},v.id=function(e){return arguments.length?(s=e,v):s},v.header=function(e){return arguments.length?(o=e,v):o},v.noData=function(e){return arguments.length?(a=e,v):a},v.filterZero=function(e){return arguments.length?(u=e,v):u},v.columns=function(e){return arguments.length?(l=e,v):l},v.tableClass=function(e){return arguments.length?(c=e,v):c},v.iconOpen=function(e){return arguments.length?(h=e,v):h},v.iconClose=function(e){return arguments.length?(p=e,v):p},v},e.models.legend=function(){function a(e){return e.each(function(e){var a=n-t.left-t.right,f=d3.select(this),l=f.selectAll("g.nv-legend").data([e]),c=l.enter().append("g").attr("class","nvd3 nv-legend").append("g"),h=l.select("g");l.attr("transform","translate("+t.left+","+t.top+")");var p=h.selectAll(".nv-series").data(function(e){return e}),d=p.enter().append("g").attr("class","nv-series").on("mouseover",function(e,t){u.legendMouseover(e,t)}).on("mouseout",function(e,t){u.legendMouseout(e,t)}).on("click",function(e,t){u.legendClick(e,t)}).on("dblclick",function(e,t){u.legendDblclick(e,t)});d.append("circle").style("stroke-width",2).attr("r",5),d.append("text").attr("text-anchor","start").attr("dy",".32em").attr("dx","8"),p.classed("disabled",function(e){return e.disabled}),p.exit().remove(),p.select("circle").style("fill",function(e,t){return e.color||s(e,t)}).style("stroke",function(e,t){return e.color||s(e,t)}),p.select("text").text(i);if(o){var v=[];p.each(function(e,t){v.push(d3.select(this).select("text").node().getComputedTextLength()+28)});var m=0,g=0,y=[];while(ga&&m>1){y=[],m--;for(k=0;k(y[k%m]||0)&&(y[k%m]=v[k]);g=y.reduce(function(e,t,n,r){return e+t})}var b=[];for(var w=0,E=0;wT&&(T=x),"translate("+N+","+S+")"}),h.attr("transform","translate("+(n-t.right-T)+","+t.top+")"),r=t.top+t.bottom+S+15}}),a}var t={top:5,right:0,bottom:5,left:0},n=400,r=20,i=function(e){return e.key},s=e.utils.defaultColor(),o=!0,u=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout");return a.dispatch=u,a.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,a):t},a.width=function(e){return arguments.length?(n=e,a):n},a.height=function(e){return arguments.length?(r=e,a):r},a.key=function(e){return arguments.length?(i=e,a):i},a.color=function(t){return arguments.length?(s=e.utils.getColor(t),a):s},a.align=function(e){return arguments.length?(o=e,a):o},a},e.models.line=function(){function m(e){return e.each(function(e){var m=r-n.left-n.right,g=i-n.top-n.bottom,b=d3.select(this);c=t.xScale(),h=t.yScale(),d=d||c,v=v||h;var w=b.selectAll("g.nv-wrap.nv-line").data([e]),E=w.enter().append("g").attr("class","nvd3 nv-wrap nv-line"),S=E.append("defs"),T=E.append("g"),N=w.select("g");T.append("g").attr("class","nv-groups"),T.append("g").attr("class","nv-scatterWrap"),w.attr("transform","translate("+n.left+","+n.top+")"),t.width(m).height(g);var C=w.select(".nv-scatterWrap");d3.transition(C).call(t),S.append("clipPath").attr("id","nv-edge-clip-"+t.id()).append("rect"),w.select("#nv-edge-clip-"+t.id()+" rect").attr("width",m).attr("height",g),N.attr("clip-path",l?"url(#nv-edge-clip-"+t.id()+")":""),C.attr("clip-path",l?"url(#nv-edge-clip-"+t.id()+")":"");var k=w.select(".nv-groups").selectAll(".nv-group").data(function(e){return e},function(e){return e.key});k.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),d3.transition(k.exit()).style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),k.attr("class",function(e,t){return"nv-group nv-series-"+t}).classed("hover",function(e){return e.hover}).style("fill",function(e,t){return s(e,t)}).style("stroke",function(e,t){return s(e,t)}),d3.transition(k).style("stroke-opacity",1).style("fill-opacity",.5);var L=k.selectAll("path.nv-area").data(function(e){return f(e)?[e]:[]});L.enter().append("path").attr("class","nv-area").attr("d",function(e){return d3.svg.area().interpolate(p).defined(a).x(function(e,t){return d(o(e,t))}).y0(function(e,t){return v(u(e,t))}).y1(function(e,t){return v(h.domain()[0]<=0?h.domain()[1]>=0?0:h.domain()[1]:h.domain()[0])}).apply(this,[e.values])}),d3.transition(k.exit().selectAll("path.nv-area")).attr("d",function(e){return d3.svg.area().interpolate(p).defined(a).x(function(e,t){return d(o(e,t))}).y0(function(e,t){return v(u(e,t))}).y1(function(e,t){return v(h.domain()[0]<=0?h.domain()[1]>=0?0:h.domain()[1]:h.domain()[0])}).apply(this,[e.values])}),d3.transition(L).attr("d",function(e){return d3.svg.area().interpolate(p).defined(a).x(function(e,t){return d(o(e,t))}).y0(function(e,t){return v(u(e,t))}).y1(function(e,t){return v(h.domain()[0]<=0?h.domain()[1]>=0?0:h.domain()[1]:h.domain()[0])}).apply(this,[e.values])});var A=k.selectAll("path.nv-line").data(function(e){return[e.values]});A.enter().append("path").attr("class","nv-line").attr("d",d3.svg.line().interpolate(p).defined(a).x(function(e,t){return d(o(e,t))}).y(function(e,t){return v(u(e,t))})),d3.transition(k.exit().selectAll("path.nv-line")).attr("d",d3.svg.line().interpolate(p).defined(a).x(function(e,t){return c(o(e,t))}).y(function(e,t){return h(u(e,t))})),d3.transition(A).attr("d",d3.svg.line().interpolate(p).defined(a).x(function(e,t){return c(o(e,t))}).y(function(e,t){return h(u(e,t))})),d=c.copy(),v=h.copy()}),m}var t=e.models.scatter(),n={top:0,right:0,bottom:0,left:0},r=960,i=500,s=e.utils.defaultColor(),o=function(e){return e.x},u=function(e){return e.y},a=function(e,t){return!isNaN(u(e,t))&&u(e,t)!==null},f=function(e){return e.area},l=!1,c,h,p="linear";t.size(16).sizeDomain([16,256]);var d,v;return m.dispatch=t.dispatch,m.scatter=t,d3.rebind(m,t,"id","interactive","size","xScale","yScale","zScale","xDomain","yDomain","sizeDomain","forceX","forceY","forceSize","clipVoronoi","clipRadius","padData"),m.margin=function(e){return arguments.length?(n.top=typeof e.top!="undefined"?e.top:n.top,n.right=typeof e.right!="undefined"?e.right:n.right,n.bottom=typeof e.bottom!="undefined"?e.bottom:n.bottom,n.left=typeof e.left!="undefined"?e.left:n.left,m):n},m.width=function(e){return arguments.length?(r=e,m):r},m.height=function(e){return arguments.length?(i=e,m):i},m.x=function(e){return arguments.length?(o=e,t.x(e),m):o},m.y=function(e){return arguments.length?(u=e,t.y(e),m):u},m.clipEdge=function(e){return arguments.length?(l=e,m):l},m.color=function(n){return arguments.length?(s=e.utils.getColor(n),t.color(s),m):s},m.interpolate=function(e){return arguments.length?(p=e,m):p},m.defined=function(e){return arguments.length?(a=e,m):a},m.isArea=function(e){return arguments.length?(f=d3.functor(e),m):f},m},e.models.lineChart=function(){function y(e){return e.each(function(c){var b=d3.select(this),w=this,E=(u||parseInt(b.style("width"))||960)-s.left-s.right,S=(a||parseInt(b.style("height"))||400)-s.top-s.bottom;y.update=function(){y(e)},y.container=this,d.disabled=c.map(function(e){return!!e.disabled});if(!c||!c.length||!c.filter(function(e){return e.values.length}).length){var T=b.selectAll(".nv-noData").data([v]);return T.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),T.attr("x",s.left+E/2).attr("y",s.top+S/2).text(function(e){return e}),y}b.selectAll(".nv-noData").remove(),h=t.xScale(),p=t.yScale();var N=b.selectAll("g.nv-wrap.nv-lineChart").data([c]),C=N.enter().append("g").attr("class","nvd3 nv-wrap nv-lineChart").append("g"),k=N.select("g");C.append("g").attr("class","nv-x nv-axis"),C.append("g").attr("class","nv-y nv-axis"),C.append("g").attr("class","nv-linesWrap"),C.append("g").attr("class","nv-legendWrap"),f&&(i.width(E),k.select(".nv-legendWrap").datum(c).call(i),s.top!=i.height()&&(s.top=i.height(),S=(a||parseInt(b.style("height"))||400)-s.top-s.bottom),N.select(".nv-legendWrap").attr("transform","translate(0,"+ -s.top+")")),N.attr("transform","translate("+s.left+","+s.top+")"),t.width(E).height(S).color(c.map(function(e,t){return e.color||o(e,t)}).filter(function(e,t){return!c[t].disabled}));var L=k.select(".nv-linesWrap").datum(c.filter(function(e){return!e.disabled}));d3.transition(L).call(t),n.scale(h).ticks(E/100).tickSize(-S,0),k.select(".nv-x.nv-axis").attr("transform","translate(0,"+p.range()[0]+")"),d3.transition(k.select(".nv-x.nv-axis")).call(n),r.scale(p).ticks(S/36).tickSize(-E,0),d3.transition(k.select(".nv-y.nv-axis")).call(r),i.dispatch.on("legendClick",function(t,n){t.disabled=!t.disabled,c.filter(function(e){return!e.disabled}).length||c.map(function(e){return e.disabled=!1,N.selectAll(".nv-series").classed("disabled",!1),e}),d.disabled=c.map(function(e){return!!e.disabled}),m.stateChange(d),e.transition().call(y)}),m.on("tooltipShow",function(e){l&&g(e,w.parentNode)}),m.on("changeState",function(t){typeof t.disabled!="undefined"&&(c.forEach(function(e,n){e.disabled=t.disabled[n]}),d.disabled=t.disabled),e.call(y)})}),y}var t=e.models.line(),n=e.models.axis(),r=e.models.axis(),i=e.models.legend(),s={top:30,right:20,bottom:50,left:60},o=e.utils.defaultColor(),u=null,a=null,f=!0,l=!0,c=function(e,t,n,r,i){return"

                              "+e+"

                              "+"

                              "+n+" at "+t+"

                              "},h,p,d={},v="No Data Available.",m=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState");n.orient("bottom").tickPadding(7),r.orient("left");var g=function(i,s){if(s){var o=d3.select(s).select("svg"),u=o.attr("viewBox");if(u){u=u.split(" ");var a=parseInt(o.style("width"))/u[2];i.pos[0]=i.pos[0]*a,i.pos[1]=i.pos[1]*a}}var f=i.pos[0]+(s.offsetLeft||0),l=i.pos[1]+(s.offsetTop||0),h=n.tickFormat()(t.x()(i.point,i.pointIndex)),p=r.tickFormat()(t.y()(i.point,i.pointIndex)),d=c(i.series.key,h,p,i,y);e.tooltip.show([f,l],d,null,null,s)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+s.left,e.pos[1]+s.top],m.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){m.tooltipHide(e)}),m.on("tooltipHide",function(){l&&e.tooltip.cleanup()}),y.dispatch=m,y.lines=t,y.legend=i,y.xAxis=n,y.yAxis=r,d3.rebind(y,t,"defined","isArea","x","y","size","xScale","yScale","xDomain","yDomain","forceX","forceY","interactive","clipEdge","clipVoronoi","id","interpolate"),y.margin=function(e){return arguments.length?(s.top=typeof e.top!="undefined"?e.top:s.top,s.right=typeof e.right!="undefined"?e.right:s.right,s.bottom=typeof e.bottom!="undefined"?e.bottom:s.bottom,s.left=typeof e.left!="undefined"?e.left:s.left,y):s},y.width=function(e){return arguments.length?(u=e,y):u},y.height=function(e){return arguments.length?(a=e,y):a},y.color=function(t){return arguments.length?(o=e.utils.getColor(t),i.color(o),y):o},y.showLegend=function(e){return arguments.length?(f=e,y):f},y.tooltips=function(e){return arguments.length?(l=e,y):l},y.tooltipContent=function(e){return arguments.length?(c=e,y):c},y.state=function(e){return arguments.length?(d=e,y):d},y.noData=function(e){return arguments.length?(v=e,y):v},y},e.models.linePlusBarChart=function(){function x(e){return e.each(function(l){var c=d3.select(this),v=this,T=(a||parseInt(c.style("width"))||960)-u.left-u.right,N=(f||parseInt(c.style("height"))||400)-u.top-u.bottom;x.update=function(){x(e)},x.container=this,E.disabled=l.map(function(e){return!!e.disabled});if(!l||!l.length||!l.filter(function(e){return e.values.length}).length){var C=c.selectAll(".nv-noData").data([b]);return C.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),C.attr("x",u.left+T/2).attr("y",u.top+N/2).text(function(e){return e}),x}c.selectAll(".nv-noData").remove();var k=l.filter(function(e){return!e.disabled&&e.bar}),L=l.filter(function(e){return!e.bar});m=L.filter(function(e){return!e.disabled}).length&&L.filter(function(e){return!e.disabled})[0].values.length?t.xScale():n.xScale(),g=n.yScale(),y=t.yScale();var A=d3.select(this).selectAll("g.nv-wrap.nv-linePlusBar").data([l]),O=A.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),M=A.select("g");O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y1 nv-axis"),O.append("g").attr("class","nv-y2 nv-axis"),O.append("g").attr("class","nv-barsWrap"),O.append("g").attr("class","nv-linesWrap"),O.append("g").attr("class","nv-legendWrap"),p&&(o.width(T/2),M.select(".nv-legendWrap").datum(l.map(function(e){return e.originalKey=e.originalKey===undefined?e.key:e.originalKey,e.key=e.originalKey+(e.bar?" (left axis)":" (right axis)"),e})).call(o),u.top!=o.height()&&(u.top=o.height(),N=(f||parseInt(c.style("height"))||400)-u.top-u.bottom),M.select(".nv-legendWrap").attr("transform","translate("+T/2+","+ -u.top+")")),A.attr("transform","translate("+u.left+","+u.top+")"),t.width(T).height(N).color(l.map(function(e,t){return e.color||h(e,t)}).filter(function(e,t){return!l[t].disabled&&!l[t].bar})),n.width(T).height(N).color(l.map(function(e,t){return e.color||h(e,t)}).filter(function(e,t){return!l[t].disabled&&l[t].bar}));var _=M.select(".nv-barsWrap").datum(k.length?k:[{values:[]}]),D=M.select(".nv-linesWrap").datum(L[0].disabled?[{values:[]}]:L);d3.transition(_).call(n),d3.transition(D).call(t),r.scale(m).ticks(T/100).tickSize(-N,0),M.select(".nv-x.nv-axis").attr("transform","translate(0,"+g.range()[0]+")"),d3.transition(M.select(".nv-x.nv-axis")).call(r),i.scale(g).ticks(N/36).tickSize(-T,0),d3.transition(M.select(".nv-y1.nv-axis")).style("opacity",k.length?1:0).call(i),s.scale(y).ticks(N/36).tickSize(k.length?0:-T,0),M.select(".nv-y2.nv-axis").style("opacity",L.length?1:0).attr("transform","translate("+T+",0)"),d3.transition(M.select(".nv-y2.nv-axis")).call(s),o.dispatch.on("legendClick",function(t,n){t.disabled=!t.disabled,l.filter(function(e){return!e.disabled}).length||l.map(function(e){return e.disabled=!1,A.selectAll(".nv-series").classed("disabled",!1),e}),E.disabled=l.map(function(e){return!!e.disabled}),w.stateChange(E),e.transition().call(x)}),w.on("tooltipShow",function(e){d&&S(e,v.parentNode)}),w.on("changeState",function(t){typeof t.disabled!="undefined"&&(l.forEach(function(e,n){e.disabled=t.disabled[n]}),E.disabled=t.disabled),e.call(x)})}),x}var t=e.models.line(),n=e.models.historicalBar(),r=e.models.axis(),i=e.models.axis(),s=e.models.axis(),o=e.models.legend(),u={top:30,right:60,bottom:50,left:60},a=null,f=null,l=function(e){return e.x},c=function(e){return e.y},h=e.utils.defaultColor(),p=!0,d=!0,v=function(e,t,n,r,i){return"

                              "+e+"

                              "+"

                              "+n+" at "+t+"

                              "},m,g,y,b="No Data Available.",w=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState");n.padData(!0),t.clipEdge(!1).padData(!0),r.orient("bottom").tickPadding(7).highlightZero(!1),i.orient("left"),s.orient("right");var E={},S=function(n,o){var u=n.pos[0]+(o.offsetLeft||0),a=n.pos[1]+(o.offsetTop||0),f=r.tickFormat()(t.x()(n.point,n.pointIndex)),l=(n.series.bar?i:s).tickFormat()(t.y()(n.point,n.pointIndex)),c=v(n.series.key,f,l,n,x);e.tooltip.show([u,a],c,n.value<0?"n":"s",null,o)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+u.left,e.pos[1]+u.top],w.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){w.tooltipHide(e)}),n.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+u.left,e.pos[1]+u.top],w.tooltipShow(e)}),n.dispatch.on("elementMouseout.tooltip",function(e){w.tooltipHide(e)}),w.on("tooltipHide",function(){d&&e.tooltip.cleanup()}),x.dispatch=w,x.legend=o,x.lines=t,x.bars=n,x.xAxis=r,x.y1Axis=i,x.y2Axis=s,d3.rebind(x,t,"defined","size","clipVoronoi","interpolate"),x.x=function(e){return arguments.length?(l=e,t.x(e),n.x(e),x):l},x.y=function(e){return arguments.length?(c=e,t.y(e),n.y(e),x):c},x.margin=function(e){return arguments.length?(u.top=typeof e.top!="undefined"?e.top:u.top,u.right=typeof e.right!="undefined"?e.right:u.right,u.bottom=typeof e.bottom!="undefined"?e.bottom:u.bottom,u.left=typeof e.left!="undefined"?e.left:u.left,x):u},x.width=function(e){return arguments.length?(a=e,x):a},x.height=function(e){return arguments.length?(f=e,x):f},x.color=function(t){return arguments.length?(h=e.utils.getColor(t),o.color(h),x):h},x.showLegend=function(e){return arguments.length?(p=e,x):p},x.tooltips=function(e){return arguments.length?(d=e,x):d},x.tooltipContent=function(e){return arguments.length?(v=e,x):v},x.state=function(e){return arguments.length?(E=e,x):E},x.noData=function(e){return arguments.length?(b=e,x):b},x},e.models.lineWithFocusChart=function(){function C(e){return e.each(function(S){function R(e){var t=+(e=="e"),n=t?1:-1,r=M/3;return"M"+.5*n+","+r+"A6,6 0 0 "+t+" "+6.5*n+","+(r+6)+"V"+(2*r-6)+"A6,6 0 0 "+t+" "+.5*n+","+2*r+"Z"+"M"+2.5*n+","+(r+8)+"V"+(2*r-8)+"M"+4.5*n+","+(r+8)+"V"+(2*r-8)}function U(){a.empty()||a.extent(w),I.data([a.empty()?g.domain():w]).each(function(e,t){var n=g(e[0])-v.range()[0],r=v.range()[1]-g(e[1]);d3.select(this).select(".left").attr("width",n<0?0:n),d3.select(this).select(".right").attr("x",g(e[1])).attr("width",r<0?0:r)})}function z(){w=a.empty()?null:a.extent(),extent=a.empty()?g.domain():a.extent(),T.brush({extent:extent,brush:a}),U();var e=H.select(".nv-focus .nv-linesWrap").datum(S.filter(function(e){return!e.disabled}).map(function(e,n){return{key:e.key,values:e.values.filter(function(e,n){return t.x()(e,n)>=extent[0]&&t.x()(e,n)<=extent[1]})}}));d3.transition(e).call(t),d3.transition(H.select(".nv-focus .nv-x.nv-axis")).call(r),d3.transition(H.select(".nv-focus .nv-y.nv-axis")).call(i)}var k=d3.select(this),L=this,A=(h||parseInt(k.style("width"))||960)-f.left-f.right,O=(p||parseInt(k.style("height"))||400)-f.top-f.bottom-d,M=d-l.top-l.bottom;C.update=function(){C(e)},C.container=this;if(!S||!S.length||!S.filter(function(e){return e.values.length}).length){var _=k.selectAll(".nv-noData").data([x]);return _.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),_.attr("x",f.left+A/2).attr("y",f.top+O/2).text(function(e){return e}),C}k.selectAll(".nv-noData").remove(),v=t.xScale(),m=t.yScale(),g=n.xScale(),y=n.yScale();var D=k.selectAll("g.nv-wrap.nv-lineWithFocusChart").data([S]),P=D.enter().append("g").attr("class","nvd3 nv-wrap nv-lineWithFocusChart").append("g"),H=D.select("g");P.append("g").attr("class","nv-legendWrap");var B=P.append("g").attr("class","nv-focus");B.append("g").attr("class","nv-x nv-axis"),B.append("g").attr("class","nv-y nv-axis"),B.append("g").attr("class","nv-linesWrap");var j=P.append("g").attr("class","nv-context");j.append("g").attr("class","nv-x nv-axis"),j.append("g").attr("class","nv-y nv-axis"),j.append("g").attr("class","nv-linesWrap"),j.append("g").attr("class","nv-brushBackground"),j.append("g").attr("class","nv-x nv-brush"),b&&(u.width(A),H.select(".nv-legendWrap").datum(S).call(u),f.top!=u.height()&&(f.top=u.height(),O=(p||parseInt(k.style("height"))||400)-f.top-f.bottom-d),H.select(".nv-legendWrap").attr("transform","translate(0,"+ -f.top+")")),D.attr("transform","translate("+f.left+","+f.top+")"),t.width(A).height(O).color(S.map(function(e,t){return e.color||c(e,t)}).filter(function(e,t){return!S[t].disabled})),n.defined(t.defined()).width(A).height(M).color(S.map(function(e,t){return e.color||c(e,t)}).filter(function(e,t){return!S[t].disabled})),H.select(".nv-context").attr("transform","translate(0,"+(O+f.bottom+l.top)+")");var F=H.select(".nv-context .nv-linesWrap").datum(S.filter(function(e){return!e.disabled}));d3.transition(F).call(n),r.scale(v).ticks(A/100).tickSize(-O,0),i.scale(m).ticks(O/36).tickSize(-A,0),H.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+O+")"),a.x(g).on("brush",z),w&&a.extent(w);var I=H.select(".nv-brushBackground").selectAll("g").data([w||a.extent()]),q=I.enter().append("g");q.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",M),q.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",M),gBrush=H.select(".nv-x.nv-brush").call(a),gBrush.selectAll("rect").attr("height",M),gBrush.selectAll(".resize").append("path").attr("d",R),z(),s.scale(g).ticks(A/100).tickSize(-M,0),H.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+y.range()[0]+")"),d3.transition(H.select(".nv-context .nv-x.nv-axis")).call(s),o.scale(y).ticks(M/36).tickSize(-A,0),d3.transition(H.select(".nv-context .nv-y.nv-axis")).call(o),H.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+y.range()[0]+")"),u.dispatch.on("legendClick",function(t,n){t.disabled=!t.disabled,S.filter(function(e){return!e.disabled}).length||S.map(function(e){return e.disabled=!1,D.selectAll(".nv-series").classed("disabled",!1),e}),e.transition().call(C)}),T.on("tooltipShow",function(e){E&&N(e,L.parentNode)})}),C}var t=e.models.line(),n=e.models.line(),r=e.models.axis(),i=e.models.axis(),s=e.models.axis(),o=e.models.axis(),u=e.models.legend(),a=d3.svg.brush(),f={top:30,right:30,bottom:30,left:60},l={top:0,right:30,bottom:20,left:60},c=e.utils.defaultColor(),h=null,p=null,d=100,v,m,g,y,b=!0,w=null,E=!0,S=function(e,t,n,r,i){return"

                              "+e+"

                              "+"

                              "+n+" at "+t+"

                              "},x="No Data Available.",T=d3.dispatch("tooltipShow","tooltipHide","brush");t.clipEdge(!0),n.interactive(!1),r.orient("bottom").tickPadding(5),i.orient("left"),s.orient("bottom").tickPadding(5),o.orient("left");var N=function(n,s){var o=n.pos[0]+(s.offsetLeft||0),u=n.pos[1]+(s.offsetTop||0),a=r.tickFormat()(t.x()(n.point,n.pointIndex)),f=i.tickFormat()(t.y()(n.point,n.pointIndex)),l=S(n.series.key,a,f,n,C);e.tooltip.show([o,u],l,null,null,s)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+f.left,e.pos[1]+f.top],T.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){T.tooltipHide(e)}),T.on("tooltipHide",function(){E&&e.tooltip.cleanup()}),C.dispatch=T,C.legend=u,C.lines=t,C.lines2=n,C.xAxis=r,C.yAxis=i,C.x2Axis=s,C.y2Axis=o,d3.rebind(C,t,"defined","isArea","size","xDomain","yDomain","forceX","forceY","interactive","clipEdge","clipVoronoi","id"),C.x=function(e){return arguments.length?(t.x(e),n.x(e),C):t.x},C.y=function(e){return arguments.length?(t.y(e),n.y(e),C):t.y},C.margin=function(e){return arguments.length?(f.top=typeof e.top!="undefined"?e.top:f.top,f.right=typeof e.right!="undefined"?e.right:f.right,f.bottom=typeof e.bottom!="undefined"?e.bottom:f.bottom,f.left=typeof e.left!="undefined"? +e.left:f.left,C):f},C.margin2=function(e){return arguments.length?(l=e,C):l},C.width=function(e){return arguments.length?(h=e,C):h},C.height=function(e){return arguments.length?(p=e,C):p},C.height2=function(e){return arguments.length?(d=e,C):d},C.color=function(t){return arguments.length?(c=e.utils.getColor(t),u.color(c),C):c},C.showLegend=function(e){return arguments.length?(b=e,C):b},C.tooltips=function(e){return arguments.length?(E=e,C):E},C.tooltipContent=function(e){return arguments.length?(S=e,C):S},C.interpolate=function(e){return arguments.length?(t.interpolate(e),n.interpolate(e),C):t.interpolate()},C.noData=function(e){return arguments.length?(x=e,C):x},C.xTickFormat=function(e){return arguments.length?(r.tickFormat(e),s.tickFormat(e),C):r.tickFormat()},C.yTickFormat=function(e){return arguments.length?(i.tickFormat(e),o.tickFormat(e),C):i.tickFormat()},C},e.models.multiBar=function(){function E(e){return e.each(function(e){var E=n-t.left-t.right,S=r-t.top-t.bottom,T=d3.select(this);c&&(e=d3.layout.stack().offset("zero").values(function(e){return e.values}).y(a)(e)),e=e.map(function(e,t){return e.values=e.values.map(function(e){return e.series=t,e}),e}),c&&e[0].values.map(function(t,n){var r=0,i=0;e.map(function(e){var t=e.values[n];t.size=Math.abs(t.y),t.y<0?(t.y1=i,i-=t.size):(t.y1=t.size+r,r+=t.size)})});var N=m&&g?[]:e.map(function(e){return e.values.map(function(e,t){return{x:u(e,t),y:a(e,t),y0:e.y0,y1:e.y1}})});i.domain(d3.merge(N).map(function(e){return e.x})).rangeBands([0,E],.1),s.domain(g||d3.extent(d3.merge(N).map(function(e){return c?e.y>0?e.y1:e.y1+e.y:e.y}).concat(f))).range([S,0]);if(i.domain()[0]===i.domain()[1]||s.domain()[0]===s.domain()[1])singlePoint=!0;i.domain()[0]===i.domain()[1]&&(i.domain()[0]?i.domain([i.domain()[0]-i.domain()[0]*.01,i.domain()[1]+i.domain()[1]*.01]):i.domain([-1,1])),s.domain()[0]===s.domain()[1]&&(s.domain()[0]?s.domain([s.domain()[0]+s.domain()[0]*.01,s.domain()[1]-s.domain()[1]*.01]):s.domain([-1,1])),b=b||i,w=w||s;var C=T.selectAll("g.nv-wrap.nv-multibar").data([e]),k=C.enter().append("g").attr("class","nvd3 nv-wrap nv-multibar"),L=k.append("defs"),A=k.append("g"),O=C.select("g");A.append("g").attr("class","nv-groups"),C.attr("transform","translate("+t.left+","+t.top+")"),L.append("clipPath").attr("id","nv-edge-clip-"+o).append("rect"),C.select("#nv-edge-clip-"+o+" rect").attr("width",E).attr("height",S),O.attr("clip-path",l?"url(#nv-edge-clip-"+o+")":"");var M=C.select(".nv-groups").selectAll(".nv-group").data(function(e){return e},function(e){return e.key});M.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),d3.transition(M.exit()).selectAll("rect.nv-bar").delay(function(t,n){return n*v/e[0].values.length}).attr("y",function(e){return c?w(e.y0):w(0)}).attr("height",0).remove(),M.attr("class",function(e,t){return"nv-group nv-series-"+t}).classed("hover",function(e){return e.hover}).style("fill",function(e,t){return h(e,t)}).style("stroke",function(e,t){return h(e,t)}),d3.transition(M).style("stroke-opacity",1).style("fill-opacity",.75);var _=M.selectAll("rect.nv-bar").data(function(e){return e.values});_.exit().remove();var D=_.enter().append("rect").attr("class",function(e,t){return a(e,t)<0?"nv-bar negative":"nv-bar positive"}).attr("x",function(t,n,r){return c?0:r*i.rangeBand()/e.length}).attr("y",function(e){return w(c?e.y0:0)}).attr("height",0).attr("width",i.rangeBand()/(c?1:e.length));_.style("fill",function(e,t,n){return h(e,n,t)}).style("stroke",function(e,t,n){return h(e,n,t)}).on("mouseover",function(t,n){d3.select(this).classed("hover",!0),y.elementMouseover({value:a(t,n),point:t,series:e[t.series],pos:[i(u(t,n))+i.rangeBand()*(c?e.length/2:t.series+.5)/e.length,s(a(t,n)+(c?t.y0:0))],pointIndex:n,seriesIndex:t.series,e:d3.event})}).on("mouseout",function(t,n){d3.select(this).classed("hover",!1),y.elementMouseout({value:a(t,n),point:t,series:e[t.series],pointIndex:n,seriesIndex:t.series,e:d3.event})}).on("click",function(t,n){y.elementClick({value:a(t,n),point:t,series:e[t.series],pos:[i(u(t,n))+i.rangeBand()*(c?e.length/2:t.series+.5)/e.length,s(a(t,n)+(c?t.y0:0))],pointIndex:n,seriesIndex:t.series,e:d3.event}),d3.event.stopPropagation()}).on("dblclick",function(t,n){y.elementDblClick({value:a(t,n),point:t,series:e[t.series],pos:[i(u(t,n))+i.rangeBand()*(c?e.length/2:t.series+.5)/e.length,s(a(t,n)+(c?t.y0:0))],pointIndex:n,seriesIndex:t.series,e:d3.event}),d3.event.stopPropagation()}),_.attr("class",function(e,t){return a(e,t)<0?"nv-bar negative":"nv-bar positive"}).attr("transform",function(e,t){return"translate("+i(u(e,t))+",0)"}),p&&(d||(d=e.map(function(){return!0})),_.style("fill",function(e,t,n){return d3.rgb(p(e,t)).darker(d.map(function(e,t){return t}).filter(function(e,t){return!d[t]})[n]).toString()}).style("stroke",function(e,t,n){return d3.rgb(p(e,t)).darker(d.map(function(e,t){return t}).filter(function(e,t){return!d[t]})[n]).toString()})),c?d3.transition(_).delay(function(t,n){return n*v/e[0].values.length}).attr("y",function(e,t){return s(c?e.y1:0)}).attr("height",function(e,t){return Math.max(Math.abs(s(e.y+(c?e.y0:0))-s(c?e.y0:0)),1)}).each("end",function(){d3.transition(d3.select(this)).attr("x",function(t,n){return c?0:t.series*i.rangeBand()/e.length}).attr("width",i.rangeBand()/(c?1:e.length))}):d3.transition(_).delay(function(t,n){return n*v/e[0].values.length}).attr("x",function(t,n){return t.series*i.rangeBand()/e.length}).attr("width",i.rangeBand()/e.length).each("end",function(){d3.transition(d3.select(this)).attr("y",function(e,t){return a(e,t)<0?s(0):s(0)-s(a(e,t))<1?s(0)-1:s(a(e,t))||0}).attr("height",function(e,t){return Math.max(Math.abs(s(a(e,t))-s(0)),1)||0})}),b=i.copy(),w=s.copy()}),E}var t={top:0,right:0,bottom:0,left:0},n=960,r=500,i=d3.scale.ordinal(),s=d3.scale.linear(),o=Math.floor(Math.random()*1e4),u=function(e){return e.x},a=function(e){return e.y},f=[0],l=!0,c=!1,h=e.utils.defaultColor(),p=null,d,v=1200,m,g,y=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout"),b,w;return E.dispatch=y,E.x=function(e){return arguments.length?(u=e,E):u},E.y=function(e){return arguments.length?(a=e,E):a},E.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,E):t},E.width=function(e){return arguments.length?(n=e,E):n},E.height=function(e){return arguments.length?(r=e,E):r},E.xScale=function(e){return arguments.length?(i=e,E):i},E.yScale=function(e){return arguments.length?(s=e,E):s},E.xDomain=function(e){return arguments.length?(m=e,E):m},E.yDomain=function(e){return arguments.length?(g=e,E):g},E.forceY=function(e){return arguments.length?(f=e,E):f},E.stacked=function(e){return arguments.length?(c=e,E):c},E.clipEdge=function(e){return arguments.length?(l=e,E):l},E.color=function(t){return arguments.length?(h=e.utils.getColor(t),E):h},E.barColor=function(t){return arguments.length?(p=e.utils.getColor(t),E):p},E.disabled=function(e){return arguments.length?(d=e,E):d},E.id=function(e){return arguments.length?(o=e,E):o},E.delay=function(e){return arguments.length?(v=e,E):v},E},e.models.multiBarChart=function(){function x(e){return e.each(function(v){var T=d3.select(this),N=this,C=(u||parseInt(T.style("width"))||960)-o.left-o.right,k=(a||parseInt(T.style("height"))||400)-o.top-o.bottom;x.update=function(){e.transition().call(x)},x.container=this,y.disabled=v.map(function(e){return!!e.disabled});if(!v||!v.length||!v.filter(function(e){return e.values.length}).length){var L=T.selectAll(".nv-noData").data([b]);return L.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),L.attr("x",o.left+C/2).attr("y",o.top+k/2).text(function(e){return e}),x}T.selectAll(".nv-noData").remove(),m=t.xScale(),g=t.yScale();var A=T.selectAll("g.nv-wrap.nv-multiBarWithLegend").data([v]),O=A.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarWithLegend").append("g"),M=A.select("g");O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y nv-axis"),O.append("g").attr("class","nv-barsWrap"),O.append("g").attr("class","nv-legendWrap"),O.append("g").attr("class","nv-controlsWrap"),c&&(i.width(C-E()),t.barColor()&&v.forEach(function(e,t){e.color=d3.rgb("#ccc").darker(t*1.5).toString()}),M.select(".nv-legendWrap").datum(v).call(i),o.top!=i.height()&&(o.top=i.height(),k=(a||parseInt(T.style("height"))||400)-o.top-o.bottom),M.select(".nv-legendWrap").attr("transform","translate("+E()+","+ -o.top+")"));if(l){var _=[{key:"Grouped",disabled:t.stacked()},{key:"Stacked",disabled:!t.stacked()}];s.width(E()).color(["#444","#444","#444"]),M.select(".nv-controlsWrap").datum(_).attr("transform","translate(0,"+ -o.top+")").call(s)}A.attr("transform","translate("+o.left+","+o.top+")"),t.disabled(v.map(function(e){return e.disabled})).width(C).height(k).color(v.map(function(e,t){return e.color||f(e,t)}).filter(function(e,t){return!v[t].disabled}));var D=M.select(".nv-barsWrap").datum(v.filter(function(e){return!e.disabled}));d3.transition(D).call(t),n.scale(m).ticks(C/100).tickSize(-k,0),M.select(".nv-x.nv-axis").attr("transform","translate(0,"+g.range()[0]+")"),d3.transition(M.select(".nv-x.nv-axis")).call(n);var P=M.select(".nv-x.nv-axis > g").selectAll("g");P.selectAll("line, text").style("opacity",1),h&&P.filter(function(e,t){return t%Math.ceil(v[0].values.length/(C/100))!==0}).selectAll("text, line").style("opacity",0),p&&P.selectAll("text").attr("transform",function(e,t,n){return"rotate("+p+" 0,0)"}).attr("text-transform",p>0?"start":"end"),M.select(".nv-x.nv-axis").selectAll("g.nv-axisMaxMin text").style("opacity",1),r.scale(g).ticks(k/36).tickSize(-C,0),d3.transition(M.select(".nv-y.nv-axis")).call(r),i.dispatch.on("legendClick",function(t,n){t.disabled=!t.disabled,v.filter(function(e){return!e.disabled}).length||v.map(function(e){return e.disabled=!1,A.selectAll(".nv-series").classed("disabled",!1),e}),y.disabled=v.map(function(e){return!!e.disabled}),w.stateChange(y),e.transition().call(x)}),s.dispatch.on("legendClick",function(n,r){if(!n.disabled)return;_=_.map(function(e){return e.disabled=!0,e}),n.disabled=!1;switch(n.key){case"Grouped":t.stacked(!1);break;case"Stacked":t.stacked(!0)}y.stacked=t.stacked(),w.stateChange(y),e.transition().call(x)}),w.on("tooltipShow",function(e){d&&S(e,N.parentNode)}),w.on("changeState",function(n){typeof n.disabled!="undefined"&&(v.forEach(function(e,t){e.disabled=n.disabled[t]}),y.disabled=n.disabled),typeof n.stacked!="undefined"&&(t.stacked(n.stacked),y.stacked=n.stacked),e.call(x)})}),x}var t=e.models.multiBar(),n=e.models.axis(),r=e.models.axis(),i=e.models.legend(),s=e.models.legend(),o={top:30,right:20,bottom:30,left:60},u=null,a=null,f=e.utils.defaultColor(),l=!0,c=!0,h=!0,p=0,d=!0,v=function(e,t,n,r,i){return"

                              "+e+"

                              "+"

                              "+n+" on "+t+"

                              "},m,g,y={stacked:!1},b="No Data Available.",w=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),E=function(){return l?180:0};t.stacked(!1),n.orient("bottom").tickPadding(7).highlightZero(!1).showMaxMin(!1).tickFormat(function(e){return e}),r.orient("left").tickFormat(d3.format(",.1f"));var S=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),a=n.tickFormat()(t.x()(i.point,i.pointIndex)),f=r.tickFormat()(t.y()(i.point,i.pointIndex)),l=v(i.series.key,a,f,i,x);e.tooltip.show([o,u],l,i.value<0?"n":"s",null,s)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+o.left,e.pos[1]+o.top],w.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){w.tooltipHide(e)}),w.on("tooltipHide",function(){d&&e.tooltip.cleanup()}),x.dispatch=w,x.multibar=t,x.legend=i,x.xAxis=n,x.yAxis=r,d3.rebind(x,t,"x","y","xDomain","yDomain","forceX","forceY","clipEdge","id","stacked","delay","barColor"),x.margin=function(e){return arguments.length?(o.top=typeof e.top!="undefined"?e.top:o.top,o.right=typeof e.right!="undefined"?e.right:o.right,o.bottom=typeof e.bottom!="undefined"?e.bottom:o.bottom,o.left=typeof e.left!="undefined"?e.left:o.left,x):o},x.width=function(e){return arguments.length?(u=e,x):u},x.height=function(e){return arguments.length?(a=e,x):a},x.color=function(t){return arguments.length?(f=e.utils.getColor(t),i.color(f),x):f},x.showControls=function(e){return arguments.length?(l=e,x):l},x.showLegend=function(e){return arguments.length?(c=e,x):c},x.reduceXTicks=function(e){return arguments.length?(h=e,x):h},x.rotateLabels=function(e){return arguments.length?(p=e,x):p},x.tooltip=function(e){return arguments.length?(v=e,x):v},x.tooltips=function(e){return arguments.length?(d=e,x):d},x.tooltipContent=function(e){return arguments.length?(v=e,x):v},x.state=function(e){return arguments.length?(y=e,x):y},x.noData=function(e){return arguments.length?(b=e,x):b},x},e.models.multiBarHorizontal=function(){function x(e){return e.each(function(e){var i=n-t.left-t.right,g=r-t.top-t.bottom,x=d3.select(this);p&&(e=d3.layout.stack().offset("zero").values(function(e){return e.values}).y(a)(e)),e=e.map(function(e,t){return e.values=e.values.map(function(e){return e.series=t,e}),e}),p&&e[0].values.map(function(t,n){var r=0,i=0;e.map(function(e){var t=e.values[n];t.size=Math.abs(t.y),t.y<0?(t.y1=i-t.size,i-=t.size):(t.y1=r,r+=t.size)})});var T=y&&b?[]:e.map(function(e){return e.values.map(function(e,t){return{x:u(e,t),y:a(e,t),y0:e.y0,y1:e.y1}})});s.domain(y||d3.merge(T).map(function(e){return e.x})).rangeBands([0,g],.1),o.domain(b||d3.extent(d3.merge(T).map(function(e){return p?e.y>0?e.y1+e.y:e.y1:e.y}).concat(f))),d&&!p?o.range([o.domain()[0]<0?v:0,i-(o.domain()[1]>0?v:0)]):o.range([0,i]),E=E||s,S=S||d3.scale.linear().domain(o.domain()).range([o(0),o(0)]);var N=d3.select(this).selectAll("g.nv-wrap.nv-multibarHorizontal").data([e]),C=N.enter().append("g").attr("class","nvd3 nv-wrap nv-multibarHorizontal"),k=C.append("defs"),L=C.append("g"),A=N.select("g");L.append("g").attr("class","nv-groups"),N.attr("transform","translate("+t.left+","+t.top+")");var O=N.select(".nv-groups").selectAll(".nv-group").data(function(e){return e},function(e){return e.key});O.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),d3.transition(O.exit()).style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),O.attr("class",function(e,t){return"nv-group nv-series-"+t}).classed("hover",function(e){return e.hover}).style("fill",function(e,t){return l(e,t)}).style("stroke",function(e,t){return l(e,t)}),d3.transition(O).style("stroke-opacity",1).style("fill-opacity",.75);var M=O.selectAll("g.nv-bar").data(function(e){return e.values});M.exit().remove();var _=M.enter().append("g").attr("transform",function(t,n,r){return"translate("+S(p?t.y0:0)+","+(p?0:r*s.rangeBand()/e.length+s(u(t,n)))+")"});_.append("rect").attr("width",0).attr("height",s.rangeBand()/(p?1:e.length)),M.on("mouseover",function(t,n){d3.select(this).classed("hover",!0),w.elementMouseover({value:a(t,n),point:t,series:e[t.series],pos:[o(a(t,n)+(p?t.y0:0)),s(u(t,n))+s.rangeBand()*(p?e.length/2:t.series+.5)/e.length],pointIndex:n,seriesIndex:t.series,e:d3.event})}).on("mouseout",function(t,n){d3.select(this).classed("hover",!1),w.elementMouseout({value:a(t,n),point:t,series:e[t.series],pointIndex:n,seriesIndex:t.series,e:d3.event})}).on("click",function(t,n){w.elementClick({value:a(t,n),point:t,series:e[t.series],pos:[s(u(t,n))+s.rangeBand()*(p?e.length/2:t.series+.5)/e.length,o(a(t,n)+(p?t.y0:0))],pointIndex:n,seriesIndex:t.series,e:d3.event}),d3.event.stopPropagation()}).on("dblclick",function(t,n){w.elementDblClick({value:a(t,n),point:t,series:e[t.series],pos:[s(u(t,n))+s.rangeBand()*(p?e.length/2:t.series+.5)/e.length,o(a(t,n)+(p?t.y0:0))],pointIndex:n,seriesIndex:t.series,e:d3.event}),d3.event.stopPropagation()}),_.append("text"),d&&!p?(M.select("text").attr("text-anchor",function(e,t){return a(e,t)<0?"end":"start"}).attr("y",s.rangeBand()/(e.length*2)).attr("dy",".32em").text(function(e,t){return m(a(e,t))}),d3.transition(M).select("text").attr("x",function(e,t){return a(e,t)<0?-4:o(a(e,t))-o(0)+4})):M.selectAll("text").text(""),M.attr("class",function(e,t){return a(e,t)<0?"nv-bar negative":"nv-bar positive"}),c&&(h||(h=e.map(function(){return!0})),M.style("fill",function(e,t,n){return d3.rgb(c(e,t)).darker(h.map(function(e,t){return t}).filter(function(e,t){return!h[t]})[n]).toString()}).style("stroke",function(e,t,n){return d3.rgb(c(e,t)).darker(h.map(function(e,t){return t}).filter(function(e,t){return!h[t]})[n]).toString()})),p?d3.transition(M).attr("transform",function(e,t){return"translate("+o(e.y1)+","+s(u(e,t))+")"}).select("rect").attr("width",function(e,t){return Math.abs(o(a(e,t)+e.y0)-o(e.y0))}).attr("height",s.rangeBand()):d3.transition(M).attr("transform",function(t,n){return"translate("+(a(t,n)<0?o(a(t,n)):o(0))+","+(t.series*s.rangeBand()/e.length+s(u(t,n)))+")"}).select("rect").attr("height",s.rangeBand()/e.length).attr("width",function(e,t){return Math.max(Math.abs(o(a(e,t))-o(0)),1)}),E=s.copy(),S=o.copy()}),x}var t={top:0,right:0,bottom:0,left:0},n=960,r=500,i=Math.floor(Math.random()*1e4),s=d3.scale.ordinal(),o=d3.scale.linear(),u=function(e){return e.x},a=function(e){return e.y},f=[0],l=e.utils.defaultColor(),c=null,h,p=!1,d=!1,v=60,m=d3.format(",.2f"),g=1200,y,b,w=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout"),E,S;return x.dispatch=w,x.x=function(e){return arguments.length?(u=e,x):u},x.y=function(e){return arguments.length?(a=e,x):a},x.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,x):t},x.width=function(e){return arguments.length?(n=e,x):n},x.height=function(e){return arguments.length?(r=e,x):r},x.xScale=function(e){return arguments.length?(s=e,x):s},x.yScale=function(e){return arguments.length?(o=e,x):o},x.xDomain=function(e){return arguments.length?(y=e,x):y},x.yDomain=function(e){return arguments.length?(b=e,x):b},x.forceY=function(e){return arguments.length?(f=e,x):f},x.stacked=function(e){return arguments.length?(p=e,x):p},x.color=function(t){return arguments.length?(l=e.utils.getColor(t),x):l},x.barColor=function(t){return arguments.length?(c=e.utils.getColor(t),x):c},x.disabled=function(e){return arguments.length?(h=e,x):h},x.id=function(e){return arguments.length?(i=e,x):i},x.delay=function(e){return arguments.length?(g=e,x):g},x.showValues=function(e){return arguments.length?(d=e,x):d},x.valueFormat=function(e){return arguments.length?(m=e,x):m},x.valuePadding=function(e){return arguments.length?(v=e,x):v},x},e.models.multiBarHorizontalChart=function(){function S(e){return e.each(function(h){var d=d3.select(this),T=this,N=(u||parseInt(d.style("width"))||960)-o.left-o.right,C=(a||parseInt(d.style("height"))||400)-o.top-o.bottom;S.update=function(){e.transition().call(S)},S.container=this,g.disabled=h.map(function(e){return!!e.disabled});if(!h||!h.length||!h.filter(function(e){return e.values.length}).length){var k=d.selectAll(".nv-noData").data([y]);return k.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),k.attr("x",o.left+N/2).attr("y",o.top+C/2).text(function(e){return e}),S}d.selectAll(".nv-noData").remove(),v=t.xScale(),m=t.yScale();var L=d.selectAll("g.nv-wrap.nv-multiBarHorizontalChart").data([h]),A=L.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarHorizontalChart").append("g"),O=L.select("g");A.append("g").attr("class","nv-x nv-axis"),A.append("g").attr("class","nv-y nv-axis"),A.append("g").attr("class","nv-barsWrap"),A.append("g").attr("class","nv-legendWrap"),A.append("g").attr("class","nv-controlsWrap"),c&&(i.width(N-w()),t.barColor()&&h.forEach(function(e,t){e.color=d3.rgb("#ccc").darker(t*1.5).toString()}),O.select(".nv-legendWrap").datum(h).call(i),o.top!=i.height()&&(o.top=i.height(),C=(a||parseInt(d.style("height"))||400)-o.top-o.bottom),O.select(".nv-legendWrap").attr("transform","translate("+w()+","+ -o.top+")"));if(l){var M=[{key:"Grouped",disabled:t.stacked()},{key:"Stacked",disabled:!t.stacked()}];s.width(w()).color(["#444","#444","#444"]),O.select(".nv-controlsWrap").datum(M).attr("transform","translate(0,"+ -o.top+")").call(s)}L.attr("transform","translate("+o.left+","+o.top+")"),t.disabled(h.map(function(e){return e.disabled})).width(N).height(C).color(h.map(function(e,t){return e.color||f(e,t)}).filter(function(e,t){return!h[t].disabled}));var _=O.select(".nv-barsWrap").datum(h.filter(function(e){return!e.disabled}));d3.transition(_).call(t),n.scale(v).ticks(C/24).tickSize(-N,0),d3.transition(O.select(".nv-x.nv-axis")).call(n);var D=O.select(".nv-x.nv-axis").selectAll("g");D.selectAll("line, text").style("opacity",1),r.scale(m).ticks(N/100).tickSize(-C,0),O.select(".nv-y.nv-axis").attr("transform","translate(0,"+C+")"),d3.transition(O.select(".nv-y.nv-axis")).call(r),i.dispatch.on("legendClick",function(t,n){t.disabled=!t.disabled,h.filter(function(e){return!e.disabled}).length||h.map(function(e){return e.disabled=!1,L.selectAll(".nv-series").classed("disabled",!1),e}),g.disabled=h.map(function(e){return!!e.disabled}),b.stateChange(g),e.transition().call(S)}),s.dispatch.on("legendClick",function(n,r){if(!n.disabled)return;M=M.map(function(e){return e.disabled=!0,e}),n.disabled=!1;switch(n.key){case"Grouped":t.stacked(!1);break;case"Stacked":t.stacked(!0)}g.stacked=t.stacked(),b.stateChange(g),e.transition().call(S)}),b.on("tooltipShow",function(e){p&&E(e,T.parentNode)}),b.on("changeState",function(n){typeof n.disabled!="undefined"&&(h.forEach(function(e,t){e.disabled=n.disabled[t]}),g.disabled=n.disabled),typeof n.stacked!="undefined"&&(t.stacked(n.stacked),g.stacked=n.stacked),e.call(S)})}),S}var t=e.models.multiBarHorizontal(),n=e.models.axis(),r=e.models.axis(),i=e.models.legend().height(30),s=e.models.legend().height(30),o={top:30,right:20,bottom:50,left:60},u=null,a=null,f=e.utils.defaultColor(),l=!0,c=!0,h=!1,p=!0,d=function(e,t,n,r,i){return"

                              "+e+" - "+t+"

                              "+"

                              "+n+"

                              "},v,m,g={stacked:h},y="No Data Available.",b=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),w=function(){return l?180:0};t.stacked(h),n.orient("left").tickPadding(5).highlightZero(!1).showMaxMin(!1).tickFormat(function(e){return e}),r.orient("bottom").tickFormat(d3.format(",.1f"));var E=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),a=n.tickFormat()(t.x()(i.point,i.pointIndex)),f=r.tickFormat()(t.y()(i.point,i.pointIndex)),l=d(i.series.key,a,f,i,S);e.tooltip.show([o,u],l,i.value<0?"e":"w",null,s)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+o.left,e.pos[1]+o.top],b.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){b.tooltipHide(e)}),b.on("tooltipHide",function(){p&&e.tooltip.cleanup()}),S.dispatch=b,S.multibar=t,S.legend=i,S.xAxis=n,S.yAxis=r,d3.rebind(S,t,"x","y","xDomain","yDomain","forceX","forceY","clipEdge","id","delay","showValues","valueFormat","stacked","barColor"),S.margin=function(e){return arguments.length?(o.top=typeof e.top!="undefined"?e.top:o.top,o.right=typeof e.right!="undefined"?e.right:o.right,o.bottom=typeof e.bottom!="undefined"?e.bottom:o.bottom,o.left=typeof e.left!="undefined"?e.left:o.left,S):o},S.width=function(e){return arguments.length?(u=e,S):u},S.height=function(e){return arguments.length?(a=e,S):a},S.color=function(t){return arguments.length?(f=e.utils.getColor(t),i.color(f),S):f},S.showControls=function(e){return arguments.length?(l=e,S):l},S.showLegend=function(e){return arguments.length?(c=e,S):c},S.tooltip=function(e){return arguments.length?(d=e,S):d},S.tooltips=function(e){return arguments.length?(p=e,S):p},S.tooltipContent=function(e){return arguments.length?(d=e,S):d},S.state=function(e){return arguments.length?(g=e,S):g},S.noData=function(e){return arguments.length?(y=e,S):y},S},e.models.multiChart=function(){function T(e){return e.each(function(u){var f=d3.select(this),N=this,C=(r||parseInt(f.style("width"))||960)-t.left-t.right,k=(i||parseInt(f.style("height"))||400)-t.top-t.bottom,L=u.filter(function(e){return!e.disabled&&e.type=="line"&&e.yAxis==1}),A=u.filter(function(e){return!e.disabled&&e.type=="line"&&e.yAxis==2}),O=u.filter(function(e){return!e.disabled&&e.type=="bar"&&e.yAxis==1}),M=u.filter(function(e){return!e.disabled&&e.type=="bar"&&e.yAxis==2}),_=u.filter(function(e){return!e.disabled&&e.type=="area"&&e.yAxis==1}),D=u.filter(function(e){return!e.disabled&&e.type=="area"&&e.yAxis==2}),P=u.filter(function(e){return!e.disabled&&e.yAxis==1}).map(function(e){return e.values.map(function(e,t){return{x:e.x,y:e.y}})}),H=u.filter(function(e){return!e.disabled&&e.yAxis==2}).map(function(e){return e.values.map(function(e,t){return{x:e.x,y:e.y}})});a.domain(d3.extent(d3.merge(P.concat(H)),function(e){return e.x})).range([0,C]);var B=f.selectAll("g.wrap.multiChart").data([u]),j=B.enter().append("g").attr("class","wrap nvd3 multiChart").append("g");j.append("g").attr("class","x axis"),j.append("g").attr("class","y1 axis"),j.append("g").attr("class","y2 axis"),j.append("g").attr("class","lines1Wrap"),j.append("g").attr("class","lines2Wrap"),j.append("g").attr("class","bars1Wrap"),j.append("g").attr("class","bars2Wrap"),j.append("g").attr("class","stack1Wrap"),j.append("g").attr("class","stack2Wrap"),j.append("g").attr("class","legendWrap");var F=B.select("g");s&&(E.width(C/2),F.select(".legendWrap").datum(u.map(function(e){return e.originalKey=e.originalKey===undefined?e.key:e.originalKey,e.key=e.originalKey+(e.yAxis==1?"":" (right axis)"),e})).call(E),t.top!=E.height()&&(t.top=E.height(),k=(i||parseInt(f.style("height"))||400)-t.top-t.bottom),F.select(".legendWrap").attr("transform","translate("+C/2+","+ -t.top+")")),h.width(C).height(k).interpolate("monotone").color(u.map(function(e,t){return e.color||n[t%n.length]}).filter(function(e,t){return!u[t].disabled&&u[t].yAxis==1&&u[t].type=="line"})),p.width(C).height(k).interpolate("monotone").color(u.map(function(e,t){return e.color||n[t%n.length]}).filter(function(e,t){return!u[t].disabled&&u[t].yAxis==2&&u[t].type=="line"})),d.width(C).height(k).color(u.map(function(e,t){return e.color||n[t%n.length]}).filter(function(e,t){return!u[t].disabled&&u[t].yAxis==1&&u[t].type=="bar"})),v.width(C).height(k).color(u.map(function(e,t){return e.color||n[t%n.length]}).filter(function(e,t){return!u[t].disabled&&u[t].yAxis==2&&u[t].type=="bar"})),m.width(C).height(k).color(u.map(function(e,t){return e.color||n[t%n.length]}).filter(function(e,t){return!u[t].disabled&&u[t].yAxis==1&&u[t].type=="area"})),g.width(C).height(k).color(u.map(function(e,t){return e.color||n[t%n.length]}).filter(function(e,t){return!u[t].disabled&&u[t].yAxis==2&&u[t].type=="area"})),F.attr("transform","translate("+t.left+","+t.top+")");var I=F.select(".lines1Wrap").datum(L),q=F.select(".bars1Wrap").datum(O),R=F.select(".stack1Wrap").datum(_),U=F.select(".lines2Wrap").datum(A),z=F.select(".bars2Wrap").datum(M),W=F.select(".stack2Wrap").datum(D),X=_.length?_.map(function(e){return e.values}).reduce(function(e,t){return e.map(function(e,n){return{x:e.x,y:e.y+t[n].y}})}).concat([{x:0,y:0}]):[],V=D.length?D.map(function(e){return e.values}).reduce(function(e,t){return e.map(function(e,n){return{x:e.x,y:e.y+t[n].y}})}).concat([{x:0,y:0}]):[];l.domain(d3.extent(d3.merge(P).concat(X),function(e){return e.y})).range([0,k]),c.domain(d3.extent(d3.merge(H).concat(V),function(e){return e.y})).range([0,k]),h.yDomain(l.domain()),d.yDomain(l.domain()),m.yDomain(l.domain()),p.yDomain(c.domain()),v.yDomain(c.domain()),g.yDomain(c.domain()),_.length&&d3.transition(R).call(m),D.length&&d3.transition(W).call(g),O.length&&d3.transition(q).call(d),M.length&&d3.transition(z).call(v),L.length&&d3.transition(I).call(h),A.length&&d3.transition(U).call(p),y.ticks(C/100).tickSize(-k,0),F.select(".x.axis").attr("transform","translate(0,"+k+")"),d3.transition(F.select(".x.axis")).call(y),b.ticks(k/36).tickSize(-C,0),d3.transition(F.select(".y1.axis")).call(b),w.ticks(k/36).tickSize(-C,0),d3.transition(F.select(".y2.axis")).call(w),F.select(".y2.axis").style("opacity",H.length?1:0).attr("transform","translate("+a.range()[1]+",0)"),E.dispatch.on("legendClick",function(t,n){t.disabled=!t.disabled,u.filter(function(e){return!e.disabled}).length||u.map(function(e){return e.disabled=!1,B.selectAll(".series").classed("disabled",!1),e}),e.transition().call(T)}),S.on("tooltipShow",function(e){o&&x(e,N.parentNode)})}),T.update=function(){T(e)},T.container=this,T}var t={top:30,right:20,bottom:50,left:60},n=d3.scale.category20().range(),r=null,i=null,s=!0,o=!0,u=function(e,t,n,r,i){return"

                              "+e+"

                              "+"

                              "+n+" at "+t+"

                              "},a,f,a=d3.scale.linear(),l=d3.scale.linear(),c=d3.scale.linear(),h=e.models.line().yScale(l),p=e.models.line().yScale(c),d=e.models.multiBar().stacked(!1).yScale(l),v=e.models.multiBar().stacked(!1).yScale(c),m=e.models.stackedArea().yScale(l),g=e.models.stackedArea().yScale(c),y=e.models.axis().scale(a).orient("bottom").tickPadding(5),b=e.models.axis().scale(l).orient("left"),w=e.models.axis().scale(c).orient("right"),E=e.models.legend().height(30),S=d3.dispatch("tooltipShow","tooltipHide"),x=function(t,n){var r=t.pos[0]+(n.offsetLeft||0),i=t.pos[1]+(n.offsetTop||0),s=y.tickFormat()(h.x()(t.point,t.pointIndex)),o=(t.series.yAxis==2?w:b).tickFormat()(h.y()(t.point,t.pointIndex)),a=u(t.series.key,s,o,t,T);e.tooltip.show([r,i],a,undefined,undefined,n.offsetParent)};return h.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],S.tooltipShow(e)}),h.dispatch.on("elementMouseout.tooltip",function(e){S.tooltipHide(e)}),p.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],S.tooltipShow(e)}),p.dispatch.on("elementMouseout.tooltip",function(e){S.tooltipHide(e)}),d.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],S.tooltipShow(e)}),d.dispatch.on("elementMouseout.tooltip",function(e){S.tooltipHide(e)}),v.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],S.tooltipShow(e)}),v.dispatch.on("elementMouseout.tooltip",function(e){S.tooltipHide(e)}),m.dispatch.on("tooltipShow",function(e){if(!Math.round(m.y()(e.point)*100))return setTimeout(function(){d3.selectAll(".point.hover").classed("hover",!1)},0),!1;e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],S.tooltipShow(e)}),m.dispatch.on("tooltipHide",function(e){S.tooltipHide(e)}),g.dispatch.on("tooltipShow",function(e){if(!Math.round(g.y()(e.point)*100))return setTimeout(function(){d3.selectAll(".point.hover").classed("hover",!1)},0),!1;e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],S.tooltipShow(e)}),g.dispatch.on("tooltipHide",function(e){S.tooltipHide(e)}),h.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],S.tooltipShow(e)}),h.dispatch.on("elementMouseout.tooltip",function(e){S.tooltipHide(e)}),p.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],S.tooltipShow(e)}),p.dispatch.on("elementMouseout.tooltip",function(e){S.tooltipHide(e)}),S.on("tooltipHide",function(){o&&e.tooltip.cleanup()}),T.dispatch=S,T.lines1=h,T.lines2=p,T.bars1=d,T.bars2=v,T.stack1=m,T.stack2=g,T.xAxis=y,T.yAxis1=b,T.yAxis2=w,T.x=function(e){return arguments.length?(getX=e,h.x(e),d.x(e),T):getX},T.y=function(e){return arguments.length?(getY=e,h.y(e),d.y(e),T):getY},T.margin=function(e){return arguments.length?(t=e,T):t},T.width=function(e){return arguments.length?(r=e,T):r},T.height=function(e){return arguments.length?(i=e,T):i},T.color=function(e){return arguments.length?(n=e,E.color(e),T):n},T.showLegend=function(e){return arguments.length?(s=e,T):s},T.tooltips=function(e){return arguments.length?(o=e,T):o},T.tooltipContent=function(e){return arguments.length?(u=e,T):u},T},e.models.ohlcBar=function(){function E(e){return e.each(function(e){var g=n-t.left-t.right,E=r-t.top-t.bottom,S=d3.select(this);s.domain(y||d3.extent(e[0].values.map(u).concat(p))),v?s.range([g*.5/e[0].values.length,g*(e[0].values.length-.5)/e[0].values.length]):s.range([0,g]),o.domain(b||[d3.min(e[0].values.map(h).concat(d)),d3.max(e[0].values.map(c).concat(d))]).range([E,0]);if(s.domain()[0]===s.domain()[1]||o.domain()[0]===o.domain()[1])singlePoint=!0;s.domain()[0]===s.domain()[1]&&(s.domain()[0]?s.domain([s.domain()[0]-s.domain()[0]*.01,s.domain()[1]+s.domain()[1]*.01]):s.domain([-1,1])),o.domain()[0]===o.domain()[1]&&(o.domain()[0]?o.domain([o.domain()[0]+o.domain()[0]*.01,o.domain()[1]-o.domain +()[1]*.01]):o.domain([-1,1]));var T=d3.select(this).selectAll("g.nv-wrap.nv-ohlcBar").data([e[0].values]),N=T.enter().append("g").attr("class","nvd3 nv-wrap nv-ohlcBar"),C=N.append("defs"),k=N.append("g"),L=T.select("g");k.append("g").attr("class","nv-ticks"),T.attr("transform","translate("+t.left+","+t.top+")"),S.on("click",function(e,t){w.chartClick({data:e,index:t,pos:d3.event,id:i})}),C.append("clipPath").attr("id","nv-chart-clip-path-"+i).append("rect"),T.select("#nv-chart-clip-path-"+i+" rect").attr("width",g).attr("height",E),L.attr("clip-path",m?"url(#nv-chart-clip-path-"+i+")":"");var A=T.select(".nv-ticks").selectAll(".nv-tick").data(function(e){return e});A.exit().remove();var O=A.enter().append("path").attr("class",function(e,t,n){return(f(e,t)>l(e,t)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+n+"-"+t}).attr("d",function(t,n){var r=g/e[0].values.length*.9;return"m0,0l0,"+(o(f(t,n))-o(c(t,n)))+"l"+ -r/2+",0l"+r/2+",0l0,"+(o(h(t,n))-o(f(t,n)))+"l0,"+(o(l(t,n))-o(h(t,n)))+"l"+r/2+",0l"+ -r/2+",0z"}).attr("transform",function(e,t){return"translate("+s(u(e,t))+","+o(c(e,t))+")"}).on("mouseover",function(t,n){d3.select(this).classed("hover",!0),w.elementMouseover({point:t,series:e[0],pos:[s(u(t,n)),o(a(t,n))],pointIndex:n,seriesIndex:0,e:d3.event})}).on("mouseout",function(t,n){d3.select(this).classed("hover",!1),w.elementMouseout({point:t,series:e[0],pointIndex:n,seriesIndex:0,e:d3.event})}).on("click",function(e,t){w.elementClick({value:a(e,t),data:e,index:t,pos:[s(u(e,t)),o(a(e,t))],e:d3.event,id:i}),d3.event.stopPropagation()}).on("dblclick",function(e,t){w.elementDblClick({value:a(e,t),data:e,index:t,pos:[s(u(e,t)),o(a(e,t))],e:d3.event,id:i}),d3.event.stopPropagation()});A.attr("class",function(e,t,n){return(f(e,t)>l(e,t)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+n+"-"+t}),d3.transition(A).attr("transform",function(e,t){return"translate("+s(u(e,t))+","+o(c(e,t))+")"}).attr("d",function(t,n){var r=g/e[0].values.length*.9;return"m0,0l0,"+(o(f(t,n))-o(c(t,n)))+"l"+ -r/2+",0l"+r/2+",0l0,"+(o(h(t,n))-o(f(t,n)))+"l0,"+(o(l(t,n))-o(h(t,n)))+"l"+r/2+",0l"+ -r/2+",0z"})}),E}var t={top:0,right:0,bottom:0,left:0},n=960,r=500,i=Math.floor(Math.random()*1e4),s=d3.scale.linear(),o=d3.scale.linear(),u=function(e){return e.x},a=function(e){return e.y},f=function(e){return e.open},l=function(e){return e.close},c=function(e){return e.high},h=function(e){return e.low},p=[],d=[],v=!1,m=!0,g=e.utils.defaultColor(),y,b,w=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout");return E.dispatch=w,E.x=function(e){return arguments.length?(u=e,E):u},E.y=function(e){return arguments.length?(a=e,E):a},E.open=function(e){return arguments.length?(f=e,E):f},E.close=function(e){return arguments.length?(l=e,E):l},E.high=function(e){return arguments.length?(c=e,E):c},E.low=function(e){return arguments.length?(h=e,E):h},E.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,E):t},E.width=function(e){return arguments.length?(n=e,E):n},E.height=function(e){return arguments.length?(r=e,E):r},E.xScale=function(e){return arguments.length?(s=e,E):s},E.yScale=function(e){return arguments.length?(o=e,E):o},E.xDomain=function(e){return arguments.length?(y=e,E):y},E.yDomain=function(e){return arguments.length?(b=e,E):b},E.forceX=function(e){return arguments.length?(p=e,E):p},E.forceY=function(e){return arguments.length?(d=e,E):d},E.padData=function(e){return arguments.length?(v=e,E):v},E.clipEdge=function(e){return arguments.length?(m=e,E):m},E.color=function(t){return arguments.length?(g=e.utils.getColor(t),E):g},E.id=function(e){return arguments.length?(i=e,E):i},E},e.models.pie=function(){function w(e){return e.each(function(e){function D(e){var t=(e.startAngle+e.endAngle)*90/Math.PI-90;return t>90?t-180:t}function P(e){v||(e.innerRadius=0);var t=d3.interpolate(this._current,e);return this._current=t(0),function(e){return k(t(e))}}function H(e){e.innerRadius=0;var t=d3.interpolate({startAngle:0,endAngle:0},e);return function(e){return k(t(e))}}var u=n-t.left-t.right,l=r-t.top-t.bottom,w=Math.min(u,l)/2,E=w-w/5,S=d3.select(this),x=S.selectAll(".nv-wrap.nv-pie").data([i(e[0])]),T=x.enter().append("g").attr("class","nvd3 nv-wrap nv-pie nv-chart-"+a),N=T.append("g"),C=x.select("g");N.append("g").attr("class","nv-pie"),x.attr("transform","translate("+t.left+","+t.top+")"),C.select(".nv-pie").attr("transform","translate("+u/2+","+l/2+")"),S.on("click",function(e,t){b.chartClick({data:e,index:t,pos:d3.event,id:a})});var k=d3.svg.arc().outerRadius(E);g&&k.startAngle(g),y&&k.endAngle(y),v&&k.innerRadius(w/2);var L=d3.layout.pie().sort(null).value(function(e){return e.disabled?0:o(e)}),A=x.select(".nv-pie").selectAll(".nv-slice").data(L);A.exit().remove();var O=A.enter().append("g").attr("class","nv-slice").on("mouseover",function(e,t){d3.select(this).classed("hover",!0),b.elementMouseover({label:s(e.data),value:o(e.data),point:e.data,pointIndex:t,pos:[d3.event.pageX,d3.event.pageY],id:a})}).on("mouseout",function(e,t){d3.select(this).classed("hover",!1),b.elementMouseout({label:s(e.data),value:o(e.data),point:e.data,index:t,id:a})}).on("click",function(e,t){b.elementClick({label:s(e.data),value:o(e.data),point:e.data,index:t,pos:d3.event,id:a}),d3.event.stopPropagation()}).on("dblclick",function(e,t){b.elementDblClick({label:s(e.data),value:o(e.data),point:e.data,index:t,pos:d3.event,id:a}),d3.event.stopPropagation()});A.attr("fill",function(e,t){return f(e,t)}).attr("stroke",function(e,t){return f(e,t)});var M=O.append("path").each(function(e){this._current=e});d3.transition(A.select("path")).attr("d",k).attrTween("d",P);if(c){var _=d3.svg.arc().innerRadius(0);h&&(_=k),p&&(_=d3.svg.arc().outerRadius(k.outerRadius())),O.append("g").classed("nv-label",!0).each(function(e,t){var n=d3.select(this);n.attr("transform",function(e){if(m){e.outerRadius=E+10,e.innerRadius=E+15;var t=(e.startAngle+e.endAngle)/2*(180/Math.PI);return(e.startAngle+e.endAngle)/2d?s(e.data):""});var r=n.select("text").node().getBBox();n.select(".nv-label rect").attr("width",r.width+10).attr("height",r.height+10).attr("transform",function(){return"translate("+[r.x-5,r.y-5]+")"})})}}),w}var t={top:0,right:0,bottom:0,left:0},n=500,r=500,i=function(e){return e.values},s=function(e){return e.x},o=function(e){return e.y},u=function(e){return e.description},a=Math.floor(Math.random()*1e4),f=e.utils.defaultColor(),l=d3.format(",.2f"),c=!0,h=!0,p=!1,d=.02,v=!1,m=!1,g=!1,y=!1,b=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout");return w.dispatch=b,w.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,w):t},w.width=function(e){return arguments.length?(n=e,w):n},w.height=function(e){return arguments.length?(r=e,w):r},w.values=function(e){return arguments.length?(i=e,w):i},w.x=function(e){return arguments.length?(s=e,w):s},w.y=function(e){return arguments.length?(o=d3.functor(e),w):o},w.description=function(e){return arguments.length?(u=e,w):u},w.showLabels=function(e){return arguments.length?(c=e,w):c},w.labelSunbeamLayout=function(e){return arguments.length?(m=e,w):m},w.donutLabelsOutside=function(e){return arguments.length?(p=e,w):p},w.pieLabelsOutside=function(e){return arguments.length?(h=e,w):h},w.donut=function(e){return arguments.length?(v=e,w):v},w.startAngle=function(e){return arguments.length?(g=e,w):g},w.endAngle=function(e){return arguments.length?(y=e,w):y},w.id=function(e){return arguments.length?(a=e,w):a},w.color=function(t){return arguments.length?(f=e.utils.getColor(t),w):f},w.valueFormat=function(e){return arguments.length?(l=e,w):l},w.labelThreshold=function(e){return arguments.length?(d=e,w):d},w},e.models.pieChart=function(){function d(e){return e.each(function(u){var a=d3.select(this),f=this,p=(i||parseInt(a.style("width"))||960)-r.left-r.right,v=(s||parseInt(a.style("height"))||400)-r.top-r.bottom;d.update=function(){d(e)},d.container=this,l.disabled=u[0].map(function(e){return!!e.disabled});if(!u||!u.length){var m=a.selectAll(".nv-noData").data([c]);return m.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),m.attr("x",r.left+p/2).attr("y",r.top+v/2).text(function(e){return e}),d}a.selectAll(".nv-noData").remove();var g=a.selectAll("g.nv-wrap.nv-pieChart").data([u]),y=g.enter().append("g").attr("class","nvd3 nv-wrap nv-pieChart").append("g"),b=g.select("g");y.append("g").attr("class","nv-pieWrap"),y.append("g").attr("class","nv-legendWrap"),o&&(n.width(p).key(t.x()),g.select(".nv-legendWrap").datum(t.values()(u[0])).call(n),r.top!=n.height()&&(r.top=n.height(),v=(s||parseInt(a.style("height"))||400)-r.top-r.bottom),g.select(".nv-legendWrap").attr("transform","translate(0,"+ -r.top+")")),g.attr("transform","translate("+r.left+","+r.top+")"),t.width(p).height(v);var w=b.select(".nv-pieWrap").datum(u);d3.transition(w).call(t),n.dispatch.on("legendClick",function(n,r,i){n.disabled=!n.disabled,t.values()(u[0]).filter(function(e){return!e.disabled}).length||t.values()(u[0]).map(function(e){return e.disabled=!1,g.selectAll(".nv-series").classed("disabled",!1),e}),l.disabled=u[0].map(function(e){return!!e.disabled}),h.stateChange(l),e.transition().call(d)}),t.dispatch.on("elementMouseout.tooltip",function(e){h.tooltipHide(e)}),h.on("changeState",function(t){typeof t.disabled!="undefined"&&(u[0].forEach(function(e,n){e.disabled=t.disabled[n]}),l.disabled=t.disabled),e.call(d)})}),d}var t=e.models.pie(),n=e.models.legend(),r={top:30,right:20,bottom:20,left:20},i=null,s=null,o=!0,u=e.utils.defaultColor(),a=!0,f=function(e,t,n,r){return"

                              "+e+"

                              "+"

                              "+t+"

                              "},l={},c="No Data Available.",h=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),p=function(n,r){var i=t.description()(n.point)||t.x()(n.point),s=n.pos[0]+(r&&r.offsetLeft||0),o=n.pos[1]+(r&&r.offsetTop||0),u=t.valueFormat()(t.y()(n.point)),a=f(i,u,n,d);e.tooltip.show([s,o],a,n.value<0?"n":"s",null,r)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+r.left,e.pos[1]+r.top],h.tooltipShow(e)}),h.on("tooltipShow",function(e){a&&p(e)}),h.on("tooltipHide",function(){a&&e.tooltip.cleanup()}),d.legend=n,d.dispatch=h,d.pie=t,d3.rebind(d,t,"valueFormat","values","x","y","description","id","showLabels","donutLabelsOutside","pieLabelsOutside","donut","labelThreshold"),d.margin=function(e){return arguments.length?(r.top=typeof e.top!="undefined"?e.top:r.top,r.right=typeof e.right!="undefined"?e.right:r.right,r.bottom=typeof e.bottom!="undefined"?e.bottom:r.bottom,r.left=typeof e.left!="undefined"?e.left:r.left,d):r},d.width=function(e){return arguments.length?(i=e,d):i},d.height=function(e){return arguments.length?(s=e,d):s},d.color=function(r){return arguments.length?(u=e.utils.getColor(r),n.color(u),t.color(u),d):u},d.showLegend=function(e){return arguments.length?(o=e,d):o},d.tooltips=function(e){return arguments.length?(a=e,d):a},d.tooltipContent=function(e){return arguments.length?(f=e,d):f},d.state=function(e){return arguments.length?(l=e,d):l},d.noData=function(e){return arguments.length?(c=e,d):c},d},e.models.scatter=function(){function B(e){return e.each(function(e){function V(){if(!g)return!1;var i,a=d3.merge(e.map(function(e,t){return e.values.map(function(e,n){return[o(f(e,n))*(Math.random()/1e12+1),u(l(e,n))*(Math.random()/1e12+1),t,n,e]}).filter(function(e,t){return y(e[4],t)})}));if(O===!0){if(S){var c=q.select("defs").selectAll(".nv-point-clips").data([s]).enter();c.append("clipPath").attr("class","nv-point-clips").attr("id","nv-points-clip-"+s);var h=q.select("#nv-points-clip-"+s).selectAll("circle").data(a);h.enter().append("circle").attr("r",x),h.exit().remove(),h.attr("cx",function(e){return e[0]}).attr("cy",function(e){return e[1]}),q.select(".nv-point-paths").attr("clip-path","url(#nv-points-clip-"+s+")")}a.push([o.range()[0]-20,u.range()[0]-20,null,null]),a.push([o.range()[1]+20,u.range()[1]+20,null,null]),a.push([o.range()[0]-20,u.range()[0]+20,null,null]),a.push([o.range()[1]+20,u.range()[1]-20,null,null]);var p=d3.geom.polygon([[-10,-10],[-10,r+10],[n+10,r+10],[n+10,-10]]),d=d3.geom.voronoi(a).map(function(e,t){return{data:p.clip(e),series:a[t][2],point:a[t][3]}}),v=q.select(".nv-point-paths").selectAll("path").data(d);v.enter().append("path").attr("class",function(e,t){return"nv-path-"+t}),v.exit().remove(),v.attr("d",function(e){return"M"+e.data.join("L")+"Z"}),v.on("click",function(n){if(H)return 0;var r=e[n.series],i=r.values[n.point];A.elementClick({point:i,series:r,pos:[o(f(i,n.point))+t.left,u(l(i,n.point))+t.top],seriesIndex:n.series,pointIndex:n.point})}).on("mouseover",function(n){if(H)return 0;var r=e[n.series],i=r.values[n.point];A.elementMouseover({point:i,series:r,pos:[o(f(i,n.point))+t.left,u(l(i,n.point))+t.top],seriesIndex:n.series,pointIndex:n.point})}).on("mouseout",function(t,n){if(H)return 0;var r=e[t.series],i=r.values[t.point];A.elementMouseout({point:i,series:r,seriesIndex:t.series,pointIndex:t.point})})}else q.select(".nv-groups").selectAll(".nv-group").selectAll(".nv-point").on("click",function(n,r){if(H||!e[n.series])return 0;var i=e[n.series],s=i.values[r];A.elementClick({point:s,series:i,pos:[o(f(s,r))+t.left,u(l(s,r))+t.top],seriesIndex:n.series,pointIndex:r})}).on("mouseover",function(n,r){if(H||!e[n.series])return 0;var i=e[n.series],s=i.values[r];A.elementMouseover({point:s,series:i,pos:[o(f(s,r))+t.left,u(l(s,r))+t.top],seriesIndex:n.series,pointIndex:r})}).on("mouseout",function(t,n){if(H||!e[t.series])return 0;var r=e[t.series],i=r.values[n];A.elementMouseout({point:i,series:r,seriesIndex:t.series,pointIndex:n})});H=!1}var B=n-t.left-t.right,j=r-t.top-t.bottom,F=d3.select(this);e=e.map(function(e,t){return e.values=e.values.map(function(e){return e.series=t,e}),e});var I=T&&N&&C?[]:d3.merge(e.map(function(e){return e.values.map(function(e,t){return{x:f(e,t),y:l(e,t),size:c(e,t)}})}));o.domain(T||d3.extent(I.map(function(e){return e.x}).concat(d))),b&&e[0]?o.range([(B*w+B)/(2*e[0].values.length),B-B*(1+w)/(2*e[0].values.length)]):o.range([0,B]),u.domain(N||d3.extent(I.map(function(e){return e.y}).concat(v))).range([j,0]),a.domain(C||d3.extent(I.map(function(e){return e.size}).concat(m))).range(k||[16,256]);if(o.domain()[0]===o.domain()[1]||u.domain()[0]===u.domain()[1])L=!0;o.domain()[0]===o.domain()[1]&&(o.domain()[0]?o.domain([o.domain()[0]-o.domain()[0]*.01,o.domain()[1]+o.domain()[1]*.01]):o.domain([-1,1])),u.domain()[0]===u.domain()[1]&&(u.domain()[0]?u.domain([u.domain()[0]+u.domain()[0]*.01,u.domain()[1]-u.domain()[1]*.01]):u.domain([-1,1])),M=M||o,_=_||u,D=D||a;var q=F.selectAll("g.nv-wrap.nv-scatter").data([e]),R=q.enter().append("g").attr("class","nvd3 nv-wrap nv-scatter nv-chart-"+s+(L?" nv-single-point":"")),U=R.append("defs"),W=R.append("g"),X=q.select("g");W.append("g").attr("class","nv-groups"),W.append("g").attr("class","nv-point-paths"),q.attr("transform","translate("+t.left+","+t.top+")"),U.append("clipPath").attr("id","nv-edge-clip-"+s).append("rect"),q.select("#nv-edge-clip-"+s+" rect").attr("width",B).attr("height",j),X.attr("clip-path",E?"url(#nv-edge-clip-"+s+")":""),H=!0;var $=q.select(".nv-groups").selectAll(".nv-group").data(function(e){return e},function(e){return e.key});$.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),d3.transition($.exit()).style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),$.attr("class",function(e,t){return"nv-group nv-series-"+t}).classed("hover",function(e){return e.hover}),d3.transition($).style("fill",function(e,t){return i(e,t)}).style("stroke",function(e,t){return i(e,t)}).style("stroke-opacity",1).style("fill-opacity",.5);if(p){var J=$.selectAll("circle.nv-point").data(function(e){return e.values});J.enter().append("circle").attr("cx",function(e,t){return M(f(e,t))}).attr("cy",function(e,t){return _(l(e,t))}).attr("r",function(e,t){return Math.sqrt(a(c(e,t))/Math.PI)}),J.exit().remove(),d3.transition($.exit().selectAll("path.nv-point")).attr("cx",function(e,t){return o(f(e,t))}).attr("cy",function(e,t){return u(l(e,t))}).remove(),J.attr("class",function(e,t){return"nv-point nv-point-"+t}),d3.transition(J).attr("cx",function(e,t){return o(f(e,t))}).attr("cy",function(e,t){return u(l(e,t))}).attr("r",function(e,t){return Math.sqrt(a(c(e,t))/Math.PI)})}else{var J=$.selectAll("path.nv-point").data(function(e){return e.values});J.enter().append("path").attr("transform",function(e,t){return"translate("+M(f(e,t))+","+_(l(e,t))+")"}).attr("d",d3.svg.symbol().type(h).size(function(e,t){return a(c(e,t))})),J.exit().remove(),d3.transition($.exit().selectAll("path.nv-point")).attr("transform",function(e,t){return"translate("+o(f(e,t))+","+u(l(e,t))+")"}).remove(),J.attr("class",function(e,t){return"nv-point nv-point-"+t}),d3.transition(J).attr("transform",function(e,t){return"translate("+o(f(e,t))+","+u(l(e,t))+")"}).attr("d",d3.svg.symbol().type(h).size(function(e,t){return a(c(e,t))}))}clearTimeout(P),P=setTimeout(V,300),M=o.copy(),_=u.copy(),D=a.copy()}),B}var t={top:0,right:0,bottom:0,left:0},n=960,r=500,i=e.utils.defaultColor(),s=Math.floor(Math.random()*1e5),o=d3.scale.linear(),u=d3.scale.linear(),a=d3.scale.linear(),f=function(e){return e.x},l=function(e){return e.y},c=function(e){return e.size||1},h=function(e){return e.shape||"circle"},p=!0,d=[],v=[],m=[],g=!0,y=function(e){return!e.notActive},b=!1,w=.1,E=!1,S=!0,x=function(){return 25},T=null,N=null,C=null,k=null,L=!1,A=d3.dispatch("elementClick","elementMouseover","elementMouseout"),O=!0,M,_,D,P,H=!1;return A.on("elementMouseover.point",function(e){g&&d3.select(".nv-chart-"+s+" .nv-series-"+e.seriesIndex+" .nv-point-"+e.pointIndex).classed("hover",!0)}),A.on("elementMouseout.point",function(e){g&&d3.select(".nv-chart-"+s+" .nv-series-"+e.seriesIndex+" .nv-point-"+e.pointIndex).classed("hover",!1)}),B.dispatch=A,B.x=function(e){return arguments.length?(f=d3.functor(e),B):f},B.y=function(e){return arguments.length?(l=d3.functor(e),B):l},B.size=function(e){return arguments.length?(c=d3.functor(e),B):c},B.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,B):t},B.width=function(e){return arguments.length?(n=e,B):n},B.height=function(e){return arguments.length?(r=e,B):r},B.xScale=function(e){return arguments.length?(o=e,B):o},B.yScale=function(e){return arguments.length?(u=e,B):u},B.zScale=function(e){return arguments.length?(a=e,B):a},B.xDomain=function(e){return arguments.length?(T=e,B):T},B.yDomain=function(e){return arguments.length?(N=e,B):N},B.sizeDomain=function(e){return arguments.length?(C=e,B):C},B.sizeRange=function(e){return arguments.length?(k=e,B):k},B.forceX=function(e){return arguments.length?(d=e,B):d},B.forceY=function(e){return arguments.length?(v=e,B):v},B.forceSize=function(e){return arguments.length?(m=e,B):m},B.interactive=function(e){return arguments.length?(g=e,B):g},B.pointActive=function(e){return arguments.length?(y=e,B):y},B.padData=function(e){return arguments.length?(b=e,B):b},B.padDataOuter=function(e){return arguments.length?(w=e,B):w},B.clipEdge=function(e){return arguments.length?(E=e,B):E},B.clipVoronoi=function(e){return arguments.length?(S=e,B):S},B.useVoronoi=function(e){return arguments.length?(O=e,O===!1&&(S=!1),B):O},B.clipRadius=function(e){return arguments.length?(x=e,B):x},B.color=function(t){return arguments.length?(i=e.utils.getColor(t),B):i},B.shape=function(e){return arguments.length?(h=e,B):h},B.onlyCircles=function(e){return arguments.length?(p=e,B):p},B.id=function(e){return arguments.length?(s=e,B):s},B.singlePoint=function(e){return arguments.length?(L=e,B):L},B},e.models.scatterChart=function(){function D(e){return e.each(function(x){function z(){if(E)return q.select(".nv-point-paths").style("pointer-events","all"),!1;q.select(".nv-point-paths").style("pointer-events","none");var e=d3.mouse(this);h.distortion(w).focus(e[0]),p.distortion(w).focus(e[1]),q.select(".nv-scatterWrap").call(t),q.select(".nv-x.nv-axis").call(n),q.select(".nv-y.nv-axis").call(r),q.select(".nv-distributionX").datum(x.filter(function(e){return!e.disabled})).call(o),q.select(".nv-distributionY").datum(x.filter(function(e){return!e.disabled})).call(u)}var T=d3.select(this),N=this,P=(f||parseInt(T.style("width"))||960)-a.left-a.right,H=(l||parseInt(T.style("height"))||400)-a.top-a.bottom;D.update=function(){D(e)},D.container=this,L.disabled=x.map(function(e){return!!e.disabled});if(!x||!x.length||!x.filter(function(e){return e.values.length}).length){var B=T.selectAll(".nv-noData").data([k]);return B.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),B.attr("x",a.left+P/2).attr("y",a.top+H/2).text(function(e){return e}),D}T.selectAll(".nv-noData").remove(),A=A||h,O=O||p;var j=T.selectAll("g.nv-wrap.nv-scatterChart").data([x]),F=j.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+t.id()),I=F.append("g"),q=j.select("g");I.append("rect").attr("class","nvd3 nv-background"),I.append("g").attr("class","nv-x nv-axis"),I.append("g").attr("class","nv-y nv-axis"),I.append("g").attr("class","nv-scatterWrap"),I.append("g").attr("class","nv-distWrap"),I.append("g").attr("class","nv-legendWrap"),I.append("g").attr("class","nv-controlsWrap"),y&&(i.width(P/2),j.select(".nv-legendWrap").datum(x).call(i),a.top!=i.height()&&(a.top=i.height(),H=(l||parseInt(T.style("height"))||400)-a.top-a.bottom),j.select(".nv-legendWrap").attr("transform","translate("+P/2+","+ -a.top+")")),b&&(s.width(180).color(["#444"]),q.select(".nv-controlsWrap").datum(_).attr("transform","translate(0,"+ -a.top+")").call(s)),j.attr("transform","translate("+a.left+","+a.top+")"),t.width(P).height(H).color(x.map(function(e,t){return e.color||c(e,t)}).filter(function(e,t){return!x[t].disabled})),j.select(".nv-scatterWrap").datum(x.filter(function(e){return!e.disabled})).call(t);if(d){var R=h.domain()[1]-h.domain()[0];h.domain([h.domain()[0]-d*R,h.domain()[1]+d*R])}if(v){var U=p.domain()[1]-p.domain()[0];p.domain([p.domain()[0]-v*U,p.domain()[1]+v*U])}n.scale(h).ticks(n.ticks()&&n.ticks().length?n.ticks():P/100).tickSize(-H,0),q.select(".nv-x.nv-axis").attr("transform","translate(0,"+p.range()[0]+")").call(n),r.scale(p).ticks(r.ticks()&&r.ticks().length?r.ticks():H/36).tickSize(-P,0),q.select(".nv-y.nv-axis").call(r),m&&(o.getData(t.x()).scale(h).width(P).color(x.map(function(e,t){return e.color||c(e,t)}).filter(function(e,t){return!x[t].disabled})),I.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),q.select(".nv-distributionX").attr("transform","translate(0,"+p.range()[0]+")").datum(x.filter(function(e){return!e.disabled})).call(o)),g&&(u.getData(t.y()).scale(p).width(H).color(x.map(function(e,t){return e.color||c(e,t)}).filter(function(e,t){return!x[t].disabled})),I.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),q.select(".nv-distributionY").attr("transform","translate(-"+u.size()+",0)").datum(x.filter(function(e){return!e.disabled})).call(u)),d3.fisheye&&(q.select(".nv-background").attr("width",P).attr("height",H),q.select(".nv-background").on("mousemove",z),q.select(".nv-background").on("click",function(){E=!E}),t.dispatch.on("elementClick.freezeFisheye",function(){E=!E})),s.dispatch.on("legendClick",function(i,s){i.disabled=!i.disabled,w=i.disabled?0:2.5,q.select(".nv-background").style("pointer-events",i.disabled?"none":"all"),q.select(".nv-point-paths").style("pointer-events",i.disabled?"all":"none"),i.disabled?(h.distortion(w).focus(0),p.distortion(w).focus(0),q.select(".nv-scatterWrap").call(t),q.select(".nv-x.nv-axis").call(n),q.select(".nv-y.nv-axis").call(r)):E=!1,D(e)}),i.dispatch.on("legendClick",function(t,n,r){t.disabled=!t.disabled,x.filter(function(e){return!e.disabled}).length||x.map(function(e){return e.disabled=!1,j.selectAll(".nv-series").classed("disabled",!1),e}),L.disabled=x.map(function(e){return!!e.disabled}),C.stateChange(L),D(e)}),t.dispatch.on("elementMouseover.tooltip",function(e){d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-distx-"+e.pointIndex).attr("y1",e.pos[1]-H),d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-disty-"+e.pointIndex).attr("x2",e.pos[0]+o.size()),e.pos=[e.pos[0]+a.left,e.pos[1]+a.top],C.tooltipShow(e)}),C.on("tooltipShow",function(e){S&&M(e,N.parentNode)}),C.on("changeState",function(t){typeof t.disabled!="undefined"&&(x.forEach(function(e,n){e.disabled=t.disabled[n]}),L.disabled=t.disabled),e.call(D)}),A=h.copy(),O=p.copy()}),D}var t=e.models.scatter(),n=e.models.axis(),r=e.models.axis(),i=e.models.legend(),s=e.models.legend(),o=e.models.distribution(),u=e.models.distribution(),a={top:30,right:20,bottom:50,left:75},f=null,l=null,c=e.utils.defaultColor(),h=d3.fisheye?d3.fisheye.scale(d3.scale.linear).distortion(0):t.xScale(),p=d3.fisheye?d3.fisheye.scale(d3.scale.linear).distortion(0):t.yScale(),d=0,v=0,m=!1,g=!1,y=!0,b=!!d3.fisheye,w=0,E=!1,S=!0,x=function(e,t,n){return""+t+""},T=function(e,t,n){return""+n+""},N=null,C=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),k="No Data Available.";t.xScale(h).yScale(p),n.orient("bottom").tickPadding(10),r.orient("left").tickPadding(10),o.axis("x"),u.axis("y");var L={},A,O,M=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),f=i.pos[0]+(s.offsetLeft||0),l=p.range()[0]+a.top+(s.offsetTop||0),c=h.range()[0]+a.left+(s.offsetLeft||0),d=i.pos[1]+(s.offsetTop||0),v=n.tickFormat()(t.x()(i.point,i.pointIndex)),m=r.tickFormat()(t.y()(i.point,i.pointIndex));x!=null&&e.tooltip.show([f,l],x(i.series.key,v,m,i,D),"n",1,s,"x-nvtooltip"),T!=null&&e.tooltip.show([c,d],T(i.series.key,v,m,i,D),"e",1,s,"y-nvtooltip"),N!=null&&e.tooltip.show([o,u],N(i.series.key,v,m,i,D),i.value<0?"n":"s",null,s)},_=[{key:"Magnify",disabled:!0}];return t.dispatch.on("elementMouseout.tooltip",function(e){C.tooltipHide(e),d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-distx-"+e.pointIndex).attr("y1",0),d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-disty-"+e.pointIndex).attr("x2",u.size())}),C.on("tooltipHide",function(){S&&e.tooltip.cleanup()}),D.dispatch=C,D.scatter=t,D.legend=i,D.controls=s,D.xAxis=n,D.yAxis=r,D.distX=o,D.distY=u,d3.rebind(D,t,"id","interactive","pointActive","x","y","shape","size","xScale","yScale","zScale","xDomain","yDomain","sizeDomain","sizeRange","forceX","forceY","forceSize","clipVoronoi","clipRadius","useVoronoi"),D.margin=function(e){return arguments.length?(a.top=typeof e.top!="undefined"?e.top:a.top,a.right=typeof e.right!="undefined"?e.right:a.right,a.bottom=typeof e.bottom!="undefined"?e.bottom:a.bottom,a.left=typeof e.left!="undefined"?e.left:a.left,D):a},D.width=function(e){return arguments.length?(f=e,D):f},D.height=function(e){return arguments.length?(l=e,D):l},D.color=function(t){return arguments.length?(c=e.utils.getColor(t),i.color(c),o.color(c),u.color(c),D):c},D.showDistX=function(e){return arguments.length?(m=e,D):m},D.showDistY=function(e){return arguments.length?(g=e,D):g},D.showControls=function(e){return arguments.length?(b=e,D):b},D.showLegend=function(e){return arguments.length?(y=e,D):y},D.fisheye=function(e){return arguments.length?(w=e,D):w},D.xPadding=function(e){return arguments.length?(d=e,D):d},D.yPadding=function(e){return arguments.length?(v=e,D):v},D.tooltips=function(e){return arguments.length?(S=e,D):S},D.tooltipContent=function(e){return arguments.length?(N=e,D):N},D.tooltipXContent=function(e){return arguments.length?(x=e,D):x},D.tooltipYContent=function(e){return arguments.length?(T=e,D):T},D.state=function(e){return arguments.length?(L=e,D):L},D.noData=function(e){return arguments.length?(k=e,D):k},D},e.models.scatterPlusLineChart=function(){function M(e){return e.each(function(E){function R(){if(b)return F.select(".nv-point-paths").style("pointer-events","all"),!1;F.select(".nv-point-paths").style("pointer-events","none");var e=d3.mouse(this);h.distortion(y).focus(e[0]),p.distortion(y).focus(e[1]),F.select(".nv-scatterWrap").datum(E.filter(function(e){return!e.disabled})).call(t),F.select(".nv-x.nv-axis").call(n),F.select(".nv-y.nv-axis").call(r),F.select(".nv-distributionX").datum(E.filter(function(e){return!e.disabled})).call(o),F.select(".nv-distributionY").datum(E.filter(function(e){return!e.disabled})).call(u)}var S=d3.select(this),x=this,_=(f||parseInt(S.style("width"))||960)-a.left-a.right,D=(l||parseInt(S.style("height"))||400)-a.top-a.bottom;M.update=function(){M(e)},M.container=this,C.disabled=E.map(function(e){return!!e.disabled});if(!E||!E.length||!E.filter(function(e){return e.values.length}).length){var P=S.selectAll(".nv-noData").data([N]);return P.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),P.attr("x",a.left+_/2).attr("y",a.top+D/2).text(function(e){return e}),M}S.selectAll(".nv-noData").remove(),h=t.xScale(),p=t.yScale(),k=k||h,L=L||p;var H=S.selectAll("g.nv-wrap.nv-scatterChart").data([E]),B=H.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+t.id()),j=B.append("g"),F=H.select("g");j.append("rect").attr("class","nvd3 nv-background"),j.append("g").attr("class","nv-x nv-axis"),j.append("g").attr("class","nv-y nv-axis"),j.append("g").attr("class","nv-scatterWrap"),j.append("g").attr("class","nv-regressionLinesWrap"),j.append("g").attr("class","nv-distWrap"),j.append("g").attr("class","nv-legendWrap"),j.append("g").attr("class","nv-controlsWrap"),H.attr("transform","translate("+a.left+","+a.top+")"),m&&(i.width(_/2),H.select(".nv-legendWrap").datum(E).call(i),a.top!=i.height()&&(a.top=i.height(),D=(l||parseInt(S.style("height"))||400)-a.top-a.bottom),H.select(".nv-legendWrap").attr("transform","translate("+_/2+","+ -a.top+")")),g&&(s.width(180).color(["#444"]),F.select(".nv-controlsWrap").datum(O).attr("transform","translate(0,"+ -a.top+")").call(s)),t.width(_).height(D).color(E.map(function(e,t){return e.color||c(e,t)}).filter(function(e,t){return!E[t].disabled})),H.select(".nv-scatterWrap").datum(E.filter(function(e){return!e.disabled})).call(t),H.select(".nv-regressionLinesWrap").attr("clip-path","url(#nv-edge-clip-"+t.id()+")");var I=H.select(".nv-regressionLinesWrap").selectAll(".nv-regLines").data(function(e){return e}),q=I.enter().append("g").attr("class","nv-regLines").append("line").attr("class","nv-regLine").style("stroke-opacity",0);I.selectAll(".nv-regLines line").attr("x1",h.range()[0]).attr("x2",h.range()[1]).attr("y1",function(e,t){return p(h.domain()[0]*e.slope+e.intercept)}).attr("y2",function(e,t){return p(h.domain()[1]*e.slope+e.intercept)}).style("stroke",function(e,t,n){return c(e,n)}).style("stroke-opacity",function(e,t){return e.disabled||typeof e.slope=="undefined"||typeof e.intercept=="undefined"?0:1}),n.scale(h).ticks(n.ticks()?n.ticks():_/100).tickSize(-D,0),F.select(".nv-x.nv-axis").attr("transform","translate(0,"+p.range()[0]+")").call(n),r.scale(p).ticks(r.ticks()?r.ticks():D/36).tickSize(-_,0),F.select(".nv-y.nv-axis").call(r),d&&(o.getData(t.x()).scale(h).width(_).color(E.map(function(e,t){return e.color||c(e,t)}).filter(function(e,t){return!E[t].disabled})),j.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),F.select(".nv-distributionX").attr("transform","translate(0,"+p.range()[0]+")").datum +(E.filter(function(e){return!e.disabled})).call(o)),v&&(u.getData(t.y()).scale(p).width(D).color(E.map(function(e,t){return e.color||c(e,t)}).filter(function(e,t){return!E[t].disabled})),j.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),F.select(".nv-distributionY").attr("transform","translate(-"+u.size()+",0)").datum(E.filter(function(e){return!e.disabled})).call(u)),d3.fisheye&&(F.select(".nv-background").attr("width",_).attr("height",D),F.select(".nv-background").on("mousemove",R),F.select(".nv-background").on("click",function(){b=!b}),t.dispatch.on("elementClick.freezeFisheye",function(){b=!b})),s.dispatch.on("legendClick",function(i,s){i.disabled=!i.disabled,y=i.disabled?0:2.5,F.select(".nv-background").style("pointer-events",i.disabled?"none":"all"),F.select(".nv-point-paths").style("pointer-events",i.disabled?"all":"none"),i.disabled?(h.distortion(y).focus(0),p.distortion(y).focus(0),F.select(".nv-scatterWrap").call(t),F.select(".nv-x.nv-axis").call(n),F.select(".nv-y.nv-axis").call(r)):b=!1,M(e)}),i.dispatch.on("legendClick",function(t,n,r){t.disabled=!t.disabled,E.filter(function(e){return!e.disabled}).length||E.map(function(e){return e.disabled=!1,H.selectAll(".nv-series").classed("disabled",!1),e}),C.disabled=E.map(function(e){return!!e.disabled}),T.stateChange(C),M(e)}),t.dispatch.on("elementMouseover.tooltip",function(e){d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-distx-"+e.pointIndex).attr("y1",e.pos[1]-D),d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-disty-"+e.pointIndex).attr("x2",e.pos[0]+o.size()),e.pos=[e.pos[0]+a.left,e.pos[1]+a.top],T.tooltipShow(e)}),T.on("tooltipShow",function(e){w&&A(e,x.parentNode)}),T.on("changeState",function(t){typeof t.disabled!="undefined"&&(E.forEach(function(e,n){e.disabled=t.disabled[n]}),C.disabled=t.disabled),e.call(M)}),k=h.copy(),L=p.copy()}),M}var t=e.models.scatter(),n=e.models.axis(),r=e.models.axis(),i=e.models.legend(),s=e.models.legend(),o=e.models.distribution(),u=e.models.distribution(),a={top:30,right:20,bottom:50,left:75},f=null,l=null,c=e.utils.defaultColor(),h=d3.fisheye?d3.fisheye.scale(d3.scale.linear).distortion(0):t.xScale(),p=d3.fisheye?d3.fisheye.scale(d3.scale.linear).distortion(0):t.yScale(),d=!1,v=!1,m=!0,g=!!d3.fisheye,y=0,b=!1,w=!0,E=function(e,t,n){return""+t+""},S=function(e,t,n){return""+n+""},x=function(e,t,n,r){return"

                              "+e+"

                              "+"

                              "+r+"

                              "},T=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),N="No Data Available.";t.xScale(h).yScale(p),n.orient("bottom").tickPadding(10),r.orient("left").tickPadding(10),o.axis("x"),u.axis("y");var C={},k,L,A=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),f=i.pos[0]+(s.offsetLeft||0),l=p.range()[0]+a.top+(s.offsetTop||0),c=h.range()[0]+a.left+(s.offsetLeft||0),d=i.pos[1]+(s.offsetTop||0),v=n.tickFormat()(t.x()(i.point,i.pointIndex)),m=r.tickFormat()(t.y()(i.point,i.pointIndex));E!=null&&e.tooltip.show([f,l],E(i.series.key,v,m,i,M),"n",1,s,"x-nvtooltip"),S!=null&&e.tooltip.show([c,d],S(i.series.key,v,m,i,M),"e",1,s,"y-nvtooltip"),x!=null&&e.tooltip.show([o,u],x(i.series.key,v,m,i.point.tooltip,i,M),i.value<0?"n":"s",null,s)},O=[{key:"Magnify",disabled:!0}];return t.dispatch.on("elementMouseout.tooltip",function(e){T.tooltipHide(e),d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-distx-"+e.pointIndex).attr("y1",0),d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-disty-"+e.pointIndex).attr("x2",u.size())}),T.on("tooltipHide",function(){w&&e.tooltip.cleanup()}),M.dispatch=T,M.scatter=t,M.legend=i,M.controls=s,M.xAxis=n,M.yAxis=r,M.distX=o,M.distY=u,d3.rebind(M,t,"id","interactive","pointActive","x","y","shape","size","xScale","yScale","zScale","xDomain","yDomain","sizeDomain","sizeRange","forceX","forceY","forceSize","clipVoronoi","clipRadius","useVoronoi"),M.margin=function(e){return arguments.length?(a.top=typeof e.top!="undefined"?e.top:a.top,a.right=typeof e.right!="undefined"?e.right:a.right,a.bottom=typeof e.bottom!="undefined"?e.bottom:a.bottom,a.left=typeof e.left!="undefined"?e.left:a.left,M):a},M.width=function(e){return arguments.length?(f=e,M):f},M.height=function(e){return arguments.length?(l=e,M):l},M.color=function(t){return arguments.length?(c=e.utils.getColor(t),i.color(c),o.color(c),u.color(c),M):c},M.showDistX=function(e){return arguments.length?(d=e,M):d},M.showDistY=function(e){return arguments.length?(v=e,M):v},M.showControls=function(e){return arguments.length?(g=e,M):g},M.showLegend=function(e){return arguments.length?(m=e,M):m},M.fisheye=function(e){return arguments.length?(y=e,M):y},M.tooltips=function(e){return arguments.length?(w=e,M):w},M.tooltipContent=function(e){return arguments.length?(x=e,M):x},M.tooltipXContent=function(e){return arguments.length?(E=e,M):E},M.tooltipYContent=function(e){return arguments.length?(S=e,M):S},M.state=function(e){return arguments.length?(C=e,M):C},M.noData=function(e){return arguments.length?(N=e,M):N},M},e.models.sparkline=function(){function h(e){return e.each(function(e){var i=n-t.left-t.right,h=r-t.top-t.bottom,p=d3.select(this);s.domain(l||d3.extent(e,u)).range([0,i]),o.domain(c||d3.extent(e,a)).range([h,0]);var d=p.selectAll("g.nv-wrap.nv-sparkline").data([e]),v=d.enter().append("g").attr("class","nvd3 nv-wrap nv-sparkline"),m=v.append("g"),g=d.select("g");d.attr("transform","translate("+t.left+","+t.top+")");var b=d.selectAll("path").data(function(e){return[e]});b.enter().append("path"),b.exit().remove(),b.style("stroke",function(e,t){return e.color||f(e,t)}).attr("d",d3.svg.line().x(function(e,t){return s(u(e,t))}).y(function(e,t){return o(a(e,t))}));var w=d.selectAll("circle.nv-point").data(function(e){function n(t){if(t!=-1){var n=e[t];return n.pointIndex=t,n}return null}var t=e.map(function(e,t){return a(e,t)}),r=n(t.lastIndexOf(o.domain()[1])),i=n(t.indexOf(o.domain()[0])),s=n(t.length-1);return[i,r,s].filter(function(e){return e!=null})});w.enter().append("circle"),w.exit().remove(),w.attr("cx",function(e,t){return s(u(e,e.pointIndex))}).attr("cy",function(e,t){return o(a(e,e.pointIndex))}).attr("r",2).attr("class",function(e,t){return u(e,e.pointIndex)==s.domain()[1]?"nv-point nv-currentValue":a(e,e.pointIndex)==o.domain()[0]?"nv-point nv-minValue":"nv-point nv-maxValue"})}),h}var t={top:2,right:0,bottom:2,left:0},n=400,r=32,i=!0,s=d3.scale.linear(),o=d3.scale.linear(),u=function(e){return e.x},a=function(e){return e.y},f=e.utils.getColor(["#000"]),l,c;return h.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,h):t},h.width=function(e){return arguments.length?(n=e,h):n},h.height=function(e){return arguments.length?(r=e,h):r},h.x=function(e){return arguments.length?(u=d3.functor(e),h):u},h.y=function(e){return arguments.length?(a=d3.functor(e),h):a},h.xScale=function(e){return arguments.length?(s=e,h):s},h.yScale=function(e){return arguments.length?(o=e,h):o},h.xDomain=function(e){return arguments.length?(l=e,h):l},h.yDomain=function(e){return arguments.length?(c=e,h):c},h.animate=function(e){return arguments.length?(i=e,h):i},h.color=function(t){return arguments.length?(f=e.utils.getColor(t),h):f},h},e.models.sparklinePlus=function(){function v(e){return e.each(function(c){function O(){if(a)return;var e=C.selectAll(".nv-hoverValue").data(u),r=e.enter().append("g").attr("class","nv-hoverValue").style("stroke-opacity",0).style("fill-opacity",0);e.exit().transition().duration(250).style("stroke-opacity",0).style("fill-opacity",0).remove(),e.attr("transform",function(e){return"translate("+s(t.x()(c[e],e))+",0)"}).transition().duration(250).style("stroke-opacity",1).style("fill-opacity",1);if(!u.length)return;r.append("line").attr("x1",0).attr("y1",-n.top).attr("x2",0).attr("y2",b),r.append("text").attr("class","nv-xValue").attr("x",-6).attr("y",-n.top).attr("text-anchor","end").attr("dy",".9em"),C.select(".nv-hoverValue .nv-xValue").text(f(t.x()(c[u[0]],u[0]))),r.append("text").attr("class","nv-yValue").attr("x",6).attr("y",-n.top).attr("text-anchor","start").attr("dy",".9em"),C.select(".nv-hoverValue .nv-yValue").text(l(t.y()(c[u[0]],u[0])))}function M(){function r(e,n){var r=Math.abs(t.x()(e[0],0)-n),i=0;for(var s=0;s"+e+""+"

                              "+n+" on "+t+"

                              "},d,v,m=d3.format(",.2f"),g={style:t.style()},y="No Data Available.",b=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),w=250;n.orient("bottom").tickPadding(7),r.orient("left"),t.scatter.pointActive(function(e){return!!Math.round(t.y()(e)*100)});var E=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),a=n.tickFormat()(t.x()(i.point,i.pointIndex)),f=r.tickFormat()(t.y()(i.point,i.pointIndex)),l=p(i.series.key,a,f,i,S);e.tooltip.show([o,u],l,i.value<0?"n":"s",null,s)};return t.dispatch.on("tooltipShow",function(e){e.pos=[e.pos[0]+o.left,e.pos[1]+o.top],b.tooltipShow(e)}),t.dispatch.on("tooltipHide",function(e){b.tooltipHide(e)}),b.on("tooltipHide",function(){h&&e.tooltip.cleanup()}),S.dispatch=b,S.stacked=t,S.legend=i,S.controls=s,S.xAxis=n,S.yAxis=r,d3.rebind(S,t,"x","y","size","xScale","yScale","xDomain","yDomain","sizeDomain","interactive","offset","order","style","clipEdge","forceX","forceY","forceSize","interpolate"),S.margin=function(e){return arguments.length?(o.top=typeof e.top!="undefined"?e.top:o.top,o.right=typeof e.right!="undefined"?e.right:o.right,o.bottom=typeof e.bottom!="undefined"?e.bottom:o.bottom,o.left=typeof e.left!="undefined"?e.left:o.left,S):o},S.width=function(e){return arguments.length?(u=e,S):getWidth},S.height=function(e){return arguments.length?(a=e,S):getHeight},S.color=function(n){return arguments.length?(f=e.utils.getColor(n),i.color(f),t.color(f),S):f},S.showControls=function(e){return arguments.length?(l=e,S):l},S.showLegend=function(e){return arguments.length?(c=e,S):c},S.tooltip=function(e){return arguments.length?(p=e,S):p},S.tooltips=function(e){return arguments.length?(h=e,S):h},S.tooltipContent=function(e){return arguments.length?(p=e,S):p},S.state=function(e){return arguments.length?(g=e,S):g},S.noData=function(e){return arguments.length?(y=e,S):y},r.setTickFormat=r.tickFormat,r.tickFormat=function(e){return arguments.length?(m=e,r):m},S}})(); \ No newline at end of file diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/core.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/core.js new file mode 100755 index 0000000000..b65cb6928d --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/core.js @@ -0,0 +1,118 @@ + +var nv = window.nv || {}; + +nv.version = '0.0.1a'; +nv.dev = true //set false when in production + +window.nv = nv; + +nv.tooltip = {}; // For the tooltip system +nv.utils = {}; // Utility subsystem +nv.models = {}; //stores all the possible models/components +nv.charts = {}; //stores all the ready to use charts +nv.graphs = []; //stores all the graphs currently on the page +nv.logs = {}; //stores some statistics and potential error messages + +nv.dispatch = d3.dispatch('render_start', 'render_end'); + +// ************************************************************************* +// Development render timers - disabled if dev = false + +if (nv.dev) { + nv.dispatch.on('render_start', function(e) { + nv.logs.startTime = +new Date(); + }); + + nv.dispatch.on('render_end', function(e) { + nv.logs.endTime = +new Date(); + nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime; + nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times + }); +} + +// ******************************************** +// Public Core NV functions + +// Logs all arguments, and returns the last so you can test things in place +nv.log = function() { + if (nv.dev && console.log && console.log.apply) + console.log.apply(console, arguments) + else if (nv.dev && console.log && Function.prototype.bind) { + var log = Function.prototype.bind.call(console.log, console); + log.apply(console, arguments); + } + return arguments[arguments.length - 1]; +}; + + +nv.render = function render(step) { + step = step || 1; // number of graphs to generate in each timeout loop + + nv.render.active = true; + nv.dispatch.render_start(); + + setTimeout(function() { + var chart, graph; + + for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) { + chart = graph.generate(); + if (typeof graph.callback == typeof(Function)) graph.callback(chart); + nv.graphs.push(chart); + } + + nv.render.queue.splice(0, i); + + if (nv.render.queue.length) setTimeout(arguments.callee, 0); + else { nv.render.active = false; nv.dispatch.render_end(); } + }, 0); +}; + +nv.render.active = false; +nv.render.queue = []; + +nv.addGraph = function(obj) { + if (typeof arguments[0] === typeof(Function)) + obj = {generate: arguments[0], callback: arguments[1]}; + + nv.render.queue.push(obj); + + if (!nv.render.active) nv.render(); +}; + +nv.identity = function(d) { return d; }; + +nv.strip = function(s) { return s.replace(/(\s|&)/g,''); }; + +function daysInMonth(month,year) { + return (new Date(year, month+1, 0)).getDate(); +} + +function d3_time_range(floor, step, number) { + return function(t0, t1, dt) { + var time = floor(t0), times = []; + if (time < t0) step(time); + if (dt > 1) { + while (time < t1) { + var date = new Date(+time); + if ((number(date) % dt === 0)) times.push(date); + step(time); + } + } else { + while (time < t1) { times.push(new Date(+time)); step(time); } + } + return times; + }; +} + +d3.time.monthEnd = function(date) { + return new Date(date.getFullYear(), date.getMonth(), 0); +}; + +d3.time.monthEnds = d3_time_range(d3.time.monthEnd, function(date) { + date.setUTCDate(date.getUTCDate() + 1); + date.setDate(daysInMonth(date.getMonth() + 1, date.getFullYear())); + }, function(date) { + return date.getMonth(); + } +); + diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/intro.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/intro.js new file mode 100644 index 0000000000..af50383ec5 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/intro.js @@ -0,0 +1 @@ +(function(){ diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/axis.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/axis.js new file mode 100644 index 0000000000..123cfbbd4a --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/axis.js @@ -0,0 +1,398 @@ +nv.models.axis = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var axis = d3.svg.axis() + ; + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 75 //only used for tickLabel currently + , height = 60 //only used for tickLabel currently + , scale = d3.scale.linear() + , axisLabelText = null + , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes + , highlightZero = true + , rotateLabels = 0 + , rotateYLabel = true + , staggerLabels = false + , isOrdinal = false + , ticks = null + ; + + axis + .scale(scale) + .orient('bottom') + .tickFormat(function(d) { return d }) + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var scale0; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + //------------------------------------------------------------ + + + if (ticks !== null) + axis.ticks(ticks); + else if (axis.orient() == 'top' || axis.orient() == 'bottom') + axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100); + + + //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component + + + d3.transition(g) + .call(axis); + + scale0 = scale0 || axis.scale(); + + var fmt = axis.tickFormat(); + if (fmt == null) { + fmt = scale0.tickFormat(); + } + + var axisLabel = g.selectAll('text.nv-axislabel') + .data([axisLabelText || null]); + axisLabel.exit().remove(); + switch (axis.orient()) { + case 'top': + axisLabel.enter().append('text').attr('class', 'nv-axislabel') + .attr('text-anchor', 'middle') + .attr('y', 0); + var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0])); + axisLabel + .attr('x', w/2); + if (showMaxMin) { + var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + .data(scale.domain()); + axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text'); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(' + scale(d) + ',0)' + }) + .select('text') + .attr('dy', '0em') + .attr('y', -axis.tickPadding()) + .attr('text-anchor', 'middle') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + d3.transition(axisMaxMin) + .attr('transform', function(d,i) { + return 'translate(' + scale.range()[i] + ',0)' + }); + } + break; + case 'bottom': + var xLabelMargin = 36; + var maxTextWidth = 30; + var xTicks = g.selectAll('g').select("text"); + if (rotateLabels%360) { + //Calculate the longest xTick width + xTicks.each(function(d,i){ + var width = this.getBBox().width; + if(width > maxTextWidth) maxTextWidth = width; + }); + //Convert to radians before calculating sin. Add 30 to margin for healthy padding. + var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180)); + var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30; + //Rotate all xTicks + xTicks + .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' }) + .attr('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end'); + } + axisLabel.enter().append('text').attr('class', 'nv-axislabel') + .attr('text-anchor', 'middle') + .attr('y', xLabelMargin); + var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0])); + axisLabel + .attr('x', w/2); + if (showMaxMin) { + //if (showMaxMin && !isOrdinal) { + var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + //.data(scale.domain()) + .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]); + axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text'); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)' + }) + .select('text') + .attr('dy', '.71em') + .attr('y', axis.tickPadding()) + .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' }) + .attr('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + d3.transition(axisMaxMin) + .attr('transform', function(d,i) { + //return 'translate(' + scale.range()[i] + ',0)' + //return 'translate(' + scale(d) + ',0)' + return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)' + }); + } + if (staggerLabels) + xTicks + .attr('transform', function(d,i) { return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' }); + + break; + case 'right': + axisLabel.enter().append('text').attr('class', 'nv-axislabel') + .attr('text-anchor', rotateYLabel ? 'middle' : 'begin') + .attr('transform', rotateYLabel ? 'rotate(90)' : '') + .attr('y', rotateYLabel ? (-Math.max(margin.right,width) + 12) : -10); //TODO: consider calculating this based on largest tick width... OR at least expose this on chart + axisLabel + .attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding()); + if (showMaxMin) { + var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + .data(scale.domain()); + axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text') + .style('opacity', 0); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(0,' + scale(d) + ')' + }) + .select('text') + .attr('dy', '.32em') + .attr('y', 0) + .attr('x', axis.tickPadding()) + .attr('text-anchor', 'start') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + d3.transition(axisMaxMin) + .attr('transform', function(d,i) { + return 'translate(0,' + scale.range()[i] + ')' + }) + .select('text') + .style('opacity', 1); + } + break; + case 'left': + /* + //For dynamically placing the label. Can be used with dynamically-sized chart axis margins + var yTicks = g.selectAll('g').select("text"); + yTicks.each(function(d,i){ + var labelPadding = this.getBBox().width + axis.tickPadding() + 16; + if(labelPadding > width) width = labelPadding; + }); + */ + axisLabel.enter().append('text').attr('class', 'nv-axislabel') + .attr('text-anchor', rotateYLabel ? 'middle' : 'end') + .attr('transform', rotateYLabel ? 'rotate(-90)' : '') + .attr('y', rotateYLabel ? (-Math.max(margin.left,width) + 12) : -10); //TODO: consider calculating this based on largest tick width... OR at least expose this on chart + axisLabel + .attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding()); + if (showMaxMin) { + var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + .data(scale.domain()); + axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text') + .style('opacity', 0); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(0,' + scale0(d) + ')' + }) + .select('text') + .attr('dy', '.32em') + .attr('y', 0) + .attr('x', -axis.tickPadding()) + .attr('text-anchor', 'end') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + d3.transition(axisMaxMin) + .attr('transform', function(d,i) { + return 'translate(0,' + scale.range()[i] + ')' + }) + .select('text') + .style('opacity', 1); + } + break; + } + axisLabel + .text(function(d) { return d }); + + + if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) { + //check if max and min overlap other values, if so, hide the values that overlap + g.selectAll('g') // the g's wrapping each tick + .each(function(d,i) { + d3.select(this).select('text').attr('opacity', 1); + if (scale(d) < scale.range()[1] + 10 || scale(d) > scale.range()[0] - 10) { // 10 is assuming text height is 16... if d is 0, leave it! + if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL + d3.select(this).attr('opacity', 0); + + d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!! + } + }); + + //if Max and Min = 0 only show min, Issue #281 + if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) + wrap.selectAll('g.nv-axisMaxMin') + .style('opacity', function(d,i) { return !i ? 1 : 0 }); + + } + + if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) { + var maxMinRange = []; + wrap.selectAll('g.nv-axisMaxMin') + .each(function(d,i) { + try { + if (i) // i== 1, max position + maxMinRange.push(scale(d) - this.getBBox().width - 4) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case) + else // i==0, min position + maxMinRange.push(scale(d) + this.getBBox().width + 4) + }catch (err) { + if (i) // i== 1, max position + maxMinRange.push(scale(d) - 4) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case) + else // i==0, min position + maxMinRange.push(scale(d) + 4) + } + }); + g.selectAll('g') // the g's wrapping each tick + .each(function(d,i) { + if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) { + if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL + d3.select(this).remove(); + else + d3.select(this).select('text').remove(); // Don't remove the ZERO line!! + } + }); + } + + + //highlight zero line ... Maybe should not be an option and should just be in CSS? + if (highlightZero) + g.selectAll('line.tick') + .filter(function(d) { return !parseFloat(Math.round(d*100000)/1000000) }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique + .classed('zero', true); + + //store old scales for use in transitions on update + scale0 = scale.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.axis = axis; + + d3.rebind(chart, axis, 'orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat'); + d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); //these are also accessible by chart.scale(), but added common ones directly for ease of use + + chart.margin = function(_) { + if(!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + } + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.ticks = function(_) { + if (!arguments.length) return ticks; + ticks = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.axisLabel = function(_) { + if (!arguments.length) return axisLabelText; + axisLabelText = _; + return chart; + } + + chart.showMaxMin = function(_) { + if (!arguments.length) return showMaxMin; + showMaxMin = _; + return chart; + } + + chart.highlightZero = function(_) { + if (!arguments.length) return highlightZero; + highlightZero = _; + return chart; + } + + chart.scale = function(_) { + if (!arguments.length) return scale; + scale = _; + axis.scale(scale); + isOrdinal = typeof scale.rangeBands === 'function'; + d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); + return chart; + } + + chart.rotateYLabel = function(_) { + if(!arguments.length) return rotateYLabel; + rotateYLabel = _; + return chart; + } + + chart.rotateLabels = function(_) { + if(!arguments.length) return rotateLabels; + rotateLabels = _; + return chart; + } + + chart.staggerLabels = function(_) { + if (!arguments.length) return staggerLabels; + staggerLabels = _; + return chart; + }; + + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/backup/bullet.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/backup/bullet.js new file mode 100644 index 0000000000..86ebeb0f19 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/backup/bullet.js @@ -0,0 +1,250 @@ + +// Chart design based on the recommendations of Stephen Few. Implementation +// based on the work of Clint Ivy, Jamie Love, and Jason Davies. +// http://projects.instantcognition.com/protovis/bulletchart/ + +nv.models.bullet = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , orient = 'left' // TODO top & bottom + , reverse = false + , ranges = function(d) { return d.ranges } + , markers = function(d) { return d.markers } + , measures = function(d) { return d.measures } + , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) + , width = 380 + , height = 30 + , tickFormat = null + , dispatch = d3.dispatch('elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(d, i) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this), + mainGroup = nv.log(this.parentNode.parentNode).getAttribute('transform'), + heightFromTop = nv.log(parseInt(mainGroup.replace(/.*,(\d+)\)/,"$1"))); //TODO: There should be a smarter way to get this value + + var rangez = ranges.call(this, d, i).slice().sort(d3.descending), + markerz = markers.call(this, d, i).slice().sort(d3.descending), + measurez = measures.call(this, d, i).slice().sort(d3.descending); + + + //------------------------------------------------------------ + // Setup Scales + + // Compute the new x-scale. + var MaxX = Math.max(rangez[0] ? rangez[0]:0 , markerz[0] ? markerz[0] : 0 , measurez[0] ? measurez[0] : 0) + var x1 = d3.scale.linear() + .domain([0, MaxX]).nice() // TODO: need to allow forceX and forceY, and xDomain, yDomain + .range(reverse ? [availableWidth, 0] : [0, availableWidth]); + + // Retrieve the old x-scale, if this is an update. + var x0 = this.__chart__ || d3.scale.linear() + .domain([0, Infinity]) + .range(x1.range()); + + // Stash the new scale. + this.__chart__ = x1; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) + w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; + + + // Update the range rects. + var range = g.selectAll('rect.nv-range') + .data(rangez); + + range.enter().append('rect') + .attr('class', function(d, i) { return 'nv-range nv-s' + i; }) + .attr('width', w0) + .attr('height', availableHeight) + .attr('x', reverse ? x0 : 0) + .on('mouseover', function(d,i) { + dispatch.elementMouseover({ + value: d, + label: (i <= 0) ? 'Maximum' : (i > 1) ? 'Minimum' : 'Mean', //TODO: make these labels a variable + pos: [x1(d), heightFromTop] + }) + }) + .on('mouseout', function(d,i) { + dispatch.elementMouseout({ + value: d, + label: (i <= 0) ? 'Minimum' : (i >=1) ? 'Maximum' : 'Mean' //TODO: make these labels a variable + }) + }) + + d3.transition(range) + .attr('x', reverse ? x1 : 0) + .attr('width', w1) + .attr('height', availableHeight); + + + // Update the measure rects. + var measure = g.selectAll('rect.nv-measure') + .data(measurez); + + measure.enter().append('rect') + .attr('class', function(d, i) { return 'nv-measure nv-s' + i; }) + .attr('width', w0) + .attr('height', availableHeight / 3) + .attr('x', reverse ? x0 : 0) + .attr('y', availableHeight / 3) + .on('mouseover', function(d) { + dispatch.elementMouseover({ + value: d, + label: 'Current', //TODO: make these labels a variable + pos: [x1(d), heightFromTop] + }) + }) + .on('mouseout', function(d) { + dispatch.elementMouseout({ + value: d, + label: 'Current' //TODO: make these labels a variable + }) + }) + + d3.transition(measure) + .attr('width', w1) + .attr('height', availableHeight / 3) + .attr('x', reverse ? x1 : 0) + .attr('y', availableHeight / 3); + + + + // Update the marker lines. + var marker = g.selectAll('path.nv-markerTriangle') + .data(markerz); + + var h3 = availableHeight / 6; + marker.enter().append('path') + .attr('class', 'nv-markerTriangle') + .attr('transform', function(d) { return 'translate(' + x0(d) + ',' + (availableHeight / 2) + ')' }) + .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z') + .on('mouseover', function(d,i) { + dispatch.elementMouseover({ + value: d, + label: 'Previous', + pos: [x1(d), heightFromTop] + }) + }) + .on('mouseout', function(d,i) { + dispatch.elementMouseout({ + value: d, + label: 'Previous' + }) + }); + + d3.transition(marker) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',' + (availableHeight / 2) + ')' }); + + marker.exit().remove(); + + }); + + d3.timer.flush(); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + // left, right, top, bottom + chart.orient = function(_) { + if (!arguments.length) return orient; + orient = _; + reverse = orient == 'right' || orient == 'bottom'; + return chart; + }; + + // ranges (bad, satisfactory, good) + chart.ranges = function(_) { + if (!arguments.length) return ranges; + ranges = _; + return chart; + }; + + // markers (previous, goal) + chart.markers = function(_) { + if (!arguments.length) return markers; + markers = _; + return chart; + }; + + // measures (actual, forecast) + chart.measures = function(_) { + if (!arguments.length) return measures; + measures = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.tickFormat = function(_) { + if (!arguments.length) return tickFormat; + tickFormat = _; + return chart; + }; + + //============================================================ + + + return chart; +}; + + diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/backup/bulletChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/backup/bulletChart.js new file mode 100644 index 0000000000..a2a0f077cc --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/backup/bulletChart.js @@ -0,0 +1,349 @@ + +// Chart design based on the recommendations of Stephen Few. Implementation +// based on the work of Clint Ivy, Jamie Love, and Jason Davies. +// http://projects.instantcognition.com/protovis/bulletchart/ +nv.models.bulletChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var bullet = nv.models.bullet() + ; + + var orient = 'left' // TODO top & bottom + , reverse = false + , margin = {top: 5, right: 40, bottom: 20, left: 120} + , ranges = function(d) { return d.ranges } + , markers = function(d) { return d.markers } + , measures = function(d) { return d.measures } + , width = null + , height = 55 + , tickFormat = null + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + e.label + '

                              ' + + '

                              ' + e.value + '

                              ' + } + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, parentElement) { + var offsetElement = parentElement.parentNode.parentNode, + left = e.pos[0] + offsetElement.offsetLeft + margin.left, + top = e.pos[1] + offsetElement.offsetTop + margin.top; + + var content = '

                              ' + e.label + '

                              ' + + '

                              ' + e.value + '

                              '; + + nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement.parentNode); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(d, i) { + var container = d3.select(this); + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + that = this; + + + chart.update = function() { chart(selection) }; + chart.container = this; + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + /* + // Disabled until I figure out a better way to check for no data with the bullet chart + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + */ + + //------------------------------------------------------------ + + + + var rangez = ranges.call(this, d, i).slice().sort(d3.descending), + markerz = markers.call(this, d, i).slice().sort(d3.descending), + measurez = measures.call(this, d, i).slice().sort(d3.descending); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-bulletWrap'); + gEnter.append('g').attr('class', 'nv-titles'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + ( margin.top + i*height )+ ')'); + + //------------------------------------------------------------ + + + // Compute the new x-scale. + var MaxX = Math.max(rangez[0] ? rangez[0]:0 , markerz[0] ? markerz[0] : 0 , measurez[0] ? measurez[0] : 0) + var x1 = d3.scale.linear() + .domain([0, MaxX]).nice() // TODO: need to allow forceX and forceY, and xDomain, yDomain + .range(reverse ? [availableWidth, 0] : [0, availableWidth]); + + // Retrieve the old x-scale, if this is an update. + var x0 = this.__chart__ || d3.scale.linear() + .domain([0, Infinity]) + .range(x1.range()); + + // Stash the new scale. + this.__chart__ = x1; + + /* + // Derive width-scales from the x-scales. + var w0 = bulletWidth(x0), + w1 = bulletWidth(x1); + + function bulletWidth(x) { + var x0 = x(0); + return function(d) { + return Math.abs(x(d) - x(0)); + }; + } + + function bulletTranslate(x) { + return function(d) { + return 'translate(' + x(d) + ',0)'; + }; + } + */ + + var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) + w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; + + + var title = gEnter.select('.nv-titles').append("g") + .attr("text-anchor", "end") + .attr("transform", "translate(-6," + (height - margin.top - margin.bottom) / 2 + ")"); + title.append("text") + .attr("class", "nv-title") + .text(function(d) { return d.title; }); + + title.append("text") + .attr("class", "nv-subtitle") + .attr("dy", "1em") + .text(function(d) { return d.subtitle; }); + + + + bullet + .width(availableWidth) + .height(availableHeight) + + var bulletWrap = g.select('.nv-bulletWrap'); + + d3.transition(bulletWrap).call(bullet); + + + + // Compute the tick format. + var format = tickFormat || x1.tickFormat(8); + + // Update the tick groups. + var tick = g.selectAll('g.nv-tick') + .data(x1.ticks(8), function(d) { + return this.textContent || format(d); + }); + + // Initialize the ticks with the old scale, x0. + var tickEnter = tick.enter().append('g') + .attr('class', 'nv-tick') + .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' }) + .style('opacity', 1e-6); + + tickEnter.append('line') + .attr('y1', availableHeight) + .attr('y2', availableHeight * 7 / 6); + + tickEnter.append('text') + .attr('text-anchor', 'middle') + .attr('dy', '1em') + .attr('y', availableHeight * 7 / 6) + .text(format); + + // Transition the entering ticks to the new scale, x1. + d3.transition(tickEnter) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1); + + // Transition the updating ticks to the new scale, x1. + var tickUpdate = d3.transition(tick) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1); + + tickUpdate.select('line') + .attr('y1', availableHeight) + .attr('y2', availableHeight * 7 / 6); + + tickUpdate.select('text') + .attr('y', availableHeight * 7 / 6); + + // Transition the exiting ticks to the new scale, x1. + d3.transition(tick.exit()) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1e-6) + .remove(); + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + //============================================================ + + }); + + d3.timer.flush(); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + bullet.dispatch.on('elementMouseover.tooltip', function(e) { + dispatch.tooltipShow(e); + }); + + bullet.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.bullet = bullet; + + // left, right, top, bottom + chart.orient = function(x) { + if (!arguments.length) return orient; + orient = x; + reverse = orient == 'right' || orient == 'bottom'; + return chart; + }; + + // ranges (bad, satisfactory, good) + chart.ranges = function(x) { + if (!arguments.length) return ranges; + ranges = x; + return chart; + }; + + // markers (previous, goal) + chart.markers = function(x) { + if (!arguments.length) return markers; + markers = x; + return chart; + }; + + // measures (actual, forecast) + chart.measures = function(x) { + if (!arguments.length) return measures; + measures = x; + return chart; + }; + + chart.width = function(x) { + if (!arguments.length) return width; + width = x; + return chart; + }; + + chart.height = function(x) { + if (!arguments.length) return height; + height = x; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.tickFormat = function(x) { + if (!arguments.length) return tickFormat; + tickFormat = x; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +}; + + diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/boilerplate.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/boilerplate.js new file mode 100644 index 0000000000..b7effc4f5e --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/boilerplate.js @@ -0,0 +1,102 @@ + +nv.models.chartName = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + + var margin = {top: 30, right: 10, bottom: 10, left: 10} + , width = 960 + , height = 500 + , color = nv.utils.getColor(d3.scale.category20c().range()) + , dispatch = d3.dispatch('stateChange', 'changeState') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup Scales + + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-chartName').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-chartName'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + gEnter.append('g').attr('class', 'nv-mainWrap'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + + chart.dispatch = dispatch; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_) + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/bullet.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/bullet.js new file mode 100644 index 0000000000..23afa8dbed --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/bullet.js @@ -0,0 +1,377 @@ + +// Chart design based on the recommendations of Stephen Few. Implementation +// based on the work of Clint Ivy, Jamie Love, and Jason Davies. +// http://projects.instantcognition.com/protovis/bulletchart/ + +nv.models.bullet = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , orient = 'left' // TODO top & bottom + , reverse = false + , ranges = function(d) { return d.ranges } + , markers = function(d) { return d.markers } + , measures = function(d) { return d.measures } + , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) + , width = 380 + , height = 30 + , tickFormat = null + , color = nv.utils.getColor(['#1f77b4']) + , dispatch = d3.dispatch('elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(d, i) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + var rangez = ranges.call(this, d, i).slice().sort(d3.descending), + markerz = markers.call(this, d, i).slice().sort(d3.descending), + measurez = measures.call(this, d, i).slice().sort(d3.descending); + + + //------------------------------------------------------------ + // Setup Scales + + // Compute the new x-scale. + var x1 = d3.scale.linear() + .domain( d3.extent(d3.merge([forceX, rangez])) ) + .range(reverse ? [availableWidth, 0] : [0, availableWidth]); + + // Retrieve the old x-scale, if this is an update. + var x0 = this.__chart__ || d3.scale.linear() + .domain([0, Infinity]) + .range(x1.range()); + + // Stash the new scale. + this.__chart__ = x1; + + + var rangeMin = d3.min(rangez), //rangez[2] + rangeMax = d3.max(rangez), //rangez[0] + rangeAvg = rangez[1]; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('rect').attr('class', 'nv-range nv-rangeMax'); + gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg'); + gEnter.append('rect').attr('class', 'nv-range nv-rangeMin'); + gEnter.append('rect').attr('class', 'nv-measure'); + gEnter.append('path').attr('class', 'nv-markerTriangle'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) + w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; + var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) }, + xp1 = function(d) { return d < 0 ? x1(d) : x1(0) }; + + + g.select('rect.nv-rangeMax') + .attr('height', availableHeight) + .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin)) + .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin)) + .datum(rangeMax > 0 ? rangeMax : rangeMin) + /* + .attr('x', rangeMin < 0 ? + rangeMax > 0 ? + x1(rangeMin) + : x1(rangeMax) + : x1(0)) + */ + + g.select('rect.nv-rangeAvg') + .attr('height', availableHeight) + .attr('width', w1(rangeAvg)) + .attr('x', xp1(rangeAvg)) + .datum(rangeAvg) + /* + .attr('width', rangeMax <= 0 ? + x1(rangeMax) - x1(rangeAvg) + : x1(rangeAvg) - x1(rangeMin)) + .attr('x', rangeMax <= 0 ? + x1(rangeAvg) + : x1(rangeMin)) + */ + + g.select('rect.nv-rangeMin') + .attr('height', availableHeight) + .attr('width', w1(rangeMax)) + .attr('x', xp1(rangeMax)) + .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax)) + .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax)) + .datum(rangeMax > 0 ? rangeMin : rangeMax) + /* + .attr('width', rangeMax <= 0 ? + x1(rangeAvg) - x1(rangeMin) + : x1(rangeMax) - x1(rangeAvg)) + .attr('x', rangeMax <= 0 ? + x1(rangeMin) + : x1(rangeAvg)) + */ + + g.select('rect.nv-measure') + .style('fill', color) + .attr('height', availableHeight / 3) + .attr('y', availableHeight / 3) + .attr('width', measurez < 0 ? + x1(0) - x1(measurez[0]) + : x1(measurez[0]) - x1(0)) + .attr('x', xp1(measurez)) + .on('mouseover', function() { + dispatch.elementMouseover({ + value: measurez[0], + label: 'Current', + pos: [x1(measurez[0]), availableHeight/2] + }) + }) + .on('mouseout', function() { + dispatch.elementMouseout({ + value: measurez[0], + label: 'Current' + }) + }) + + var h3 = availableHeight / 6; + if (markerz[0]) { + g.selectAll('path.nv-markerTriangle') + .attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' }) + .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z') + .on('mouseover', function() { + dispatch.elementMouseover({ + value: markerz[0], + label: 'Previous', + pos: [x1(markerz[0]), availableHeight/2] + }) + }) + .on('mouseout', function() { + dispatch.elementMouseout({ + value: markerz[0], + label: 'Previous' + }) + }); + } else { + g.selectAll('path.nv-markerTriangle').remove(); + } + + + wrap.selectAll('.nv-range') + .on('mouseover', function(d,i) { + var label = !i ? "Maximum" : i == 1 ? "Mean" : "Minimum"; + + dispatch.elementMouseover({ + value: d, + label: label, + pos: [x1(d), availableHeight/2] + }) + }) + .on('mouseout', function(d,i) { + var label = !i ? "Maximum" : i == 1 ? "Mean" : "Minimum"; + + dispatch.elementMouseout({ + value: d, + label: label + }) + }) + +/* // THIS IS THE PREVIOUS BULLET IMPLEMENTATION, WILL REMOVE SHORTLY + // Update the range rects. + var range = g.selectAll('rect.nv-range') + .data(rangez); + + range.enter().append('rect') + .attr('class', function(d, i) { return 'nv-range nv-s' + i; }) + .attr('width', w0) + .attr('height', availableHeight) + .attr('x', reverse ? x0 : 0) + .on('mouseover', function(d,i) { + dispatch.elementMouseover({ + value: d, + label: (i <= 0) ? 'Maximum' : (i > 1) ? 'Minimum' : 'Mean', //TODO: make these labels a variable + pos: [x1(d), availableHeight/2] + }) + }) + .on('mouseout', function(d,i) { + dispatch.elementMouseout({ + value: d, + label: (i <= 0) ? 'Minimum' : (i >=1) ? 'Maximum' : 'Mean' //TODO: make these labels a variable + }) + }) + + d3.transition(range) + .attr('x', reverse ? x1 : 0) + .attr('width', w1) + .attr('height', availableHeight); + + + // Update the measure rects. + var measure = g.selectAll('rect.nv-measure') + .data(measurez); + + measure.enter().append('rect') + .attr('class', function(d, i) { return 'nv-measure nv-s' + i; }) + .style('fill', function(d,i) { return color(d,i ) }) + .attr('width', w0) + .attr('height', availableHeight / 3) + .attr('x', reverse ? x0 : 0) + .attr('y', availableHeight / 3) + .on('mouseover', function(d) { + dispatch.elementMouseover({ + value: d, + label: 'Current', //TODO: make these labels a variable + pos: [x1(d), availableHeight/2] + }) + }) + .on('mouseout', function(d) { + dispatch.elementMouseout({ + value: d, + label: 'Current' //TODO: make these labels a variable + }) + }) + + d3.transition(measure) + .attr('width', w1) + .attr('height', availableHeight / 3) + .attr('x', reverse ? x1 : 0) + .attr('y', availableHeight / 3); + + + + // Update the marker lines. + var marker = g.selectAll('path.nv-markerTriangle') + .data(markerz); + + var h3 = availableHeight / 6; + marker.enter().append('path') + .attr('class', 'nv-markerTriangle') + .attr('transform', function(d) { return 'translate(' + x0(d) + ',' + (availableHeight / 2) + ')' }) + .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z') + .on('mouseover', function(d,i) { + dispatch.elementMouseover({ + value: d, + label: 'Previous', + pos: [x1(d), availableHeight/2] + }) + }) + .on('mouseout', function(d,i) { + dispatch.elementMouseout({ + value: d, + label: 'Previous' + }) + }); + + d3.transition(marker) + .attr('transform', function(d) { return 'translate(' + (x1(d) - x1(0)) + ',' + (availableHeight / 2) + ')' }); + + marker.exit().remove(); +*/ + + }); + + // d3.timer.flush(); // Not needed? + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + // left, right, top, bottom + chart.orient = function(_) { + if (!arguments.length) return orient; + orient = _; + reverse = orient == 'right' || orient == 'bottom'; + return chart; + }; + + // ranges (bad, satisfactory, good) + chart.ranges = function(_) { + if (!arguments.length) return ranges; + ranges = _; + return chart; + }; + + // markers (previous, goal) + chart.markers = function(_) { + if (!arguments.length) return markers; + markers = _; + return chart; + }; + + // measures (actual, forecast) + chart.measures = function(_) { + if (!arguments.length) return measures; + measures = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.tickFormat = function(_) { + if (!arguments.length) return tickFormat; + tickFormat = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + //============================================================ + + + return chart; +}; + + diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/bulletChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/bulletChart.js new file mode 100644 index 0000000000..005825ccc7 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/bulletChart.js @@ -0,0 +1,341 @@ + +// Chart design based on the recommendations of Stephen Few. Implementation +// based on the work of Clint Ivy, Jamie Love, and Jason Davies. +// http://projects.instantcognition.com/protovis/bulletchart/ +nv.models.bulletChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var bullet = nv.models.bullet() + ; + + var orient = 'left' // TODO top & bottom + , reverse = false + , margin = {top: 5, right: 40, bottom: 20, left: 120} + , ranges = function(d) { return d.ranges } + , markers = function(d) { return d.markers } + , measures = function(d) { return d.measures } + , width = null + , height = 55 + , tickFormat = null + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + x + '

                              ' + + '

                              ' + y + '

                              ' + } + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left, + top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top, + content = tooltip(e.key, e.label, e.value, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(d, i) { + var container = d3.select(this); + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + that = this; + + + chart.update = function() { chart(selection) }; + chart.container = this; + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!d || !ranges.call(this, d, i)) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', 18 + margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + + var rangez = ranges.call(this, d, i).slice().sort(d3.descending), + markerz = markers.call(this, d, i).slice().sort(d3.descending), + measurez = measures.call(this, d, i).slice().sort(d3.descending); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-bulletWrap'); + gEnter.append('g').attr('class', 'nv-titles'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + // Compute the new x-scale. + var x1 = d3.scale.linear() + .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain + .range(reverse ? [availableWidth, 0] : [0, availableWidth]); + + // Retrieve the old x-scale, if this is an update. + var x0 = this.__chart__ || d3.scale.linear() + .domain([0, Infinity]) + .range(x1.range()); + + // Stash the new scale. + this.__chart__ = x1; + + /* + // Derive width-scales from the x-scales. + var w0 = bulletWidth(x0), + w1 = bulletWidth(x1); + + function bulletWidth(x) { + var x0 = x(0); + return function(d) { + return Math.abs(x(d) - x(0)); + }; + } + + function bulletTranslate(x) { + return function(d) { + return 'translate(' + x(d) + ',0)'; + }; + } + */ + + var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) + w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; + + + var title = gEnter.select('.nv-titles').append('g') + .attr('text-anchor', 'end') + .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')'); + title.append('text') + .attr('class', 'nv-title') + .text(function(d) { return d.title; }); + + title.append('text') + .attr('class', 'nv-subtitle') + .attr('dy', '1em') + .text(function(d) { return d.subtitle; }); + + + + bullet + .width(availableWidth) + .height(availableHeight) + + var bulletWrap = g.select('.nv-bulletWrap'); + + d3.transition(bulletWrap).call(bullet); + + + + // Compute the tick format. + var format = tickFormat || x1.tickFormat( availableWidth / 100 ); + + // Update the tick groups. + var tick = g.selectAll('g.nv-tick') + .data(x1.ticks( availableWidth / 50 ), function(d) { + return this.textContent || format(d); + }); + + // Initialize the ticks with the old scale, x0. + var tickEnter = tick.enter().append('g') + .attr('class', 'nv-tick') + .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' }) + .style('opacity', 1e-6); + + tickEnter.append('line') + .attr('y1', availableHeight) + .attr('y2', availableHeight * 7 / 6); + + tickEnter.append('text') + .attr('text-anchor', 'middle') + .attr('dy', '1em') + .attr('y', availableHeight * 7 / 6) + .text(format); + + + // Transition the updating ticks to the new scale, x1. + var tickUpdate = d3.transition(tick) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1); + + tickUpdate.select('line') + .attr('y1', availableHeight) + .attr('y2', availableHeight * 7 / 6); + + tickUpdate.select('text') + .attr('y', availableHeight * 7 / 6); + + // Transition the exiting ticks to the new scale, x1. + d3.transition(tick.exit()) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1e-6) + .remove(); + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + dispatch.on('tooltipShow', function(e) { + e.key = data[0].title; + if (tooltips) showTooltip(e, that.parentNode); + }); + + //============================================================ + + }); + + d3.timer.flush(); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + bullet.dispatch.on('elementMouseover.tooltip', function(e) { + dispatch.tooltipShow(e); + }); + + bullet.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.bullet = bullet; + + d3.rebind(chart, bullet, 'color'); + + // left, right, top, bottom + chart.orient = function(x) { + if (!arguments.length) return orient; + orient = x; + reverse = orient == 'right' || orient == 'bottom'; + return chart; + }; + + // ranges (bad, satisfactory, good) + chart.ranges = function(x) { + if (!arguments.length) return ranges; + ranges = x; + return chart; + }; + + // markers (previous, goal) + chart.markers = function(x) { + if (!arguments.length) return markers; + markers = x; + return chart; + }; + + // measures (actual, forecast) + chart.measures = function(x) { + if (!arguments.length) return measures; + measures = x; + return chart; + }; + + chart.width = function(x) { + if (!arguments.length) return width; + width = x; + return chart; + }; + + chart.height = function(x) { + if (!arguments.length) return height; + height = x; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.tickFormat = function(x) { + if (!arguments.length) return tickFormat; + tickFormat = x; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +}; + + diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/cumulativeLineChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/cumulativeLineChart.js new file mode 100644 index 0000000000..2f311d3115 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/cumulativeLineChart.js @@ -0,0 +1,610 @@ + +nv.models.cumulativeLineChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + ; + + var margin = {top: 30, right: 30, bottom: 50, left: 60} + , color = nv.utils.defaultColor() + , width = null + , height = null + , showLegend = true + , tooltips = true + , showControls = true + , rescaleY = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' at ' + x + '

                              ' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , id = lines.id() + , state = { index: 0, rescaleY: rescaleY } + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + ; + + xAxis + .orient('bottom') + .tickPadding(7) + ; + yAxis + .orient('left') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var dx = d3.scale.linear() + , index = {i: 0, x: 0} + ; + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, null, null, offsetElement); + }; + +/* + //Moved to see if we can get better behavior to fix issue #315 + var indexDrag = d3.behavior.drag() + .on('dragstart', dragStart) + .on('drag', dragMove) + .on('dragend', dragEnd); + + function dragStart(d,i) { + d3.select(chart.container) + .style('cursor', 'ew-resize'); + } + + function dragMove(d,i) { + d.x += d3.event.dx; + d.i = Math.round(dx.invert(d.x)); + + d3.select(this).attr('transform', 'translate(' + dx(d.i) + ',0)'); + chart.update(); + } + + function dragEnd(d,i) { + d3.select(chart.container) + .style('cursor', 'auto'); + chart.update(); + } +*/ + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this).classed('nv-chart-' + id, true), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + + chart.update = function() { chart(selection) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + var indexDrag = d3.behavior.drag() + .on('dragstart', dragStart) + .on('drag', dragMove) + .on('dragend', dragEnd); + + + function dragStart(d,i) { + d3.select(chart.container) + .style('cursor', 'ew-resize'); + } + + function dragMove(d,i) { + index.x = d3.event.x; + index.i = Math.round(dx.invert(index.x)); + updateZero(); + } + + function dragEnd(d,i) { + d3.select(chart.container) + .style('cursor', 'auto'); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + } + + + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = lines.xScale(); + y = lines.yScale(); + + + if (!rescaleY) { + var seriesDomains = data + .filter(function(series) { return !series.disabled }) + .map(function(series,i) { + var initialDomain = d3.extent(series.values, lines.y()); + + //account for series being disabled when losing 95% or more + if (initialDomain[0] < -.95) initialDomain[0] = -.95; + + return [ + (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]), + (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0]) + ]; + }); + + var completeDomain = [ + d3.min(seriesDomains, function(d) { return d[0] }), + d3.max(seriesDomains, function(d) { return d[1] }) + ] + + lines.yDomain(completeDomain); + } else { + lines.yDomain(null); + } + + + dx .domain([0, data[0].values.length - 1]) //Assumes all series have same length + .range([0, availableWidth]) + .clamp(true); + + //------------------------------------------------------------ + + + var data = indexify(index.i, data); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-background'); + gEnter.append('g').attr('class', 'nv-linesWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + var controlsData = [ + { key: 'Re-scale y-axis', disabled: !rescaleY } + ]; + + controls.width(140).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + // Show error if series goes below 100% + var tempDisabled = data.filter(function(d) { return d.tempDisabled }); + + wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates + if (tempDisabled.length) { + wrap.append('text').attr('class', 'tempDisabled') + .attr('x', availableWidth / 2) + .attr('y', '-.71em') + .style('text-anchor', 'end') + .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.'); + } + + + + //------------------------------------------------------------ + // Main Chart Component(s) + + gEnter.select('.nv-background') + .append('rect'); + + g.select('.nv-background rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + lines + //.x(function(d) { return d.x }) + .y(function(d) { return d.display.y }) + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled })); + + + + var linesWrap = g.select('.nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled })); + + //d3.transition(linesWrap).call(lines); + linesWrap.call(lines); + + + var indexLine = linesWrap.selectAll('.nv-indexLine') + .data([index]); + indexLine.enter().append('rect').attr('class', 'nv-indexLine') + .attr('width', 3) + .attr('x', -2) + .attr('fill', 'red') + .attr('fill-opacity', .5) + .call(indexDrag) + + indexLine + .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' }) + .attr('height', availableHeight) + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + //Suggest how many ticks based on the chart width and D3 should listen (70 is the optimal number for MM/DD/YY dates) + .ticks( Math.min(data[0].values.length,availableWidth/70) ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + + function updateZero() { + indexLine + .data([index]); + + chart.update(); + } + + g.select('.nv-background rect') + .on('click', function() { + index.x = d3.mouse(this)[0]; + index.i = Math.round(dx.invert(index.x)); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + + updateZero(); + }); + + lines.dispatch.on('elementClick', function(e) { + index.i = e.pointIndex; + index.x = dx(index.i); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + + updateZero(); + }); + + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + rescaleY = !d.disabled; + + state.rescaleY = rescaleY; + dispatch.stateChange(state); + + //selection.transition().call(chart); + selection.call(chart); + }); + + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + //selection.transition().call(chart); + selection.call(chart); + }); + +/* + // + legend.dispatch.on('legendMouseover', function(d, i) { + d.hover = true; + selection.transition().call(chart) + }); + + legend.dispatch.on('legendMouseout', function(d, i) { + d.hover = false; + selection.transition().call(chart) + }); +*/ + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + + if (typeof e.index !== 'undefined') { + index.i = e.index; + index.x = dx(index.i); + + state.index = e.index; + + indexLine + .data([index]); + } + + + if (typeof e.rescaleY !== 'undefined') { + rescaleY = e.rescaleY; + } + + selection.call(chart); + }); + + //============================================================ + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.lines = lines; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.rescaleY = function(_) { + if (!arguments.length) return rescaleY; + rescaleY = _ + return rescaleY; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + //============================================================ + // Functions + //------------------------------------------------------------ + + /* Normalize the data according to an index point. */ + function indexify(idx, data) { + return data.map(function(line, i) { + var v = lines.y()(line.values[idx], idx); + + //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue + if (v < -.95) { + //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100) + line.tempDisabled = true; + return line; + } + + line.tempDisabled = false; + + line.values = line.values.map(function(point, pointIndex) { + point.display = {'y': (lines.y()(point, pointIndex) - v) / (1 + v) }; + return point; + }) + + return line; + }) + } + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/discreteBar.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/discreteBar.js new file mode 100644 index 0000000000..e9cc6687b7 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/discreteBar.js @@ -0,0 +1,327 @@ +//TODO: consider deprecating by adding necessary features to multiBar model +nv.models.discreteBar = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , x = d3.scale.ordinal() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , color = nv.utils.defaultColor() + , showValues = false + , valueFormat = d3.format(',.2f') + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + , rectClass = 'discreteBar' + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + //add series index to each data point for reference + data = data.map(function(series, i) { + series.values = series.values.map(function(point) { + point.series = i; + return point; + }); + return series; + }); + + + //------------------------------------------------------------ + // Setup Scales + + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0 } + }) + }); + + x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands([0, availableWidth], .1); + + y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY))); + + + // If showValues, pad the Y axis range to account for label height + if (showValues) y.range([availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]); + else y.range([availableHeight, 0]); + + //store old scales if they exist + x0 = x0 || x; + y0 = y0 || y.copy().range([y(0),y(0)]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-groups'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }); + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .75); + + + var bars = groups.selectAll('g.nv-bar') + .data(function(d) { return d.values }); + + bars.exit().remove(); + + + var barsEnter = bars.enter().append('g') + .attr('transform', function(d,i,j) { + return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')' + }) + .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + value: getY(d,i), + point: d, + series: data[d.series], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }); + + barsEnter.append('rect') + .attr('height', 0) + .attr('width', x.rangeBand() * .9 / data.length ) + + if (showValues) { + barsEnter.append('text') + .attr('text-anchor', 'middle') + bars.select('text') + .attr('x', x.rangeBand() * .9 / 2) + .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 }) + .text(function(d,i) { return valueFormat(getY(d,i)) }); + } else { + bars.selectAll('text').remove(); + } + + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' }) + .style('fill', function(d,i) { return d.color || color(d,i) }) + .style('stroke', function(d,i) { return d.color || color(d,i) }) + .select('rect') + .attr('class', rectClass) + .attr('width', x.rangeBand() * .9 / data.length); + d3.transition(bars) + //.delay(function(d,i) { return i * 1200 / data[0].values.length }) + .attr('transform', function(d,i) { + var left = x(getX(d,i)) + x.rangeBand() * .05, + top = getY(d,i) < 0 ? + y(0) : + y(0) - y(getY(d,i)) < 1 ? + y(0) - 1 : //make 1 px positive bars show up above y=0 + y(getY(d,i)); + + return 'translate(' + left + ', ' + top + ')' + }) + .select('rect') + .attr('height', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y(0)) || 1) + }); + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.showValues = function(_) { + if (!arguments.length) return showValues; + showValues = _; + return chart; + }; + + chart.valueFormat= function(_) { + if (!arguments.length) return valueFormat; + valueFormat = _; + return chart; + }; + + chart.rectClass= function(_) { + if (!arguments.length) return rectClass; + rectClass = _; + return chart; + } + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/discreteBarChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/discreteBarChart.js new file mode 100644 index 0000000000..f5482e62a3 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/discreteBarChart.js @@ -0,0 +1,290 @@ + +nv.models.discreteBarChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var discretebar = nv.models.discreteBar() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + ; + + var margin = {top: 15, right: 10, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.getColor() + , staggerLabels = false + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + x + '

                              ' + + '

                              ' + y + '

                              ' + } + , x + , y + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate') + ; + + xAxis + .orient('bottom') + .highlightZero(false) + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient('left') + .tickFormat(d3.format(',.1f')) + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + + chart.update = function() { dispatch.beforeUpdate(); selection.transition().call(chart); }; + chart.container = this; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = discretebar.xScale(); + y = discretebar.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g'); + var defsEnter = gEnter.append('defs'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Main Chart Component(s) + + discretebar + .width(availableWidth) + .height(availableHeight); + + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(barsWrap).call(discretebar); + + //------------------------------------------------------------ + + + + defsEnter.append('clipPath') + .attr('id', 'nv-x-label-clip-' + discretebar.id()) + .append('rect'); + + g.select('#nv-x-label-clip-' + discretebar.id() + ' rect') + .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1)) + .attr('height', 16) + .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 )); + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')'); + //d3.transition(g.select('.nv-x.nv-axis')) + g.select('.nv-x.nv-axis').transition().duration(0) + .call(xAxis); + + + var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); + + if (staggerLabels) { + xTicks + .selectAll('text') + .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' }) + } + + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + //============================================================ + + + }); + + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + discretebar.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + discretebar.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.discretebar = discretebar; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, discretebar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'id', 'showValues', 'valueFormat'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + discretebar.color(color); + return chart; + }; + + chart.staggerLabels = function(_) { + if (!arguments.length) return staggerLabels; + staggerLabels = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/distribution.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/distribution.js new file mode 100644 index 0000000000..72fa8d20df --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/distribution.js @@ -0,0 +1,146 @@ + +nv.models.distribution = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 400 //technically width or height depending on x or y.... + , size = 8 + , axis = 'x' // 'x' or 'y'... horizontal or vertical + , getData = function(d) { return d[axis] } // defaults d.x or d.y + , color = nv.utils.defaultColor() + , scale = d3.scale.linear() + , domain + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var scale0; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom), + naxis = axis == 'x' ? 'y' : 'x', + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup Scales + + scale0 = scale0 || scale; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-distribution').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + + //------------------------------------------------------------ + + + var distWrap = g.selectAll('g.nv-dist') + .data(function(d) { return d }, function(d) { return d.key }); + + distWrap.enter().append('g'); + distWrap + .attr('class', function(d,i) { return 'nv-dist nv-series-' + i }) + .style('stroke', function(d,i) { return color(d, i) }); + + var dist = distWrap.selectAll('line.nv-dist' + axis) + .data(function(d) { return d.values }) + dist.enter().append('line') + .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) }) + d3.transition(distWrap.exit().selectAll('line.nv-dist' + axis)) + .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) + .style('stroke-opacity', 0) + .remove(); + dist + .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i }) + .attr(naxis + '1', 0) + .attr(naxis + '2', size); + d3.transition(dist) + .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) + + + scale0 = scale.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.axis = function(_) { + if (!arguments.length) return axis; + axis = _; + return chart; + }; + + chart.size = function(_) { + if (!arguments.length) return size; + size = _; + return chart; + }; + + chart.getData = function(_) { + if (!arguments.length) return getData; + getData = d3.functor(_); + return chart; + }; + + chart.scale = function(_) { + if (!arguments.length) return scale; + scale = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/historicalBar.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/historicalBar.js new file mode 100644 index 0000000000..0538f9071f --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/historicalBar.js @@ -0,0 +1,289 @@ +//TODO: consider deprecating and using multibar with single series for this +nv.models.historicalBar = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceX = [] + , forceY = [0] + , padData = false + , clipEdge = true + , color = nv.utils.defaultColor() + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup Scales + + x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )) + + if (padData) + x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range([0, availableWidth]); + + y .domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) )) + .range([availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-bar').data([data[0].values]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-bars'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + + defsEnter.append('clipPath') + .attr('id', 'nv-chart-clip-path-' + id) + .append('rect'); + + wrap.select('#nv-chart-clip-path-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + + + + var bars = wrap.select('.nv-bars').selectAll('.nv-bar') + .data(function(d) { return d }); + + bars.exit().remove(); + + + var barsEnter = bars.enter().append('rect') + //.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) + .attr('x', 0 ) + .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }) + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + point: d, + series: data[0], + pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + point: d, + series: data[0], + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }); + + bars + .attr('fill', function(d,i) { return color(d, i); }) + .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) + .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) //TODO: better width calculations that don't assume always uniform data spacing;w + .attr('width', (availableWidth / data[0].values.length) * .9 ) + + + d3.transition(bars) + //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + .attr('y', function(d,i) { + return getY(d,i) < 0 ? + y(0) : + y(0) - y(getY(d,i)) < 1 ? + y(0) - 1 : + y(getY(d,i)) + }) + .attr('height', function(d,i) { return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) }); + //.order(); // not sure if this makes any sense for this model + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.padData = function(_) { + if (!arguments.length) return padData; + padData = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/indentedTree.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/indentedTree.js new file mode 100644 index 0000000000..183806183a --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/indentedTree.js @@ -0,0 +1,314 @@ +nv.models.indentedTree = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} //TODO: implement, maybe as margin on the containing div + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() + , id = Math.floor(Math.random() * 10000) + , header = true + , filterZero = false + , noData = "No Data Available." + , childIndent = 20 + , columns = [{key:'key', label: 'Name', type:'text'}] //TODO: consider functions like chart.addColumn, chart.removeColumn, instead of a block like this + , tableClass = null + , iconOpen = 'images/grey-plus.png' //TODO: consider removing this and replacing with a '+' or '-' unless user defines images + , iconClose = 'images/grey-minus.png' + , dispatch = d3.dispatch('elementClick', 'elementDblclick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var i = 0, + depth = 1; + + var tree = d3.layout.tree() + .children(function(d) { return d.values }) + .size([height, childIndent]); //Not sure if this is needed now that the result is HTML + + chart.update = function() { selection.transition().call(chart) }; + chart.container = this; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + if (!data[0]) data[0] = {key: noData}; + + //------------------------------------------------------------ + + + var nodes = tree.nodes(data[0]); + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = d3.select(this).selectAll('div').data([[nodes]]); + var wrapEnter = wrap.enter().append('div').attr('class', 'nvd3 nv-wrap nv-indentedtree'); + var tableEnter = wrapEnter.append('table'); + var table = wrap.select('table').attr('width', '100%').attr('class', tableClass); + + //------------------------------------------------------------ + + + if (header) { + var thead = tableEnter.append('thead'); + + var theadRow1 = thead.append('tr'); + + columns.forEach(function(column) { + theadRow1 + .append('th') + .attr('width', column.width ? column.width : '10%') + .style('text-align', column.type == 'numeric' ? 'right' : 'left') + .append('span') + .text(column.label); + }); + } + + + var tbody = table.selectAll('tbody') + .data(function(d) { return d }); + tbody.enter().append('tbody'); + + + + //compute max generations + depth = d3.max(nodes, function(node) { return node.depth }); + tree.size([height, depth * childIndent]); //TODO: see if this is necessary at all + + + // Update the nodes… + var node = tbody.selectAll('tr') + .data(function(d) { return d.filter(function(d) { return (filterZero && !d.children) ? filterZero(d) : true; }) }, function(d) { return d.id || (d.id == ++i)}); + //.style('display', 'table-row'); //TODO: see if this does anything + + node.exit().remove(); + + + node.select('img.nv-treeicon') + .attr('src', icon) + .classed('folded', folded); + + var nodeEnter = node.enter().append('tr'); + + + columns.forEach(function(column, index) { + + var nodeName = nodeEnter.append('td') + .style('padding-left', function(d) { return (index ? 0 : d.depth * childIndent + 12 + (icon(d) ? 0 : 16)) + 'px' }, 'important') //TODO: check why I did the ternary here + .style('text-align', column.type == 'numeric' ? 'right' : 'left'); + + + if (index == 0) { + nodeName.append('img') + .classed('nv-treeicon', true) + .classed('nv-folded', folded) + .attr('src', icon) + .style('width', '14px') + .style('height', '14px') + .style('padding', '0 1px') + .style('display', function(d) { return icon(d) ? 'inline-block' : 'none'; }) + .on('click', click); + } + + + nodeName.append('span') + .attr('class', d3.functor(column.classes) ) + .text(function(d) { return column.format ? column.format(d) : + (d[column.key] || '-') }); + + if (column.showCount) { + nodeName.append('span') + .attr('class', 'nv-childrenCount'); + + node.selectAll('span.nv-childrenCount').text(function(d) { + return ((d.values && d.values.length) || (d._values && d._values.length)) ? //If this is a parent + '(' + ((d.values && (d.values.filter(function(d) { return filterZero ? filterZero(d) : true; }).length)) //If children are in values check its children and filter + || (d._values && d._values.filter(function(d) { return filterZero ? filterZero(d) : true; }).length) //Otherwise, do the same, but with the other name, _values... + || 0) + ')' //This is the catch-all in case there are no children after a filter + : '' //If this is not a parent, just give an empty string + }); + } + + if (column.click) + nodeName.select('span').on('click', column.click); + + }); + + + node + .order() + .on('click', function(d) { + dispatch.elementClick({ + row: this, //TODO: decide whether or not this should be consistent with scatter/line events or should be an html link (a href) + data: d, + pos: [d.x, d.y] + }); + }) + .on('dblclick', function(d) { + dispatch.elementDblclick({ + row: this, + data: d, + pos: [d.x, d.y] + }); + }) + .on('mouseover', function(d) { + dispatch.elementMouseover({ + row: this, + data: d, + pos: [d.x, d.y] + }); + }) + .on('mouseout', function(d) { + dispatch.elementMouseout({ + row: this, + data: d, + pos: [d.x, d.y] + }); + }); + + + + + // Toggle children on click. + function click(d, _, unshift) { + d3.event.stopPropagation(); + + if(d3.event.shiftKey && !unshift) { + //If you shift-click, it'll toggle fold all the children, instead of itself + d3.event.shiftKey = false; + d.values && d.values.forEach(function(node){ + if (node.values || node._values) { + click(node, 0, true); + } + }); + return true; + } + if(!hasChildren(d)) { + //download file + //window.location.href = d.url; + return true; + } + if (d.values) { + d._values = d.values; + d.values = null; + } else { + d.values = d._values; + d._values = null; + } + chart.update(); + } + + + function icon(d) { + return (d._values && d._values.length) ? iconOpen : (d.values && d.values.length) ? iconClose : ''; + } + + function folded(d) { + return (d._values && d._values.length); + } + + function hasChildren(d) { + var values = d.values || d._values; + + return (values && values.length); + } + + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + scatter.color(color); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.header = function(_) { + if (!arguments.length) return header; + header = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + chart.filterZero = function(_) { + if (!arguments.length) return filterZero; + filterZero = _; + return chart; + }; + + chart.columns = function(_) { + if (!arguments.length) return columns; + columns = _; + return chart; + }; + + chart.tableClass = function(_) { + if (!arguments.length) return tableClass; + tableClass = _; + return chart; + }; + + chart.iconOpen = function(_){ + if (!arguments.length) return iconOpen; + iconOpen = _; + return chart; + } + + chart.iconClose = function(_){ + if (!arguments.length) return iconClose; + iconClose = _; + return chart; + } + + //============================================================ + + + return chart; +}; \ No newline at end of file diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/legend.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/legend.js new file mode 100644 index 0000000000..eb5093c238 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/legend.js @@ -0,0 +1,203 @@ +nv.models.legend = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 5, right: 0, bottom: 5, left: 0} + , width = 400 + , height = 20 + , getKey = function(d) { return d.key } + , color = nv.utils.defaultColor() + , align = true + , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-legend').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + var series = g.selectAll('.nv-series') + .data(function(d) { return d }); + var seriesEnter = series.enter().append('g').attr('class', 'nv-series') + .on('mouseover', function(d,i) { + dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects + }) + .on('mouseout', function(d,i) { + dispatch.legendMouseout(d,i); + }) + .on('click', function(d,i) { + dispatch.legendClick(d,i); + }) + .on('dblclick', function(d,i) { + dispatch.legendDblclick(d,i); + }); + seriesEnter.append('circle') + .style('stroke-width', 2) + .attr('r', 5); + seriesEnter.append('text') + .attr('text-anchor', 'start') + .attr('dy', '.32em') + .attr('dx', '8'); + series.classed('disabled', function(d) { return d.disabled }); + series.exit().remove(); + series.select('circle') + .style('fill', function(d,i) { return d.color || color(d,i)}) + .style('stroke', function(d,i) { return d.color || color(d, i) }); + series.select('text').text(getKey); + + + //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option) + + // NEW ALIGNING CODE, TODO: clean up + if (align) { + var seriesWidths = []; + series.each(function(d,i) { + seriesWidths.push(d3.select(this).select('text').node().getComputedTextLength() + 28); // 28 is ~ the width of the circle plus some padding + }); + + //nv.log('Series Widths: ', JSON.stringify(seriesWidths)); + + var seriesPerRow = 0; + var legendWidth = 0; + var columnWidths = []; + + while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) { + columnWidths[seriesPerRow] = seriesWidths[seriesPerRow]; + legendWidth += seriesWidths[seriesPerRow++]; + } + + + while ( legendWidth > availableWidth && seriesPerRow > 1 ) { + columnWidths = []; + seriesPerRow--; + + for (k = 0; k < seriesWidths.length; k++) { + if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) ) + columnWidths[k % seriesPerRow] = seriesWidths[k]; + } + + legendWidth = columnWidths.reduce(function(prev, cur, index, array) { + return prev + cur; + }); + } + //console.log(columnWidths, legendWidth, seriesPerRow); + + var xPositions = []; + for (var i = 0, curX = 0; i < seriesPerRow; i++) { + xPositions[i] = curX; + curX += columnWidths[i]; + } + + series + .attr('transform', function(d, i) { + return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')'; + }); + + //position legend as far right as possible within the total width + g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')'); + + height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20); + + } else { + + var ypos = 5, + newxpos = 5, + maxwidth = 0, + xpos; + series + .attr('transform', function(d, i) { + var length = d3.select(this).select('text').node().getComputedTextLength() + 28; + xpos = newxpos; + + if (width < margin.left + margin.right + xpos + length) { + newxpos = xpos = 5; + ypos += 20; + } + + newxpos += length; + if (newxpos > maxwidth) maxwidth = newxpos; + + return 'translate(' + xpos + ',' + ypos + ')'; + }); + + //position legend as far right as possible within the total width + g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')'); + + height = margin.top + margin.bottom + ypos + 15; + + } + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.key = function(_) { + if (!arguments.length) return getKey; + getKey = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.align = function(_) { + if (!arguments.length) return align; + align = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/line.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/line.js new file mode 100644 index 0000000000..4ae68d8ea5 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/line.js @@ -0,0 +1,284 @@ + +nv.models.line = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var scatter = nv.models.scatter() + ; + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() // a function that returns a color + , getX = function(d) { return d.x } // accessor to get the x value from a data point + , getY = function(d) { return d.y } // accessor to get the y value from a data point + , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continuous when it is not defined + , isArea = function(d) { return d.area } // decides if a line is an area or just a line + , clipEdge = false // if true, masks lines within x and y scale + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , interpolate = "linear" // controls the line interpolation + ; + + scatter + .size(16) // default size + .sizeDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0 //used to store previous scales + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + //------------------------------------------------------------ + // Setup Scales + + x = scatter.xScale(); + y = scatter.yScale(); + + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + gEnter.append('g').attr('class', 'nv-groups'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + + scatter + .width(availableWidth) + .height(availableHeight) + + var scatterWrap = wrap.select('.nv-scatterWrap'); + //.datum(data); // Data automatically trickles down from the wrap + + d3.transition(scatterWrap).call(scatter); + + + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + scatter.id()) + .append('rect'); + + wrap.select('#nv-edge-clip-' + scatter.id() + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); + scatterWrap + .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); + + + + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i)}); + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .5); + + + + var areaPaths = groups.selectAll('path.nv-area') + .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area + areaPaths.enter().append('path') + .attr('class', 'nv-area') + .attr('d', function(d) { + return d3.svg.area() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x0(getX(d,i)) }) + .y0(function(d,i) { return y0(getY(d,i)) }) + .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) + //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this + .apply(this, [d.values]) + }); + d3.transition(groups.exit().selectAll('path.nv-area')) + .attr('d', function(d) { + return d3.svg.area() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x0(getX(d,i)) }) + .y0(function(d,i) { return y0(getY(d,i)) }) + .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) + //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this + .apply(this, [d.values]) + }); + d3.transition(areaPaths) + .attr('d', function(d) { + return d3.svg.area() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x0(getX(d,i)) }) + .y0(function(d,i) { return y0(getY(d,i)) }) + .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) + //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this + .apply(this, [d.values]) + }); + + + + var linePaths = groups.selectAll('path.nv-line') + .data(function(d) { return [d.values] }); + linePaths.enter().append('path') + .attr('class', 'nv-line') + .attr('d', + d3.svg.line() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x0(getX(d,i)) }) + .y(function(d,i) { return y0(getY(d,i)) }) + ); + d3.transition(groups.exit().selectAll('path.nv-line')) + .attr('d', + d3.svg.line() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ); + d3.transition(linePaths) + .attr('d', + d3.svg.line() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ); + + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = scatter.dispatch; + chart.scatter = scatter; + + d3.rebind(chart, scatter, 'id', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'padData'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + scatter.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + scatter.y(_); + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + scatter.color(color); + return chart; + }; + + chart.interpolate = function(_) { + if (!arguments.length) return interpolate; + interpolate = _; + return chart; + }; + + chart.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return chart; + }; + + chart.isArea = function(_) { + if (!arguments.length) return isArea; + isArea = d3.functor(_); + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/lineChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/lineChart.js new file mode 100644 index 0000000000..8e8c319f64 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/lineChart.js @@ -0,0 +1,362 @@ + +nv.models.lineChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + ; + +//set margin.right to 23 to fit dates on the x-axis within the chart + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , color = nv.utils.defaultColor() + , width = null + , height = null + , showLegend = true + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' at ' + x + '

                              ' + } + , x + , y + , state = {} + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + ; + + xAxis + .orient('bottom') + .tickPadding(7) + ; + yAxis + .orient('left') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + + // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else + if (offsetElement) { + var svg = d3.select(offsetElement).select('svg'); + var viewBox = svg.attr('viewBox'); + if (viewBox) { + viewBox = viewBox.split(' '); + var ratio = parseInt(svg.style('width')) / viewBox[2]; + e.pos[0] = e.pos[0] * ratio; + e.pos[1] = e.pos[1] * ratio; + } + } + + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, null, null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + + chart.update = function() { chart(selection) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = lines.xScale(); + y = lines.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-linesWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + + //------------------------------------------------------------ + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + lines + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + + + var linesWrap = g.select('.nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(linesWrap).call(lines); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + +/* + legend.dispatch.on('legendMouseover', function(d, i) { + d.hover = true; + selection.transition().call(chart) + }); + + legend.dispatch.on('legendMouseout', function(d, i) { + d.hover = false; + selection.transition().call(chart) + }); +*/ + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + selection.call(chart); + }); + + //============================================================ + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.lines = lines; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/linePlusBarChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/linePlusBarChart.js new file mode 100644 index 0000000000..5a90ae0a81 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/linePlusBarChart.js @@ -0,0 +1,425 @@ + +nv.models.linePlusBarChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , bars = nv.models.historicalBar() + , xAxis = nv.models.axis() + , y1Axis = nv.models.axis() + , y2Axis = nv.models.axis() + , legend = nv.models.legend() + ; + + var margin = {top: 30, right: 60, bottom: 50, left: 60} + , width = null + , height = null + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , color = nv.utils.defaultColor() + , showLegend = true + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' at ' + x + '

                              '; + } + , x + , y1 + , y2 + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + ; + + bars + .padData(true) + ; + lines + .clipEdge(false) + .padData(true) + ; + xAxis + .orient('bottom') + .tickPadding(7) + .highlightZero(false) + ; + y1Axis + .orient('left') + ; + y2Axis + .orient('right') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var state = {}, + showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + } + ; + + //------------------------------------------------------------ + + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { chart(selection) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + var dataBars = data.filter(function(d) { return !d.disabled && d.bar }); + var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240 + + //x = xAxis.scale(); + x = dataLines.filter(function(d) { return !d.disabled; }).length && dataLines.filter(function(d) { return !d.disabled; })[0].values.length ? lines.xScale() : bars.xScale(); + //x = dataLines.filter(function(d) { return !d.disabled; }).length ? lines.xScale() : bars.xScale(); //old code before change above + y1 = bars.yScale(); + y2 = lines.yScale(); + + //------------------------------------------------------------ + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-linePlusBar').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y1 nv-axis'); + gEnter.append('g').attr('class', 'nv-y2 nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-linesWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width( availableWidth / 2 ); + + g.select('.nv-legendWrap') + .datum(data.map(function(series) { + series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; + series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)'); + return series; + })) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + + lines + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].bar })) + + bars + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && data[i].bar })) + + + + var barsWrap = g.select('.nv-barsWrap') + .datum(dataBars.length ? dataBars : [{values:[]}]) + + var linesWrap = g.select('.nv-linesWrap') + .datum(!dataLines[0].disabled ? dataLines : [{values:[]}] ); + //.datum(!dataLines[0].disabled ? dataLines : [{values:dataLines[0].values.map(function(d) { return [d[0], null] }) }] ); + + d3.transition(barsWrap).call(bars); + d3.transition(linesWrap).call(lines); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y1.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + + y1Axis + .scale(y1) + .ticks( availableHeight / 36 ) + .tickSize(-availableWidth, 0); + + d3.transition(g.select('.nv-y1.nv-axis')) + .style('opacity', dataBars.length ? 1 : 0) + .call(y1Axis); + + + y2Axis + .scale(y2) + .ticks( availableHeight / 36 ) + .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none + + g.select('.nv-y2.nv-axis') + .style('opacity', dataLines.length ? 1 : 0) + .attr('transform', 'translate(' + availableWidth + ',0)'); + //.attr('transform', 'translate(' + x.range()[1] + ',0)'); + + d3.transition(g.select('.nv-y2.nv-axis')) + .call(y2Axis); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + selection.call(chart); + }); + + //============================================================ + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + bars.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.legend = legend; + chart.lines = lines; + chart.bars = bars; + chart.xAxis = xAxis; + chart.y1Axis = y1Axis; + chart.y2Axis = y2Axis; + + d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate'); + //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc. + //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + lines.x(_); + bars.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + lines.y(_); + bars.y(_); + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/linePlusBarWithFocusChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/linePlusBarWithFocusChart.js new file mode 100644 index 0000000000..277f86fc6c --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/linePlusBarWithFocusChart.js @@ -0,0 +1,657 @@ + +nv.models.linePlusBarWithFocusChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , lines2 = nv.models.line() + , bars = nv.models.historicalBar() + , bars2 = nv.models.historicalBar() + , xAxis = nv.models.axis() + , x2Axis = nv.models.axis() + , y1Axis = nv.models.axis() + , y2Axis = nv.models.axis() + , y3Axis = nv.models.axis() + , y4Axis = nv.models.axis() + , legend = nv.models.legend() + , brush = d3.svg.brush() + ; + + var margin = {top: 30, right: 30, bottom: 30, left: 60} + , margin2 = {top: 0, right: 30, bottom: 20, left: 60} + , width = null + , height = null + , height2 = 100 + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , color = nv.utils.defaultColor() + , showLegend = true + , extent + , brushExtent = null + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' at ' + x + '

                              '; + } + , x + , x2 + , y1 + , y2 + , y3 + , y4 + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush') + ; + + lines + .clipEdge(true) + ; + lines2 + .interactive(false) + ; + xAxis + .orient('bottom') + .tickPadding(5) + ; + y1Axis + .orient('left') + ; + y2Axis + .orient('right') + ; + x2Axis + .orient('bottom') + .tickPadding(5) + ; + y3Axis + .orient('left') + ; + y4Axis + .orient('right') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + if (extent) { + e.pointIndex += Math.ceil(extent[0]); + } + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //------------------------------------------------------------ + + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight1 = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom - height2, + availableHeight2 = height2 - margin2.top - margin2.bottom; + + chart.update = function() { chart(selection) }; + chart.container = this; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight1 / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + var dataBars = data.filter(function(d) { return !d.disabled && d.bar }); + var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240 + + x = bars.xScale(); + x2 = x2Axis.scale(); + y1 = bars.yScale(); + y2 = lines.yScale(); + y3 = bars2.yScale(); + y4 = lines2.yScale(); + + var series1 = data + .filter(function(d) { return !d.disabled && d.bar }) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i) } + }) + }); + + var series2 = data + .filter(function(d) { return !d.disabled && !d.bar }) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i) } + }) + }); + + x .range([0, availableWidth]); + + x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } )) + .range([0, availableWidth]); + + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-legendWrap'); + + var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); + focusEnter.append('g').attr('class', 'nv-x nv-axis'); + focusEnter.append('g').attr('class', 'nv-y1 nv-axis'); + focusEnter.append('g').attr('class', 'nv-y2 nv-axis'); + focusEnter.append('g').attr('class', 'nv-barsWrap'); + focusEnter.append('g').attr('class', 'nv-linesWrap'); + + var contextEnter = gEnter.append('g').attr('class', 'nv-context'); + contextEnter.append('g').attr('class', 'nv-x nv-axis'); + contextEnter.append('g').attr('class', 'nv-y1 nv-axis'); + contextEnter.append('g').attr('class', 'nv-y2 nv-axis'); + contextEnter.append('g').attr('class', 'nv-barsWrap'); + contextEnter.append('g').attr('class', 'nv-linesWrap'); + contextEnter.append('g').attr('class', 'nv-brushBackground'); + contextEnter.append('g').attr('class', 'nv-x nv-brush'); + + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width( availableWidth / 2 ); + + g.select('.nv-legendWrap') + .datum(data.map(function(series) { + series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; + series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)'); + return series; + })) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight1 = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom - height2; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Context Components + + bars2 + .width(availableWidth) + .height(availableHeight2) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && data[i].bar })); + + lines2 + .width(availableWidth) + .height(availableHeight2) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].bar })); + + var bars2Wrap = g.select('.nv-context .nv-barsWrap') + .datum(dataBars.length ? dataBars : [{values:[]}]); + + var lines2Wrap = g.select('.nv-context .nv-linesWrap') + .datum(!dataLines[0].disabled ? dataLines : [{values:[]}]); + + g.select('.nv-context') + .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')') + + d3.transition(bars2Wrap).call(bars2); + d3.transition(lines2Wrap).call(lines2); + + //------------------------------------------------------------ + + + + //------------------------------------------------------------ + // Setup Brush + + brush + .x(x2) + .on('brush', onBrush); + + if (brushExtent) brush.extent(brushExtent); + + var brushBG = g.select('.nv-brushBackground').selectAll('g') + .data([brushExtent || brush.extent()]) + + var brushBGenter = brushBG.enter() + .append('g'); + + brushBGenter.append('rect') + .attr('class', 'left') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight2); + + brushBGenter.append('rect') + .attr('class', 'right') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight2); + + var gBrush = g.select('.nv-x.nv-brush') + .call(brush); + gBrush.selectAll('rect') + //.attr('y', -5) + .attr('height', availableHeight2); + gBrush.selectAll('.resize').append('path').attr('d', resizePath); + + //------------------------------------------------------------ + + //------------------------------------------------------------ + // Setup Secondary (Context) Axes + + x2Axis + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight2, 0); + + g.select('.nv-context .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y3.range()[0] + ')'); + d3.transition(g.select('.nv-context .nv-x.nv-axis')) + .call(x2Axis); + + + y3Axis + .scale(y3) + .ticks( availableHeight2 / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-context .nv-y1.nv-axis') + .style('opacity', dataBars.length ? 1 : 0) + .attr('transform', 'translate(0,' + x2.range()[0] + ')'); + + d3.transition(g.select('.nv-context .nv-y1.nv-axis')) + .call(y3Axis); + + + y4Axis + .scale(y4) + .ticks( availableHeight2 / 36 ) + .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none + + g.select('.nv-context .nv-y2.nv-axis') + .style('opacity', dataLines.length ? 1 : 0) + .attr('transform', 'translate(' + x2.range()[1] + ',0)'); + + d3.transition(g.select('.nv-context .nv-y2.nv-axis')) + .call(y4Axis); + + //------------------------------------------------------------ + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + selection.call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + //============================================================ + + + //============================================================ + // Functions + //------------------------------------------------------------ + + // Taken from crossfilter (http://square.github.com/crossfilter/) + function resizePath(d) { + var e = +(d == 'e'), + x = e ? 1 : -1, + y = availableHeight2 / 3; + return 'M' + (.5 * x) + ',' + y + + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) + + 'V' + (2 * y - 6) + + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y) + + 'Z' + + 'M' + (2.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8) + + 'M' + (4.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8); + } + + + function updateBrushBG() { + if (!brush.empty()) brush.extent(brushExtent); + brushBG + .data([brush.empty() ? x2.domain() : brushExtent]) + .each(function(d,i) { + var leftWidth = x2(d[0]) - x2.range()[0], + rightWidth = x2.range()[1] - x2(d[1]); + d3.select(this).select('.left') + .attr('width', leftWidth < 0 ? 0 : leftWidth); + + d3.select(this).select('.right') + .attr('x', x2(d[1])) + .attr('width', rightWidth < 0 ? 0 : rightWidth); + }); + } + + + function onBrush() { + brushExtent = brush.empty() ? null : brush.extent(); + extent = brush.empty() ? x2.domain() : brush.extent(); + + + dispatch.brush({extent: extent, brush: brush}); + + updateBrushBG(); + + + //------------------------------------------------------------ + // Prepare Main (Focus) Bars and Lines + + bars + .width(availableWidth) + .height(availableHeight1) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && data[i].bar })); + + lines + .width(availableWidth) + .height(availableHeight1) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].bar })); + + var focusBarsWrap = g.select('.nv-focus .nv-barsWrap') + .datum(!dataBars.length ? [{values:[]}] : + dataBars + .map(function(d,i) { + return { + key: d.key, + values: d.values.filter(function(d,i) { + return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1]; + }) + } + }) + ); + + var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') + .datum(dataLines[0].disabled ? [{values:[]}] : + dataLines + .map(function(d,i) { + return { + key: d.key, + values: d.values.filter(function(d,i) { + return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; + }) + } + }) + ); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Update Main (Focus) X Axis + + if (dataBars.length) { + x = bars.xScale(); + } else { + x = lines.xScale(); + } + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight1, 0); + + xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]); + + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Update Main (Focus) Bars and Lines + + d3.transition(focusBarsWrap).call(bars); + d3.transition(focusLinesWrap).call(lines); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup and Update Main (Focus) Y Axes + + g.select('.nv-focus .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y1.range()[0] + ')'); + + + y1Axis + .scale(y1) + .ticks( availableHeight1 / 36 ) + .tickSize(-availableWidth, 0); + + g.select('.nv-focus .nv-y1.nv-axis') + .style('opacity', dataBars.length ? 1 : 0); + + + y2Axis + .scale(y2) + .ticks( availableHeight1 / 36 ) + .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none + + g.select('.nv-focus .nv-y2.nv-axis') + .style('opacity', dataLines.length ? 1 : 0) + .attr('transform', 'translate(' + x.range()[1] + ',0)'); + + d3.transition(g.select('.nv-focus .nv-y1.nv-axis')) + .call(y1Axis); + d3.transition(g.select('.nv-focus .nv-y2.nv-axis')) + .call(y2Axis); + } + + //============================================================ + + onBrush(); + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + bars.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.legend = legend; + chart.lines = lines; + chart.lines2 = lines2; + chart.bars = bars; + chart.bars2 = bars2; + chart.xAxis = xAxis; + chart.x2Axis = x2Axis; + chart.y1Axis = y1Axis; + chart.y2Axis = y2Axis; + chart.y3Axis = y3Axis; + chart.y4Axis = y4Axis; + + d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate'); + //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc. + //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + lines.x(_); + bars.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + lines.y(_); + bars.y(_); + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/lineWithFisheye.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/lineWithFisheye.js new file mode 100644 index 0000000000..3576e334ec --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/lineWithFisheye.js @@ -0,0 +1,197 @@ + +nv.models.line = function() { + //Default Settings + var margin = {top: 0, right: 0, bottom: 0, left: 0}, + width = 960, + height = 500, + color = nv.utils.defaultColor(), // function that returns colors + id = Math.floor(Math.random() * 10000), //Create semi-unique ID incase user doesn't select one + getX = function(d) { return d.x }, // accessor to get the x value from a data point + getY = function(d) { return d.y }, // accessor to get the y value from a data point + clipEdge = false, // if true, masks lines within x and y scale + interpolate = "linear"; // controls the line interpolation + + + var scatter = nv.models.scatter() + .id(id) + .size(16) // default size + .sizeDomain([16,256]), //set to speed up calculation, needs to be unset if there is a custom size accessor + //x = scatter.xScale(), + //y = scatter.yScale(), + x, y, + x0, y0, timeoutID; + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom; + + //get the scales inscase scatter scale was set manually + x = x || scatter.xScale(); + y = y || scatter.yScale(); + + //store old scales if they exist + x0 = x0 || x; + y0 = y0 || y; + + + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-line').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + wrapEnter.append('g').attr('class', 'nv-scatterWrap'); + var scatterWrap = wrap.select('.nv-scatterWrap').datum(data); + + gEnter.append('g').attr('class', 'nv-groups'); + + + scatter + .width(availableWidth) + .height(availableHeight) + + d3.transition(scatterWrap).call(scatter); + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + scatterWrap + .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + + + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i) }) + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .5) + + + var paths = groups.selectAll('path') + .data(function(d, i) { return [d.values] }); + paths.enter().append('path') + .attr('class', 'nv-line') + .attr('d', d3.svg.line() + .interpolate(interpolate) + .x(function(d,i) { return x0(getX(d,i)) }) + .y(function(d,i) { return y0(getY(d,i)) }) + ); + d3.transition(groups.exit().selectAll('path')) + .attr('d', d3.svg.line() + .interpolate(interpolate) + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ) + .remove(); // redundant? line is already being removed + d3.transition(paths) + .attr('d', d3.svg.line() + .interpolate(interpolate) + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ); + + + //store old scales for use in transitions on update, to animate from old to new positions + x0 = x.copy(); + y0 = y.copy(); + + }); + + return chart; + } + + + chart.dispatch = scatter.dispatch; + + d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin = _; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + scatter.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + scatter.y(_); + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + scatter.color(color); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.interpolate = function(_) { + if (!arguments.length) return interpolate; + interpolate = _; + return chart; + }; + + chart.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return chart; + }; + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/lineWithFisheyeChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/lineWithFisheyeChart.js new file mode 100644 index 0000000000..0d315bea7a --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/lineWithFisheyeChart.js @@ -0,0 +1,324 @@ + +nv.models.lineChart = function() { + var margin = {top: 30, right: 20, bottom: 50, left: 60}, + color = nv.utils.defaultColor(), + width = null, + height = null, + showLegend = true, + showControls = true, + fisheye = 0, + pauseFisheye = false, + tooltips = true, + tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' at ' + x + '

                              ' + }, + noData = "No Data Available." + ; + + + var x = d3.fisheye.scale(d3.scale.linear).distortion(0); + + var lines = nv.models.line().xScale(x), + //x = lines.xScale(), + y = lines.yScale(), + xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5), + yAxis = nv.models.axis().scale(y).orient('left'), + legend = nv.models.legend().height(30), + controls = nv.models.legend().height(30), + dispatch = d3.dispatch('tooltipShow', 'tooltipHide'); + + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, null, null, offsetElement); + }; + + + var controlsData = [ + { key: 'Magnify', disabled: true } + ]; + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + container.append('text') + .attr('class', 'nvd3 nv-noData') + .attr('x', availableWidth / 2) + .attr('y', availableHeight / 2) + .attr('dy', '-.7em') + .style('text-anchor', 'middle') + .text(noData); + return chart; + } else { + container.select('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + + var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g'); + + + gEnter.append('rect') + .attr('class', 'nvd3 nv-background') + .attr('width', availableWidth) + .attr('height', availableHeight); + + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-linesWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + + var g = wrap.select('g'); + + + + + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + + if (showControls) { + controls.width(180).color(['#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + + + lines + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + + + + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + var linesWrap = g.select('.nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(linesWrap).call(lines); + + + + xAxis + //.scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + + yAxis + //.scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + + + g.select('.nv-background').on('mousemove', updateFisheye); + g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye; }); + //g.select('.point-paths').on('mousemove', updateFisheye); + + + function updateFisheye() { + if (pauseFisheye) { + //g.select('.background') .style('pointer-events', 'none'); + g.select('.nv-point-paths').style('pointer-events', 'all'); + return false; + } + + g.select('.nv-background') .style('pointer-events', 'all'); + g.select('.nv-point-paths').style('pointer-events', 'none' ); + + var mouse = d3.mouse(this); + linesWrap.call(lines); + g.select('.nv-x.nv-axis').call(xAxis); + x.distortion(fisheye).focus(mouse[0]); + } + + + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + fisheye = d.disabled ? 0 : 5; + g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all'); + g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' ); + + //scatter.interactive(d.disabled); + //tooltips = d.disabled; + + if (d.disabled) { + x.distortion(fisheye).focus(0); + + linesWrap.call(lines); + g.select('.nv-x.nv-axis').call(xAxis); + } else { + pauseFisheye = false; + } + + selection.transition().call(chart); + }); + + + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + selection.transition().call(chart); + }); + +/* + // + legend.dispatch.on('legendMouseover', function(d, i) { + d.hover = true; + selection.transition().call(chart) + }); + + legend.dispatch.on('legendMouseout', function(d, i) { + d.hover = false; + selection.transition().call(chart) + }); +*/ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + if (tooltips) dispatch.on('tooltipShow', function(e) { showTooltip(e, that.parentNode) } ); // TODO: maybe merge with above? + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + if (tooltips) dispatch.on('tooltipHide', nv.tooltip.cleanup); + + }); + + + //TODO: decide if this is a good idea, and if it should be in all models + chart.update = function() { chart(selection) }; + chart.container = this; // I need a reference to the container in order to have outside code check if the chart is visible or not + + + return chart; + } + + + chart.dispatch = dispatch; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, lines, 'defined', 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate'); + + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin = _; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/lineWithFocusChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/lineWithFocusChart.js new file mode 100644 index 0000000000..8c396c6cb2 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/lineWithFocusChart.js @@ -0,0 +1,560 @@ + +nv.models.lineWithFocusChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , lines2 = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , x2Axis = nv.models.axis() + , y2Axis = nv.models.axis() + , legend = nv.models.legend() + , brush = d3.svg.brush() + ; + + var margin = {top: 30, right: 30, bottom: 30, left: 60} + , margin2 = {top: 0, right: 30, bottom: 20, left: 60} + , color = nv.utils.defaultColor() + , width = null + , height = null + , height2 = 100 + , x + , y + , x2 + , y2 + , showLegend = true + , brushExtent = null + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' at ' + x + '

                              ' + } + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush') + ; + + lines + .clipEdge(true) + ; + lines2 + .interactive(false) + ; + xAxis + .orient('bottom') + .tickPadding(5) + ; + yAxis + .orient('left') + ; + x2Axis + .orient('bottom') + .tickPadding(5) + ; + y2Axis + .orient('left') + ; + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, null, null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight1 = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom - height2, + availableHeight2 = height2 - margin2.top - margin2.bottom; + + chart.update = function() { chart(selection) }; + chart.container = this; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight1 / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = lines.xScale(); + y = lines.yScale(); + x2 = lines2.xScale(); + y2 = lines2.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-legendWrap'); + + var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); + focusEnter.append('g').attr('class', 'nv-x nv-axis'); + focusEnter.append('g').attr('class', 'nv-y nv-axis'); + focusEnter.append('g').attr('class', 'nv-linesWrap'); + + var contextEnter = gEnter.append('g').attr('class', 'nv-context'); + contextEnter.append('g').attr('class', 'nv-x nv-axis'); + contextEnter.append('g').attr('class', 'nv-y nv-axis'); + contextEnter.append('g').attr('class', 'nv-linesWrap'); + contextEnter.append('g').attr('class', 'nv-brushBackground'); + contextEnter.append('g').attr('class', 'nv-x nv-brush'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight1 = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom - height2; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + lines + .width(availableWidth) + .height(availableHeight1) + .color( + data + .map(function(d,i) { + return d.color || color(d, i); + }) + .filter(function(d,i) { + return !data[i].disabled; + }) + ); + + lines2 + .defined(lines.defined()) + .width(availableWidth) + .height(availableHeight2) + .color( + data + .map(function(d,i) { + return d.color || color(d, i); + }) + .filter(function(d,i) { + return !data[i].disabled; + }) + ); + + g.select('.nv-context') + .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')') + + var contextLinesWrap = g.select('.nv-context .nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(contextLinesWrap).call(lines2); + + //------------------------------------------------------------ + + + /* + var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(focusLinesWrap).call(lines); + */ + + + //------------------------------------------------------------ + // Setup Main (Focus) Axes + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight1, 0); + + yAxis + .scale(y) + .ticks( availableHeight1 / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-focus .nv-x.nv-axis') + .attr('transform', 'translate(0,' + availableHeight1 + ')'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Brush + + brush + .x(x2) + .on('brush', onBrush); + + if (brushExtent) brush.extent(brushExtent); + + var brushBG = g.select('.nv-brushBackground').selectAll('g') + .data([brushExtent || brush.extent()]) + + var brushBGenter = brushBG.enter() + .append('g'); + + brushBGenter.append('rect') + .attr('class', 'left') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight2); + + brushBGenter.append('rect') + .attr('class', 'right') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight2); + + gBrush = g.select('.nv-x.nv-brush') + .call(brush); + gBrush.selectAll('rect') + //.attr('y', -5) + .attr('height', availableHeight2); + gBrush.selectAll('.resize').append('path').attr('d', resizePath); + + onBrush(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Secondary (Context) Axes + + x2Axis + .scale(x2) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight2, 0); + + g.select('.nv-context .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y2.range()[0] + ')'); + d3.transition(g.select('.nv-context .nv-x.nv-axis')) + .call(x2Axis); + + + y2Axis + .scale(y2) + .ticks( availableHeight2 / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-context .nv-y.nv-axis')) + .call(y2Axis); + + g.select('.nv-context .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y2.range()[0] + ')'); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + selection.transition().call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + //============================================================ + + + //============================================================ + // Functions + //------------------------------------------------------------ + + // Taken from crossfilter (http://square.github.com/crossfilter/) + function resizePath(d) { + var e = +(d == 'e'), + x = e ? 1 : -1, + y = availableHeight2 / 3; + return 'M' + (.5 * x) + ',' + y + + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) + + 'V' + (2 * y - 6) + + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y) + + 'Z' + + 'M' + (2.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8) + + 'M' + (4.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8); + } + + + function updateBrushBG() { + if (!brush.empty()) brush.extent(brushExtent); + brushBG + .data([brush.empty() ? x2.domain() : brushExtent]) + .each(function(d,i) { + var leftWidth = x2(d[0]) - x.range()[0], + rightWidth = x.range()[1] - x2(d[1]); + d3.select(this).select('.left') + .attr('width', leftWidth < 0 ? 0 : leftWidth); + + d3.select(this).select('.right') + .attr('x', x2(d[1])) + .attr('width', rightWidth < 0 ? 0 : rightWidth); + }); + } + + + function onBrush() { + brushExtent = brush.empty() ? null : brush.extent(); + extent = brush.empty() ? x2.domain() : brush.extent(); + + + dispatch.brush({extent: extent, brush: brush}); + + + updateBrushBG(); + + // Update Main (Focus) + var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') + .datum( + data + .filter(function(d) { return !d.disabled }) + .map(function(d,i) { + return { + key: d.key, + values: d.values.filter(function(d,i) { + return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; + }) + } + }) + ); + d3.transition(focusLinesWrap).call(lines); + + + // Update Main (Focus) Axes + d3.transition(g.select('.nv-focus .nv-x.nv-axis')) + .call(xAxis); + d3.transition(g.select('.nv-focus .nv-y.nv-axis')) + .call(yAxis); + } + + //============================================================ + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.legend = legend; + chart.lines = lines; + chart.lines2 = lines2; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.x2Axis = x2Axis; + chart.y2Axis = y2Axis; + + d3.rebind(chart, lines, 'defined', 'isArea', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); + + chart.x = function(_) { + if (!arguments.length) return lines.x; + lines.x(_); + lines2.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return lines.y; + lines.y(_); + lines2.y(_); + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.margin2 = function(_) { + if (!arguments.length) return margin2; + margin2 = _; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.height2 = function(_) { + if (!arguments.length) return height2; + height2 = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color =nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.interpolate = function(_) { + if (!arguments.length) return lines.interpolate(); + lines.interpolate(_); + lines2.interpolate(_); + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + // Chart has multiple similar Axes, to prevent code duplication, probably need to link all axis functions manually like below + chart.xTickFormat = function(_) { + if (!arguments.length) return xAxis.tickFormat(); + xAxis.tickFormat(_); + x2Axis.tickFormat(_); + return chart; + }; + + chart.yTickFormat = function(_) { + if (!arguments.length) return yAxis.tickFormat(); + yAxis.tickFormat(_); + y2Axis.tickFormat(_); + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBar.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBar.js new file mode 100644 index 0000000000..7267af8c73 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBar.js @@ -0,0 +1,416 @@ + +nv.models.multiBar = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , x = d3.scale.ordinal() + , y = d3.scale.linear() + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , clipEdge = true + , stacked = false + , color = nv.utils.defaultColor() + , barColor = null // adding the ability to set the color for each rather than the whole group + , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled + , delay = 1200 + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0 //used to store previous scales + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + if (stacked) + data = d3.layout.stack() + .offset('zero') + .values(function(d){ return d.values }) + .y(getY) + (data); + + + //add series index to each data point for reference + data = data.map(function(series, i) { + series.values = series.values.map(function(point) { + point.series = i; + return point; + }); + return series; + }); + + + //------------------------------------------------------------ + // HACK for negative value stacking + if (stacked) + data[0].values.map(function(d,i) { + var posBase = 0, negBase = 0; + data.map(function(d) { + var f = d.values[i] + f.size = Math.abs(f.y); + if (f.y<0) { + f.y1 = negBase; + negBase = negBase - f.size; + } else + { + f.y1 = f.size + posBase; + posBase = posBase + f.size; + } + }); + }); + + //------------------------------------------------------------ + // Setup Scales + + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 } + }) + }); + + x .domain(d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands([0, availableWidth], .1); + + //y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y1 : 0) }).concat(forceY))) + y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 : d.y1 + d.y ) : d.y }).concat(forceY))) + .range([availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + gEnter.append('g').attr('class', 'nv-groups'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + //.style('stroke-opacity', 1e-6) + //.style('fill-opacity', 1e-6) + .selectAll('rect.nv-bar') + .delay(function(d,i) { return i * delay/ data[0].values.length }) + .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) }) + .attr('height', 0) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i) }); + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .75); + + + var bars = groups.selectAll('rect.nv-bar') + .data(function(d) { return d.values }); + + bars.exit().remove(); + + + var barsEnter = bars.enter().append('rect') + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + .attr('x', function(d,i,j) { + return stacked ? 0 : (j * x.rangeBand() / data.length ) + }) + .attr('y', function(d) { return y0(stacked ? d.y0 : 0) }) + .attr('height', 0) + .attr('width', x.rangeBand() / (stacked ? 1 : data.length) ); + bars + .style('fill', function(d,i,j){ return color(d, j, i); }) + .style('stroke', function(d,i,j){ return color(d, j, i); }) + .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + value: getY(d,i), + point: d, + series: data[d.series], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }); + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; }) + + if (barColor) { + if (!disabled) disabled = data.map(function() { return true }); + bars + //.style('fill', barColor) + //.style('stroke', barColor) + //.style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(j).toString(); }) + //.style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(j).toString(); }) + .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }) + .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }); + } + + + if (stacked) + d3.transition(bars) + .delay(function(d,i) { return i * delay / data[0].values.length }) + .attr('y', function(d,i) { + + return y((stacked ? d.y1 : 0)); + }) + .attr('height', function(d,i) { + return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1); + }) + .each('end', function() { + d3.transition(d3.select(this)) + .attr('x', function(d,i) { + return stacked ? 0 : (d.series * x.rangeBand() / data.length ) + }) + .attr('width', x.rangeBand() / (stacked ? 1 : data.length) ); + }) + else + d3.transition(bars) + .delay(function(d,i) { return i * delay/ data[0].values.length }) + .attr('x', function(d,i) { + return d.series * x.rangeBand() / data.length + }) + .attr('width', x.rangeBand() / data.length) + .each('end', function() { + d3.transition(d3.select(this)) + .attr('y', function(d,i) { + return getY(d,i) < 0 ? + y(0) : + y(0) - y(getY(d,i)) < 1 ? + y(0) - 1 : + y(getY(d,i)) || 0; + }) + .attr('height', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0; + }); + }) + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.stacked = function(_) { + if (!arguments.length) return stacked; + stacked = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.barColor = function(_) { + if (!arguments.length) return barColor; + barColor = nv.utils.getColor(_); + return chart; + }; + + chart.disabled = function(_) { + if (!arguments.length) return disabled; + disabled = _; + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.delay = function(_) { + if (!arguments.length) return delay; + delay = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBarChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBarChart.js new file mode 100644 index 0000000000..7a45a885c0 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBarChart.js @@ -0,0 +1,449 @@ + +nv.models.multiBarChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var multibar = nv.models.multiBar() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + ; + + var margin = {top: 30, right: 20, bottom: 30, left: 60} + , width = null + , height = null + , color = nv.utils.defaultColor() + , showControls = true + , showLegend = true + , reduceXTicks = true // if false a tick will show for every data point + , rotateLabels = 0 + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' on ' + x + '

                              ' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , state = { stacked: false } + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , controlWidth = function() { return showControls ? 180 : 0 } + ; + + multibar + .stacked(false) + ; + xAxis + .orient('bottom') + .tickPadding(7) + .highlightZero(false) + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient('left') + .tickFormat(d3.format(',.1f')) + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { selection.transition().call(chart) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = multibar.xScale(); + y = multibar.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth - controlWidth()); + + if (multibar.barColor()) + data.forEach(function(series,i) { + series.color = d3.rgb('#ccc').darker(i * 1.5).toString(); + }) + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + var controlsData = [ + { key: 'Grouped', disabled: multibar.stacked() }, + { key: 'Stacked', disabled: !multibar.stacked() } + ]; + + controls.width(controlWidth()).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + multibar + .disabled(data.map(function(series) { return series.disabled })) + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) + + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(barsWrap).call(multibar); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g'); + + xTicks + .selectAll('line, text') + .style('opacity', 1) + + if (reduceXTicks) + xTicks + .filter(function(d,i) { + return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0; + }) + .selectAll('text, line') + .style('opacity', 0); + + if(rotateLabels) + xTicks + .selectAll('text') + .attr('transform', function(d,i,j) { return 'rotate('+rotateLabels+' 0,0)' }) + .attr('text-transform', rotateLabels > 0 ? 'start' : 'end'); + + g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text') + .style('opacity', 1); + + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + //------------------------------------------------------------ + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + switch (d.key) { + case 'Grouped': + multibar.stacked(false); + break; + case 'Stacked': + multibar.stacked(true); + break; + } + + state.stacked = multibar.stacked(); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode) + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + if (typeof e.stacked !== 'undefined') { + multibar.stacked(e.stacked); + state.stacked = e.stacked; + } + + selection.call(chart); + }); + + //============================================================ + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + multibar.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + multibar.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.multibar = multibar; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id', 'stacked', 'delay', 'barColor'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.reduceXTicks= function(_) { + if (!arguments.length) return reduceXTicks; + reduceXTicks = _; + return chart; + }; + + chart.rotateLabels = function(_) { + if (!arguments.length) return rotateLabels; + rotateLabels = _; + return chart; + } + + chart.tooltip = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBarHorizontal.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBarHorizontal.js new file mode 100644 index 0000000000..d317731b14 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBarHorizontal.js @@ -0,0 +1,420 @@ + +nv.models.multiBarHorizontal = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , x = d3.scale.ordinal() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , color = nv.utils.defaultColor() + , barColor = null // adding the ability to set the color for each rather than the whole group + , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled + , stacked = false + , showValues = false + , valuePadding = 60 + , valueFormat = d3.format(',.2f') + , delay = 1200 + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0 //used to store previous scales + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + if (stacked) + data = d3.layout.stack() + .offset('zero') + .values(function(d){ return d.values }) + .y(getY) + (data); + + + //add series index to each data point for reference + data = data.map(function(series, i) { + series.values = series.values.map(function(point) { + point.series = i; + return point; + }); + return series; + }); + + + + //------------------------------------------------------------ + // HACK for negative value stacking + if (stacked) + data[0].values.map(function(d,i) { + var posBase = 0, negBase = 0; + data.map(function(d) { + var f = d.values[i] + f.size = Math.abs(f.y); + if (f.y<0) { + f.y1 = negBase - f.size; + negBase = negBase - f.size; + } else + { + f.y1 = posBase; + posBase = posBase + f.size; + } + }); + }); + + + + //------------------------------------------------------------ + // Setup Scales + + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 } + }) + }); + + x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands([0, availableHeight], .1); + + //y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY))) + y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 + d.y : d.y1 ) : d.y }).concat(forceY))) + + if (showValues && !stacked) + y.range([(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]); + else + y.range([0, availableWidth]); + + x0 = x0 || x; + y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-groups'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i) }); + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .75); + + + var bars = groups.selectAll('g.nv-bar') + .data(function(d) { return d.values }); + + bars.exit().remove(); + + + var barsEnter = bars.enter().append('g') + .attr('transform', function(d,i,j) { + return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')' + }); + + barsEnter.append('rect') + .attr('width', 0) + .attr('height', x.rangeBand() / (stacked ? 1 : data.length) ) + + bars + .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + value: getY(d,i), + point: d, + series: data[d.series], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }); + + + barsEnter.append('text'); + + if (showValues && !stacked) { + bars.select('text') + .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' }) + .attr('y', x.rangeBand() / (data.length * 2)) + .attr('dy', '.32em') + .text(function(d,i) { return valueFormat(getY(d,i)) }) + d3.transition(bars) + //.delay(function(d,i) { return i * delay / data[0].values.length }) + .select('text') + .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 }) + } else { + //bars.selectAll('text').remove(); + bars.selectAll('text').text(''); + } + + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + + if (barColor) { + if (!disabled) disabled = data.map(function() { return true }); + bars + //.style('fill', barColor) + //.style('stroke', barColor) + //.style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(j).toString(); }) + //.style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(j).toString(); }) + .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }) + .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }); + } + + if (stacked) + d3.transition(bars) + //.delay(function(d,i) { return i * delay / data[0].values.length }) + .attr('transform', function(d,i) { + //return 'translate(' + y(d.y0) + ',0)' + //return 'translate(' + y(d.y0) + ',' + x(getX(d,i)) + ')' + return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')' + }) + .select('rect') + .attr('width', function(d,i) { + return Math.abs(y(getY(d,i) + d.y0) - y(d.y0)) + }) + .attr('height', x.rangeBand() ); + else + d3.transition(bars) + //.delay(function(d,i) { return i * delay / data[0].values.length }) + .attr('transform', function(d,i) { + //TODO: stacked must be all positive or all negative, not both? + return 'translate(' + + (getY(d,i) < 0 ? y(getY(d,i)) : y(0)) + + ',' + + (d.series * x.rangeBand() / data.length + + + x(getX(d,i)) ) + + ')' + }) + .select('rect') + .attr('height', x.rangeBand() / data.length ) + .attr('width', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) + }); + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.stacked = function(_) { + if (!arguments.length) return stacked; + stacked = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.barColor = function(_) { + if (!arguments.length) return barColor; + barColor = nv.utils.getColor(_); + return chart; + }; + + chart.disabled = function(_) { + if (!arguments.length) return disabled; + disabled = _; + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.delay = function(_) { + if (!arguments.length) return delay; + delay = _; + return chart; + }; + + chart.showValues = function(_) { + if (!arguments.length) return showValues; + showValues = _; + return chart; + }; + + chart.valueFormat= function(_) { + if (!arguments.length) return valueFormat; + valueFormat = _; + return chart; + }; + + chart.valuePadding = function(_) { + if (!arguments.length) return valuePadding; + valuePadding = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBarHorizontalChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBarHorizontalChart.js new file mode 100644 index 0000000000..4637767a86 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBarHorizontalChart.js @@ -0,0 +1,419 @@ + +nv.models.multiBarHorizontalChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var multibar = nv.models.multiBarHorizontal() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend().height(30) + , controls = nv.models.legend().height(30) + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.defaultColor() + , showControls = true + , showLegend = true + , stacked = false + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + ' - ' + x + '

                              ' + + '

                              ' + y + '

                              ' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , state = { stacked: stacked } + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , controlWidth = function() { return showControls ? 180 : 0 } + ; + + multibar + .stacked(stacked) + ; + xAxis + .orient('left') + .tickPadding(5) + .highlightZero(false) + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient('bottom') + .tickFormat(d3.format(',.1f')) + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { selection.transition().call(chart) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = multibar.xScale(); + y = multibar.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth - controlWidth()); + + if (multibar.barColor()) + data.forEach(function(series,i) { + series.color = d3.rgb('#ccc').darker(i * 1.5).toString(); + }) + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + var controlsData = [ + { key: 'Grouped', disabled: multibar.stacked() }, + { key: 'Stacked', disabled: !multibar.stacked() } + ]; + + controls.width(controlWidth()).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + multibar + .disabled(data.map(function(series) { return series.disabled })) + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) + + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(barsWrap).call(multibar); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( availableHeight / 24 ) + .tickSize(-availableWidth, 0); + + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); + + xTicks + .selectAll('line, text') + .style('opacity', 1) + + + yAxis + .scale(y) + .ticks( availableWidth / 100 ) + .tickSize( -availableHeight, 0); + + g.select('.nv-y.nv-axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + //------------------------------------------------------------ + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + switch (d.key) { + case 'Grouped': + multibar.stacked(false); + break; + case 'Stacked': + multibar.stacked(true); + break; + } + + state.stacked = multibar.stacked(); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + if (typeof e.stacked !== 'undefined') { + multibar.stacked(e.stacked); + state.stacked = e.stacked; + } + + selection.call(chart); + }); + //============================================================ + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + multibar.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + multibar.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.multibar = multibar; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id', 'delay', 'showValues', 'valueFormat', 'stacked', 'barColor'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltip = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBarTimeSeries.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBarTimeSeries.js new file mode 100644 index 0000000000..57ca8736cf --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiBarTimeSeries.js @@ -0,0 +1,371 @@ +nv.models.multiBarTimeSeries = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , x = d3.time.scale() + , y = d3.scale.linear() + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , clipEdge = true + , stacked = false + , color = nv.utils.defaultColor() + , delay = 1200 + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0 //used to store previous scales + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + if (stacked) + data = d3.layout.stack() + .offset('zero') + .values(function(d){ return d.values }) + .y(getY) + (data); + + + //add series index to each data point for reference + data = data.map(function(series, i) { + series.values = series.values.map(function(point) { + point.series = i; + return point; + }); + return series; + }); + + //------------------------------------------------------------ + // Setup Scales + + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0 } + }) + }); + + x .domain(d3.extent(d3.merge(seriesData).map(function(d) { return d.x }))) + .range([0, availableWidth]); + + y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY))) + .range([availableHeight, 0]); + + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + gEnter.append('g').attr('class', 'nv-groups'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + //.style('stroke-opacity', 1e-6) + //.style('fill-opacity', 1e-6) + .selectAll('rect.nv-bar') + .delay(function(d,i) { return i * delay/ data[0].values.length }) + .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) }) + .attr('height', 0) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i) }); + d3.transition(groups) + .style('stroke-opacity', 1) + .style('fill-opacity', .75); + + + var bars = groups.selectAll('rect.nv-bar') + .data(function(d) { return d.values }); + + bars.exit().remove(); + + var maxElements = 0; + for(var ei=0; ei' + key + '' + + '

                              ' + y + ' on ' + x + '

                              ' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide') + ; + + multibar + .stacked(false) + ; + xAxis + .orient('bottom') + .tickPadding(7) + .highlightZero(false) + .showMaxMin(false) + ; + yAxis + .orient('left') + .tickFormat(d3.format(',.1f')) + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { selection.transition().call(chart) }; + chart.container = this; + + + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = multibar.xScale(); + y = multibar.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth / 2); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + var controlsData = [ + { key: 'Grouped', disabled: multibar.stacked() }, + { key: 'Stacked', disabled: !multibar.stacked() } + ]; + + controls.width(180).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + multibar + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) + + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(barsWrap).call(multibar); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks(availableWidth / 100) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g'); + + xTicks + .selectAll('line, text') + .style('opacity', 1) + + if (reduceXTicks) + xTicks + .filter(function(d,i) { + return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0; + }) + .selectAll('text, line') + .style('opacity', 0); + + if(rotateLabels) + xTicks + .selectAll('text') + .attr('transform', function(d,i,j) { return 'rotate('+rotateLabels+' 0,0)' }) + .attr('text-transform', rotateLabels > 0 ? 'start' : 'end'); + + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + + //------------------------------------------------------------ + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + selection.transition().call(chart); + }); + + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + switch (d.key) { + case 'Grouped': + multibar.stacked(false); + break; + case 'Stacked': + multibar.stacked(true); + break; + } + + selection.transition().call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode) + }); + + //============================================================ + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + multibar.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + multibar.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.multibar = multibar; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id', 'stacked', 'delay'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.reduceXTicks= function(_) { + if (!arguments.length) return reduceXTicks; + reduceXTicks = _; + return chart; + }; + + chart.rotateLabels = function(_) { + if (!arguments.length) return rotateLabels; + rotateLabels = _; + return chart; + } + + chart.tooltip = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} + diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiChart.js new file mode 100644 index 0000000000..fdfe24da5c --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/multiChart.js @@ -0,0 +1,444 @@ +nv.models.multiChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 30, right: 20, bottom: 50, left: 60}, + color = d3.scale.category20().range(), + width = null, + height = null, + showLegend = true, + tooltips = true, + tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' at ' + x + '

                              ' + }, + x, y; //can be accessed via chart.lines.[x/y]Scale() + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x = d3.scale.linear(), + yScale1 = d3.scale.linear(), + yScale2 = d3.scale.linear(), + + lines1 = nv.models.line().yScale(yScale1), + lines2 = nv.models.line().yScale(yScale2), + + bars1 = nv.models.multiBar().stacked(false).yScale(yScale1), + bars2 = nv.models.multiBar().stacked(false).yScale(yScale2), + + stack1 = nv.models.stackedArea().yScale(yScale1), + stack2 = nv.models.stackedArea().yScale(yScale2), + + xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5), + yAxis1 = nv.models.axis().scale(yScale1).orient('left'), + yAxis2 = nv.models.axis().scale(yScale2).orient('right'), + + legend = nv.models.legend().height(30), + dispatch = d3.dispatch('tooltipShow', 'tooltipHide'); + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)), + y = ((e.series.yAxis == 2) ? yAxis2 : yAxis1).tickFormat()(lines1.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent); + }; + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + var dataLines1 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 1}) + var dataLines2 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 2}) + var dataBars1 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 1}) + var dataBars2 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 2}) + var dataStack1 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 1}) + var dataStack2 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 2}) + + var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1}) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: d.x, y: d.y } + }) + }) + + var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2}) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: d.x, y: d.y } + }) + }) + + x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } )) + .range([0, availableWidth]); + + var wrap = container.selectAll('g.wrap.multiChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g'); + + gEnter.append('g').attr('class', 'x axis'); + gEnter.append('g').attr('class', 'y1 axis'); + gEnter.append('g').attr('class', 'y2 axis'); + gEnter.append('g').attr('class', 'lines1Wrap'); + gEnter.append('g').attr('class', 'lines2Wrap'); + gEnter.append('g').attr('class', 'bars1Wrap'); + gEnter.append('g').attr('class', 'bars2Wrap'); + gEnter.append('g').attr('class', 'stack1Wrap'); + gEnter.append('g').attr('class', 'stack2Wrap'); + gEnter.append('g').attr('class', 'legendWrap'); + + var g = wrap.select('g'); + + if (showLegend) { + legend.width( availableWidth / 2 ); + + g.select('.legendWrap') + .datum(data.map(function(series) { + series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; + series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)'); + return series; + })) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.legendWrap') + .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')'); + } + + + lines1 + .width(availableWidth) + .height(availableHeight) + .interpolate("monotone") + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'})); + + lines2 + .width(availableWidth) + .height(availableHeight) + .interpolate("monotone") + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'})); + + bars1 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'})); + + bars2 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'})); + + stack1 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'})); + + stack2 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'})); + + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + var lines1Wrap = g.select('.lines1Wrap') + .datum(dataLines1) + var bars1Wrap = g.select('.bars1Wrap') + .datum(dataBars1) + var stack1Wrap = g.select('.stack1Wrap') + .datum(dataStack1) + + var lines2Wrap = g.select('.lines2Wrap') + .datum(dataLines2) + var bars2Wrap = g.select('.bars2Wrap') + .datum(dataBars2) + var stack2Wrap = g.select('.stack2Wrap') + .datum(dataStack2) + + var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){ + return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) + }).concat([{x:0, y:0}]) : [] + var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){ + return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) + }).concat([{x:0, y:0}]) : [] + + yScale1 .domain(d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } )) + .range([0, availableHeight]) + + yScale2 .domain(d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } )) + .range([0, availableHeight]) + + lines1.yDomain(yScale1.domain()) + bars1.yDomain(yScale1.domain()) + stack1.yDomain(yScale1.domain()) + + lines2.yDomain(yScale2.domain()) + bars2.yDomain(yScale2.domain()) + stack2.yDomain(yScale2.domain()) + + if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);} + if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);} + + if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);} + if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);} + + if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);} + if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);} + + + + xAxis + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.x.axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + d3.transition(g.select('.x.axis')) + .call(xAxis); + + yAxis1 + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + + d3.transition(g.select('.y1.axis')) + .call(yAxis1); + + yAxis2 + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.y2.axis')) + .call(yAxis2); + + g.select('.y2.axis') + .style('opacity', series2.length ? 1 : 0) + .attr('transform', 'translate(' + x.range()[1] + ',0)'); + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.series').classed('disabled', false); + return d; + }); + } + selection.transition().call(chart); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + }); + + chart.update = function() { chart(selection) }; + chart.container = this; + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines1.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines1.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + lines2.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines2.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + bars1.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars1.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + bars2.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars2.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + stack1.dispatch.on('tooltipShow', function(e) { + //disable tooltips when value ~= 0 + //// TODO: consider removing points from voronoi that have 0 value instead of this hack + if (!Math.round(stack1.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range + setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); + return false; + } + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); + + stack1.dispatch.on('tooltipHide', function(e) { + dispatch.tooltipHide(e); + }); + + stack2.dispatch.on('tooltipShow', function(e) { + //disable tooltips when value ~= 0 + //// TODO: consider removing points from voronoi that have 0 value instead of this hack + if (!Math.round(stack2.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range + setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); + return false; + } + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); + + stack2.dispatch.on('tooltipHide', function(e) { + dispatch.tooltipHide(e); + }); + + lines1.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines1.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + lines2.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines2.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + + + //============================================================ + // Global getters and setters + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.lines1 = lines1; + chart.lines2 = lines2; + chart.bars1 = bars1; + chart.bars2 = bars2; + chart.stack1 = stack1; + chart.stack2 = stack2; + chart.xAxis = xAxis; + chart.yAxis1 = yAxis1; + chart.yAxis2 = yAxis2; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + lines1.x(_); + bars1.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + lines1.y(_); + bars1.y(_); + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin = _; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = _; + legend.color(_); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + return chart; +} + diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/ohlcBar.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/ohlcBar.js new file mode 100644 index 0000000000..64c7305b83 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/ohlcBar.js @@ -0,0 +1,365 @@ + +nv.models.ohlcBar = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , getOpen = function(d) { return d.open } + , getClose = function(d) { return d.close } + , getHigh = function(d) { return d.high } + , getLow = function(d) { return d.low } + , forceX = [] + , forceY = [] + , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart + , clipEdge = true + , color = nv.utils.defaultColor() + , xDomain + , yDomain + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + //TODO: store old scales for transitions + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup Scales + + x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); + + if (padData) + x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range([0, availableWidth]); + + y .domain(yDomain || [ + d3.min(data[0].values.map(getLow).concat(forceY)), + d3.max(data[0].values.map(getHigh).concat(forceY)) + ]) + .range([availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-ticks'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + + defsEnter.append('clipPath') + .attr('id', 'nv-chart-clip-path-' + id) + .append('rect'); + + wrap.select('#nv-chart-clip-path-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + + + + var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') + .data(function(d) { return d }); + + ticks.exit().remove(); + + + var ticksEnter = ticks.enter().append('path') + .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) + .attr('d', function(d,i) { + var w = (availableWidth / data[0].values.length) * .9; + return 'm0,0l0,' + + (y(getOpen(d,i)) + - y(getHigh(d,i))) + + 'l' + + (-w/2) + + ',0l' + + (w/2) + + ',0l0,' + + (y(getLow(d,i)) - y(getOpen(d,i))) + + 'l0,' + + (y(getClose(d,i)) + - y(getLow(d,i))) + + 'l' + + (w/2) + + ',0l' + + (-w/2) + + ',0z'; + }) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) + //.attr('fill', function(d,i) { return color[0]; }) + //.attr('stroke', function(d,i) { return color[0]; }) + //.attr('x', 0 ) + //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }) + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + point: d, + series: data[0], + pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + point: d, + series: data[0], + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }); + + ticks + .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) + d3.transition(ticks) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) + .attr('d', function(d,i) { + var w = (availableWidth / data[0].values.length) * .9; + return 'm0,0l0,' + + (y(getOpen(d,i)) + - y(getHigh(d,i))) + + 'l' + + (-w/2) + + ',0l' + + (w/2) + + ',0l0,' + + (y(getLow(d,i)) + - y(getOpen(d,i))) + + 'l0,' + + (y(getClose(d,i)) + - y(getLow(d,i))) + + 'l' + + (w/2) + + ',0l' + + (-w/2) + + ',0z'; + }) + //.attr('width', (availableWidth / data[0].values.length) * .9 ) + + + //d3.transition(ticks) + //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }); + //.order(); // not sure if this makes any sense for this model + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.open = function(_) { + if (!arguments.length) return getOpen; + getOpen = _; + return chart; + }; + + chart.close = function(_) { + if (!arguments.length) return getClose; + getClose = _; + return chart; + }; + + chart.high = function(_) { + if (!arguments.length) return getHigh; + getHigh = _; + return chart; + }; + + chart.low = function(_) { + if (!arguments.length) return getLow; + getLow = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.padData = function(_) { + if (!arguments.length) return padData; + padData = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/parallelCoordinates.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/parallelCoordinates.js new file mode 100644 index 0000000000..5969cd1c8c --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/parallelCoordinates.js @@ -0,0 +1,234 @@ + +//Code adapted from Jason Davies' "Parallel Coordinates" +// http://bl.ocks.org/jasondavies/1341281 + +nv.models.parallelCoordinates = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + + var margin = {top: 30, right: 10, bottom: 10, left: 10} + , width = 960 + , height = 500 + , x = d3.scale.ordinal() + , y = {} + , dimensions = [] + , color = nv.utils.getColor(d3.scale.category20c().range()) + , axisLabel = function(d) { return d; } + , filters = [] + , active = [] + , dispatch = d3.dispatch('brush') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + active = data; //set all active before first brush call + + //------------------------------------------------------------ + // Setup Scales + + x + .rangePoints([0, availableWidth], 1) + .domain(dimensions); + + // Extract the list of dimensions and create a scale for each. + dimensions.forEach(function(d) { + y[d] = d3.scale.linear() + .domain(d3.extent(data, function(p) { return +p[d]; })) + .range([availableHeight, 0]); + + y[d].brush = d3.svg.brush().y(y[d]).on('brush', brush); + + return d != 'name'; + }) + + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + gEnter.append('g').attr('class', 'nv-parallelCoordinatesWrap'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + var line = d3.svg.line(), + axis = d3.svg.axis().orient('left'), + background, + foreground; + + + // Add grey background lines for context. + background = g.append('g') + .attr('class', 'background') + .selectAll('path') + .data(data) + .enter().append('path') + .attr('d', path); + + // Add blue foreground lines for focus. + foreground = g.append('g') + .attr('class', 'foreground') + .selectAll('path') + .data(data) + .enter().append('path') + .attr('d', path); + + // Add a group element for each dimension. + var dimension = g.selectAll('.dimension') + .data(dimensions) + .enter().append('g') + .attr('class', 'dimension') + .attr('transform', function(d) { return 'translate(' + x(d) + ',0)'; }); + + // Add an axis and title. + dimension.append('g') + .attr('class', 'axis') + .each(function(d) { d3.select(this).call(axis.scale(y[d])); }) + .append('text') + .attr('text-anchor', 'middle') + .attr('y', -9) + .text(String); + + // Add and store a brush for each axis. + dimension.append('g') + .attr('class', 'brush') + .each(function(d) { d3.select(this).call(y[d].brush); }) + .selectAll('rect') + .attr('x', -8) + .attr('width', 16); + + + // Returns the path for a given data point. + function path(d) { + return line(dimensions.map(function(p) { return [x(p), y[p](d[p])]; })); + } + + // Handles a brush event, toggling the display of foreground lines. + function brush() { + var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }), + extents = actives.map(function(p) { return y[p].brush.extent(); }); + + filters = []; //erase current filters + actives.forEach(function(d,i) { + filters[i] = { + dimension: d, + extent: extents[i] + } + }); + + active = []; //erase current active list + foreground.style('display', function(d) { + var isActive = actives.every(function(p, i) { + return extents[i][0] <= d[p] && d[p] <= extents[i][1]; + }); + if (isActive) active.push(d); + return isActive ? null : 'none'; + }); + + dispatch.brush({ + filters: filters, + active: active + }); + + } + + + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + + chart.dispatch = dispatch; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_) + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.dimensions = function(_) { + if (!arguments.length) return dimensions; + dimensions = _; + return chart; + }; + + chart.filters = function() { + return filters; + }; + + chart.active = function() { + return active; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/pie.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/pie.js new file mode 100644 index 0000000000..6140452876 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/pie.js @@ -0,0 +1,379 @@ + +nv.models.pie = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 500 + , height = 500 + , getValues = function(d) { return d.values } + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , getDescription = function(d) { return d.description } + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , color = nv.utils.defaultColor() + , valueFormat = d3.format(',.2f') + , showLabels = true + , pieLabelsOutside = true + , donutLabelsOutside = false + , labelThreshold = .02 //if slice percentage is under this, don't show label + , donut = false + , labelSunbeamLayout = false + , startAngle = false + , endAngle = false + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + radius = Math.min(availableWidth, availableHeight) / 2, + arcRadius = radius-(radius / 5), + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + //var wrap = container.selectAll('.nv-wrap.nv-pie').data([data]); + var wrap = container.selectAll('.nv-wrap.nv-pie').data([getValues(data[0])]); + var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-pie'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); + + //------------------------------------------------------------ + + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + + var arc = d3.svg.arc() + .outerRadius(arcRadius); + + if (startAngle) arc.startAngle(startAngle) + if (endAngle) arc.endAngle(endAngle); + if (donut) arc.innerRadius(radius / 2); + + + // Setup the Pie chart and choose the data element + var pie = d3.layout.pie() + .sort(null) + .value(function(d) { return d.disabled ? 0 : getY(d) }); + + var slices = wrap.select('.nv-pie').selectAll('.nv-slice') + .data(pie); + + slices.exit().remove(); + + var ae = slices.enter().append('g') + .attr('class', 'nv-slice') + .on('mouseover', function(d,i){ + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + pointIndex: i, + pos: [d3.event.pageX, d3.event.pageY], + id: id + }); + }) + .on('mouseout', function(d,i){ + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + index: i, + id: id + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + index: i, + pos: d3.event, + id: id + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + index: i, + pos: d3.event, + id: id + }); + d3.event.stopPropagation(); + }); + + slices + .attr('fill', function(d,i) { return color(d, i); }) + .attr('stroke', function(d,i) { return color(d, i); }); + + var paths = ae.append('path') + .each(function(d) { this._current = d; }); + //.attr('d', arc); + + d3.transition(slices.select('path')) + .attr('d', arc) + .attrTween('d', arcTween); + + if (showLabels) { + // This does the normal label + var labelsArc = d3.svg.arc().innerRadius(0); + + if (pieLabelsOutside){ labelsArc = arc; } + + if (donutLabelsOutside) { labelsArc = d3.svg.arc().outerRadius(arc.outerRadius()); } + + ae.append("g").classed("nv-label", true) + .each(function(d, i) { + var group = d3.select(this); + + group + .attr('transform', function(d) { + if (labelSunbeamLayout) { + d.outerRadius = arcRadius + 10; // Set Outer Coordinate + d.innerRadius = arcRadius + 15; // Set Inner Coordinate + var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI); + if ((d.startAngle+d.endAngle)/2 < Math.PI) { + rotateAngle -= 90; + } else { + rotateAngle += 90; + } + return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')'; + } else { + d.outerRadius = radius + 10; // Set Outer Coordinate + d.innerRadius = radius + 15; // Set Inner Coordinate + return 'translate(' + labelsArc.centroid(d) + ')' + } + }); + + group.append('rect') + .style('stroke', '#fff') + .style('fill', '#fff') + .attr("rx", 3) + .attr("ry", 3); + + group.append('text') + .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned + .style('fill', '#000') + + + }); + + slices.select(".nv-label").transition() + .attr('transform', function(d) { + if (labelSunbeamLayout) { + d.outerRadius = arcRadius + 10; // Set Outer Coordinate + d.innerRadius = arcRadius + 15; // Set Inner Coordinate + var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI); + if ((d.startAngle+d.endAngle)/2 < Math.PI) { + rotateAngle -= 90; + } else { + rotateAngle += 90; + } + return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')'; + } else { + d.outerRadius = radius + 10; // Set Outer Coordinate + d.innerRadius = radius + 15; // Set Inner Coordinate + return 'translate(' + labelsArc.centroid(d) + ')' + } + }); + + slices.each(function(d, i) { + var slice = d3.select(this); + + slice + .select(".nv-label text") + .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned + .text(function(d, i) { + var percent = (d.endAngle - d.startAngle) / (2 * Math.PI); + return (d.value && percent > labelThreshold) ? getX(d.data) : ''; + }); + + var textBox = slice.select('text').node().getBBox(); + slice.select(".nv-label rect") + .attr("width", textBox.width + 10) + .attr("height", textBox.height + 10) + .attr("transform", function() { + return "translate(" + [textBox.x - 5, textBox.y - 5] + ")"; + }); + }); + } + + + // Computes the angle of an arc, converting from radians to degrees. + function angle(d) { + var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90; + return a > 90 ? a - 180 : a; + } + + function arcTween(a) { + if (!donut) a.innerRadius = 0; + var i = d3.interpolate(this._current, a); + this._current = i(0); + return function(t) { + return arc(i(t)); + }; + } + + function tweenPie(b) { + b.innerRadius = 0; + var i = d3.interpolate({startAngle: 0, endAngle: 0}, b); + return function(t) { + return arc(i(t)); + }; + } + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.values = function(_) { + if (!arguments.length) return getValues; + getValues = _; + return chart; + }; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + }; + + chart.description = function(_) { + if (!arguments.length) return getDescription; + getDescription = _; + return chart; + }; + + chart.showLabels = function(_) { + if (!arguments.length) return showLabels; + showLabels = _; + return chart; + }; + + chart.labelSunbeamLayout = function(_) { + if (!arguments.length) return labelSunbeamLayout; + labelSunbeamLayout = _; + return chart; + }; + + chart.donutLabelsOutside = function(_) { + if (!arguments.length) return donutLabelsOutside; + donutLabelsOutside = _; + return chart; + }; + + chart.pieLabelsOutside = function(_) { + if (!arguments.length) return pieLabelsOutside; + pieLabelsOutside = _; + return chart; + }; + + chart.donut = function(_) { + if (!arguments.length) return donut; + donut = _; + return chart; + }; + + chart.startAngle = function(_) { + if (!arguments.length) return startAngle; + startAngle = _; + return chart; + }; + + chart.endAngle = function(_) { + if (!arguments.length) return endAngle; + endAngle = _; + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.valueFormat = function(_) { + if (!arguments.length) return valueFormat; + valueFormat = _; + return chart; + }; + + chart.labelThreshold = function(_) { + if (!arguments.length) return labelThreshold; + labelThreshold = _; + return chart; + }; + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/pieChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/pieChart.js new file mode 100644 index 0000000000..7d2a53cd37 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/pieChart.js @@ -0,0 +1,285 @@ + +nv.models.pieChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var pie = nv.models.pie() + , legend = nv.models.legend() + ; + + var margin = {top: 30, right: 20, bottom: 20, left: 20} + , width = null + , height = null + , showLegend = true + , color = nv.utils.defaultColor() + , tooltips = true + , tooltip = function(key, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + '

                              ' + } + , state = {} + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var tooltipLabel = pie.description()(e.point) || pie.x()(e.point) + var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ), + top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0), + y = pie.valueFormat()(pie.y()(e.point)), + content = tooltip(tooltipLabel, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { chart(selection); }; + chart.container = this; + + //set state.disabled + state.disabled = data[0].map(function(d) { return !!d.disabled }); + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-pieWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend + .width( availableWidth ) + .key(pie.x()); + + wrap.select('.nv-legendWrap') + .datum(pie.values()(data[0])) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + pie + .width(availableWidth) + .height(availableHeight); + + + var pieWrap = g.select('.nv-pieWrap') + .datum(data); + + d3.transition(pieWrap).call(pie); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i, that) { + d.disabled = !d.disabled; + + if (!pie.values()(data[0]).filter(function(d) { return !d.disabled }).length) { + pie.values()(data[0]).map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data[0].map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart) + }); + + pie.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data[0].forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + selection.call(chart); + }); + + //============================================================ + + + }); + + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + pie.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.legend = legend; + chart.dispatch = dispatch; + chart.pie = pie; + + d3.rebind(chart, pie, 'valueFormat', 'values', 'x', 'y', 'description', 'id', 'showLabels', 'donutLabelsOutside', 'pieLabelsOutside', 'donut', 'labelThreshold'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + pie.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/scatter.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/scatter.js new file mode 100644 index 0000000000..780d29c560 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/scatter.js @@ -0,0 +1,625 @@ + +nv.models.scatter = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() // chooses color + , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one + , x = d3.scale.linear() + , y = d3.scale.linear() + , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area + , getX = function(d) { return d.x } // accessor to get the x value + , getY = function(d) { return d.y } // accessor to get the y value + , getSize = function(d) { return d.size || 1} // accessor to get the point size + , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape + , onlyCircles = true // Set to false to use shapes + , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) + , forceY = [] // List of numbers to Force into the Y scale + , forceSize = [] // List of numbers to Force into the Size scale + , interactive = true // If true, plots a voronoi overlay for advanced point intersection + , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out + , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart + , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding + , clipEdge = false // if true, masks points within x and y scale + , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance + , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips + , xDomain = null // Override x domain (skips the calculation from data) + , yDomain = null // Override y domain + , sizeDomain = null // Override point size domain + , sizeRange = null + , singlePoint = false + , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout') + , useVoronoi = true + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0, z0 // used to store previous scales + , timeoutID + , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + //add series index to each data point for reference + data = data.map(function(series, i) { + series.values = series.values.map(function(point) { + point.series = i; + return point; + }); + return series; + }); + + //------------------------------------------------------------ + // Setup Scales + + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain && sizeDomain) ? [] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance + d3.merge( + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) } + }) + }) + ); + + x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x }).concat(forceX))) + + if (padData && data[0]) + x.range([(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]); + //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range([0, availableWidth]); + + y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY))) + .range([availableHeight, 0]); + + z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize))) + .range(sizeRange || [16, 256]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + + x0 = x0 || x; + y0 = y0 || y; + z0 = z0 || z; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id + (singlePoint ? ' nv-single-point' : '')); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-groups'); + gEnter.append('g').attr('class', 'nv-point-paths'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + + function updateInteractiveLayer() { + + if (!interactive) return false; + + var eventElements; + + var vertices = d3.merge(data.map(function(group, groupIndex) { + return group.values + .map(function(point, pointIndex) { + // *Adding noise to make duplicates very unlikely + // **Injecting series and point index for reference + return [x(getX(point,pointIndex)) * (Math.random() / 1e12 + 1) , y(getY(point,pointIndex)) * (Math.random() / 1e12 + 1), groupIndex, pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates + }) + .filter(function(pointArray, pointIndex) { + return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct! + }) + }) + ); + + + + //inject series and point index for reference into voronoi + if (useVoronoi === true) { + + if (clipVoronoi) { + var pointClipsEnter = wrap.select('defs').selectAll('.nv-point-clips') + .data([id]) + .enter(); + + pointClipsEnter.append('clipPath') + .attr('class', 'nv-point-clips') + .attr('id', 'nv-points-clip-' + id); + + var pointClips = wrap.select('#nv-points-clip-' + id).selectAll('circle') + .data(vertices); + pointClips.enter().append('circle') + .attr('r', clipRadius); + pointClips.exit().remove(); + pointClips + .attr('cx', function(d) { return d[0] }) + .attr('cy', function(d) { return d[1] }); + + wrap.select('.nv-point-paths') + .attr('clip-path', 'url(#nv-points-clip-' + id + ')'); + } + + + // if(vertices.length < 3) { + // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work + vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]); + vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]); + vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]); + vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]); + // } + + var bounds = d3.geom.polygon([ + [-10,-10], + [-10,height + 10], + [width + 10,height + 10], + [width + 10,-10] + ]); + + var voronoi = d3.geom.voronoi(vertices).map(function(d, i) { + return { + 'data': bounds.clip(d), + 'series': vertices[i][2], + 'point': vertices[i][3] + } + }); + + + + var pointPaths = wrap.select('.nv-point-paths').selectAll('path') + .data(voronoi); + pointPaths.enter().append('path') + .attr('class', function(d,i) { return 'nv-path-'+i; }); + pointPaths.exit().remove(); + pointPaths + .attr('d', function(d) { return 'M' + d.data.join('L') + 'Z'; }); + + pointPaths + .on('click', function(d) { + if (needsUpdate) return 0; + var series = data[d.series], + point = series.values[d.point]; + + dispatch.elementClick({ + point: point, + series: series, + pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top], + seriesIndex: d.series, + pointIndex: d.point + }); + }) + .on('mouseover', function(d) { + if (needsUpdate) return 0; + var series = data[d.series], + point = series.values[d.point]; + + dispatch.elementMouseover({ + point: point, + series: series, + pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top], + seriesIndex: d.series, + pointIndex: d.point + }); + }) + .on('mouseout', function(d, i) { + if (needsUpdate) return 0; + var series = data[d.series], + point = series.values[d.point]; + + dispatch.elementMouseout({ + point: point, + series: series, + seriesIndex: d.series, + pointIndex: d.point + }); + }); + + + } else { + /* + // bring data in form needed for click handlers + var dataWithPoints = vertices.map(function(d, i) { + return { + 'data': d, + 'series': vertices[i][2], + 'point': vertices[i][3] + } + }); + */ + + // add event handlers to points instead voronoi paths + wrap.select('.nv-groups').selectAll('.nv-group') + .selectAll('.nv-point') + //.data(dataWithPoints) + //.style('pointer-events', 'auto') // recativate events, disabled by css + .on('click', function(d,i) { + //nv.log('test', d, i); + if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point + var series = data[d.series], + point = series.values[i]; + + dispatch.elementClick({ + point: point, + series: series, + pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], + seriesIndex: d.series, + pointIndex: i + }); + }) + .on('mouseover', function(d,i) { + if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point + var series = data[d.series], + point = series.values[i]; + + dispatch.elementMouseover({ + point: point, + series: series, + pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], + seriesIndex: d.series, + pointIndex: i + }); + }) + .on('mouseout', function(d,i) { + if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point + var series = data[d.series], + point = series.values[i]; + + dispatch.elementMouseout({ + point: point, + series: series, + seriesIndex: d.series, + pointIndex: i + }); + }); + } + + needsUpdate = false; + } + + needsUpdate = true; + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + d3.transition(groups.exit()) + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }); + d3.transition(groups) + .style('fill', function(d,i) { return color(d, i) }) + .style('stroke', function(d,i) { return color(d, i) }) + .style('stroke-opacity', 1) + .style('fill-opacity', .5); + + + if (onlyCircles) { + + var points = groups.selectAll('circle.nv-point') + .data(function(d) { return d.values }); + points.enter().append('circle') + .attr('cx', function(d,i) { return x0(getX(d,i)) }) + .attr('cy', function(d,i) { return y0(getY(d,i)) }) + .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) }); + points.exit().remove(); + d3.transition(groups.exit().selectAll('path.nv-point')) + .attr('cx', function(d,i) { return x(getX(d,i)) }) + .attr('cy', function(d,i) { return y(getY(d,i)) }) + .remove(); + points.attr('class', function(d,i) { return 'nv-point nv-point-' + i }); + d3.transition(points) + .attr('cx', function(d,i) { return x(getX(d,i)) }) + .attr('cy', function(d,i) { return y(getY(d,i)) }) + .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) }); + + } else { + + var points = groups.selectAll('path.nv-point') + .data(function(d) { return d.values }); + points.enter().append('path') + .attr('transform', function(d,i) { + return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')' + }) + .attr('d', + d3.svg.symbol() + .type(getShape) + .size(function(d,i) { return z(getSize(d,i)) }) + ); + points.exit().remove(); + d3.transition(groups.exit().selectAll('path.nv-point')) + .attr('transform', function(d,i) { + return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')' + }) + .remove(); + points.attr('class', function(d,i) { return 'nv-point nv-point-' + i }); + d3.transition(points) + .attr('transform', function(d,i) { + //nv.log(d,i,getX(d,i), x(getX(d,i))); + return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')' + }) + .attr('d', + d3.svg.symbol() + .type(getShape) + .size(function(d,i) { return z(getSize(d,i)) }) + ); + } + + + // Delay updating the invisible interactive layer for smoother animation + clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer + timeoutID = setTimeout(updateInteractiveLayer, 300); + //updateInteractiveLayer(); + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + z0 = z.copy(); + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + dispatch.on('elementMouseover.point', function(d) { + if (interactive) + d3.select('.nv-chart-' + id + ' .nv-series-' + d.seriesIndex + ' .nv-point-' + d.pointIndex) + .classed('hover', true); + }); + + dispatch.on('elementMouseout.point', function(d) { + if (interactive) + d3.select('.nv-chart-' + id + ' .nv-series-' + d.seriesIndex + ' .nv-point-' + d.pointIndex) + .classed('hover', false); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = d3.functor(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + }; + + chart.size = function(_) { + if (!arguments.length) return getSize; + getSize = d3.functor(_); + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.zScale = function(_) { + if (!arguments.length) return z; + z = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.sizeDomain = function(_) { + if (!arguments.length) return sizeDomain; + sizeDomain = _; + return chart; + }; + + chart.sizeRange = function(_) { + if (!arguments.length) return sizeRange; + sizeRange = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.forceSize = function(_) { + if (!arguments.length) return forceSize; + forceSize = _; + return chart; + }; + + chart.interactive = function(_) { + if (!arguments.length) return interactive; + interactive = _; + return chart; + }; + + chart.pointActive = function(_) { + if (!arguments.length) return pointActive; + pointActive = _; + return chart; + }; + + chart.padData = function(_) { + if (!arguments.length) return padData; + padData = _; + return chart; + }; + + chart.padDataOuter = function(_) { + if (!arguments.length) return padDataOuter; + padDataOuter = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.clipVoronoi= function(_) { + if (!arguments.length) return clipVoronoi; + clipVoronoi = _; + return chart; + }; + + chart.useVoronoi= function(_) { + if (!arguments.length) return useVoronoi; + useVoronoi = _; + if (useVoronoi === false) { + clipVoronoi = false; + } + return chart; + }; + + chart.clipRadius = function(_) { + if (!arguments.length) return clipRadius; + clipRadius = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.shape = function(_) { + if (!arguments.length) return getShape; + getShape = _; + return chart; + }; + + chart.onlyCircles = function(_) { + if (!arguments.length) return onlyCircles; + onlyCircles = _; + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.singlePoint = function(_) { + if (!arguments.length) return singlePoint; + singlePoint = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/scatterChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/scatterChart.js new file mode 100644 index 0000000000..825450bcc0 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/scatterChart.js @@ -0,0 +1,578 @@ + +nv.models.scatterChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var scatter = nv.models.scatter() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + , distX = nv.models.distribution() + , distY = nv.models.distribution() + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 75} + , width = null + , height = null + , color = nv.utils.defaultColor() + , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale() + , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale() + , xPadding = 0 + , yPadding = 0 + , showDistX = false + , showDistY = false + , showLegend = true + , showControls = !!d3.fisheye + , fisheye = 0 + , pauseFisheye = false + , tooltips = true + , tooltipX = function(key, x, y) { return '' + x + '' } + , tooltipY = function(key, x, y) { return '' + y + '' } + //, tooltip = function(key, x, y) { return '

                              ' + key + '

                              ' } + , tooltip = null + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , noData = "No Data Available." + ; + + scatter + .xScale(x) + .yScale(y) + ; + xAxis + .orient('bottom') + .tickPadding(10) + ; + yAxis + .orient('left') + .tickPadding(10) + ; + distX + .axis('x') + ; + distY + .axis('y') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var state = {}, + x0, y0; + + var showTooltip = function(e, offsetElement) { + //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?) + + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0), + leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ), + topY = e.pos[1] + ( offsetElement.offsetTop || 0), + xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)), + yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex)); + + if( tooltipX != null ) + nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip'); + if( tooltipY != null ) + nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip'); + if( tooltip != null ) + nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + var controlsData = [ + { key: 'Magnify', disabled: true } + ]; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { chart(selection) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id()); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + // background for pointer events + gEnter.append('rect').attr('class', 'nvd3 nv-background') + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + gEnter.append('g').attr('class', 'nv-distWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width( availableWidth / 2 ); + + wrap.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + controls.width(180).color(['#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + scatter + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) + + wrap.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + .call(scatter); + + + //Adjust for x and y padding + if (xPadding) { + var xRange = x.domain()[1] - x.domain()[0]; + x.domain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]); + } + + if (yPadding) { + var yRange = y.domain()[1] - y.domain()[0]; + y.domain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : availableWidth / 100 ) + .tickSize( -availableHeight , 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .call(xAxis); + + + yAxis + .scale(y) + .ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis') + .call(yAxis); + + + if (showDistX) { + distX + .getData(scatter.x()) + .scale(x) + .width(availableWidth) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionX'); + g.select('.nv-distributionX') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + } + + if (showDistY) { + distY + .getData(scatter.y()) + .scale(y) + .width(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionY'); + g.select('.nv-distributionY') + .attr('transform', 'translate(-' + distY.size() + ',0)') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } + + //------------------------------------------------------------ + + + + + if (d3.fisheye) { + g.select('.nv-background') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g.select('.nv-background').on('mousemove', updateFisheye); + g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;}); + scatter.dispatch.on('elementClick.freezeFisheye', function() { + pauseFisheye = !pauseFisheye; + }); + } + + + function updateFisheye() { + if (pauseFisheye) { + g.select('.nv-point-paths').style('pointer-events', 'all'); + return false; + } + + g.select('.nv-point-paths').style('pointer-events', 'none' ); + + var mouse = d3.mouse(this); + x.distortion(fisheye).focus(mouse[0]); + y.distortion(fisheye).focus(mouse[1]); + + g.select('.nv-scatterWrap') + .call(scatter); + + g.select('.nv-x.nv-axis').call(xAxis); + g.select('.nv-y.nv-axis').call(yAxis); + g.select('.nv-distributionX') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + g.select('.nv-distributionY') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + fisheye = d.disabled ? 0 : 2.5; + g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all'); + g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' ); + + if (d.disabled) { + x.distortion(fisheye).focus(0); + y.distortion(fisheye).focus(0); + + g.select('.nv-scatterWrap').call(scatter); + g.select('.nv-x.nv-axis').call(xAxis); + g.select('.nv-y.nv-axis').call(yAxis); + } else { + pauseFisheye = false; + } + + chart(selection); + }); + + legend.dispatch.on('legendClick', function(d,i, that) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + chart(selection); + }); + + /* + legend.dispatch.on('legendMouseover', function(d, i) { + d.hover = true; + chart(selection); + }); + + legend.dispatch.on('legendMouseout', function(d, i) { + d.hover = false; + chart(selection); + }); + */ + + scatter.dispatch.on('elementMouseover.tooltip', function(e) { + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) + .attr('y1', e.pos[1] - availableHeight); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) + .attr('x2', e.pos[0] + distX.size()); + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + selection.call(chart); + }); + + //============================================================ + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + scatter.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) + .attr('y1', 0); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) + .attr('x2', distY.size()); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.scatter = scatter; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.distX = distX; + chart.distY = distY; + + d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + distX.color(color); + distY.color(color); + return chart; + }; + + chart.showDistX = function(_) { + if (!arguments.length) return showDistX; + showDistX = _; + return chart; + }; + + chart.showDistY = function(_) { + if (!arguments.length) return showDistY; + showDistY = _; + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.fisheye = function(_) { + if (!arguments.length) return fisheye; + fisheye = _; + return chart; + }; + + chart.xPadding = function(_) { + if (!arguments.length) return xPadding; + xPadding = _; + return chart; + }; + + chart.yPadding = function(_) { + if (!arguments.length) return yPadding; + yPadding = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltipXContent = function(_) { + if (!arguments.length) return tooltipX; + tooltipX = _; + return chart; + }; + + chart.tooltipYContent = function(_) { + if (!arguments.length) return tooltipY; + tooltipY = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/scatterPlusLineChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/scatterPlusLineChart.js new file mode 100644 index 0000000000..c8a8b2062c --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/scatterPlusLineChart.js @@ -0,0 +1,580 @@ + +nv.models.scatterPlusLineChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var scatter = nv.models.scatter() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + , distX = nv.models.distribution() + , distY = nv.models.distribution() + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 75} + , width = null + , height = null + , color = nv.utils.defaultColor() + , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale() + , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale() + , showDistX = false + , showDistY = false + , showLegend = true + , showControls = !!d3.fisheye + , fisheye = 0 + , pauseFisheye = false + , tooltips = true + , tooltipX = function(key, x, y) { return '' + x + '' } + , tooltipY = function(key, x, y) { return '' + y + '' } + , tooltip = function(key, x, y, date) { return '

                              ' + key + '

                              ' + + '

                              ' + date + '

                              ' } + //, tooltip = null + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , noData = "No Data Available." + ; + + scatter + .xScale(x) + .yScale(y) + ; + xAxis + .orient('bottom') + .tickPadding(10) + ; + yAxis + .orient('left') + .tickPadding(10) + ; + distX + .axis('x') + ; + distY + .axis('y') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var state = {}, + x0, y0; + + var showTooltip = function(e, offsetElement) { + //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?) + + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0), + leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ), + topY = e.pos[1] + ( offsetElement.offsetTop || 0), + xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)), + yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex)); + + if( tooltipX != null ) + nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip'); + if( tooltipY != null ) + nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip'); + if( tooltip != null ) + nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e.point.tooltip, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + var controlsData = [ + { key: 'Magnify', disabled: true } + ]; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { chart(selection) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = scatter.xScale(); + y = scatter.yScale(); + + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id()); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + // background for pointer events + gEnter.append('rect').attr('class', 'nvd3 nv-background') + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + gEnter.append('g').attr('class', 'nv-regressionLinesWrap'); + gEnter.append('g').attr('class', 'nv-distWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width( availableWidth / 2 ); + + wrap.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + controls.width(180).color(['#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Main Chart Component(s) + + scatter + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) + + wrap.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + .call(scatter); + + + wrap.select('.nv-regressionLinesWrap') + .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')'); + + var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines') + .data(function(d) { return d }); + + var reglines = regWrap.enter() + .append('g').attr('class', 'nv-regLines') + .append('line').attr('class', 'nv-regLine') + .style('stroke-opacity', 0); + + //d3.transition(regWrap.selectAll('.nv-regLines line')) + regWrap.selectAll('.nv-regLines line') + .attr('x1', x.range()[0]) + .attr('x2', x.range()[1]) + .attr('y1', function(d,i) { return y(x.domain()[0] * d.slope + d.intercept) }) + .attr('y2', function(d,i) { return y(x.domain()[1] * d.slope + d.intercept) }) + .style('stroke', function(d,i,j) { return color(d,j) }) + .style('stroke-opacity', function(d,i) { + return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1 + }); + + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( xAxis.ticks() ? xAxis.ticks() : availableWidth / 100 ) + .tickSize( -availableHeight , 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .call(xAxis); + + + yAxis + .scale(y) + .ticks( yAxis.ticks() ? yAxis.ticks() : availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis') + .call(yAxis); + + + if (showDistX) { + distX + .getData(scatter.x()) + .scale(x) + .width(availableWidth) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionX'); + g.select('.nv-distributionX') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + } + + if (showDistY) { + distY + .getData(scatter.y()) + .scale(y) + .width(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionY'); + g.select('.nv-distributionY') + .attr('transform', 'translate(-' + distY.size() + ',0)') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } + + //------------------------------------------------------------ + + + + + if (d3.fisheye) { + g.select('.nv-background') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g.select('.nv-background').on('mousemove', updateFisheye); + g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;}); + scatter.dispatch.on('elementClick.freezeFisheye', function() { + pauseFisheye = !pauseFisheye; + }); + } + + + function updateFisheye() { + if (pauseFisheye) { + g.select('.nv-point-paths').style('pointer-events', 'all'); + return false; + } + + g.select('.nv-point-paths').style('pointer-events', 'none' ); + + var mouse = d3.mouse(this); + x.distortion(fisheye).focus(mouse[0]); + y.distortion(fisheye).focus(mouse[1]); + + g.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + .call(scatter); + g.select('.nv-x.nv-axis').call(xAxis); + g.select('.nv-y.nv-axis').call(yAxis); + g.select('.nv-distributionX') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + g.select('.nv-distributionY') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + fisheye = d.disabled ? 0 : 2.5; + g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all'); + g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' ); + + if (d.disabled) { + x.distortion(fisheye).focus(0); + y.distortion(fisheye).focus(0); + + g.select('.nv-scatterWrap').call(scatter); + g.select('.nv-x.nv-axis').call(xAxis); + g.select('.nv-y.nv-axis').call(yAxis); + } else { + pauseFisheye = false; + } + + chart(selection); + }); + + legend.dispatch.on('legendClick', function(d,i, that) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + chart(selection); + }); + + /* + legend.dispatch.on('legendMouseover', function(d, i) { + d.hover = true; + chart(selection); + }); + + legend.dispatch.on('legendMouseout', function(d, i) { + d.hover = false; + chart(selection); + }); + */ + + scatter.dispatch.on('elementMouseover.tooltip', function(e) { + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) + .attr('y1', e.pos[1] - availableHeight); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) + .attr('x2', e.pos[0] + distX.size()); + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + selection.call(chart); + }); + + //============================================================ + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + scatter.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) + .attr('y1', 0); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) + .attr('x2', distY.size()); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.scatter = scatter; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.distX = distX; + chart.distY = distY; + + d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + distX.color(color); + distY.color(color); + return chart; + }; + + chart.showDistX = function(_) { + if (!arguments.length) return showDistX; + showDistX = _; + return chart; + }; + + chart.showDistY = function(_) { + if (!arguments.length) return showDistY; + showDistY = _; + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.fisheye = function(_) { + if (!arguments.length) return fisheye; + fisheye = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltipXContent = function(_) { + if (!arguments.length) return tooltipX; + tooltipX = _; + return chart; + }; + + chart.tooltipYContent = function(_) { + if (!arguments.length) return tooltipY; + tooltipY = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/sparkline.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/sparkline.js new file mode 100644 index 0000000000..43c05b6e46 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/sparkline.js @@ -0,0 +1,179 @@ + +nv.models.sparkline = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 2, right: 0, bottom: 2, left: 0} + , width = 400 + , height = 32 + , animate = true + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , color = nv.utils.getColor(['#000']) + , xDomain + , yDomain + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup Scales + + x .domain(xDomain || d3.extent(data, getX )) + .range([0, availableWidth]); + + y .domain(yDomain || d3.extent(data, getY )) + .range([availableHeight, 0]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + + //------------------------------------------------------------ + + + var paths = wrap.selectAll('path') + .data(function(d) { return [d] }); + paths.enter().append('path'); + paths.exit().remove(); + paths + .style('stroke', function(d,i) { return d.color || color(d, i) }) + .attr('d', d3.svg.line() + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ); + + + // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent) + var points = wrap.selectAll('circle.nv-point') + .data(function(data) { + var yValues = data.map(function(d, i) { return getY(d,i); }); + function pointIndex(index) { + if (index != -1) { + var result = data[index]; + result.pointIndex = index; + return result; + } else { + return null; + } + } + var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])), + minPoint = pointIndex(yValues.indexOf(y.domain()[0])), + currentPoint = pointIndex(yValues.length - 1); + return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;}); + }); + points.enter().append('circle'); + points.exit().remove(); + points + .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) }) + .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) }) + .attr('r', 2) + .attr('class', function(d,i) { + return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' : + getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue' + }); + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = d3.functor(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.animate = function(_) { + if (!arguments.length) return animate; + animate = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/sparklinePlus.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/sparklinePlus.js new file mode 100644 index 0000000000..862df88c62 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/sparklinePlus.js @@ -0,0 +1,291 @@ + +nv.models.sparklinePlus = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var sparkline = nv.models.sparkline(); + + var margin = {top: 15, right: 100, bottom: 10, left: 50} + , width = null + , height = null + , x + , y + , index = [] + , paused = false + , xTickFormat = d3.format(',r') + , yTickFormat = d3.format(',.2f') + , showValue = true + , alignValue = true + , rightAlignValue = false + , noData = "No Data Available." + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this); + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + var currentValue = sparkline.y()(data[data.length-1], data.length-1); + + chart.update = function() { chart(selection) }; + chart.container = this; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + + //------------------------------------------------------------ + // Setup Scales + + x = sparkline.xScale(); + y = sparkline.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-sparklineWrap'); + gEnter.append('g').attr('class', 'nv-valueWrap'); + gEnter.append('g').attr('class', 'nv-hoverArea'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Main Chart Component(s) + + var sparklineWrap = g.select('.nv-sparklineWrap'); + + sparkline + .width(availableWidth) + .height(availableHeight); + + sparklineWrap + .call(sparkline); + + //------------------------------------------------------------ + + + var valueWrap = g.select('.nv-valueWrap'); + + var value = valueWrap.selectAll('.nv-currentValue') + .data([currentValue]); + + value.enter().append('text').attr('class', 'nv-currentValue') + .attr('dx', rightAlignValue ? -8 : 8) + .attr('dy', '.9em') + .style('text-anchor', rightAlignValue ? 'end' : 'start'); + + value + .attr('x', availableWidth + (rightAlignValue ? margin.right : 0)) + .attr('y', alignValue ? function(d) { return y(d) } : 0) + .style('fill', sparkline.color()(data[data.length-1], data.length-1)) + .text(yTickFormat(currentValue)); + + + + gEnter.select('.nv-hoverArea').append('rect') + .on('mousemove', sparklineHover) + .on('click', function() { paused = !paused }) + .on('mouseout', function() { index = []; updateValueLine(); }); + //.on('mouseout', function() { index = null; updateValueLine(); }); + + g.select('.nv-hoverArea rect') + .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' }) + .attr('width', availableWidth + margin.left + margin.right) + .attr('height', availableHeight + margin.top); + + + + function updateValueLine() { //index is currently global (within the chart), may or may not keep it that way + if (paused) return; + + var hoverValue = g.selectAll('.nv-hoverValue').data(index) + + var hoverEnter = hoverValue.enter() + .append('g').attr('class', 'nv-hoverValue') + .style('stroke-opacity', 0) + .style('fill-opacity', 0); + + hoverValue.exit() + .transition().duration(250) + .style('stroke-opacity', 0) + .style('fill-opacity', 0) + .remove(); + + hoverValue + .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' }) + .transition().duration(250) + .style('stroke-opacity', 1) + .style('fill-opacity', 1); + + if (!index.length) return; + + hoverEnter.append('line') + .attr('x1', 0) + .attr('y1', -margin.top) + .attr('x2', 0) + .attr('y2', availableHeight); + + + hoverEnter.append('text').attr('class', 'nv-xValue') + .attr('x', -6) + .attr('y', -margin.top) + .attr('text-anchor', 'end') + .attr('dy', '.9em') + + + g.select('.nv-hoverValue .nv-xValue') + .text(xTickFormat(sparkline.x()(data[index[0]], index[0]))); + + hoverEnter.append('text').attr('class', 'nv-yValue') + .attr('x', 6) + .attr('y', -margin.top) + .attr('text-anchor', 'start') + .attr('dy', '.9em') + + g.select('.nv-hoverValue .nv-yValue') + .text(yTickFormat(sparkline.y()(data[index[0]], index[0]))); + + } + + + function sparklineHover() { + if (paused) return; + + var pos = d3.mouse(this)[0] - margin.left; + + function getClosestIndex(data, x) { + var distance = Math.abs(sparkline.x()(data[0], 0) - x); + var closestIndex = 0; + for (var i = 0; i < data.length; i++){ + if (Math.abs(sparkline.x()(data[i], i) - x) < distance) { + distance = Math.abs(sparkline.x()(data[i], i) - x); + closestIndex = i; + } + } + return closestIndex; + } + + index = [getClosestIndex(data, Math.round(x.invert(pos)))]; + + updateValueLine(); + } + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.sparkline = sparkline; + + d3.rebind(chart, sparkline, 'x', 'y', 'xScale', 'yScale', 'color'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xTickFormat = function(_) { + if (!arguments.length) return xTickFormat; + xTickFormat = _; + return chart; + }; + + chart.yTickFormat = function(_) { + if (!arguments.length) return yTickFormat; + yTickFormat = _; + return chart; + }; + + chart.showValue = function(_) { + if (!arguments.length) return showValue; + showValue = _; + return chart; + }; + + chart.alignValue = function(_) { + if (!arguments.length) return alignValue; + alignValue = _; + return chart; + }; + + chart.rightAlignValue = function(_) { + if (!arguments.length) return rightAlignValue; + rightAlignValue = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/stackedArea.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/stackedArea.js new file mode 100644 index 0000000000..cf4217a4c7 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/stackedArea.js @@ -0,0 +1,336 @@ + +nv.models.stackedArea = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() // a function that computes the color + , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one + , getX = function(d) { return d.x } // accessor to get the x value from a data point + , getY = function(d) { return d.y } // accessor to get the y value from a data point + , style = 'stack' + , offset = 'zero' + , order = 'default' + , interpolate = 'linear' // controls the line interpolation + , clipEdge = false // if true, masks lines within x and y scale + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , scatter = nv.models.scatter() + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout') + ; + + scatter + .size(2.2) // default size + .sizeDomain([2.2]) // all the same size by default + ; + + /************************************ + * offset: + * 'wiggle' (stream) + * 'zero' (stacked) + * 'expand' (normalize to 100%) + * 'silhouette' (simple centered) + * + * order: + * 'inside-out' (stream) + * 'default' (input order) + ************************************/ + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + //------------------------------------------------------------ + // Setup Scales + + x = scatter.xScale(); + y = scatter.yScale(); + + //------------------------------------------------------------ + + + // Injecting point index into each point because d3.layout.stack().out does not give index + // ***Also storing getY(d,i) as stackedY so that it can be set to 0 if series is disabled + data = data.map(function(aseries, i) { + aseries.values = aseries.values.map(function(d, j) { + d.index = j; + d.stackedY = aseries.disabled ? 0 : getY(d,j); + return d; + }) + return aseries; + }); + + + data = d3.layout.stack() + .order(order) + .offset(offset) + .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion + .x(getX) + .y(function(d) { return d.stackedY }) + .out(function(d, y0, y) { + d.display = { + y: y, + y0: y0 + }; + }) + (data); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-areaWrap'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + scatter + .width(availableWidth) + .height(availableHeight) + .x(getX) + .y(function(d) { return d.display.y + d.display.y0 }) + .forceY([0]) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + + + var scatterWrap = g.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + //d3.transition(scatterWrap).call(scatter); + scatterWrap.call(scatter); + + + + + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + + + + var area = d3.svg.area() + .x(function(d,i) { return x(getX(d,i)) }) + .y0(function(d) { return y(d.display.y0) }) + .y1(function(d) { return y(d.display.y + d.display.y0) }) + .interpolate(interpolate); + + var zeroArea = d3.svg.area() + .x(function(d,i) { return x(getX(d,i)) }) + .y0(function(d) { return y(d.display.y0) }) + .y1(function(d) { return y(d.display.y0) }); + + + var path = g.select('.nv-areaWrap').selectAll('path.nv-area') + .data(function(d) { return d }); + //.data(function(d) { return d }, function(d) { return d.key }); + path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i }) + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.areaMouseover({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: i + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.areaMouseout({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: i + }); + }) + .on('click', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.areaClick({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: i + }); + }) + //d3.transition(path.exit()) + path.exit() + .attr('d', function(d,i) { return zeroArea(d.values,i) }) + .remove(); + path + .style('fill', function(d,i){ return d.color || color(d, i) }) + .style('stroke', function(d,i){ return d.color || color(d, i) }); + //d3.transition(path) + path + .attr('d', function(d,i) { return area(d.values,i) }) + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + scatter.dispatch.on('elementMouseover.area', function(e) { + g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true); + }); + scatter.dispatch.on('elementMouseout.area', function(e) { + g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false); + }); + + //============================================================ + + }); + + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + scatter.dispatch.on('elementClick.area', function(e) { + dispatch.areaClick(e); + }) + scatter.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); + scatter.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + //============================================================ + + + //============================================================ + // Global getters and setters + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.scatter = scatter; + + d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius'); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = d3.functor(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + } + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.offset = function(_) { + if (!arguments.length) return offset; + offset = _; + return chart; + }; + + chart.order = function(_) { + if (!arguments.length) return order; + order = _; + return chart; + }; + + //shortcut for offset + order + chart.style = function(_) { + if (!arguments.length) return style; + style = _; + + switch (style) { + case 'stack': + chart.offset('zero'); + chart.order('default'); + break; + case 'stream': + chart.offset('wiggle'); + chart.order('inside-out'); + break; + case 'stream-center': + chart.offset('silhouette'); + chart.order('inside-out'); + break; + case 'expand': + chart.offset('expand'); + chart.order('default'); + break; + } + + return chart; + }; + + chart.interpolate = function(_) { + if (!arguments.length) return interpolate; + interpolate = _; + return interpolate; + + }; + + //============================================================ + + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/models/stackedAreaChart.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/stackedAreaChart.js new file mode 100644 index 0000000000..c386cd8389 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/models/stackedAreaChart.js @@ -0,0 +1,460 @@ + +nv.models.stackedAreaChart = function() { + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var stacked = nv.models.stackedArea() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + ; + + var margin = {top: 30, right: 25, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.defaultColor() // a function that takes in d, i and returns color + , showControls = true + , showLegend = true + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '

                              ' + key + '

                              ' + + '

                              ' + y + ' on ' + x + '

                              ' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , yAxisTickFormat = d3.format(',.2f') + , state = { style: stacked.style() } + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , controlWidth = 250 + ; + + xAxis + .orient('bottom') + .tickPadding(7) + ; + yAxis + .orient('left') + ; + stacked.scatter + .pointActive(function(d) { + //console.log(stacked.y()(d), !!Math.round(stacked.y()(d) * 100)); + return !!Math.round(stacked.y()(d) * 100); + }) + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { chart(selection) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = stacked.xScale(); + y = stacked.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-stackedWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend + .width( availableWidth - controlWidth ); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + controlWidth + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + var controlsData = [ + { key: 'Stacked', disabled: stacked.offset() != 'zero' }, + { key: 'Stream', disabled: stacked.offset() != 'wiggle' }, + { key: 'Expanded', disabled: stacked.offset() != 'expand' } + ]; + + controls + .width( controlWidth ) + .color(['#444', '#444', '#444']); + + g.select('.nv-controlsWrap') + .datum(controlsData) + .call(controls); + + + if ( margin.top != Math.max(controls.height(), legend.height()) ) { + margin.top = Math.max(controls.height(), legend.height()); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + + g.select('.nv-controlsWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + stacked + .width(availableWidth) + .height(availableHeight) + + var stackedWrap = g.select('.nv-stackedWrap') + .datum(data); + //d3.transition(stackedWrap).call(stacked); + stackedWrap.call(stacked); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize( -availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + //d3.transition(g.select('.nv-x.nv-axis')) + g.select('.nv-x.nv-axis') + .transition().duration(0) + .call(xAxis); + + yAxis + .scale(y) + .ticks(stacked.offset() == 'wiggle' ? 0 : availableHeight / 36) + .tickSize(-availableWidth, 0) + .setTickFormat(stacked.offset() == 'expand' ? d3.format('%') : yAxisTickFormat); + + //d3.transition(g.select('.nv-y.nv-axis')) + g.select('.nv-y.nv-axis') + .transition().duration(0) + .call(yAxis); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + stacked.dispatch.on('areaClick.toggle', function(e) { + if (data.filter(function(d) { return !d.disabled }).length === 1) + data = data.map(function(d) { + d.disabled = false; + return d + }); + else + data = data.map(function(d,i) { + d.disabled = (i != e.seriesIndex); + return d + }); + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + //selection.transition().call(chart); + chart(selection); + }); + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + //selection.transition().call(chart); + chart(selection); + }); + + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + switch (d.key) { + case 'Stacked': + stacked.style('stack'); + break; + case 'Stream': + stacked.style('stream'); + break; + case 'Expanded': + stacked.style('expand'); + break; + } + + state.style = stacked.style(); + dispatch.stateChange(state); + + //selection.transition().call(chart); + chart(selection); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + if (typeof e.style !== 'undefined') { + stacked.style(e.style); + } + + selection.call(chart); + }); + + }); + + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + stacked.dispatch.on('tooltipShow', function(e) { + //disable tooltips when value ~= 0 + //// TODO: consider removing points from voronoi that have 0 value instead of this hack + /* + if (!Math.round(stacked.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range + setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); + return false; + } + */ + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); + + stacked.dispatch.on('tooltipHide', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.stacked = stacked; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'sizeDomain', 'interactive', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate'); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return getWidth; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return getHeight; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + stacked.color(color); + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltip = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + yAxis.setTickFormat = yAxis.tickFormat; + yAxis.tickFormat = function(_) { + if (!arguments.length) return yAxisTickFormat; + yAxisTickFormat = _; + return yAxis; + }; + + //============================================================ + + return chart; +} diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/nv.d3.css b/docdoku-web-front/app/js/lib/charts/nv3d/src/nv.d3.css new file mode 100644 index 0000000000..ca63f83923 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/nv.d3.css @@ -0,0 +1,671 @@ + +/******************** + * HTML CSS + */ + + +.chartWrap { + margin: 0; + padding: 0; + overflow: hidden; +} + + +/******************** + * TOOLTIP CSS + */ + +.nvtooltip { + position: absolute; + background-color: rgba(255,255,255,1); + padding: 1px; + border: 1px solid rgba(0,0,0,.2); + z-index: 10000; + + font-family: Arial; + font-size: 13px; + + transition: opacity 500ms linear; + -moz-transition: opacity 500ms linear; + -webkit-transition: opacity 500ms linear; + + transition-delay: 500ms; + -moz-transition-delay: 500ms; + -webkit-transition-delay: 500ms; + + -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); + -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); + box-shadow: 0 5px 10px rgba(0,0,0,.2); + + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + + pointer-events: none; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.nvtooltip.x-nvtooltip, +.nvtooltip.y-nvtooltip { + padding: 8px; +} + +.nvtooltip h3 { + margin: 0; + padding: 4px 14px; + line-height: 18px; + font-weight: normal; + background-color: #f7f7f7; + text-align: center; + + border-bottom: 1px solid #ebebeb; + + -webkit-border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + border-radius: 5px 5px 0 0; +} + +.nvtooltip p { + margin: 0; + padding: 5px 14px; + text-align: center; +} + +.nvtooltip span { + display: inline-block; + margin: 2px 0; +} + +.nvtooltip-pending-removal { + position: absolute; + pointer-events: none; +} + + +/******************** + * SVG CSS + */ + + +svg { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + /* Trying to get SVG to act like a greedy block in all browsers */ + display: block; + width:100%; + height:100%; +} + + +svg text { + font: normal 12px Arial; +} + +svg .title { + font: bold 14px Arial; +} + +.nvd3 .nv-background { + fill: white; + fill-opacity: 0; + /* + pointer-events: none; + */ +} + +.nvd3.nv-noData { + font-size: 18px; + font-weight: bold; +} + + +/********** +* Brush +*/ + +.nv-brush .extent { + fill-opacity: .125; + shape-rendering: crispEdges; +} + + + +/********** +* Legend +*/ + +.nvd3 .nv-legend .nv-series { + cursor: pointer; +} + +.nvd3 .nv-legend .disabled circle { + fill-opacity: 0; +} + + + +/********** +* Axes +*/ + +.nvd3 .nv-axis path { + fill: none; + stroke: #000; + stroke-opacity: .75; + shape-rendering: crispEdges; +} + +.nvd3 .nv-axis path.domain { + stroke-opacity: .75; +} + +.nvd3 .nv-axis.nv-x path.domain { + stroke-opacity: 0; +} + +.nvd3 .nv-axis line { + fill: none; + stroke: #000; + stroke-opacity: .25; + shape-rendering: crispEdges; +} + +.nvd3 .nv-axis line.zero { + stroke-opacity: .75; +} + +.nvd3 .nv-axis .nv-axisMaxMin text { + font-weight: bold; +} + +.nvd3 .x .nv-axis .nv-axisMaxMin text, +.nvd3 .x2 .nv-axis .nv-axisMaxMin text, +.nvd3 .x3 .nv-axis .nv-axisMaxMin text { + text-anchor: middle +} + + + +/********** +* Brush +*/ + +.nv-brush .resize path { + fill: #eee; + stroke: #666; +} + + + +/********** +* Bars +*/ + +.nvd3 .nv-bars .negative rect { + zfill: brown; +} + +.nvd3 .nv-bars rect { + zfill: steelblue; + fill-opacity: .75; + + transition: fill-opacity 250ms linear; + -moz-transition: fill-opacity 250ms linear; + -webkit-transition: fill-opacity 250ms linear; +} + +.nvd3 .nv-bars rect:hover { + fill-opacity: 1; +} + +.nvd3 .nv-bars .hover rect { + fill: lightblue; +} + +.nvd3 .nv-bars text { + fill: rgba(0,0,0,0); +} + +.nvd3 .nv-bars .hover text { + fill: rgba(0,0,0,1); +} + + +/********** +* Bars +*/ + +.nvd3 .nv-multibar .nv-groups rect, +.nvd3 .nv-multibarHorizontal .nv-groups rect, +.nvd3 .nv-discretebar .nv-groups rect { + stroke-opacity: 0; + + transition: fill-opacity 250ms linear; + -moz-transition: fill-opacity 250ms linear; + -webkit-transition: fill-opacity 250ms linear; +} + +.nvd3 .nv-multibar .nv-groups rect:hover, +.nvd3 .nv-multibarHorizontal .nv-groups rect:hover, +.nvd3 .nv-discretebar .nv-groups rect:hover { + fill-opacity: 1; +} + +.nvd3 .nv-discretebar .nv-groups text, +.nvd3 .nv-multibarHorizontal .nv-groups text { + font-weight: bold; + fill: rgba(0,0,0,1); + stroke: rgba(0,0,0,0); +} + +/*********** +* Pie Chart +*/ + +.nvd3.nv-pie path { + stroke-opacity: 0; + + transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; + -moz-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; + -webkit-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; + +} + +.nvd3.nv-pie .nv-slice text { + stroke: #000; + stroke-width: 0; +} + +.nvd3.nv-pie path { + stroke: #fff; + stroke-width: 1px; + stroke-opacity: 1; +} + +.nvd3.nv-pie .hover path { + fill-opacity: .7; +/* + stroke-width: 6px; + stroke-opacity: 1; +*/ +} + +.nvd3.nv-pie .nv-label rect { + fill-opacity: 0; + stroke-opacity: 0; +} + +/********** +* Lines +*/ + +.nvd3 .nv-groups path.nv-line { + fill: none; + stroke-width: 2.5px; + /* + stroke-linecap: round; + shape-rendering: geometricPrecision; + + transition: stroke-width 250ms linear; + -moz-transition: stroke-width 250ms linear; + -webkit-transition: stroke-width 250ms linear; + + transition-delay: 250ms + -moz-transition-delay: 250ms; + -webkit-transition-delay: 250ms; + */ +} + +.nvd3 .nv-groups path.nv-area { + stroke: none; + /* + stroke-linecap: round; + shape-rendering: geometricPrecision; + + stroke-width: 2.5px; + transition: stroke-width 250ms linear; + -moz-transition: stroke-width 250ms linear; + -webkit-transition: stroke-width 250ms linear; + + transition-delay: 250ms + -moz-transition-delay: 250ms; + -webkit-transition-delay: 250ms; + */ +} + +.nvd3 .nv-line.hover path { + stroke-width: 6px; +} + +/* +.nvd3.scatter .groups .point { + fill-opacity: 0.1; + stroke-opacity: 0.1; +} + */ + +.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point { + fill-opacity: 0; + stroke-opacity: 0; +} + +.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point { + fill-opacity: .5 !important; + stroke-opacity: .5 !important; +} + + +.nvd3 .nv-groups .nv-point { + transition: stroke-width 250ms linear, stroke-opacity 250ms linear; + -moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; + -webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; +} + +.nvd3.nv-scatter .nv-groups .nv-point.hover, +.nvd3 .nv-groups .nv-point.hover { + stroke-width: 20px; + fill-opacity: .5 !important; + stroke-opacity: .5 !important; +} + + +.nvd3 .nv-point-paths path { + stroke: #aaa; + stroke-opacity: 0; + fill: #eee; + fill-opacity: 0; +} + + + +.nvd3 .nv-indexLine { + cursor: ew-resize; +} + + +/********** +* Distribution +*/ + +.nvd3 .nv-distribution { + pointer-events: none; +} + + + +/********** +* Scatter +*/ + +/* **Attempting to remove this for useVoronoi(false), need to see if it's required anywhere +.nvd3 .nv-groups .nv-point { + pointer-events: none; +} +*/ + +.nvd3 .nv-groups .nv-point.hover { + stroke-width: 20px; + stroke-opacity: .5; +} + +.nvd3 .nv-scatter .nv-point.hover { + fill-opacity: 1; +} + +/* +.nv-group.hover .nv-point { + fill-opacity: 1; +} +*/ + + +/********** +* Stacked Area +*/ + +.nvd3.nv-stackedarea path.nv-area { + fill-opacity: .7; + /* + stroke-opacity: .65; + fill-opacity: 1; + */ + stroke-opacity: 0; + + transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; + -moz-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; + -webkit-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; + + /* + transition-delay: 500ms; + -moz-transition-delay: 500ms; + -webkit-transition-delay: 500ms; + */ + +} + +.nvd3.nv-stackedarea path.nv-area.hover { + fill-opacity: .9; + /* + stroke-opacity: .85; + */ +} +/* +.d3stackedarea .groups path { + stroke-opacity: 0; +} + */ + + + +.nvd3.nv-stackedarea .nv-groups .nv-point { + stroke-opacity: 0; + fill-opacity: 0; +} + +.nvd3.nv-stackedarea .nv-groups .nv-point.hover { + stroke-width: 20px; + stroke-opacity: .75; + fill-opacity: 1; +} + + + +/********** +* Line Plus Bar +*/ + +.nvd3.nv-linePlusBar .nv-bar rect { + fill-opacity: .75; +} + +.nvd3.nv-linePlusBar .nv-bar rect:hover { + fill-opacity: 1; +} + + +/********** +* Bullet +*/ + +.nvd3.nv-bullet { font: 10px sans-serif; } +.nvd3.nv-bullet .nv-measure { fill-opacity: .8; } +.nvd3.nv-bullet .nv-measure:hover { fill-opacity: 1; } +.nvd3.nv-bullet .nv-marker { stroke: #000; stroke-width: 2px; } +.nvd3.nv-bullet .nv-markerTriangle { stroke: #000; fill: #fff; stroke-width: 1.5px; } +.nvd3.nv-bullet .nv-tick line { stroke: #666; stroke-width: .5px; } +.nvd3.nv-bullet .nv-range.nv-s0 { fill: #eee; } +.nvd3.nv-bullet .nv-range.nv-s1 { fill: #ddd; } +.nvd3.nv-bullet .nv-range.nv-s2 { fill: #ccc; } +.nvd3.nv-bullet .nv-title { font-size: 14px; font-weight: bold; } +.nvd3.nv-bullet .nv-subtitle { fill: #999; } + + +.nvd3.nv-bullet .nv-range { + fill: #999; + fill-opacity: .4; +} +.nvd3.nv-bullet .nv-range:hover { + fill-opacity: .7; +} + + + +/********** +* Sparkline +*/ + +.nvd3.nv-sparkline path { + fill: none; +} + +.nvd3.nv-sparklineplus g.nv-hoverValue { + pointer-events: none; +} + +.nvd3.nv-sparklineplus .nv-hoverValue line { + stroke: #333; + stroke-width: 1.5px; + } + +.nvd3.nv-sparklineplus, +.nvd3.nv-sparklineplus g { + pointer-events: all; +} + +.nvd3 .nv-hoverArea { + fill-opacity: 0; + stroke-opacity: 0; +} + +.nvd3.nv-sparklineplus .nv-xValue, +.nvd3.nv-sparklineplus .nv-yValue { + /* + stroke: #666; + */ + stroke-width: 0; + font-size: .9em; + font-weight: normal; +} + +.nvd3.nv-sparklineplus .nv-yValue { + stroke: #f66; +} + +.nvd3.nv-sparklineplus .nv-maxValue { + stroke: #2ca02c; + fill: #2ca02c; +} + +.nvd3.nv-sparklineplus .nv-minValue { + stroke: #d62728; + fill: #d62728; +} + +.nvd3.nv-sparklineplus .nv-currentValue { + /* + stroke: #444; + fill: #000; + */ + font-weight: bold; + font-size: 1.1em; +} + +/********** +* historical stock +*/ + +.nvd3.nv-ohlcBar .nv-ticks .nv-tick { + stroke-width: 2px; +} + +.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover { + stroke-width: 4px; +} + +.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive { + stroke: #2ca02c; +} + +.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative { + stroke: #d62728; +} + +.nvd3.nv-historicalStockChart .nv-axis .nv-axislabel { + font-weight: bold; +} + +.nvd3.nv-historicalStockChart .nv-dragTarget { + fill-opacity: 0; + stroke: none; + cursor: move; +} + +.nvd3 .nv-brush .extent { + /* + cursor: ew-resize !important; + */ + fill-opacity: 0 !important; +} + +.nvd3 .nv-brushBackground rect { + stroke: #000; + stroke-width: .4; + fill: #fff; + fill-opacity: .7; +} + + + +/********** +* Indented Tree +*/ + + +/** + * TODO: the following 3 selectors are based on classes used in the example. I should either make them standard and leave them here, or move to a CSS file not included in the library + */ +.nvd3.nv-indentedtree .name { + margin-left: 5px; +} + +.nvd3.nv-indentedtree .clickable { + color: #08C; + cursor: pointer; +} + +.nvd3.nv-indentedtree span.clickable:hover { + color: #005580; + text-decoration: underline; +} + + +.nvd3.nv-indentedtree .nv-childrenCount { + display: inline-block; + margin-left: 5px; +} + +.nvd3.nv-indentedtree .nv-treeicon { + cursor: pointer; + /* + cursor: n-resize; + */ +} + +.nvd3.nv-indentedtree .nv-treeicon.nv-folded { + cursor: pointer; + /* + cursor: s-resize; + */ +} + + diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/outro.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/outro.js new file mode 100644 index 0000000000..158693a025 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/outro.js @@ -0,0 +1 @@ +})(); \ No newline at end of file diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/tooltip.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/tooltip.js new file mode 100644 index 0000000000..ce7ef0cbf4 --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/tooltip.js @@ -0,0 +1,129 @@ + +/***** + * A no-frills tooltip implementation. + *****/ + + +(function() { + + var nvtooltip = window.nv.tooltip = {}; + + nvtooltip.show = function(pos, content, gravity, dist, parentContainer, classes) { + + var container = document.createElement('div'); + container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip'); + + gravity = gravity || 's'; + dist = dist || 20; + + var body = parentContainer ? parentContainer : document.getElementsByTagName('body')[0]; + + container.innerHTML = content; + container.style.left = 0; + container.style.top = 0; + container.style.opacity = 0; + + body.appendChild(container); + + var height = parseInt(container.offsetHeight), + width = parseInt(container.offsetWidth), + windowWidth = nv.utils.windowSize().width, + windowHeight = nv.utils.windowSize().height, + scrollTop = window.scrollY, + scrollLeft = window.scrollX, + left, top; + + windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16; + windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16; + + var tooltipTop = function ( Elem ) { + var offsetTop = top; + do { + if( !isNaN( Elem.offsetTop ) ) { + offsetTop += (Elem.offsetTop); + } + } while( Elem = Elem.offsetParent ); + return offsetTop; + } + + var tooltipLeft = function ( Elem ) { + var offsetLeft = left; + do { + if( !isNaN( Elem.offsetLeft ) ) { + offsetLeft += (Elem.offsetLeft); + } + } while( Elem = Elem.offsetParent ); + return offsetLeft; + } + + switch (gravity) { + case 'e': + left = pos[0] - width - dist; + top = pos[1] - (height / 2); + var tLeft = tooltipLeft(container); + var tTop = tooltipTop(container); + if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left; + if (tTop < scrollTop) top = scrollTop - tTop + top; + if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; + break; + case 'w': + left = pos[0] + dist; + top = pos[1] - (height / 2); + if (tLeft + width > windowWidth) left = pos[0] - width - dist; + if (tTop < scrollTop) top = scrollTop + 5; + if (tTop + height > scrollTop + windowHeight) top = scrollTop - height - 5; + break; + case 'n': + left = pos[0] - (width / 2) - 5; + top = pos[1] + dist; + var tLeft = tooltipLeft(container); + var tTop = tooltipTop(container); + if (tLeft < scrollLeft) left = scrollLeft + 5; + if (tLeft + width > windowWidth) left = left - width/2 + 5; + if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; + break; + case 's': + left = pos[0] - (width / 2); + top = pos[1] - height - dist; + var tLeft = tooltipLeft(container); + var tTop = tooltipTop(container); + if (tLeft < scrollLeft) left = scrollLeft + 5; + if (tLeft + width > windowWidth) left = left - width/2 + 5; + if (scrollTop > tTop) top = scrollTop; + break; + } + + + container.style.left = left+'px'; + container.style.top = top+'px'; + container.style.opacity = 1; + container.style.position = 'absolute'; //fix scroll bar issue + container.style.pointerEvents = 'none'; //fix scroll bar issue + + return container; + }; + + nvtooltip.cleanup = function() { + + // Find the tooltips, mark them for removal by this class (so others cleanups won't find it) + var tooltips = document.getElementsByClassName('nvtooltip'); + var purging = []; + while(tooltips.length) { + purging.push(tooltips[0]); + tooltips[0].style.transitionDelay = '0 !important'; + tooltips[0].style.opacity = 0; + tooltips[0].className = 'nvtooltip-pending-removal'; + } + + + setTimeout(function() { + + while (purging.length) { + var removeMe = purging.pop(); + removeMe.parentNode.removeChild(removeMe); + } + }, 500); + }; + + +})(); diff --git a/docdoku-web-front/app/js/lib/charts/nv3d/src/utils.js b/docdoku-web-front/app/js/lib/charts/nv3d/src/utils.js new file mode 100644 index 0000000000..ce298c390d --- /dev/null +++ b/docdoku-web-front/app/js/lib/charts/nv3d/src/utils.js @@ -0,0 +1,105 @@ + +nv.utils.windowSize = function() { + // Sane defaults + var size = {width: 640, height: 480}; + + // Earlier IE uses Doc.body + if (document.body && document.body.offsetWidth) { + size.width = document.body.offsetWidth; + size.height = document.body.offsetHeight; + } + + // IE can use depending on mode it is in + if (document.compatMode=='CSS1Compat' && + document.documentElement && + document.documentElement.offsetWidth ) { + size.width = document.documentElement.offsetWidth; + size.height = document.documentElement.offsetHeight; + } + + // Most recent browsers use + if (window.innerWidth && window.innerHeight) { + size.width = window.innerWidth; + size.height = window.innerHeight; + } + return (size); +}; + + + +// Easy way to bind multiple functions to window.onresize +// TODO: give a way to remove a function after its bound, other than removing all of them +nv.utils.windowResize = function(fun){ + var oldresize = window.onresize; + + window.onresize = function(e) { + if (typeof oldresize == 'function') oldresize(e); + fun(e); + } +} + +// Backwards compatible way to implement more d3-like coloring of graphs. +// If passed an array, wrap it in a function which implements the old default +// behavior +nv.utils.getColor = function(color) { + if (!arguments.length) return nv.utils.defaultColor(); //if you pass in nothing, get default colors back + + if( Object.prototype.toString.call( color ) === '[object Array]' ) + return function(d, i) { return d.color || color[i % color.length]; }; + else + return color; + //can't really help it if someone passes rubbish as color +} + +// Default color chooser uses the index of an object as before. +nv.utils.defaultColor = function() { + var colors = d3.scale.category20().range(); + return function(d, i) { return d.color || colors[i % colors.length] }; +} + + +// Returns a color function that takes the result of 'getKey' for each series and +// looks for a corresponding color from the dictionary, +nv.utils.customTheme = function(dictionary, getKey, defaultColors) { + getKey = getKey || function(series) { return series.key }; // use default series.key if getKey is undefined + defaultColors = defaultColors || d3.scale.category20().range(); //default color function + + var defIndex = defaultColors.length; //current default color (going in reverse) + + return function(series, index) { + var key = getKey(series); + + if (!defIndex) defIndex = defaultColors.length; //used all the default colors, start over + + if (typeof dictionary[key] !== "undefined") + return (typeof dictionary[key] === "function") ? dictionary[key]() : dictionary[key]; + else + return defaultColors[--defIndex]; // no match in dictionary, use default color + } +} + + + +// From the PJAX example on d3js.org, while this is not really directly needed +// it's a very cool method for doing pjax, I may expand upon it a little bit, +// open to suggestions on anything that may be useful +nv.utils.pjax = function(links, content) { + d3.selectAll(links).on("click", function() { + history.pushState(this.href, this.textContent, this.href); + load(this.href); + d3.event.preventDefault(); + }); + + function load(href) { + d3.html(href, function(fragment) { + var target = d3.select(content).node(); + target.parentNode.replaceChild(d3.select(fragment).select(content).node(), target); + nv.utils.pjax(links, content); + }); + } + + d3.select(window).on("popstate", function() { + if (d3.event.state) load(d3.event.state); + }); +} + diff --git a/docdoku-web-front/app/js/lib/empty.pdf b/docdoku-web-front/app/js/lib/empty.pdf new file mode 100644 index 0000000000..8c5ca5424e Binary files /dev/null and b/docdoku-web-front/app/js/lib/empty.pdf differ diff --git a/docdoku-web-front/app/js/lib/helvetiker_regular.typeface.js b/docdoku-web-front/app/js/lib/helvetiker_regular.typeface.js new file mode 100644 index 0000000000..3823b6aa7c --- /dev/null +++ b/docdoku-web-front/app/js/lib/helvetiker_regular.typeface.js @@ -0,0 +1 @@ +if (_typeface_js && _typeface_js.loadFace) _typeface_js.loadFace({"glyphs":{"ο":{"x_min":0,"x_max":712,"ha":815,"o":"m 356 -25 q 96 88 192 -25 q 0 368 0 201 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 "},"S":{"x_min":0,"x_max":788,"ha":890,"o":"m 788 291 q 662 54 788 144 q 397 -26 550 -26 q 116 68 226 -26 q 0 337 0 168 l 131 337 q 200 152 131 220 q 384 85 269 85 q 557 129 479 85 q 650 270 650 183 q 490 429 650 379 q 194 513 341 470 q 33 739 33 584 q 142 964 33 881 q 388 1041 242 1041 q 644 957 543 1041 q 756 716 756 867 l 625 716 q 561 874 625 816 q 395 933 497 933 q 243 891 309 933 q 164 759 164 841 q 325 609 164 656 q 625 526 475 568 q 788 291 788 454 "},"¦":{"x_min":343,"x_max":449,"ha":792,"o":"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"/":{"x_min":183.25,"x_max":608.328125,"ha":792,"o":"m 608 1041 l 266 -129 l 183 -129 l 520 1041 l 608 1041 "},"Τ":{"x_min":-0.4375,"x_max":777.453125,"ha":839,"o":"m 777 893 l 458 893 l 458 0 l 319 0 l 319 892 l 0 892 l 0 1013 l 777 1013 l 777 893 "},"y":{"x_min":0,"x_max":684.78125,"ha":771,"o":"m 684 738 l 388 -83 q 311 -216 356 -167 q 173 -279 252 -279 q 97 -266 133 -279 l 97 -149 q 132 -155 109 -151 q 168 -160 155 -160 q 240 -114 213 -160 q 274 -26 248 -98 l 0 738 l 137 737 l 341 139 l 548 737 l 684 738 "},"Π":{"x_min":0,"x_max":803,"ha":917,"o":"m 803 0 l 667 0 l 667 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 803 1012 l 803 0 "},"ΐ":{"x_min":-111,"x_max":339,"ha":361,"o":"m 339 800 l 229 800 l 229 925 l 339 925 l 339 800 m -1 800 l -111 800 l -111 925 l -1 925 l -1 800 m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 m 302 1040 l 113 819 l 30 819 l 165 1040 l 302 1040 "},"g":{"x_min":0,"x_max":686,"ha":838,"o":"m 686 34 q 586 -213 686 -121 q 331 -306 487 -306 q 131 -252 216 -306 q 31 -84 31 -190 l 155 -84 q 228 -174 166 -138 q 345 -207 284 -207 q 514 -109 454 -207 q 564 89 564 -27 q 461 6 521 36 q 335 -23 401 -23 q 88 100 184 -23 q 0 370 0 215 q 87 634 0 522 q 330 758 183 758 q 457 728 398 758 q 564 644 515 699 l 564 737 l 686 737 l 686 34 m 582 367 q 529 560 582 481 q 358 652 468 652 q 189 561 250 652 q 135 369 135 482 q 189 176 135 255 q 361 85 251 85 q 529 176 468 85 q 582 367 582 255 "},"²":{"x_min":0,"x_max":442,"ha":539,"o":"m 442 383 l 0 383 q 91 566 0 492 q 260 668 176 617 q 354 798 354 727 q 315 875 354 845 q 227 905 277 905 q 136 869 173 905 q 99 761 99 833 l 14 761 q 82 922 14 864 q 232 974 141 974 q 379 926 316 974 q 442 797 442 878 q 351 635 442 704 q 183 539 321 611 q 92 455 92 491 l 442 455 l 442 383 "},"–":{"x_min":0,"x_max":705.5625,"ha":803,"o":"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},"Κ":{"x_min":0,"x_max":819.5625,"ha":893,"o":"m 819 0 l 650 0 l 294 509 l 139 356 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},"ƒ":{"x_min":-46.265625,"x_max":392,"ha":513,"o":"m 392 651 l 259 651 l 79 -279 l -46 -278 l 134 651 l 14 651 l 14 751 l 135 751 q 151 948 135 900 q 304 1041 185 1041 q 334 1040 319 1041 q 392 1034 348 1039 l 392 922 q 337 931 360 931 q 271 883 287 931 q 260 793 260 853 l 260 751 l 392 751 l 392 651 "},"e":{"x_min":0,"x_max":714,"ha":813,"o":"m 714 326 l 140 326 q 200 157 140 227 q 359 87 260 87 q 488 130 431 87 q 561 245 545 174 l 697 245 q 577 48 670 123 q 358 -26 484 -26 q 97 85 195 -26 q 0 363 0 197 q 94 642 0 529 q 358 765 195 765 q 626 627 529 765 q 714 326 714 503 m 576 429 q 507 583 564 522 q 355 650 445 650 q 206 583 266 650 q 140 429 152 522 l 576 429 "},"ό":{"x_min":0,"x_max":712,"ha":815,"o":"m 356 -25 q 94 91 194 -25 q 0 368 0 202 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 m 576 1040 l 387 819 l 303 819 l 438 1040 l 576 1040 "},"J":{"x_min":0,"x_max":588,"ha":699,"o":"m 588 279 q 287 -26 588 -26 q 58 73 126 -26 q 0 327 0 158 l 133 327 q 160 172 133 227 q 288 96 198 96 q 426 171 391 96 q 449 336 449 219 l 449 1013 l 588 1013 l 588 279 "},"»":{"x_min":-1,"x_max":503,"ha":601,"o":"m 503 302 l 280 136 l 281 256 l 429 373 l 281 486 l 280 608 l 503 440 l 503 302 m 221 302 l 0 136 l 0 255 l 145 372 l 0 486 l -1 608 l 221 440 l 221 302 "},"©":{"x_min":-3,"x_max":1008,"ha":1106,"o":"m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 741 394 q 661 246 731 302 q 496 190 591 190 q 294 285 369 190 q 228 497 228 370 q 295 714 228 625 q 499 813 370 813 q 656 762 588 813 q 733 625 724 711 l 634 625 q 589 704 629 673 q 498 735 550 735 q 377 666 421 735 q 334 504 334 597 q 374 340 334 408 q 490 272 415 272 q 589 304 549 272 q 638 394 628 337 l 741 394 "},"ώ":{"x_min":0,"x_max":922,"ha":1030,"o":"m 687 1040 l 498 819 l 415 819 l 549 1040 l 687 1040 m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 338 0 202 q 45 551 0 444 q 161 737 84 643 l 302 737 q 175 552 219 647 q 124 336 124 446 q 155 179 124 248 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 341 797 257 q 745 555 797 450 q 619 737 705 637 l 760 737 q 874 551 835 640 q 922 339 922 444 "},"^":{"x_min":193.0625,"x_max":598.609375,"ha":792,"o":"m 598 772 l 515 772 l 395 931 l 277 772 l 193 772 l 326 1013 l 462 1013 l 598 772 "},"«":{"x_min":0,"x_max":507.203125,"ha":604,"o":"m 506 136 l 284 302 l 284 440 l 506 608 l 507 485 l 360 371 l 506 255 l 506 136 m 222 136 l 0 302 l 0 440 l 222 608 l 221 486 l 73 373 l 222 256 l 222 136 "},"D":{"x_min":0,"x_max":828,"ha":935,"o":"m 389 1013 q 714 867 593 1013 q 828 521 828 729 q 712 161 828 309 q 382 0 587 0 l 0 0 l 0 1013 l 389 1013 m 376 124 q 607 247 523 124 q 681 510 681 355 q 607 771 681 662 q 376 896 522 896 l 139 896 l 139 124 l 376 124 "},"∙":{"x_min":0,"x_max":142,"ha":239,"o":"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"ÿ":{"x_min":0,"x_max":47,"ha":125,"o":"m 47 3 q 37 -7 47 -7 q 28 0 30 -7 q 39 -4 32 -4 q 45 3 45 -1 l 37 0 q 28 9 28 0 q 39 19 28 19 l 47 16 l 47 19 l 47 3 m 37 1 q 44 8 44 1 q 37 16 44 16 q 30 8 30 16 q 37 1 30 1 m 26 1 l 23 22 l 14 0 l 3 22 l 3 3 l 0 25 l 13 1 l 22 25 l 26 1 "},"w":{"x_min":0,"x_max":1009.71875,"ha":1100,"o":"m 1009 738 l 783 0 l 658 0 l 501 567 l 345 0 l 222 0 l 0 738 l 130 738 l 284 174 l 432 737 l 576 738 l 721 173 l 881 737 l 1009 738 "},"$":{"x_min":0,"x_max":700,"ha":793,"o":"m 664 717 l 542 717 q 490 825 531 785 q 381 872 450 865 l 381 551 q 620 446 540 522 q 700 241 700 370 q 618 45 700 116 q 381 -25 536 -25 l 381 -152 l 307 -152 l 307 -25 q 81 62 162 -25 q 0 297 0 149 l 124 297 q 169 146 124 204 q 307 81 215 89 l 307 441 q 80 536 148 469 q 13 725 13 603 q 96 910 13 839 q 307 982 180 982 l 307 1077 l 381 1077 l 381 982 q 574 917 494 982 q 664 717 664 845 m 307 565 l 307 872 q 187 831 233 872 q 142 724 142 791 q 180 618 142 656 q 307 565 218 580 m 381 76 q 562 237 562 96 q 517 361 562 313 q 381 423 472 409 l 381 76 "},"\\":{"x_min":-0.015625,"x_max":425.0625,"ha":522,"o":"m 425 -129 l 337 -129 l 0 1041 l 83 1041 l 425 -129 "},"µ":{"x_min":0,"x_max":697.21875,"ha":747,"o":"m 697 -4 q 629 -14 658 -14 q 498 97 513 -14 q 422 9 470 41 q 313 -23 374 -23 q 207 4 258 -23 q 119 81 156 32 l 119 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 173 124 246 q 308 83 216 83 q 452 178 402 83 q 493 359 493 255 l 493 738 l 617 738 l 617 214 q 623 136 617 160 q 673 92 637 92 q 697 96 684 92 l 697 -4 "},"Ι":{"x_min":42,"x_max":181,"ha":297,"o":"m 181 0 l 42 0 l 42 1013 l 181 1013 l 181 0 "},"Ύ":{"x_min":0,"x_max":1144.5,"ha":1214,"o":"m 1144 1012 l 807 416 l 807 0 l 667 0 l 667 416 l 325 1012 l 465 1012 l 736 533 l 1004 1012 l 1144 1012 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"’":{"x_min":0,"x_max":139,"ha":236,"o":"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"Ν":{"x_min":0,"x_max":801,"ha":915,"o":"m 801 0 l 651 0 l 131 822 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 191 l 670 1013 l 801 1013 l 801 0 "},"-":{"x_min":8.71875,"x_max":350.390625,"ha":478,"o":"m 350 317 l 8 317 l 8 428 l 350 428 l 350 317 "},"Q":{"x_min":0,"x_max":968,"ha":1072,"o":"m 954 5 l 887 -79 l 744 35 q 622 -11 687 2 q 483 -26 556 -26 q 127 130 262 -26 q 0 504 0 279 q 127 880 0 728 q 484 1041 262 1041 q 841 884 708 1041 q 968 507 968 735 q 933 293 968 398 q 832 104 899 188 l 954 5 m 723 191 q 802 330 777 248 q 828 499 828 412 q 744 790 828 673 q 483 922 650 922 q 228 791 322 922 q 142 505 142 673 q 227 221 142 337 q 487 91 323 91 q 632 123 566 91 l 520 215 l 587 301 l 723 191 "},"ς":{"x_min":1,"x_max":676.28125,"ha":740,"o":"m 676 460 l 551 460 q 498 595 542 546 q 365 651 448 651 q 199 578 263 651 q 136 401 136 505 q 266 178 136 241 q 508 106 387 142 q 640 -50 640 62 q 625 -158 640 -105 q 583 -278 611 -211 l 465 -278 q 498 -182 490 -211 q 515 -80 515 -126 q 381 12 515 -15 q 134 91 197 51 q 1 388 1 179 q 100 651 1 542 q 354 761 199 761 q 587 680 498 761 q 676 460 676 599 "},"M":{"x_min":0,"x_max":954,"ha":1067,"o":"m 954 0 l 819 0 l 819 869 l 537 0 l 405 0 l 128 866 l 128 0 l 0 0 l 0 1013 l 200 1013 l 472 160 l 757 1013 l 954 1013 l 954 0 "},"Ψ":{"x_min":0,"x_max":1006,"ha":1094,"o":"m 1006 678 q 914 319 1006 429 q 571 200 814 200 l 571 0 l 433 0 l 433 200 q 92 319 194 200 q 0 678 0 429 l 0 1013 l 139 1013 l 139 679 q 191 417 139 492 q 433 326 255 326 l 433 1013 l 571 1013 l 571 326 l 580 326 q 813 423 747 326 q 868 679 868 502 l 868 1013 l 1006 1013 l 1006 678 "},"C":{"x_min":0,"x_max":886,"ha":944,"o":"m 886 379 q 760 87 886 201 q 455 -26 634 -26 q 112 136 236 -26 q 0 509 0 283 q 118 882 0 737 q 469 1041 245 1041 q 748 955 630 1041 q 879 708 879 859 l 745 708 q 649 862 724 805 q 473 920 573 920 q 219 791 312 920 q 136 509 136 675 q 217 229 136 344 q 470 99 311 99 q 672 179 591 99 q 753 379 753 259 l 886 379 "},"!":{"x_min":0,"x_max":138,"ha":236,"o":"m 138 684 q 116 409 138 629 q 105 244 105 299 l 33 244 q 16 465 33 313 q 0 684 0 616 l 0 1013 l 138 1013 l 138 684 m 138 0 l 0 0 l 0 151 l 138 151 l 138 0 "},"{":{"x_min":0,"x_max":480.5625,"ha":578,"o":"m 480 -286 q 237 -213 303 -286 q 187 -45 187 -159 q 194 48 187 -15 q 201 141 201 112 q 164 264 201 225 q 0 314 118 314 l 0 417 q 164 471 119 417 q 201 605 201 514 q 199 665 201 644 q 193 772 193 769 q 241 941 193 887 q 480 1015 308 1015 l 480 915 q 336 866 375 915 q 306 742 306 828 q 310 662 306 717 q 314 577 314 606 q 288 452 314 500 q 176 365 256 391 q 289 275 257 337 q 314 143 314 226 q 313 84 314 107 q 310 -11 310 -5 q 339 -131 310 -94 q 480 -182 377 -182 l 480 -286 "},"X":{"x_min":-0.015625,"x_max":854.15625,"ha":940,"o":"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 428 637 l 675 1013 l 836 1013 l 504 520 l 854 0 "},"#":{"x_min":0,"x_max":963.890625,"ha":1061,"o":"m 963 690 l 927 590 l 719 590 l 655 410 l 876 410 l 840 310 l 618 310 l 508 -3 l 393 -2 l 506 309 l 329 310 l 215 -2 l 102 -3 l 212 310 l 0 310 l 36 410 l 248 409 l 312 590 l 86 590 l 120 690 l 347 690 l 459 1006 l 573 1006 l 462 690 l 640 690 l 751 1006 l 865 1006 l 754 690 l 963 690 m 606 590 l 425 590 l 362 410 l 543 410 l 606 590 "},"ι":{"x_min":42,"x_max":284,"ha":361,"o":"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 738 l 167 738 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 "},"Ά":{"x_min":0,"x_max":906.953125,"ha":982,"o":"m 283 1040 l 88 799 l 5 799 l 145 1040 l 283 1040 m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1012 l 529 1012 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},")":{"x_min":0,"x_max":318,"ha":415,"o":"m 318 365 q 257 25 318 191 q 87 -290 197 -141 l 0 -290 q 140 21 93 -128 q 193 360 193 189 q 141 704 193 537 q 0 1024 97 850 l 87 1024 q 257 706 197 871 q 318 365 318 542 "},"ε":{"x_min":0,"x_max":634.71875,"ha":714,"o":"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 314 0 265 q 128 390 67 353 q 56 460 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 "},"Δ":{"x_min":0,"x_max":952.78125,"ha":1028,"o":"m 952 0 l 0 0 l 400 1013 l 551 1013 l 952 0 m 762 124 l 476 867 l 187 124 l 762 124 "},"}":{"x_min":0,"x_max":481,"ha":578,"o":"m 481 314 q 318 262 364 314 q 282 136 282 222 q 284 65 282 97 q 293 -58 293 -48 q 241 -217 293 -166 q 0 -286 174 -286 l 0 -182 q 143 -130 105 -182 q 171 -2 171 -93 q 168 81 171 22 q 165 144 165 140 q 188 275 165 229 q 306 365 220 339 q 191 455 224 391 q 165 588 165 505 q 168 681 165 624 q 171 742 171 737 q 141 865 171 827 q 0 915 102 915 l 0 1015 q 243 942 176 1015 q 293 773 293 888 q 287 675 293 741 q 282 590 282 608 q 318 466 282 505 q 481 417 364 417 l 481 314 "},"‰":{"x_min":-3,"x_max":1672,"ha":1821,"o":"m 846 0 q 664 76 732 0 q 603 244 603 145 q 662 412 603 344 q 846 489 729 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 846 0 962 0 m 845 103 q 945 143 910 103 q 981 243 981 184 q 947 340 981 301 q 845 385 910 385 q 745 342 782 385 q 709 243 709 300 q 742 147 709 186 q 845 103 781 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 m 1428 0 q 1246 76 1314 0 q 1185 244 1185 145 q 1244 412 1185 344 q 1428 489 1311 489 q 1610 412 1542 489 q 1672 244 1672 343 q 1612 76 1672 144 q 1428 0 1545 0 m 1427 103 q 1528 143 1492 103 q 1564 243 1564 184 q 1530 340 1564 301 q 1427 385 1492 385 q 1327 342 1364 385 q 1291 243 1291 300 q 1324 147 1291 186 q 1427 103 1363 103 "},"a":{"x_min":0,"x_max":698.609375,"ha":794,"o":"m 698 0 q 661 -12 679 -7 q 615 -17 643 -17 q 536 12 564 -17 q 500 96 508 41 q 384 6 456 37 q 236 -25 312 -25 q 65 31 130 -25 q 0 194 0 88 q 118 390 0 334 q 328 435 180 420 q 488 483 476 451 q 495 523 495 504 q 442 619 495 584 q 325 654 389 654 q 209 617 257 654 q 152 513 161 580 l 33 513 q 123 705 33 633 q 332 772 207 772 q 528 712 448 772 q 617 531 617 645 l 617 163 q 624 108 617 126 q 664 90 632 90 l 698 94 l 698 0 m 491 262 l 491 372 q 272 329 350 347 q 128 201 128 294 q 166 113 128 144 q 264 83 205 83 q 414 130 346 83 q 491 262 491 183 "},"—":{"x_min":0,"x_max":941.671875,"ha":1039,"o":"m 941 334 l 0 334 l 0 410 l 941 410 l 941 334 "},"=":{"x_min":8.71875,"x_max":780.953125,"ha":792,"o":"m 780 510 l 8 510 l 8 606 l 780 606 l 780 510 m 780 235 l 8 235 l 8 332 l 780 332 l 780 235 "},"N":{"x_min":0,"x_max":801,"ha":914,"o":"m 801 0 l 651 0 l 131 823 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 193 l 670 1013 l 801 1013 l 801 0 "},"ρ":{"x_min":0,"x_max":712,"ha":797,"o":"m 712 369 q 620 94 712 207 q 362 -26 521 -26 q 230 2 292 -26 q 119 83 167 30 l 119 -278 l 0 -278 l 0 362 q 91 643 0 531 q 355 764 190 764 q 617 647 517 764 q 712 369 712 536 m 583 366 q 530 559 583 480 q 359 651 469 651 q 190 562 252 651 q 135 370 135 483 q 189 176 135 257 q 359 85 250 85 q 528 175 466 85 q 583 366 583 254 "},"2":{"x_min":59,"x_max":731,"ha":792,"o":"m 731 0 l 59 0 q 197 314 59 188 q 457 487 199 315 q 598 691 598 580 q 543 819 598 772 q 411 867 488 867 q 272 811 328 867 q 209 630 209 747 l 81 630 q 182 901 81 805 q 408 986 271 986 q 629 909 536 986 q 731 694 731 826 q 613 449 731 541 q 378 316 495 383 q 201 122 235 234 l 731 122 l 731 0 "},"¯":{"x_min":0,"x_max":941.671875,"ha":938,"o":"m 941 1033 l 0 1033 l 0 1109 l 941 1109 l 941 1033 "},"Z":{"x_min":0,"x_max":779,"ha":849,"o":"m 779 0 l 0 0 l 0 113 l 621 896 l 40 896 l 40 1013 l 779 1013 l 778 887 l 171 124 l 779 124 l 779 0 "},"u":{"x_min":0,"x_max":617,"ha":729,"o":"m 617 0 l 499 0 l 499 110 q 391 10 460 45 q 246 -25 322 -25 q 61 58 127 -25 q 0 258 0 136 l 0 738 l 125 738 l 125 284 q 156 148 125 202 q 273 82 197 82 q 433 165 369 82 q 493 340 493 243 l 493 738 l 617 738 l 617 0 "},"k":{"x_min":0,"x_max":612.484375,"ha":697,"o":"m 612 738 l 338 465 l 608 0 l 469 0 l 251 382 l 121 251 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 402 l 456 738 l 612 738 "},"Η":{"x_min":0,"x_max":803,"ha":917,"o":"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"Α":{"x_min":0,"x_max":906.953125,"ha":985,"o":"m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},"s":{"x_min":0,"x_max":604,"ha":697,"o":"m 604 217 q 501 36 604 104 q 292 -23 411 -23 q 86 43 166 -23 q 0 238 0 114 l 121 237 q 175 122 121 164 q 300 85 223 85 q 415 112 363 85 q 479 207 479 147 q 361 309 479 276 q 140 372 141 370 q 21 544 21 426 q 111 708 21 647 q 298 761 190 761 q 492 705 413 761 q 583 531 583 643 l 462 531 q 412 625 462 594 q 298 657 363 657 q 199 636 242 657 q 143 558 143 608 q 262 454 143 486 q 484 394 479 397 q 604 217 604 341 "},"B":{"x_min":0,"x_max":778,"ha":876,"o":"m 580 546 q 724 469 670 535 q 778 311 778 403 q 673 83 778 171 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 892 q 691 633 732 693 q 580 546 650 572 m 393 899 l 139 899 l 139 588 l 379 588 q 521 624 462 588 q 592 744 592 667 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 303 635 219 q 559 436 635 389 q 402 477 494 477 l 139 477 l 139 124 l 419 124 "},"…":{"x_min":0,"x_max":614,"ha":708,"o":"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 m 378 0 l 236 0 l 236 151 l 378 151 l 378 0 m 614 0 l 472 0 l 472 151 l 614 151 l 614 0 "},"?":{"x_min":0,"x_max":607,"ha":704,"o":"m 607 777 q 543 599 607 674 q 422 474 482 537 q 357 272 357 391 l 236 272 q 297 487 236 395 q 411 619 298 490 q 474 762 474 691 q 422 885 474 838 q 301 933 371 933 q 179 880 228 933 q 124 706 124 819 l 0 706 q 94 963 0 872 q 302 1044 177 1044 q 511 973 423 1044 q 607 777 607 895 m 370 0 l 230 0 l 230 151 l 370 151 l 370 0 "},"H":{"x_min":0,"x_max":803,"ha":915,"o":"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"ν":{"x_min":0,"x_max":675,"ha":761,"o":"m 675 738 l 404 0 l 272 0 l 0 738 l 133 738 l 340 147 l 541 738 l 675 738 "},"c":{"x_min":1,"x_max":701.390625,"ha":775,"o":"m 701 264 q 584 53 681 133 q 353 -26 487 -26 q 91 91 188 -26 q 1 370 1 201 q 92 645 1 537 q 353 761 190 761 q 572 688 479 761 q 690 493 666 615 l 556 493 q 487 606 545 562 q 356 650 428 650 q 186 563 246 650 q 134 372 134 487 q 188 179 134 258 q 359 88 250 88 q 492 136 437 88 q 566 264 548 185 l 701 264 "},"¶":{"x_min":0,"x_max":566.671875,"ha":678,"o":"m 21 892 l 52 892 l 98 761 l 145 892 l 176 892 l 178 741 l 157 741 l 157 867 l 108 741 l 88 741 l 40 871 l 40 741 l 21 741 l 21 892 m 308 854 l 308 731 q 252 691 308 691 q 227 691 240 691 q 207 696 213 695 l 207 712 l 253 706 q 288 733 288 706 l 288 763 q 244 741 279 741 q 193 797 193 741 q 261 860 193 860 q 287 860 273 860 q 308 854 302 855 m 288 842 l 263 843 q 213 796 213 843 q 248 756 213 756 q 288 796 288 756 l 288 842 m 566 988 l 502 988 l 502 -1 l 439 -1 l 439 988 l 317 988 l 317 -1 l 252 -1 l 252 602 q 81 653 155 602 q 0 805 0 711 q 101 989 0 918 q 309 1053 194 1053 l 566 1053 l 566 988 "},"β":{"x_min":0,"x_max":660,"ha":745,"o":"m 471 550 q 610 450 561 522 q 660 280 660 378 q 578 64 660 151 q 367 -22 497 -22 q 239 5 299 -22 q 126 82 178 32 l 126 -278 l 0 -278 l 0 593 q 54 903 0 801 q 318 1042 127 1042 q 519 964 436 1042 q 603 771 603 887 q 567 644 603 701 q 471 550 532 586 m 337 79 q 476 138 418 79 q 535 279 535 198 q 427 437 535 386 q 226 477 344 477 l 226 583 q 398 620 329 583 q 486 762 486 668 q 435 884 486 833 q 312 935 384 935 q 169 861 219 935 q 126 698 126 797 l 126 362 q 170 169 126 242 q 337 79 224 79 "},"Μ":{"x_min":0,"x_max":954,"ha":1068,"o":"m 954 0 l 819 0 l 819 868 l 537 0 l 405 0 l 128 865 l 128 0 l 0 0 l 0 1013 l 199 1013 l 472 158 l 758 1013 l 954 1013 l 954 0 "},"Ό":{"x_min":0.109375,"x_max":1120,"ha":1217,"o":"m 1120 505 q 994 132 1120 282 q 642 -29 861 -29 q 290 130 422 -29 q 167 505 167 280 q 294 883 167 730 q 650 1046 430 1046 q 999 882 868 1046 q 1120 505 1120 730 m 977 504 q 896 784 977 669 q 644 915 804 915 q 391 785 484 915 q 307 504 307 669 q 391 224 307 339 q 644 95 486 95 q 894 224 803 95 q 977 504 977 339 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ή":{"x_min":0,"x_max":1158,"ha":1275,"o":"m 1158 0 l 1022 0 l 1022 475 l 496 475 l 496 0 l 356 0 l 356 1012 l 496 1012 l 496 599 l 1022 599 l 1022 1012 l 1158 1012 l 1158 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"•":{"x_min":0,"x_max":663.890625,"ha":775,"o":"m 663 529 q 566 293 663 391 q 331 196 469 196 q 97 294 194 196 q 0 529 0 393 q 96 763 0 665 q 331 861 193 861 q 566 763 469 861 q 663 529 663 665 "},"¥":{"x_min":0.1875,"x_max":819.546875,"ha":886,"o":"m 563 561 l 697 561 l 696 487 l 520 487 l 482 416 l 482 380 l 697 380 l 695 308 l 482 308 l 482 0 l 342 0 l 342 308 l 125 308 l 125 380 l 342 380 l 342 417 l 303 487 l 125 487 l 125 561 l 258 561 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 l 563 561 "},"(":{"x_min":0,"x_max":318.0625,"ha":415,"o":"m 318 -290 l 230 -290 q 61 23 122 -142 q 0 365 0 190 q 62 712 0 540 q 230 1024 119 869 l 318 1024 q 175 705 219 853 q 125 360 125 542 q 176 22 125 187 q 318 -290 223 -127 "},"U":{"x_min":0,"x_max":796,"ha":904,"o":"m 796 393 q 681 93 796 212 q 386 -25 566 -25 q 101 95 208 -25 q 0 393 0 211 l 0 1013 l 138 1013 l 138 391 q 204 191 138 270 q 394 107 276 107 q 586 191 512 107 q 656 391 656 270 l 656 1013 l 796 1013 l 796 393 "},"γ":{"x_min":0.5,"x_max":744.953125,"ha":822,"o":"m 744 737 l 463 54 l 463 -278 l 338 -278 l 338 54 l 154 495 q 104 597 124 569 q 13 651 67 651 l 0 651 l 0 751 l 39 753 q 168 711 121 753 q 242 594 207 676 l 403 208 l 617 737 l 744 737 "},"α":{"x_min":0,"x_max":765.5625,"ha":809,"o":"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 728 407 760 q 563 637 524 696 l 563 739 l 685 739 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 96 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 "},"F":{"x_min":0,"x_max":683.328125,"ha":717,"o":"m 683 888 l 140 888 l 140 583 l 613 583 l 613 458 l 140 458 l 140 0 l 0 0 l 0 1013 l 683 1013 l 683 888 "},"­":{"x_min":0,"x_max":705.5625,"ha":803,"o":"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},":":{"x_min":0,"x_max":142,"ha":239,"o":"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"Χ":{"x_min":0,"x_max":854.171875,"ha":935,"o":"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 427 637 l 675 1013 l 836 1013 l 504 521 l 854 0 "},"*":{"x_min":116,"x_max":674,"ha":792,"o":"m 674 768 l 475 713 l 610 544 l 517 477 l 394 652 l 272 478 l 178 544 l 314 713 l 116 766 l 153 876 l 341 812 l 342 1013 l 446 1013 l 446 811 l 635 874 l 674 768 "},"†":{"x_min":0,"x_max":777,"ha":835,"o":"m 458 804 l 777 804 l 777 683 l 458 683 l 458 0 l 319 0 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 "},"°":{"x_min":0,"x_max":347,"ha":444,"o":"m 173 802 q 43 856 91 802 q 0 977 0 905 q 45 1101 0 1049 q 173 1153 90 1153 q 303 1098 255 1153 q 347 977 347 1049 q 303 856 347 905 q 173 802 256 802 m 173 884 q 238 910 214 884 q 262 973 262 937 q 239 1038 262 1012 q 173 1064 217 1064 q 108 1037 132 1064 q 85 973 85 1010 q 108 910 85 937 q 173 884 132 884 "},"V":{"x_min":0,"x_max":862.71875,"ha":940,"o":"m 862 1013 l 505 0 l 361 0 l 0 1013 l 143 1013 l 434 165 l 718 1012 l 862 1013 "},"Ξ":{"x_min":0,"x_max":734.71875,"ha":763,"o":"m 723 889 l 9 889 l 9 1013 l 723 1013 l 723 889 m 673 463 l 61 463 l 61 589 l 673 589 l 673 463 m 734 0 l 0 0 l 0 124 l 734 124 l 734 0 "}," ":{"x_min":0,"x_max":0,"ha":853},"Ϋ":{"x_min":0.328125,"x_max":819.515625,"ha":889,"o":"m 588 1046 l 460 1046 l 460 1189 l 588 1189 l 588 1046 m 360 1046 l 232 1046 l 232 1189 l 360 1189 l 360 1046 m 819 1012 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1012 l 140 1012 l 411 533 l 679 1012 l 819 1012 "},"0":{"x_min":73,"x_max":715,"ha":792,"o":"m 394 -29 q 153 129 242 -29 q 73 479 73 272 q 152 829 73 687 q 394 989 241 989 q 634 829 545 989 q 715 479 715 684 q 635 129 715 270 q 394 -29 546 -29 m 394 89 q 546 211 489 89 q 598 479 598 322 q 548 748 598 640 q 394 871 491 871 q 241 748 298 871 q 190 479 190 637 q 239 211 190 319 q 394 89 296 89 "},"”":{"x_min":0,"x_max":347,"ha":454,"o":"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 m 347 851 q 310 737 347 784 q 208 669 273 690 l 208 734 q 267 787 250 741 q 280 873 280 821 l 208 873 l 208 1013 l 347 1013 l 347 851 "},"@":{"x_min":0,"x_max":1260,"ha":1357,"o":"m 1098 -45 q 877 -160 1001 -117 q 633 -203 752 -203 q 155 -29 327 -203 q 0 360 0 127 q 176 802 0 616 q 687 1008 372 1008 q 1123 854 969 1008 q 1260 517 1260 718 q 1155 216 1260 341 q 868 82 1044 82 q 772 106 801 82 q 737 202 737 135 q 647 113 700 144 q 527 82 594 82 q 367 147 420 82 q 314 312 314 212 q 401 565 314 452 q 639 690 498 690 q 810 588 760 690 l 849 668 l 938 668 q 877 441 900 532 q 833 226 833 268 q 853 182 833 198 q 902 167 873 167 q 1088 272 1012 167 q 1159 512 1159 372 q 1051 793 1159 681 q 687 925 925 925 q 248 747 415 925 q 97 361 97 586 q 226 26 97 159 q 627 -122 370 -122 q 856 -87 737 -122 q 1061 8 976 -53 l 1098 -45 m 786 488 q 738 580 777 545 q 643 615 700 615 q 483 517 548 615 q 425 322 425 430 q 457 203 425 250 q 552 156 490 156 q 722 273 665 156 q 786 488 738 309 "},"Ί":{"x_min":0,"x_max":499,"ha":613,"o":"m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 m 499 0 l 360 0 l 360 1012 l 499 1012 l 499 0 "},"i":{"x_min":14,"x_max":136,"ha":275,"o":"m 136 873 l 14 873 l 14 1013 l 136 1013 l 136 873 m 136 0 l 14 0 l 14 737 l 136 737 l 136 0 "},"Β":{"x_min":0,"x_max":778,"ha":877,"o":"m 580 545 q 724 468 671 534 q 778 310 778 402 q 673 83 778 170 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 891 q 691 632 732 692 q 580 545 650 571 m 393 899 l 139 899 l 139 587 l 379 587 q 521 623 462 587 q 592 744 592 666 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 302 635 219 q 559 435 635 388 q 402 476 494 476 l 139 476 l 139 124 l 419 124 "},"υ":{"x_min":0,"x_max":617,"ha":725,"o":"m 617 352 q 540 94 617 199 q 308 -24 455 -24 q 76 94 161 -24 q 0 352 0 199 l 0 739 l 126 739 l 126 355 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 355 492 257 l 492 739 l 617 739 l 617 352 "},"]":{"x_min":0,"x_max":275,"ha":372,"o":"m 275 -281 l 0 -281 l 0 -187 l 151 -187 l 151 920 l 0 920 l 0 1013 l 275 1013 l 275 -281 "},"m":{"x_min":0,"x_max":1019,"ha":1128,"o":"m 1019 0 l 897 0 l 897 454 q 860 591 897 536 q 739 660 816 660 q 613 586 659 660 q 573 436 573 522 l 573 0 l 447 0 l 447 455 q 412 591 447 535 q 294 657 372 657 q 165 586 213 657 q 122 437 122 521 l 122 0 l 0 0 l 0 738 l 117 738 l 117 640 q 202 730 150 697 q 316 763 254 763 q 437 730 381 763 q 525 642 494 697 q 621 731 559 700 q 753 763 682 763 q 943 694 867 763 q 1019 512 1019 625 l 1019 0 "},"χ":{"x_min":8.328125,"x_max":780.5625,"ha":815,"o":"m 780 -278 q 715 -294 747 -294 q 616 -257 663 -294 q 548 -175 576 -227 l 379 133 l 143 -277 l 9 -277 l 313 254 l 163 522 q 127 586 131 580 q 36 640 91 640 q 8 637 27 640 l 8 752 l 52 757 q 162 719 113 757 q 236 627 200 690 l 383 372 l 594 737 l 726 737 l 448 250 l 625 -69 q 670 -153 647 -110 q 743 -188 695 -188 q 780 -184 759 -188 l 780 -278 "},"8":{"x_min":55,"x_max":736,"ha":792,"o":"m 571 527 q 694 424 652 491 q 736 280 736 358 q 648 71 736 158 q 395 -26 551 -26 q 142 69 238 -26 q 55 279 55 157 q 96 425 55 359 q 220 527 138 491 q 120 615 153 562 q 88 726 88 668 q 171 904 88 827 q 395 986 261 986 q 618 905 529 986 q 702 727 702 830 q 670 616 702 667 q 571 527 638 565 m 394 565 q 519 610 475 565 q 563 717 563 655 q 521 823 563 781 q 392 872 474 872 q 265 824 312 872 q 224 720 224 783 q 265 613 224 656 q 394 565 312 565 m 395 91 q 545 150 488 91 q 597 280 597 204 q 546 408 597 355 q 395 465 492 465 q 244 408 299 465 q 194 280 194 356 q 244 150 194 203 q 395 91 299 91 "},"ί":{"x_min":42,"x_max":326.71875,"ha":361,"o":"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 102 239 101 q 284 112 257 104 l 284 3 m 326 1040 l 137 819 l 54 819 l 189 1040 l 326 1040 "},"Ζ":{"x_min":0,"x_max":779.171875,"ha":850,"o":"m 779 0 l 0 0 l 0 113 l 620 896 l 40 896 l 40 1013 l 779 1013 l 779 887 l 170 124 l 779 124 l 779 0 "},"R":{"x_min":0,"x_max":781.953125,"ha":907,"o":"m 781 0 l 623 0 q 587 242 590 52 q 407 433 585 433 l 138 433 l 138 0 l 0 0 l 0 1013 l 396 1013 q 636 946 539 1013 q 749 731 749 868 q 711 597 749 659 q 608 502 674 534 q 718 370 696 474 q 729 207 722 352 q 781 26 736 62 l 781 0 m 373 551 q 533 594 465 551 q 614 731 614 645 q 532 859 614 815 q 373 896 465 896 l 138 896 l 138 551 l 373 551 "},"o":{"x_min":0,"x_max":713,"ha":821,"o":"m 357 -25 q 94 91 194 -25 q 0 368 0 202 q 93 642 0 533 q 357 761 193 761 q 618 644 518 761 q 713 368 713 533 q 619 91 713 201 q 357 -25 521 -25 m 357 85 q 528 175 465 85 q 584 369 584 255 q 529 562 584 484 q 357 651 467 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 357 85 250 85 "},"5":{"x_min":54.171875,"x_max":738,"ha":792,"o":"m 738 314 q 626 60 738 153 q 382 -23 526 -23 q 155 47 248 -23 q 54 256 54 125 l 183 256 q 259 132 204 174 q 382 91 314 91 q 533 149 471 91 q 602 314 602 213 q 538 469 602 411 q 386 528 475 528 q 284 506 332 528 q 197 439 237 484 l 81 439 l 159 958 l 684 958 l 684 840 l 254 840 l 214 579 q 306 627 258 612 q 407 643 354 643 q 636 552 540 643 q 738 314 738 457 "},"7":{"x_min":58.71875,"x_max":730.953125,"ha":792,"o":"m 730 839 q 469 448 560 641 q 335 0 378 255 l 192 0 q 328 441 235 252 q 593 830 421 630 l 58 830 l 58 958 l 730 958 l 730 839 "},"K":{"x_min":0,"x_max":819.46875,"ha":906,"o":"m 819 0 l 649 0 l 294 509 l 139 355 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},",":{"x_min":0,"x_max":142,"ha":239,"o":"m 142 -12 q 105 -132 142 -82 q 0 -205 68 -182 l 0 -138 q 57 -82 40 -124 q 70 0 70 -51 l 0 0 l 0 151 l 142 151 l 142 -12 "},"d":{"x_min":0,"x_max":683,"ha":796,"o":"m 683 0 l 564 0 l 564 93 q 456 6 516 38 q 327 -25 395 -25 q 87 100 181 -25 q 0 365 0 215 q 90 639 0 525 q 343 763 187 763 q 564 647 486 763 l 564 1013 l 683 1013 l 683 0 m 582 373 q 529 562 582 484 q 361 653 468 653 q 190 561 253 653 q 135 365 135 479 q 189 175 135 254 q 358 85 251 85 q 529 178 468 85 q 582 373 582 258 "},"¨":{"x_min":-109,"x_max":247,"ha":232,"o":"m 247 1046 l 119 1046 l 119 1189 l 247 1189 l 247 1046 m 19 1046 l -109 1046 l -109 1189 l 19 1189 l 19 1046 "},"E":{"x_min":0,"x_max":736.109375,"ha":789,"o":"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},"Y":{"x_min":0,"x_max":820,"ha":886,"o":"m 820 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 534 l 679 1012 l 820 1013 "},"\"":{"x_min":0,"x_max":299,"ha":396,"o":"m 299 606 l 203 606 l 203 988 l 299 988 l 299 606 m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"‹":{"x_min":17.984375,"x_max":773.609375,"ha":792,"o":"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"„":{"x_min":0,"x_max":364,"ha":467,"o":"m 141 -12 q 104 -132 141 -82 q 0 -205 67 -182 l 0 -138 q 56 -82 40 -124 q 69 0 69 -51 l 0 0 l 0 151 l 141 151 l 141 -12 m 364 -12 q 327 -132 364 -82 q 222 -205 290 -182 l 222 -138 q 279 -82 262 -124 q 292 0 292 -51 l 222 0 l 222 151 l 364 151 l 364 -12 "},"δ":{"x_min":1,"x_max":710,"ha":810,"o":"m 710 360 q 616 87 710 196 q 356 -28 518 -28 q 99 82 197 -28 q 1 356 1 192 q 100 606 1 509 q 355 703 199 703 q 180 829 288 754 q 70 903 124 866 l 70 1012 l 643 1012 l 643 901 l 258 901 q 462 763 422 794 q 636 592 577 677 q 710 360 710 485 m 584 365 q 552 501 584 447 q 451 602 521 555 q 372 611 411 611 q 197 541 258 611 q 136 355 136 472 q 190 171 136 245 q 358 85 252 85 q 528 173 465 85 q 584 365 584 252 "},"έ":{"x_min":0,"x_max":634.71875,"ha":714,"o":"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 313 0 265 q 128 390 67 352 q 56 459 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 m 520 1040 l 331 819 l 248 819 l 383 1040 l 520 1040 "},"ω":{"x_min":0,"x_max":922,"ha":1031,"o":"m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 339 0 203 q 45 551 0 444 q 161 738 84 643 l 302 738 q 175 553 219 647 q 124 336 124 446 q 155 179 124 249 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 342 797 257 q 745 556 797 450 q 619 738 705 638 l 760 738 q 874 551 835 640 q 922 339 922 444 "},"´":{"x_min":0,"x_max":96,"ha":251,"o":"m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"±":{"x_min":11,"x_max":781,"ha":792,"o":"m 781 490 l 446 490 l 446 255 l 349 255 l 349 490 l 11 490 l 11 586 l 349 586 l 349 819 l 446 819 l 446 586 l 781 586 l 781 490 m 781 21 l 11 21 l 11 115 l 781 115 l 781 21 "},"|":{"x_min":343,"x_max":449,"ha":792,"o":"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"ϋ":{"x_min":0,"x_max":617,"ha":725,"o":"m 482 800 l 372 800 l 372 925 l 482 925 l 482 800 m 239 800 l 129 800 l 129 925 l 239 925 l 239 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 "},"§":{"x_min":0,"x_max":593,"ha":690,"o":"m 593 425 q 554 312 593 369 q 467 233 516 254 q 537 83 537 172 q 459 -74 537 -12 q 288 -133 387 -133 q 115 -69 184 -133 q 47 96 47 -6 l 166 96 q 199 7 166 40 q 288 -26 232 -26 q 371 -5 332 -26 q 420 60 420 21 q 311 201 420 139 q 108 309 210 255 q 0 490 0 383 q 33 602 0 551 q 124 687 66 654 q 75 743 93 712 q 58 812 58 773 q 133 984 58 920 q 300 1043 201 1043 q 458 987 394 1043 q 529 814 529 925 l 411 814 q 370 908 404 877 q 289 939 336 939 q 213 911 246 939 q 180 841 180 883 q 286 720 180 779 q 484 612 480 615 q 593 425 593 534 m 467 409 q 355 544 467 473 q 196 630 228 612 q 146 587 162 609 q 124 525 124 558 q 239 387 124 462 q 398 298 369 315 q 448 345 429 316 q 467 409 467 375 "},"b":{"x_min":0,"x_max":685,"ha":783,"o":"m 685 372 q 597 99 685 213 q 347 -25 501 -25 q 219 5 277 -25 q 121 93 161 36 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 634 q 214 723 157 692 q 341 754 272 754 q 591 637 493 754 q 685 372 685 526 m 554 356 q 499 550 554 470 q 328 644 437 644 q 162 556 223 644 q 108 369 108 478 q 160 176 108 256 q 330 83 221 83 q 498 169 435 83 q 554 356 554 245 "},"q":{"x_min":0,"x_max":683,"ha":876,"o":"m 683 -278 l 564 -278 l 564 97 q 474 8 533 39 q 345 -23 415 -23 q 91 93 188 -23 q 0 364 0 203 q 87 635 0 522 q 337 760 184 760 q 466 727 408 760 q 564 637 523 695 l 564 737 l 683 737 l 683 -278 m 582 375 q 527 564 582 488 q 358 652 466 652 q 190 565 253 652 q 135 377 135 488 q 189 179 135 261 q 361 84 251 84 q 530 179 469 84 q 582 375 582 260 "},"Ω":{"x_min":-0.171875,"x_max":969.5625,"ha":1068,"o":"m 969 0 l 555 0 l 555 123 q 744 308 675 194 q 814 558 814 423 q 726 812 814 709 q 484 922 633 922 q 244 820 334 922 q 154 567 154 719 q 223 316 154 433 q 412 123 292 199 l 412 0 l 0 0 l 0 124 l 217 124 q 68 327 122 210 q 15 572 15 444 q 144 911 15 781 q 484 1041 274 1041 q 822 909 691 1041 q 953 569 953 777 q 899 326 953 443 q 750 124 846 210 l 969 124 l 969 0 "},"ύ":{"x_min":0,"x_max":617,"ha":725,"o":"m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 535 1040 l 346 819 l 262 819 l 397 1040 l 535 1040 "},"z":{"x_min":-0.015625,"x_max":613.890625,"ha":697,"o":"m 613 0 l 0 0 l 0 100 l 433 630 l 20 630 l 20 738 l 594 738 l 593 636 l 163 110 l 613 110 l 613 0 "},"™":{"x_min":0,"x_max":894,"ha":1000,"o":"m 389 951 l 229 951 l 229 503 l 160 503 l 160 951 l 0 951 l 0 1011 l 389 1011 l 389 951 m 894 503 l 827 503 l 827 939 l 685 503 l 620 503 l 481 937 l 481 503 l 417 503 l 417 1011 l 517 1011 l 653 580 l 796 1010 l 894 1011 l 894 503 "},"ή":{"x_min":0.78125,"x_max":697,"ha":810,"o":"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 721 124 755 q 200 630 193 687 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 m 479 1040 l 290 819 l 207 819 l 341 1040 l 479 1040 "},"Θ":{"x_min":0,"x_max":960,"ha":1056,"o":"m 960 507 q 833 129 960 280 q 476 -32 698 -32 q 123 129 255 -32 q 0 507 0 280 q 123 883 0 732 q 476 1045 255 1045 q 832 883 696 1045 q 960 507 960 732 m 817 500 q 733 789 817 669 q 476 924 639 924 q 223 792 317 924 q 142 507 142 675 q 222 222 142 339 q 476 89 315 89 q 730 218 636 89 q 817 500 817 334 m 716 449 l 243 449 l 243 571 l 716 571 l 716 449 "},"®":{"x_min":-3,"x_max":1008,"ha":1106,"o":"m 503 532 q 614 562 566 532 q 672 658 672 598 q 614 747 672 716 q 503 772 569 772 l 338 772 l 338 532 l 503 532 m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 788 146 l 678 146 q 653 316 655 183 q 527 449 652 449 l 338 449 l 338 146 l 241 146 l 241 854 l 518 854 q 688 808 621 854 q 766 658 766 755 q 739 563 766 607 q 668 497 713 519 q 751 331 747 472 q 788 164 756 190 l 788 146 "},"~":{"x_min":0,"x_max":833,"ha":931,"o":"m 833 958 q 778 753 833 831 q 594 665 716 665 q 402 761 502 665 q 240 857 302 857 q 131 795 166 857 q 104 665 104 745 l 0 665 q 54 867 0 789 q 237 958 116 958 q 429 861 331 958 q 594 765 527 765 q 704 827 670 765 q 729 958 729 874 l 833 958 "},"Ε":{"x_min":0,"x_max":736.21875,"ha":778,"o":"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},"³":{"x_min":0,"x_max":450,"ha":547,"o":"m 450 552 q 379 413 450 464 q 220 366 313 366 q 69 414 130 366 q 0 567 0 470 l 85 567 q 126 470 85 504 q 225 437 168 437 q 320 467 280 437 q 360 552 360 498 q 318 632 360 608 q 213 657 276 657 q 195 657 203 657 q 176 657 181 657 l 176 722 q 279 733 249 722 q 334 815 334 752 q 300 881 334 856 q 220 907 267 907 q 133 875 169 907 q 97 781 97 844 l 15 781 q 78 926 15 875 q 220 972 135 972 q 364 930 303 972 q 426 817 426 888 q 344 697 426 733 q 421 642 392 681 q 450 552 450 603 "},"[":{"x_min":0,"x_max":273.609375,"ha":371,"o":"m 273 -281 l 0 -281 l 0 1013 l 273 1013 l 273 920 l 124 920 l 124 -187 l 273 -187 l 273 -281 "},"L":{"x_min":0,"x_max":645.828125,"ha":696,"o":"m 645 0 l 0 0 l 0 1013 l 140 1013 l 140 126 l 645 126 l 645 0 "},"σ":{"x_min":0,"x_max":803.390625,"ha":894,"o":"m 803 628 l 633 628 q 713 368 713 512 q 618 93 713 204 q 357 -25 518 -25 q 94 91 194 -25 q 0 368 0 201 q 94 644 0 533 q 356 761 194 761 q 481 750 398 761 q 608 739 564 739 l 803 739 l 803 628 m 360 85 q 529 180 467 85 q 584 374 584 262 q 527 566 584 490 q 352 651 463 651 q 187 559 247 651 q 135 368 135 478 q 189 175 135 254 q 360 85 251 85 "},"ζ":{"x_min":0,"x_max":573,"ha":642,"o":"m 573 -40 q 553 -162 573 -97 q 510 -278 543 -193 l 400 -278 q 441 -187 428 -219 q 462 -90 462 -132 q 378 -14 462 -14 q 108 45 197 -14 q 0 290 0 117 q 108 631 0 462 q 353 901 194 767 l 55 901 l 55 1012 l 561 1012 l 561 924 q 261 669 382 831 q 128 301 128 489 q 243 117 128 149 q 458 98 350 108 q 573 -40 573 80 "},"θ":{"x_min":0,"x_max":674,"ha":778,"o":"m 674 496 q 601 160 674 304 q 336 -26 508 -26 q 73 153 165 -26 q 0 485 0 296 q 72 840 0 683 q 343 1045 166 1045 q 605 844 516 1045 q 674 496 674 692 m 546 579 q 498 798 546 691 q 336 935 437 935 q 178 798 237 935 q 126 579 137 701 l 546 579 m 546 475 l 126 475 q 170 233 126 348 q 338 80 230 80 q 504 233 447 80 q 546 475 546 346 "},"Ο":{"x_min":0,"x_max":958,"ha":1054,"o":"m 485 1042 q 834 883 703 1042 q 958 511 958 735 q 834 136 958 287 q 481 -26 701 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 729 q 485 1042 263 1042 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 670 q 480 913 640 913 q 226 785 321 913 q 142 504 142 671 q 226 224 142 339 q 480 98 319 98 "},"Γ":{"x_min":0,"x_max":705.28125,"ha":749,"o":"m 705 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 705 1012 l 705 886 "}," ":{"x_min":0,"x_max":0,"ha":375},"%":{"x_min":-3,"x_max":1089,"ha":1186,"o":"m 845 0 q 663 76 731 0 q 602 244 602 145 q 661 412 602 344 q 845 489 728 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 845 0 962 0 m 844 103 q 945 143 909 103 q 981 243 981 184 q 947 340 981 301 q 844 385 909 385 q 744 342 781 385 q 708 243 708 300 q 741 147 708 186 q 844 103 780 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 "},"P":{"x_min":0,"x_max":726,"ha":806,"o":"m 424 1013 q 640 931 555 1013 q 726 719 726 850 q 637 506 726 587 q 413 426 548 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 379 889 l 140 889 l 140 548 l 372 548 q 522 589 459 548 q 593 720 593 637 q 528 845 593 801 q 379 889 463 889 "},"Έ":{"x_min":0,"x_max":1078.21875,"ha":1118,"o":"m 1078 0 l 342 0 l 342 1013 l 1067 1013 l 1067 889 l 481 889 l 481 585 l 1019 585 l 1019 467 l 481 467 l 481 125 l 1078 125 l 1078 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ώ":{"x_min":0.125,"x_max":1136.546875,"ha":1235,"o":"m 1136 0 l 722 0 l 722 123 q 911 309 842 194 q 981 558 981 423 q 893 813 981 710 q 651 923 800 923 q 411 821 501 923 q 321 568 321 720 q 390 316 321 433 q 579 123 459 200 l 579 0 l 166 0 l 166 124 l 384 124 q 235 327 289 210 q 182 572 182 444 q 311 912 182 782 q 651 1042 441 1042 q 989 910 858 1042 q 1120 569 1120 778 q 1066 326 1120 443 q 917 124 1013 210 l 1136 124 l 1136 0 m 277 1040 l 83 800 l 0 800 l 140 1041 l 277 1040 "},"_":{"x_min":0,"x_max":705.5625,"ha":803,"o":"m 705 -334 l 0 -334 l 0 -234 l 705 -234 l 705 -334 "},"Ϊ":{"x_min":-110,"x_max":246,"ha":275,"o":"m 246 1046 l 118 1046 l 118 1189 l 246 1189 l 246 1046 m 18 1046 l -110 1046 l -110 1189 l 18 1189 l 18 1046 m 136 0 l 0 0 l 0 1012 l 136 1012 l 136 0 "},"+":{"x_min":23,"x_max":768,"ha":792,"o":"m 768 372 l 444 372 l 444 0 l 347 0 l 347 372 l 23 372 l 23 468 l 347 468 l 347 840 l 444 840 l 444 468 l 768 468 l 768 372 "},"½":{"x_min":0,"x_max":1050,"ha":1149,"o":"m 1050 0 l 625 0 q 712 178 625 108 q 878 277 722 187 q 967 385 967 328 q 932 456 967 429 q 850 484 897 484 q 759 450 798 484 q 721 352 721 416 l 640 352 q 706 502 640 448 q 851 551 766 551 q 987 509 931 551 q 1050 385 1050 462 q 976 251 1050 301 q 829 179 902 215 q 717 68 740 133 l 1050 68 l 1050 0 m 834 985 l 215 -28 l 130 -28 l 750 984 l 834 985 m 224 422 l 142 422 l 142 811 l 0 811 l 0 867 q 104 889 62 867 q 164 973 157 916 l 224 973 l 224 422 "},"Ρ":{"x_min":0,"x_max":720,"ha":783,"o":"m 424 1013 q 637 933 554 1013 q 720 723 720 853 q 633 508 720 591 q 413 426 546 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 378 889 l 140 889 l 140 548 l 371 548 q 521 589 458 548 q 592 720 592 637 q 527 845 592 801 q 378 889 463 889 "},"'":{"x_min":0,"x_max":139,"ha":236,"o":"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"ª":{"x_min":0,"x_max":350,"ha":397,"o":"m 350 625 q 307 616 328 616 q 266 631 281 616 q 247 673 251 645 q 190 628 225 644 q 116 613 156 613 q 32 641 64 613 q 0 722 0 669 q 72 826 0 800 q 247 866 159 846 l 247 887 q 220 934 247 916 q 162 953 194 953 q 104 934 129 953 q 76 882 80 915 l 16 882 q 60 976 16 941 q 166 1011 104 1011 q 266 979 224 1011 q 308 891 308 948 l 308 706 q 311 679 308 688 q 331 670 315 670 l 350 672 l 350 625 m 247 757 l 247 811 q 136 790 175 798 q 64 726 64 773 q 83 682 64 697 q 132 667 103 667 q 207 690 174 667 q 247 757 247 718 "},"΅":{"x_min":0,"x_max":450,"ha":553,"o":"m 450 800 l 340 800 l 340 925 l 450 925 l 450 800 m 406 1040 l 212 800 l 129 800 l 269 1040 l 406 1040 m 110 800 l 0 800 l 0 925 l 110 925 l 110 800 "},"T":{"x_min":0,"x_max":777,"ha":835,"o":"m 777 894 l 458 894 l 458 0 l 319 0 l 319 894 l 0 894 l 0 1013 l 777 1013 l 777 894 "},"Φ":{"x_min":0,"x_max":915,"ha":997,"o":"m 527 0 l 389 0 l 389 122 q 110 231 220 122 q 0 509 0 340 q 110 785 0 677 q 389 893 220 893 l 389 1013 l 527 1013 l 527 893 q 804 786 693 893 q 915 509 915 679 q 805 231 915 341 q 527 122 696 122 l 527 0 m 527 226 q 712 310 641 226 q 779 507 779 389 q 712 705 779 627 q 527 787 641 787 l 527 226 m 389 226 l 389 787 q 205 698 275 775 q 136 505 136 620 q 206 308 136 391 q 389 226 276 226 "},"⁋":{"x_min":0,"x_max":0,"ha":694},"j":{"x_min":-77.78125,"x_max":167,"ha":349,"o":"m 167 871 l 42 871 l 42 1013 l 167 1013 l 167 871 m 167 -80 q 121 -231 167 -184 q -26 -278 76 -278 l -77 -278 l -77 -164 l -41 -164 q 26 -143 11 -164 q 42 -65 42 -122 l 42 737 l 167 737 l 167 -80 "},"Σ":{"x_min":0,"x_max":756.953125,"ha":819,"o":"m 756 0 l 0 0 l 0 107 l 395 523 l 22 904 l 22 1013 l 745 1013 l 745 889 l 209 889 l 566 523 l 187 125 l 756 125 l 756 0 "},"1":{"x_min":215.671875,"x_max":574,"ha":792,"o":"m 574 0 l 442 0 l 442 697 l 215 697 l 215 796 q 386 833 330 796 q 475 986 447 875 l 574 986 l 574 0 "},"›":{"x_min":18.0625,"x_max":774,"ha":792,"o":"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},"<":{"x_min":17.984375,"x_max":773.609375,"ha":792,"o":"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"£":{"x_min":0,"x_max":704.484375,"ha":801,"o":"m 704 41 q 623 -10 664 5 q 543 -26 583 -26 q 359 15 501 -26 q 243 36 288 36 q 158 23 197 36 q 73 -21 119 10 l 6 76 q 125 195 90 150 q 175 331 175 262 q 147 443 175 383 l 0 443 l 0 512 l 108 512 q 43 734 43 623 q 120 929 43 854 q 358 1010 204 1010 q 579 936 487 1010 q 678 729 678 857 l 678 684 l 552 684 q 504 838 552 780 q 362 896 457 896 q 216 852 263 896 q 176 747 176 815 q 199 627 176 697 q 248 512 217 574 l 468 512 l 468 443 l 279 443 q 297 356 297 398 q 230 194 297 279 q 153 107 211 170 q 227 133 190 125 q 293 142 264 142 q 410 119 339 142 q 516 96 482 96 q 579 105 550 96 q 648 142 608 115 l 704 41 "},"t":{"x_min":0,"x_max":367,"ha":458,"o":"m 367 0 q 312 -5 339 -2 q 262 -8 284 -8 q 145 28 183 -8 q 108 143 108 64 l 108 638 l 0 638 l 0 738 l 108 738 l 108 944 l 232 944 l 232 738 l 367 738 l 367 638 l 232 638 l 232 185 q 248 121 232 140 q 307 102 264 102 q 345 104 330 102 q 367 107 360 107 l 367 0 "},"¬":{"x_min":0,"x_max":706,"ha":803,"o":"m 706 411 l 706 158 l 630 158 l 630 335 l 0 335 l 0 411 l 706 411 "},"λ":{"x_min":0,"x_max":750,"ha":803,"o":"m 750 -7 q 679 -15 716 -15 q 538 59 591 -15 q 466 214 512 97 l 336 551 l 126 0 l 0 0 l 270 705 q 223 837 247 770 q 116 899 190 899 q 90 898 100 899 l 90 1004 q 152 1011 125 1011 q 298 938 244 1011 q 373 783 326 901 l 605 192 q 649 115 629 136 q 716 95 669 95 l 736 95 q 750 97 745 97 l 750 -7 "},"W":{"x_min":0,"x_max":1263.890625,"ha":1351,"o":"m 1263 1013 l 995 0 l 859 0 l 627 837 l 405 0 l 265 0 l 0 1013 l 136 1013 l 342 202 l 556 1013 l 701 1013 l 921 207 l 1133 1012 l 1263 1013 "},">":{"x_min":18.0625,"x_max":774,"ha":792,"o":"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},"v":{"x_min":0,"x_max":675.15625,"ha":761,"o":"m 675 738 l 404 0 l 272 0 l 0 738 l 133 737 l 340 147 l 541 737 l 675 738 "},"τ":{"x_min":0.28125,"x_max":644.5,"ha":703,"o":"m 644 628 l 382 628 l 382 179 q 388 120 382 137 q 436 91 401 91 q 474 94 447 91 q 504 97 501 97 l 504 0 q 454 -9 482 -5 q 401 -14 426 -14 q 278 67 308 -14 q 260 233 260 118 l 260 628 l 0 628 l 0 739 l 644 739 l 644 628 "},"ξ":{"x_min":0,"x_max":624.9375,"ha":699,"o":"m 624 -37 q 608 -153 624 -96 q 563 -278 593 -211 l 454 -278 q 491 -183 486 -200 q 511 -83 511 -126 q 484 -23 511 -44 q 370 1 452 1 q 323 0 354 1 q 283 -1 293 -1 q 84 76 169 -1 q 0 266 0 154 q 56 431 0 358 q 197 538 108 498 q 94 613 134 562 q 54 730 54 665 q 77 823 54 780 q 143 901 101 867 l 27 901 l 27 1012 l 576 1012 l 576 901 l 380 901 q 244 863 303 901 q 178 745 178 820 q 312 600 178 636 q 532 582 380 582 l 532 479 q 276 455 361 479 q 118 281 118 410 q 165 173 118 217 q 274 120 208 133 q 494 101 384 110 q 624 -37 624 76 "},"&":{"x_min":-3,"x_max":894.25,"ha":992,"o":"m 894 0 l 725 0 l 624 123 q 471 0 553 40 q 306 -41 390 -41 q 168 -7 231 -41 q 62 92 105 26 q 14 187 31 139 q -3 276 -3 235 q 55 433 -3 358 q 248 581 114 508 q 170 689 196 640 q 137 817 137 751 q 214 985 137 922 q 384 1041 284 1041 q 548 988 483 1041 q 622 824 622 928 q 563 666 622 739 q 431 556 516 608 l 621 326 q 649 407 639 361 q 663 493 653 426 l 781 493 q 703 229 781 352 l 894 0 m 504 818 q 468 908 504 877 q 384 940 433 940 q 293 907 331 940 q 255 818 255 875 q 289 714 255 767 q 363 628 313 678 q 477 729 446 682 q 504 818 504 771 m 556 209 l 314 499 q 179 395 223 449 q 135 283 135 341 q 146 222 135 253 q 183 158 158 192 q 333 80 241 80 q 556 209 448 80 "},"Λ":{"x_min":0,"x_max":862.5,"ha":942,"o":"m 862 0 l 719 0 l 426 847 l 143 0 l 0 0 l 356 1013 l 501 1013 l 862 0 "},"I":{"x_min":41,"x_max":180,"ha":293,"o":"m 180 0 l 41 0 l 41 1013 l 180 1013 l 180 0 "},"G":{"x_min":0,"x_max":921,"ha":1011,"o":"m 921 0 l 832 0 l 801 136 q 655 15 741 58 q 470 -28 568 -28 q 126 133 259 -28 q 0 499 0 284 q 125 881 0 731 q 486 1043 259 1043 q 763 957 647 1043 q 905 709 890 864 l 772 709 q 668 866 747 807 q 486 926 589 926 q 228 795 322 926 q 142 507 142 677 q 228 224 142 342 q 483 94 323 94 q 712 195 625 94 q 796 435 796 291 l 477 435 l 477 549 l 921 549 l 921 0 "},"ΰ":{"x_min":0,"x_max":617,"ha":725,"o":"m 524 800 l 414 800 l 414 925 l 524 925 l 524 800 m 183 800 l 73 800 l 73 925 l 183 925 l 183 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 489 1040 l 300 819 l 216 819 l 351 1040 l 489 1040 "},"`":{"x_min":0,"x_max":138.890625,"ha":236,"o":"m 138 699 l 0 699 l 0 861 q 36 974 0 929 q 138 1041 72 1020 l 138 977 q 82 931 95 969 q 69 839 69 893 l 138 839 l 138 699 "},"·":{"x_min":0,"x_max":142,"ha":239,"o":"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"Υ":{"x_min":0.328125,"x_max":819.515625,"ha":889,"o":"m 819 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 "},"r":{"x_min":0,"x_max":355.5625,"ha":432,"o":"m 355 621 l 343 621 q 179 569 236 621 q 122 411 122 518 l 122 0 l 0 0 l 0 737 l 117 737 l 117 604 q 204 719 146 686 q 355 753 262 753 l 355 621 "},"x":{"x_min":0,"x_max":675,"ha":764,"o":"m 675 0 l 525 0 l 331 286 l 144 0 l 0 0 l 256 379 l 12 738 l 157 737 l 336 473 l 516 738 l 661 738 l 412 380 l 675 0 "},"μ":{"x_min":0,"x_max":696.609375,"ha":747,"o":"m 696 -4 q 628 -14 657 -14 q 498 97 513 -14 q 422 8 470 41 q 313 -24 374 -24 q 207 3 258 -24 q 120 80 157 31 l 120 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 172 124 246 q 308 82 216 82 q 451 177 402 82 q 492 358 492 254 l 492 738 l 616 738 l 616 214 q 623 136 616 160 q 673 92 636 92 q 696 95 684 92 l 696 -4 "},"h":{"x_min":0,"x_max":615,"ha":724,"o":"m 615 472 l 615 0 l 490 0 l 490 454 q 456 590 490 535 q 338 654 416 654 q 186 588 251 654 q 122 436 122 522 l 122 0 l 0 0 l 0 1013 l 122 1013 l 122 633 q 218 727 149 694 q 362 760 287 760 q 552 676 484 760 q 615 472 615 600 "},".":{"x_min":0,"x_max":142,"ha":239,"o":"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"φ":{"x_min":-2,"x_max":878,"ha":974,"o":"m 496 -279 l 378 -279 l 378 -17 q 101 88 204 -17 q -2 367 -2 194 q 68 626 -2 510 q 283 758 151 758 l 283 646 q 167 537 209 626 q 133 373 133 462 q 192 177 133 254 q 378 93 259 93 l 378 758 q 445 764 426 763 q 476 765 464 765 q 765 659 653 765 q 878 377 878 553 q 771 96 878 209 q 496 -17 665 -17 l 496 -279 m 496 93 l 514 93 q 687 183 623 93 q 746 380 746 265 q 691 569 746 491 q 522 658 629 658 l 496 656 l 496 93 "},";":{"x_min":0,"x_max":142,"ha":239,"o":"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 -12 q 105 -132 142 -82 q 0 -206 68 -182 l 0 -138 q 58 -82 43 -123 q 68 0 68 -56 l 0 0 l 0 151 l 142 151 l 142 -12 "},"f":{"x_min":0,"x_max":378,"ha":472,"o":"m 378 638 l 246 638 l 246 0 l 121 0 l 121 638 l 0 638 l 0 738 l 121 738 q 137 935 121 887 q 290 1028 171 1028 q 320 1027 305 1028 q 378 1021 334 1026 l 378 908 q 323 918 346 918 q 257 870 273 918 q 246 780 246 840 l 246 738 l 378 738 l 378 638 "},"“":{"x_min":1,"x_max":348.21875,"ha":454,"o":"m 140 670 l 1 670 l 1 830 q 37 943 1 897 q 140 1011 74 990 l 140 947 q 82 900 97 940 q 68 810 68 861 l 140 810 l 140 670 m 348 670 l 209 670 l 209 830 q 245 943 209 897 q 348 1011 282 990 l 348 947 q 290 900 305 940 q 276 810 276 861 l 348 810 l 348 670 "},"A":{"x_min":0.03125,"x_max":906.953125,"ha":1008,"o":"m 906 0 l 756 0 l 648 303 l 251 303 l 142 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 610 421 l 452 867 l 293 421 l 610 421 "},"6":{"x_min":53,"x_max":739,"ha":792,"o":"m 739 312 q 633 62 739 162 q 400 -31 534 -31 q 162 78 257 -31 q 53 439 53 206 q 178 859 53 712 q 441 986 284 986 q 643 912 559 986 q 732 713 732 833 l 601 713 q 544 830 594 786 q 426 875 494 875 q 268 793 331 875 q 193 517 193 697 q 301 597 240 570 q 427 624 362 624 q 643 540 552 624 q 739 312 739 451 m 603 298 q 540 461 603 400 q 404 516 484 516 q 268 461 323 516 q 207 300 207 401 q 269 137 207 198 q 405 83 325 83 q 541 137 486 83 q 603 298 603 197 "},"‘":{"x_min":1,"x_max":139.890625,"ha":236,"o":"m 139 670 l 1 670 l 1 830 q 37 943 1 897 q 139 1011 74 990 l 139 947 q 82 900 97 940 q 68 810 68 861 l 139 810 l 139 670 "},"ϊ":{"x_min":-70,"x_max":283,"ha":361,"o":"m 283 800 l 173 800 l 173 925 l 283 925 l 283 800 m 40 800 l -70 800 l -70 925 l 40 925 l 40 800 m 283 3 q 232 -10 257 -5 q 181 -15 206 -15 q 84 26 118 -15 q 41 200 41 79 l 41 737 l 166 737 l 167 215 q 171 141 167 157 q 225 101 182 101 q 247 103 238 101 q 283 112 256 104 l 283 3 "},"π":{"x_min":-0.21875,"x_max":773.21875,"ha":857,"o":"m 773 -7 l 707 -11 q 575 40 607 -11 q 552 174 552 77 l 552 226 l 552 626 l 222 626 l 222 0 l 97 0 l 97 626 l 0 626 l 0 737 l 773 737 l 773 626 l 676 626 l 676 171 q 695 103 676 117 q 773 90 714 90 l 773 -7 "},"ά":{"x_min":0,"x_max":765.5625,"ha":809,"o":"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 727 407 760 q 563 637 524 695 l 563 738 l 685 738 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 95 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 m 604 1040 l 415 819 l 332 819 l 466 1040 l 604 1040 "},"O":{"x_min":0,"x_max":958,"ha":1057,"o":"m 485 1041 q 834 882 702 1041 q 958 512 958 734 q 834 136 958 287 q 481 -26 702 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 728 q 485 1041 263 1041 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 669 q 480 912 640 912 q 226 784 321 912 q 142 504 142 670 q 226 224 142 339 q 480 98 319 98 "},"n":{"x_min":0,"x_max":615,"ha":724,"o":"m 615 463 l 615 0 l 490 0 l 490 454 q 453 592 490 537 q 331 656 410 656 q 178 585 240 656 q 117 421 117 514 l 117 0 l 0 0 l 0 738 l 117 738 l 117 630 q 218 728 150 693 q 359 764 286 764 q 552 675 484 764 q 615 463 615 593 "},"3":{"x_min":54,"x_max":737,"ha":792,"o":"m 737 284 q 635 55 737 141 q 399 -25 541 -25 q 156 52 248 -25 q 54 308 54 140 l 185 308 q 245 147 185 202 q 395 96 302 96 q 539 140 484 96 q 602 280 602 190 q 510 429 602 390 q 324 454 451 454 l 324 565 q 487 584 441 565 q 565 719 565 617 q 515 835 565 791 q 395 879 466 879 q 255 824 307 879 q 203 661 203 769 l 78 661 q 166 909 78 822 q 387 992 250 992 q 603 921 513 992 q 701 723 701 844 q 669 607 701 656 q 578 524 637 558 q 696 434 655 499 q 737 284 737 369 "},"9":{"x_min":53,"x_max":739,"ha":792,"o":"m 739 524 q 619 94 739 241 q 362 -32 516 -32 q 150 47 242 -32 q 59 244 59 126 l 191 244 q 246 129 191 176 q 373 82 301 82 q 526 161 466 82 q 597 440 597 255 q 363 334 501 334 q 130 432 216 334 q 53 650 53 521 q 134 880 53 786 q 383 986 226 986 q 659 841 566 986 q 739 524 739 719 m 388 449 q 535 514 480 449 q 585 658 585 573 q 535 805 585 744 q 388 873 480 873 q 242 809 294 873 q 191 658 191 745 q 239 514 191 572 q 388 449 292 449 "},"l":{"x_min":41,"x_max":166,"ha":279,"o":"m 166 0 l 41 0 l 41 1013 l 166 1013 l 166 0 "},"¤":{"x_min":40.09375,"x_max":728.796875,"ha":825,"o":"m 728 304 l 649 224 l 512 363 q 383 331 458 331 q 256 363 310 331 l 119 224 l 40 304 l 177 441 q 150 553 150 493 q 184 673 150 621 l 40 818 l 119 898 l 267 749 q 321 766 291 759 q 384 773 351 773 q 447 766 417 773 q 501 749 477 759 l 649 898 l 728 818 l 585 675 q 612 618 604 648 q 621 553 621 587 q 591 441 621 491 l 728 304 m 384 682 q 280 643 318 682 q 243 551 243 604 q 279 461 243 499 q 383 423 316 423 q 487 461 449 423 q 525 553 525 500 q 490 641 525 605 q 384 682 451 682 "},"κ":{"x_min":0,"x_max":632.328125,"ha":679,"o":"m 632 0 l 482 0 l 225 384 l 124 288 l 124 0 l 0 0 l 0 738 l 124 738 l 124 446 l 433 738 l 596 738 l 312 466 l 632 0 "},"4":{"x_min":48,"x_max":742.453125,"ha":792,"o":"m 742 243 l 602 243 l 602 0 l 476 0 l 476 243 l 48 243 l 48 368 l 476 958 l 602 958 l 602 354 l 742 354 l 742 243 m 476 354 l 476 792 l 162 354 l 476 354 "},"p":{"x_min":0,"x_max":685,"ha":786,"o":"m 685 364 q 598 96 685 205 q 350 -23 504 -23 q 121 89 205 -23 l 121 -278 l 0 -278 l 0 738 l 121 738 l 121 633 q 220 726 159 691 q 351 761 280 761 q 598 636 504 761 q 685 364 685 522 m 557 371 q 501 560 557 481 q 330 651 437 651 q 162 559 223 651 q 108 366 108 479 q 162 177 108 254 q 333 87 224 87 q 502 178 441 87 q 557 371 557 258 "},"‡":{"x_min":0,"x_max":777,"ha":835,"o":"m 458 238 l 458 0 l 319 0 l 319 238 l 0 238 l 0 360 l 319 360 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 l 777 804 l 777 683 l 458 683 l 458 360 l 777 360 l 777 238 l 458 238 "},"ψ":{"x_min":0,"x_max":808,"ha":907,"o":"m 465 -278 l 341 -278 l 341 -15 q 87 102 180 -15 q 0 378 0 210 l 0 739 l 133 739 l 133 379 q 182 195 133 275 q 341 98 242 98 l 341 922 l 465 922 l 465 98 q 623 195 563 98 q 675 382 675 278 l 675 742 l 808 742 l 808 381 q 720 104 808 213 q 466 -13 627 -13 l 465 -278 "},"η":{"x_min":0.78125,"x_max":697,"ha":810,"o":"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 720 124 755 q 200 630 193 686 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 "}},"cssFontWeight":"normal","ascender":1189,"underlinePosition":-100,"cssFontStyle":"normal","boundingBox":{"yMin":-334,"xMin":-111,"yMax":1189,"xMax":1672},"resolution":1000,"original_font_information":{"postscript_name":"Helvetiker-Regular","version_string":"Version 1.00 2004 initial release","vendor_url":"http://www.magenta.gr/","full_font_name":"Helvetiker","font_family_name":"Helvetiker","copyright":"Copyright (c) Μagenta ltd, 2004","description":"","trademark":"","designer":"","designer_url":"","unique_font_identifier":"Μagenta ltd:Helvetiker:22-10-104","license_url":"http://www.ellak.gr/fonts/MgOpen/license.html","license_description":"Copyright (c) 2004 by MAGENTA Ltd. All Rights Reserved.\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license (\"Fonts\") and associated documentation files (the \"Font Software\"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: \r\n\r\nThe above copyright and this permission notice shall be included in all copies of one or more of the Font Software typefaces.\r\n\r\nThe Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing the word \"MgOpen\", or if the modifications are accepted for inclusion in the Font Software itself by the each appointed Administrator.\r\n\r\nThis License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the \"MgOpen\" name.\r\n\r\nThe Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. \r\n\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL MAGENTA OR PERSONS OR BODIES IN CHARGE OF ADMINISTRATION AND MAINTENANCE OF THE FONT SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.","manufacturer_name":"Μagenta ltd","font_sub_family_name":"Regular"},"descender":-334,"familyName":"Helvetiker","lineHeight":1522,"underlineThickness":50}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/lib/plugin-detect.js b/docdoku-web-front/app/js/lib/plugin-detect.js new file mode 100644 index 0000000000..2f2e9e0f19 --- /dev/null +++ b/docdoku-web-front/app/js/lib/plugin-detect.js @@ -0,0 +1,22 @@ +/* + PluginDetect v0.9.1 + www.pinlady.net/PluginDetect/license/ + [ PDFjs ] + [ isMinVersion getVersion hasMimeType onDetectionDone ] + [ AllowActiveX ] + */ +(function(){var j={version:"0.9.1",name:"PluginDetect",addPlugin:function(p,q){if(p&&j.isString(p)&&q&&j.isFunc(q.getVersion)){p=p.replace(/\s/g,"").toLowerCase();j.Plugins[p]=q;if(!j.isDefined(q.getVersionDone)){q.installed=null;q.version=null;q.version0=null;q.getVersionDone=null;q.pluginName=p;}}},uniqueName:function(){return j.name+"998"},openTag:"<",hasOwnPROP:({}).constructor.prototype.hasOwnProperty,hasOwn:function(s,t){var p;try{p=j.hasOwnPROP.call(s,t)}catch(q){}return !!p},rgx:{str:/string/i,num:/number/i,fun:/function/i,arr:/array/i},toString:({}).constructor.prototype.toString,isDefined:function(p){return typeof p!="undefined"},isArray:function(p){return j.rgx.arr.test(j.toString.call(p))},isString:function(p){return j.rgx.str.test(j.toString.call(p))},isNum:function(p){return j.rgx.num.test(j.toString.call(p))},isStrNum:function(p){return j.isString(p)&&(/\d/).test(p)},isFunc:function(p){return j.rgx.fun.test(j.toString.call(p))},getNumRegx:/[\d][\d\.\_,\-]*/,splitNumRegx:/[\.\_,\-]/g,getNum:function(q,r){var p=j.isStrNum(q)?(r&&j.isString(r)?new RegExp(r):j.getNumRegx).exec(q):null;return p?p[0]:null},compareNums:function(w,u,t){var s,r,q,v=parseInt;if(j.isStrNum(w)&&j.isStrNum(u)){if(j.isDefined(t)&&t.compareNums){return t.compareNums(w,u)}s=w.split(j.splitNumRegx);r=u.split(j.splitNumRegx);for(q=0;qv(r[q],10)){return 1}if(v(s[q],10)r||!(/\d/).test(s[p])){s[p]="0"}}return s.slice(0,4).join(",")},pd:{getPROP:function(s,q,p){try{if(s){p=s[q]}}catch(r){this.errObj=r;}return p},findNavPlugin:function(u){if(u.dbug){return u.dbug}var A=null;if(window.navigator){var z={Find:j.isString(u.find)?new RegExp(u.find,"i"):u.find,Find2:j.isString(u.find2)?new RegExp(u.find2,"i"):u.find2,Avoid:u.avoid?(j.isString(u.avoid)?new RegExp(u.avoid,"i"):u.avoid):0,Num:u.num?/\d/:0},s,r,t,y,x,q,p=navigator.mimeTypes,w=navigator.plugins;if(u.mimes&&p){y=j.isArray(u.mimes)?[].concat(u.mimes):(j.isString(u.mimes)?[u.mimes]:[]);for(s=0;s=0;q=q-2){if(s[q]&&new RegExp(s[q],"i").test(p)){j.OS=s[q+1];break}}}},detectIE:function(){var r=this,u=document,t,q,v=window.navigator?navigator.userAgent||"":"",w,p,y;r.ActiveXFilteringEnabled=!1;r.ActiveXEnabled=!1;try{r.ActiveXFilteringEnabled=!!window.external.msActiveXFilteringEnabled()}catch(s){}p=["Msxml2.XMLHTTP","Msxml2.DOMDocument","Microsoft.XMLDOM","TDCCtl.TDCCtl","Shell.UIHelper","HtmlDlgSafeHelper.HtmlDlgSafeHelper","Scripting.Dictionary"];y=["WMPlayer.OCX","ShockwaveFlash.ShockwaveFlash","AgControl.AgControl"];w=p.concat(y);for(t=0;t=7?u.documentMode:0)||((/^(?:.*?[^a-zA-Z])??(?:MSIE|rv\s*\:)\s*(\d+\.?\d*)/i).test(v)?parseFloat(RegExp.$1,10):7)}},detectNonIE:function(){var q=this,p=0,t=window.navigator?navigator:{},s=q.isIE?"":t.userAgent||"",u=t.vendor||"",r=t.product||"";q.isGecko=!p&&(/Gecko/i).test(r)&&(/Gecko\s*\/\s*\d/i).test(s);p=p||q.isGecko;q.verGecko=q.isGecko?j.formatNum((/rv\s*\:\s*([\.\,\d]+)/i).test(s)?RegExp.$1:"0.9"):null;q.isOpera=!p&&(/(OPR\s*\/|Opera\s*\/\s*\d.*\s*Version\s*\/|Opera\s*[\/]?)\s*(\d+[\.,\d]*)/i).test(s);p=p||q.isOpera;q.verOpera=q.isOpera?j.formatNum(RegExp.$2):null;q.isEdge=!p&&(/(Edge)\s*\/\s*(\d[\d\.]*)/i).test(s);p=p||q.isEdge;q.verEdgeHTML=q.isEdge?j.formatNum(RegExp.$2):null;q.isChrome=!p&&(/(Chrome|CriOS)\s*\/\s*(\d[\d\.]*)/i).test(s);p=p||q.isChrome;q.verChrome=q.isChrome?j.formatNum(RegExp.$2):null;q.isSafari=!p&&((/Apple/i).test(u)||!u)&&(/Safari\s*\/\s*(\d[\d\.]*)/i).test(s);p=p||q.isSafari;q.verSafari=q.isSafari&&(/Version\s*\/\s*(\d[\d\.]*)/i).test(s)?j.formatNum(RegExp.$1):null;},init:function(){var p=this;p.detectPlatform();p.detectIE();p.detectNonIE()}},init:{hasRun:0,library:function(){window[j.name]=j;var q=this,p=document;j.win.init();j.head=p.getElementsByTagName("head")[0]||p.getElementsByTagName("body")[0]||p.body||null;j.browser.init();q.hasRun=1;}},ev:{addEvent:function(r,q,p){if(r&&q&&p){if(r.addEventListener){r.addEventListener(q,p,false)}else{if(r.attachEvent){r.attachEvent("on"+q,p)}else{r["on"+q]=this.concatFn(p,r["on"+q])}}}},removeEvent:function(r,q,p){if(r&&q&&p){if(r.removeEventListener){r.removeEventListener(q,p,false)}else{if(r.detachEvent){r.detachEvent("on"+q,p)}}}},concatFn:function(q,p){return function(){q();if(typeof p=="function"){p()}}},handler:function(t,s,r,q,p){return function(){t(s,r,q,p)}},handlerOnce:function(s,r,q,p){return function(){var u=j.uniqueName();if(!s[u]){s[u]=1;s(r,q,p)}}},handlerWait:function(s,u,r,q,p){var t=this;return function(){t.setTimeout(t.handler(u,r,q,p),s)}},setTimeout:function(q,p){if(j.win&&j.win.unload){return}setTimeout(q,p)},fPush:function(q,p){if(j.isArray(p)&&(j.isFunc(q)||(j.isArray(q)&&q.length>0&&j.isFunc(q[0])))){p.push(q)}},call0:function(q){var p=j.isArray(q)?q.length:-1;if(p>0&&j.isFunc(q[0])){q[0](j,p>1?q[1]:0,p>2?q[2]:0,p>3?q[3]:0)}else{if(j.isFunc(q)){q(j)}}},callArray0:function(p){var q=this,r;if(j.isArray(p)){while(p.length){r=p[0];p.splice(0,1);if(j.win&&j.win.unload&&p!==j.win.unloadHndlrs){}else{q.call0(r)}}}},call:function(q){var p=this;p.call0(q);p.ifDetectDoneCallHndlrs()},callArray:function(p){var q=this;q.callArray0(p);q.ifDetectDoneCallHndlrs()},allDoneHndlrs:[],ifDetectDoneCallHndlrs:function(){var r=this,p,q;if(!r.allDoneHndlrs.length){return}if(j.win){if(!j.win.loaded||j.win.loadPrvtHndlrs.length||j.win.loadPblcHndlrs.length){return}}if(j.Plugins){for(p in j.Plugins){if(j.hasOwn(j.Plugins,p)){q=j.Plugins[p];if(q&&j.isFunc(q.getVersion)){if(q.OTF==3||(q.DoneHndlrs&&q.DoneHndlrs.length)||(q.BIHndlrs&&q.BIHndlrs.length)){return}}}}}r.callArray0(r.allDoneHndlrs);}},isMinVersion:function(v,u,r,q){var s=j.pd.findPlugin(v),t,p=-1;if(s.status<0){return s.status}t=s.plugin;u=j.formatNum(j.isNum(u)?u.toString():(j.isStrNum(u)?j.getNum(u):"0"));if(t.getVersionDone!=1){t.getVersion(u,r,q);if(t.getVersionDone===null){t.getVersionDone=1}}if(t.installed!==null){p=t.installed<=0.5?t.installed:(t.installed==0.7?1:(t.version===null?0:(j.compareNums(t.version,u,t)>=0?1:-0.1)))}return p},getVersion:function(u,r,q){var s=j.pd.findPlugin(u),t,p;if(s.status<0){return null}t=s.plugin;if(t.getVersionDone!=1){t.getVersion(null,r,q);if(t.getVersionDone===null){t.getVersionDone=1}}p=(t.version||t.version0);p=p?p.replace(j.splitNumRegx,j.pd.getVersionDelimiter):p;return p},hasMimeType:function(t){if(t&&window.navigator&&navigator.mimeTypes){var w,v,q,s,p=navigator.mimeTypes,r=j.isArray(t)?[].concat(t):(j.isString(t)?[t]:[]);s=r.length;for(q=0;q=0;q--){v=w[q];if(v){w[q]=0;t.emptyNode(v.span());t.removeNode(v.span());v.span=0;v.spanObj=0;v.doc=0;v.objectProperty=0}}}r=t.getDiv();t.emptyNode(r);t.removeNode(r);v=0;s=0;r=0;t.div=0},span:function(){var p=this;if(!p.spanObj){p.spanObj=p.doc.getElementById(p.spanId)}return p.spanObj||null},width:function(){var t=this,s=t.span(),q,r,p=-1;q=s&&j.isNum(s.scrollWidth)?s.scrollWidth:p;r=s&&j.isNum(s.offsetWidth)?s.offsetWidth:p;s=0;return r>0?r:(q>0?q:Math.max(r,q))},obj:function(){var p=this.span();return p?p.firstChild||null:null},readyState:function(){var p=this;return j.browser.isIE&&j.isDefined(j.pd.getPROP(p.span(),"readyState"))?j.pd.getPROP(p.obj(),"readyState"):j.UNDEFINED},objectProperty:function(){var r=this,q=r.DOM,p;if(q.isEnabled.objectProperty(r)){p=j.pd.getPROP(r.obj(),"object")}return p},onLoadHdlr:function(p,q){q.loaded=1},getTagStatus:function(q,A,E,D,t,H,v){var F=this;if(!q||!q.span()){return -2}var y=q.width(),r=q.obj()?1:0,s=q.readyState(),p=q.objectProperty();if(p){return 1.5}var u=/clsid\s*\:/i,C=E&&u.test(E.outerHTML||"")?E:(D&&u.test(D.outerHTML||"")?D:0),w=E&&!u.test(E.outerHTML||"")?E:(D&&!u.test(D.outerHTML||"")?D:0),z=q&&u.test(q.outerHTML||"")?C:w;if(!A||!A.span()||!z||!z.span()){return -2}var x=z.width(),B=A.width(),G=z.readyState();if(y<0||x<0||B<=F.pluginSize){return 0}if(v&&!q.pi&&j.isDefined(p)&&j.browser.isIE&&q.tagName==z.tagName&&q.time<=z.time&&y===x&&s===0&&G!==0){q.pi=1}if(x.'+j.openTag+"/div>");q=s.getElementById(u)}catch(r){}}p=s.getElementsByTagName("body")[0]||s.body;if(p){p.insertBefore(v,p.firstChild);if(q){p.removeChild(q)}}v=0},iframe:{onLoad:function(p,q){j.ev.callArray(p);},insert:function(s,v){var q=this,w=j.DOM,p,r=document.createElement("iframe"),x,t;w.setStyle(r,w.getStyle.iframe());r.width=w.iframeWidth;r.height=w.iframeHeight;w.initDiv();p=w.getDiv();p.appendChild(r);try{q.doc(r).open()}catch(u){}r[j.uniqueName()]=[];x=j.ev.handlerOnce(j.isNum(s)&&s>0?j.ev.handlerWait(s,q.onLoad,r[j.uniqueName()],v):j.ev.handler(q.onLoad,r[j.uniqueName()],v));j.ev.addEvent(r,"load",x);if(!r.onload){r.onload=x}t=q.win(r);j.ev.addEvent(t,"load",x);if(t&&!t.onload){t.onload=x}return r},addHandler:function(q,p){if(q){j.ev.fPush(p,q[j.uniqueName()])}},close:function(p){try{this.doc(p).close()}catch(q){}},write:function(q,u){var t=this.doc(q),p=-1,s;try{s=new Date().getTime();t.write(u);p=new Date().getTime()-s}catch(r){}return p},win:function(p){try{return p.contentWindow}catch(q){}return null},doc:function(p){var r;try{r=p.contentWindow.document}catch(q){}try{if(!r){r=p.contentDocument}}catch(q){}return r||null}},insert:function(t,s,u,p,z,y,v){var E=this,G,F,D,C,B,w;if(!v){E.initDiv();v=E.getDiv()}if(v){if((/div/i).test(v.tagName)){C=v.ownerDocument}if((/iframe/i).test(v.tagName)){C=E.iframe.doc(v)}}if(C&&C.createElement){}else{C=document}if(!j.isDefined(p)){p=""}if(j.isString(t)&&(/[^\s]/).test(t)){t=t.toLowerCase().replace(/\s/g,"");G=j.openTag+t+" ";G+='style="'+E.getStyle.plugin(y)+'" ';var r=1,q=1;for(B=0;B"}else{G+=">";for(B=0;B'}}G+=p+j.openTag+"/"+t+">"}}else{t="";G=p}F={spanId:"",spanObj:null,span:E.span,loaded:null,tagName:t,outerHTML:G,DOM:E,time:new Date().getTime(),insertDomDelay:-1,width:E.width,obj:E.obj,readyState:E.readyState,objectProperty:E.objectProperty,doc:C};if(v&&v.parentNode){if((/iframe/i).test(v.tagName)){E.iframe.addHandler(v,[E.onLoadHdlr,F]);F.loaded=0;F.spanId=j.name+"Span"+E.HTML.length;D=''+G+"";F.time=new Date().getTime();w=E.iframe.write(v,D);if(w>=0){F.insertDomDelay=w}}else{if((/div/i).test(v.tagName)){D=C.createElement("span");E.setStyle(D,E.getStyle.span());v.appendChild(D);try{F.time=new Date().getTime();D.innerHTML=G;F.insertDomDelay=new Date().getTime()-F.time}catch(A){}F.spanObj=D}}}D=0;v=0;E.HTML.push(F);return F}},file:{any:"fileStorageAny999",valid:"fileStorageValid999",save:function(s,t,r){var q=this,p;if(s&&j.isDefined(r)){if(!s[q.any]){s[q.any]=[]}if(!s[q.valid]){s[q.valid]=[]}s[q.any].push(r);p=q.split(t,r);if(p){s[q.valid].push(p)}}},getValidLength:function(p){return p&&p[this.valid]?p[this.valid].length:0},getAnyLength:function(p){return p&&p[this.any]?p[this.any].length:0},getValid:function(r,p){var q=this;return r&&r[q.valid]?q.get(r[q.valid],p):null},getAny:function(r,p){var q=this;return r&&r[q.any]?q.get(r[q.any],p):null},get:function(s,p){var r=s.length-1,q=j.isNum(p)?p:r;return(q<0||q>r)?null:s[q]},split:function(t,q){var s=null,p,r;t=t?t.replace(".","\\."):"";r=new RegExp("^(.*[^\\/])("+t+"\\s*)$");if(j.isString(q)&&r.test(q)){p=(RegExp.$1).split("/");s={name:p[p.length-1],ext:RegExp.$2,full:q};p[p.length-1]="";s.path=p.join("/")}return s}},Plugins:{}};j.init.library();var b={OTF:null,setPluginStatus:function(){var q=this,r=q.doc.result,p=q.OTF;q.version=null;if(p==3){q.installed=-0.5}else{q.installed=r>0?0:-1}if(q.verify&&q.verify.isEnabled()){q.getVersionDone=0}else{if(q.getVersionDone!=1){q.getVersionDone=(q.installed==-0.5||(q.installed==-1&&q.doc.isDisabled()<2))?0:1}}},getVersion:function(r,q){var s=this,p=false,u=s.verify,t=s.doc;if(s.getVersionDone===null){s.OTF=0;if(u){u.init()}}j.file.save(s,".pdf",q);if(s.getVersionDone===0){if(u&&u.isEnabled()&&j.isNum(s.installed)&&s.installed>=0){return}}if((!p||j.dbug)&&t.insertHTMLQuery()>0){p=true}s.setPluginStatus()},doc:{result:0,mimeType:"application/pdf",mimeType_dummy:"application/dummymimepdf",DummySpanTagHTML:0,HTML:0,DummyObjTagHTML1:0,isDisabled:function(){var t=this,s=b,r=0,p=j.browser,q;if(s.OTF>=2||!j.DOM.isEnabled.objectTag()||j.DOM.isEnabled.objectTagUsingActiveX()){r=2}else{if(j.dbug){}else{if(!p.isGecko||j.compareNums(p.verGecko,j.formatNum("10"))<0||(j.compareNums(p.verGecko,j.formatNum("19"))<0&&j.hasMimeType(t.mimeType))){r=2}}}if(r<2){q=j.file.getValid(s);if(!q||!q.full){r=1}}return r},tabIndex:null,method:"",queryObject:function(r){var u=this,t=u.HTML?u.HTML.obj():0,v,q,p=j.dbug&&(u.HTML&&!u.HTML.loaded)?0:1;v=j.DOM.getTagStatus(u.HTML,u.DummySpanTagHTML,u.DummyObjTagHTML1,0);if((!u.result||j.dbug)&&v<-0.1){if(p){u.result=-1}u.method+="1,";}if((!u.result||j.dbug)&&v>0&&!j.hasMimeType(u.mimeType)){if(p){u.result=1}u.method+="2,";}try{q=t?t.tabIndex:null}catch(s){}if(!j.isNum(u.tabIndex)&&j.isNum(q)){u.tabIndex=q}if((!u.result||j.dbug)&&v>0){if(j.isNum(q)&&j.isNum(u.tabIndex)&&u.tabIndex!==q){if(p){u.result=1}u.method+="4,";}else{if(p){u.result=-1}u.method+="5,";}}return u.result},insertHTMLQuery:function(){var u=this,s=b,q,r,t=1,p=j.DOM.altHTML;if(u.isDisabled()){return u.result}if(s.OTF<2){s.OTF=2}q=j.file.getValid(s).full;r=j.DOM.iframe.insert(99,"PDFjs");u.DummySpanTagHTML=j.DOM.insert("",[],[],p,s,t,r);u.HTML=j.DOM.insert("object",["type",u.mimeType,"data",q],["src",q],p,s,t,r);u.DummyObjTagHTML1=j.DOM.insert("object",["type",u.mimeType_dummy],[],p,s,t,r);j.DOM.iframe.close(r);u.queryObject();if(u.result&&!j.dbug){return u.result}s.NOTF.init();return u.result}},NOTF:{count:0,intervalLength:250,init:function(){var r=this,p=b,q=p.doc;if(p.OTF<3&&q.HTML){p.OTF=3;j.ev.setTimeout(r.onIntervalQuery,r.intervalLength);}},onIntervalQuery:function(){var p=b.doc,q=b.NOTF;q.count++;if(b.OTF==3){p.queryObject(q.count);if(p.result){q.queryCompleted()}}if(b.OTF==3){j.ev.setTimeout(q.onIntervalQuery,q.intervalLength)}},queryCompleted:function(){var q=this,p=b;if(p.OTF==4){return}p.OTF=4;p.setPluginStatus();j.ev.callArray(p.DoneHndlrs);}}};j.addPlugin("pdfjs",b);})(); + +PluginDetect.canHandlePDF = function(callback){ + if(PluginDetect.hasMimeType("application/pdf") || + PluginDetect.hasMimeType("application/x-pdf") || + PluginDetect.hasMimeType("text/pdf") || + PluginDetect.hasMimeType("text/x-pdf")){ + callback(true); + }else{ + PluginDetect.onDetectionDone('PDFjs', function(p){ + var status = p.isMinVersion('PDFjs', 0); + callback(status === 0); + }, '/js/lib/empty.pdf'); + } +}; diff --git a/docdoku-web-front/app/js/localization/nls/account-management.js b/docdoku-web-front/app/js/localization/nls/account-management.js new file mode 100644 index 0000000000..94b1afde95 --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/account-management.js @@ -0,0 +1,14 @@ +/*global define*/ +define({ + root: { + ACCOUNT_NAME : 'Firstname, name', + ACCOUNT_EDITION:'Edit your account', + ACCOUNT_UPDATED:'Account updated', + CHANGE_PASSWORD:'Change password', + LANG:'Language', + TIMEZONE:'Timezone', + NEED_PAGE_RELOAD_CHANGED_LANG:'The page needs to be refreshed to take new language settings' + }, + 'fr': true, + 'es': true +}); diff --git a/docdoku-web-front/app/js/localization/nls/change-management.js b/docdoku-web-front/app/js/localization/nls/change-management.js new file mode 100644 index 0000000000..318e8f75da --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/change-management.js @@ -0,0 +1,17 @@ +/*global define*/ +define({ + root: { + ADVICE_CREATE_WORKFLOW:'Here you can create processes that define the life cycle of your documents and your parts', + CONFIRM_DELETE_ISSUE:'Do you want to delete selected issues?', + CONFIRM_DELETE_ORDER:'Do you want to delete selected orders?', + CONFIRM_DELETE_REQUEST:'Do you want to delete selected requests?', + CONFIRM_DELETE_WORKFLOW:'Do you want to delete selected workflows?', + ERROR_WORKFLOW_REFERENCE_MISSING:'You\'re trying to create a workflow without reference', + NEW_ROLES_NAME:'Enter the new role name', + WARNING_ANY_ROLE:'Create some role before create a Workflow', + WARNING_FINAL_STATE_MISSING:'You should give a final state name for your workflow', + WARNING_ACTIVITIES_MISSING:'You should create at least an activity and some tasks' + }, + fr: true, + es: true +}); diff --git a/docdoku-web-front/app/js/localization/nls/common.js b/docdoku-web-front/app/js/localization/nls/common.js new file mode 100644 index 0000000000..7aad785452 --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/common.js @@ -0,0 +1,694 @@ +/*global define*/ +define({ + root: { + ABOUT_DOCDOKUPLM: 'About DocdokuPLM', + ABSOLUTE: 'Absolute', + ACCEPT_VIDEO_INVITE: 'Accept', + ACCESS_RIGHTS: 'Access rights', + ACL: 'ACL', + ACTIVITY_STATE_PLACEHOLDER: 'New state', + ADD_ACTIVITY: 'Add activity', + ADD: 'Add', + ADD_CALCULATION: 'Add calculation', + ADD_COMMENT: 'Add comment', + ADD_DOCUMENT: 'Add document', + ADD_ISSUE: 'Add issue', + ADD_NEW_TAG: 'Enter a new tag', + ADD_PART: 'Add part', + ADD_REQUEST: 'Add request', + ADD_REVISION_NOTE: 'Add revision note', + ADD_TASK: 'Add task', + ADD_TEMPLATE_ATTRIBUTES:'Add deliverable attributes', + ADMINISTRATED: 'Administrated', + ADVANCED: 'Advanced', + ADVANCED_SEARCH: 'Advanced search', + AFFECTED_ELEMENT: 'Affected items', + ALERT_ROLE_IN_USE: 'This role cannot be removed, because it\'s in use in a workflow', + ALL: 'All', + ALL_PRODUCTS: 'All products', + ALL_TASKS: 'All', + AMOUNT: 'Amount', + APPEND: 'Add', + APPEND_LAYER: 'Create a new layer', + APPROVE_TASK: 'Mark task as done', + ASSIGNED_ROLE_PRE_FILL:'Default assignments', + ASSIGNED_ROLE_REQUIRED:'You must assign at least one user or one group', + ASSIGNED_USERS:'Assigned users', + ASSIGNED_USERS_PRE_FILL:'Assigned users default pre-fill', + ASSIGNED_USERGROUPS:'Assigned groups', + ASSIGNED_USERGROUPS_PRE_FILL:'Assigned groups default pre-fill', + ASSIGNMENTS:'Assignments', + ATTACHED_FILES: 'Attached files', + ATTRIBUTES: 'Attributes', + AUTHOR: 'Author', + AUTHOR_LOGIN: 'Author\'s login', + AUTHOR_NAME: 'Author\'s name', + AUTHOR_DOCUMENT_HELP: 'Author of the document, or user having worked on the document', + AUTHOR_PART_HELP: 'Author of the part, or user having worked on the part', + AUTO_CHECKIN:'Automatically check in parts which have been checked out during the import', + AUTO_CHECKOUT:'Automatically check out parts if needed', + AVG: 'Average', + BACK: 'Back', + BASED_ON: 'Based on', + BASELINE: 'Baseline', + BASELINE_DELETION_ERROR:'Cannot delete baseline : ', + BASELINE_OF: 'Baseline of', + BASELINES: 'Baselines', + BEST_FIT_VIEW:'Best fit view', + BLOCKER_TEXT: '(Z, Q, S, D = Move, SPACE = Up, CTRL = Down, MOUSE = Look around, ESC = Quit)', + BLOCKER_TITLE: 'Click to launch the scene', + BOM_VIEW: 'Product Breakdown Structure', + BOOLEAN: 'Boolean', + BY: 'by', + CAD_FILE: 'CAD file', + CAD_FILES: 'CAD files', + CALCULATIONS: 'Calculations', + CALL_TO_TITLE: 'Call to', + CANCEL: 'Cancel', + CANCEL_CHECKOUT: 'Undo check out', + CANCEL_PART_CREATION: 'Cancel part creation', + CANNOT_CREATE_PC: 'Cannot create RTCPeerConnection object, WebRTC is not supported by this browse.', + CANNOT_DELETE_FILE:'Cannot delete file : ', + CHANGE_ITEM_ASSIGNEE: 'Assignee', + CHANGE_ITEM_CATEGORY: 'Category', + CHANGE_ITEM_PRIORITY: 'Priority', + CHANGES: 'Changes management', + CHANGES_MANAGEMENT: 'Changes management', + CHANNEL_NOT_READY_ERROR: 'Cannot communicate with the call server. Please insure that your browser is websocket capable, or your internet provider doesn\'t block them.', + CHECK_ALL: 'Check all', + CHECKED_IN: 'checked in', + CHECKIN: 'Check in', + CHECKIN_DATE: 'Checkin date', + CHECKOUT_BY: 'Checked out by', + CHECKOUT: 'Check out', + CHECKOUT_DATE: 'Checkout date', + CHECKOUTS: 'Checked out', + CHOICES:'Choices', + CHOOSE_A_PRODUCT: 'Choose a product', + CHOSEN_OPTIONALS:'Excluded optionals', + CHOSEN_SUBSTITUTES:'Substitutes chosen', + CLEAR: 'Clear', + CLIPPING: 'Clipping', + CLOSE: 'Close', + CLOSING_PEER_CON: 'Closing peer connection ...', + CLOSURE_COMMENT: 'Closure comment', + COLLABORATIVE: 'Co-browsing', + COLLABORATIVE_INVITE: 'You are invited to join a room', + COLLABORATIVE_KICKED: 'You\'ve been kicked out the room', + COLLABORATIVE_LEAD: 'Presenter', + COLLABORATIVE_NO_MASTER: 'absent', + COLLABORATIVE_NOT_INVITED:'You are not invited to join this room.', + COLLABORATIVE_WITHDRAW_INVITATION: 'Invitation canceled', + COMMENT: 'Comment', + COMPONENT_SELECTED: 'Selected part', + CONFIG_SPEC: 'Config spec', + CONFIGURATION: 'Configuration', + CONFIGURATIONS: 'Configurations', + CONFIRM: 'Confirm', + CONFIRM_IMPORT:'Confirm import?', + CONFIRM_IMPORT_WITH_OPTION:'Confirm import with options:', + CONFIRM_PASSWORD: 'Confirm password', + CONNECTED: 'Connected', + CONNECTING: 'Connecting...', + CONTAINS_OBSOLETE_PARTS:'Contains obsolete parts', + CONTENT: 'Content', + CONTENT_HELP: 'File(s) content', + CONTROLS_SHORTCUTS: 'Mouse/keyboard shortcuts', + CONVERSION:'Conversion', + CONVERSION_STATUS:'Conversion status', + CONVERSION_PENDING:'Conversion pending', + CONVERSION_SUCCEED:'Conversion succeed', + CONVERSION_FAILED:'Conversion failed', + COPY: 'Copy', + COWORKERS: 'Coworkers', + CREATE_BASELINE: 'Create a baseline', + CREATE: 'Create', + CREATE_NEW_PART: 'Create a new part', + CREATE_NEW_PART_AS_SUBSTITUTE: 'Create a new part as a substitute', + CREATE_NEW_VERSION: 'Create new version', + CREATE_PART: 'Create part', + CREATE_PART_SUBSTITUTE_TOOLTIP: 'To create a substitute link, you should select a usage link first', + CREATE_PART_TOOLTIP: 'To create a new usage link, none of them should be selected', + CREATE_PRODUCT: 'Create Product', + CREATE_ROOM: 'Start a digital mockup review', + CREATION_DATE: 'Creation date', + CREATION_ERROR: 'An error occurred during creation', + CURRENT_TAGS: 'Tags related', + CURRENT_WORKFLOW: 'Current workflow', + CUTPLAN: 'Cut plan', + DATE: 'Date', + _DATE_FORMAT: 'MM-DD-YYYY HH:mm:ss', + _DATE_PICKER_DATE_FORMAT:'YYYY-MM-DD' , + _DATE_SHORT_FORMAT: 'MM-DD-YYYY', + DATE_SORT: 'date_sort', + DBL_CLICK: 'double click', + DELETE_ACTIVITY: 'Remove activity', + DELETE: 'Delete', + DELETE_FOLDER_QUESTION: 'Delete the folder?', + DELETE_LINKED_ITEM: 'Delete link', + DELETE_MARKER: 'Delete marker', + DELETE_PRODUCT_CONFIRM: 'Are you sure to want to delete this product?', + DELETE_PRODUCT: 'Delete Product', + DELETE_PRODUCT_ERROR: 'Error on product deletion', + DELETE_SELECTION: 'Delete selection', + DELETE_SELECTION_QUESTION: 'Delete selection?', + DELETE_TAG_QUESTION: 'Delete the tag?', + DELETE_QUERY_QUESTION: 'Delete the query?', + DELETE_TASK: 'Remove task', + DESCRIPTION: 'Description', + DEVICE_ERROR: 'Cannot access you audio/video device, please insure your browser is WebRTC capable, and if there\'s no exception for this website.', + DISPLAY_COMMENTS: 'Show/Hide descriptions', + DOCUMENT: 'Document', + DOCUMENT_REVISION_NOTE_PROMPT_LABEL: 'Do you want to add a revision note to documents without one?', + DOCUMENT_S_DESCRIPTION: 'Document description', + DOCUMENT_S_REFERENCE: 'Document ID', + DOCUMENT_S_TITLE: 'Document title', + DOCUMENT_TEMPLATES: 'Document templates', + DOCUMENT_TYPE_HELP: 'Type of the document', + DOCUMENT_VERSION_HELP: 'Version of the document', + DOCUMENTS: 'Documents', + DOCUMENTS_COLLECTION: 'Collection de documents', + DOCUMENTS_COLLECTIONS: 'Collections de documents', + DOCUMENTS_MANAGEMENT: 'Documents management', + DOWNLOAD_DPLM_CLIENT: 'Download DPLM client', + DOWNLOAD_ZIP:'Download all files in a zip', + DROP_FILE_HERE: 'or drop it here', + DROP_FILES_HERE: 'or drop them here', + DUE_DATE: 'Due date', + DUPLICATE: 'Duplicate', + DUPLICATE_WORKFLOW: 'Duplicate workflow', + EDIT_BASELINE: 'Edit baseline', + EDIT: 'Edit', + EDIT_ISSUE: 'Edit the issue', + EDIT_LINKED_DOCUMENTS: 'Edit links', + EDIT_MILESTONE: 'Edit the milestone', + EDIT_ORDER: 'Edit the order', + EDIT_PRODUCT: 'Edit product', + EDIT_PRODUCT_INSTANCE: 'Edit the deliverable', + EDIT_REQUEST: 'Edit the request', + EDITION_ERROR:'An error occurred while the edition', + EMBED_SCENE: 'Embed Scene', + EMAIL:'Email', + EMPTY_CHOICES:'Cannot create a configuration without choices.', + EMPTY_REVISION_NOTE:'Empty revision note.Please uncheck option or write note', + ENTER_NAV: 'Start the navigation', + ERROR: 'Error', + ERRORS: 'Errors:', + ERROR_404:'We couldn\'t find what you are looking for ...', + ERROR_404_SORRY:'Sorry !', + EXISTING_TAGS: 'Tags available', + EXIT: 'Leave digital mockup review', + EXPIRE_DATE: 'Expiration date', + EXPLODE: 'Explode', + EXPORT: 'Export', + EVERYTHING: 'All', + FALSE: 'False', + FILE: 'File', + FILE_DELETION_ERROR: 'The file %{id} could not be deleted', + FILE_UPLOADED: 'The file has been uploaded', + FILES_UPLOADED: 'The files have been uploaded', + FILES: 'Files', + FILTER: 'Filter', + FINAL_STATE_PLACEHOLDER: 'Final state', + FIRST_PAGE: 'First page', + FIT_VIEW: 'Fit view', + FLYING_MODE: 'Fly mode', + FLY_TO: 'Fly to', + FOLDER: 'Folder', + FOLDERS: 'Folders', + FOLDER_S_NAME: 'Folder name', + FOLD_TASK: 'Minimize task', + FORBIDDEN: 'Forbidden', + FORBIDDEN_MESSAGE:'You cannot access to this resource, you need to log in before continue', + FORM_HELPER: 'Leave these inputs empty if you don\'t want to specify a password or an expiration date', + FREEZE_CURRENT_ITERATION: 'Freeze current iteration', + FREEZE_AFTER_MODIFICATION: 'Freeze iterations after modification', + FROM:'From', + FULL_ACCESS: 'Full access', + FULL_SCREEN: 'Full screen', + GENERAL: 'General', + GENERATED_URL: 'Generated url', + GIVE_HAND: 'Give the hand', + GO_TO_PAGE: 'Go to page ...', + GOTO_PARALLEL_MODE: 'Switch activity to parallel', + GOTO_SEQUENTIAL_MODE: 'Switch activity to sequential', + GRID_SWITCH: 'Show/Hide the grid', + GROUPS: 'Groups', + HAS_SUBSTITUTES: 'Has substitutes', + HEAD_CHECKIN:'Latest checked in', + HEAD_RELEASED:'Latest released', + HEAD_WIP:'Work in progress', + HIDE_SUBSTITUTES:'Hide substitutes', + HOME_PAGE: 'Home', + HOURS: 'h', + HISTORY: 'History', + ID_GENERATION: 'Id generation', + IGNORE: 'Ignore', + IMPORT_ATTRIBUTE_FILE:'Import attribute\'s file', + IMPORT_DELIVERABLE_DATA:"Import deliverable data", + IMPORT_PART_DATA:'Import part data', + IMPORTER: 'Import', + IMPORT_PENDING:'Import pending', + IMPORT_SUCCEED:'Import succeed', + IMPORT_FAILED:'Import failed', + INIT_VALUE: 'Init value', + IN_PROGRESS: 'Work in progress', + INITIATOR: 'Initiator', + INSTANCE_SELECTED: 'Selected instance', + INSTRUCTIONS: 'Instructions', + INVERT_SOURCE_DESTINATION:'Invert source and destination', + INVITE: 'Invite coworkers', + ISSUES: 'Issues', + IS_COMPONENT_OF:'Is component of :', + IS_SUBSTITUTE_IN:'Is substitute in :', + IS_SUBSTITUTE: 'Is substitute', + ITERATION_CHANGE: 'Iteration change', + ITERATION: 'Iteration', + KEYBOARD_SHORTCUTS: 'Keyboard shortcuts', + KG: 'kg', + LAST_ITERATION: 'Current iteration', + LAST_PAGE: 'Last page', + LATEST: 'Latest', + LATEST_SHORT:'Latest versions', + LAYERS: 'Layers', + LEAVE_NAV: 'Stop the navigation', + LEFT_CLICK: 'left click', + LIFECYCLE_STATE: 'Life cycle state', + LINK_DESCRIPTION: 'Link description', + LINKED_DOCUMENTS: 'Linked documents', + LINKS: 'Links', + LOADING:'Loading', + LOCK_ATTRIBUTES: 'Freeze attributes structure', + LOCKED:'Locked', + LOGIN:'Login', + LOGOUT: 'Logout', + LOOK_AT: 'Look at', + MANAGE_PRODUCT: 'Products management', + MANAGE_ROLES: 'Manage roles', + MANDATORY: 'Mandatory', + MARK_ALL_AS_VERIFIED: 'Mark all as verified', + MARK_AS_VERIFIED: 'Mark as verified', + MARK_AS_OBSOLETE: 'Mark as obsolete', + MARK_DOCUMENT_AS_OBSOLETE_QUESTION: 'Mark as obsolete the selected document?', + MARK_PART_AS_OBSOLETE_QUESTION: 'Mark as obsolete the selected part?', + MARKER_INFO: 'Get marker informations', + MARKERS: 'Markers', + MARKERS_OPACITY: 'Change markers opacity', + MARKERS_SIZE_MAX: 'Increase markers size', + MARKERS_SIZE_MIN: 'Reduce markers size', + MASK_HELP: 'Define the mask format for the IDs of documents/parts that will instanciate this template.\n\nAdditionally to any regular characters, you can use \'#\' symbol for any valid number and \'*\' for any character.\n\nFor instance \'FAX-***-##\' will accept \'FAX-AZE-12\' as a valid ID.\n\nA mask wihtout any wildcards character won\'t be usable more than once.', + MASK: 'Mask', + MEASURE: 'Measure', + METER:'m', + MILESTONE: 'Milestone', + MILESTONES: 'Milestones', + MODIFICATION_DATE: 'Modification date', + MODIFICATION_NOTIFICATION_LIST_DESCRIPTION: 'Subcomponents changelog', + MOUSE_CONTROLS: 'Mouse controls', + MOVE: 'move', + MOVE_BACKWARD: 'Move backward', + MOVE_DOWN: 'Move down', + MOVE_FORWARD: 'Move forward', + MOVE_LEFT: 'Move left', + MOVE_RIGHT: 'Move right', + MOVE_SCENE: 'Move into the scene', + MOVE_UP: 'Move up', + MY_ACCOUNT: 'My account', + MY_ORGANIZATION: 'My organization', + MY_WORKSPACES: 'My workspaces', + NAME: 'Name', + NAVIGATION: 'Navigation', + NB_TASKS_TO_COMPLETE: 'Tasks to complete', + NEW_BASELINE: 'New baseline', + NEW_CONFIGURATION:'New configuration', + NEW_DOCUMENT: 'New document', + NEW_DOCUMENTS_COLLECTION: 'New documents collection', + NEW_FOLDER: 'New folder', + NEW_ISSUE: 'New change issue', + NEW_LAYER: 'New layer', + NEW_LINK_TYPE: 'Create new type', + NEW_MARKER: 'New marker', + NEW_MILESTONE: 'New milestone', + NEW_ORDER: 'New change order', + NEW_PART: 'New part', + NEW_PART_TEMPLATE: 'New part template', + NEW_PRODUCT_INSTANCE: 'New deliverable', + NEW_PRODUCT: 'New product', + NEW_REQUEST: 'New change request', + NEW_ROLE: 'New role', + NEW_TAG: 'New tag', + NEW_TEMPLATE: 'New document template', + NEW_UNIT:'Unit', + NEW_VERSION: 'New version', + NEW_VERSION_OF: 'New version of', + NEW_WORKFLOW: 'New workflow', + NEXT_PAGE: 'Next page', + NO_CONVERSION: 'No conversion', + NO_COWORKERS:'No coworkers', + NO_DATA: 'No data available in table', + NO_FILE_TO_IMPORT:'No file to import', + NO_FILTERED_DATA: 'No matching records found', + NO_LAYERS: 'No layers', + NO_PARTICIPANT: 'No participant. Invite users from Coworkers menu.', + NOTIFICATIONS: 'Notifications', + NOT_INVITED_COLLABORATIVE_ROOM:'You are not invited to join this room.', + NOT_VERIFIED: 'Not verified', + NO_WEBGL: 'No WebGL available or activated', + NOT_YET_REGISTERED_QUESTION:'Not a registered user?', + NOT_YET_REGISTERED_REGISTER:'sign up!', + NODE_ASSEMBLIES_VISITED:'Visited assemblies', + NODE_INSTANCES_VISITED:'Visited instances', + NONE:'None', + NUMBER: 'Number', + LITER: 'l', + LIST_LOV:'Edit lists of values', + LOCK:'Lock', + LOV:'List of values', + OBSOLETE: 'Obsolete', + OBSOLETE_AUTHOR: 'Obsolete status author', + OBSOLETE_DATE: 'Obsolete status date', + OFFICIAL_SITE:'PLM community', + OFFLINE: 'Offline', + OK: 'OK', + ON: 'on', + OR: 'or', + ONLINE: 'Online', + OPEN_NEW_WINDOW:'Open in a new window', + OPTIONAL:'Optional', + OPTIONAL_REMOVE:'None', + OPTIONS: 'Options', + OR_ADD_PART: 'Or add an existing part', + ORBIT_MODE: 'Orbit mode', + ORDERS_NUMBER: 'Number of orders', + ORDERS: 'Orders', + OTHERS: 'Others', + PATH_TO_PATH_LINKS:'Typed links', + PARALLEL_ACTIVITY: 'In parallel, requires ', + PART_ATTRIBUTES: 'Part attributes', + PART_ATTRIBUTES_TEMPLATES: 'Templates of part attributes', + PARTICIPANT: 'Participant', + PART_DESCRIPTION:'Description', + PART_NAME: 'Part Name', + PART_NOT_FOUND: 'The root part has not been found', + PART_NUMBER_HELP: 'Number of the part', + PART_NUMBER: 'Part Number', + PARTS: 'Parts', + PART_REVISION_NOTE_PROMPT_LABEL: 'Do you want to add a revision note to parts without one?', + PART_SEARCH_HELP: 'text', + PART_SUBSTITUTE: 'substitute part', + PARTS_SUBSTITUTES: 'substitute parts', + PART_TEMPLATE: 'Part template', + PART_TEMPLATES: 'Templates', + PART_TYPE_HELP: 'Type of the part', + PART_VERSION_HELP: 'Version of the part', + PASSWORD_NOT_CONFIRMED: 'Password and Confirm password must match', + PASSWORD: 'Password', + PATH: 'Folder', + PATH_TO_PATH_SOURCE: 'Source', + PATH_TO_PATH_TARGET: 'Target', + PATH_TO_PATH_SOURCE_LINK: 'Source links', + PATH_TO_PATH_TARGET_LINK: 'Target links', + PENDING: 'Absent participant', + PERMALINK: 'Permalink', + PERMISSIVE_UPDATE_PART: 'Permissive update (only modifiable parts will be considered)', + PERMISSIVE_UPDATE_PATH_DATA: 'Permissive update (only modifiable deliverable data will be considered)', + POSSIBLE_VALUE: 'possible values', + PREVIOUS_PAGE: 'Previous page', + PRIVATE_SHARE_TITLE: 'Private share', + PRODUCT: 'Product', + PRODUCT_ID: 'Product Id', + PRODUCT_INSTANCE: 'Deliverable', + PRODUCT_INSTANCES: 'Deliverables', + PRODUCT_INSTANCE_DATA: 'Deliverable data', + PRODUCT_INSTANCE_DATA_ATTRIBUTES: 'Deliverable data attributes', + PRODUCT_INSTANCE_DATA_ATTRIBUTES_TEMPLATES: 'Templates of deliverable data attributes', + PRODUCT_NAME: 'Product name', + PRODUCTS_MANAGEMENT: 'Products management', + PRODUCTS: 'Products', + PRODUCT_STRUCTURE: 'Product structure', + PROTECTED_RESOURCE : 'This resource is password protected', + PUBLIC_ACCESS_TITLE: 'Public access to permalink', + PUBLIC_SHARE: 'Public share', + PUBLISH: 'Publish', + QUERY_EXPORT_EXCEL_LOADING:'Export...', + QUERY_EXPORT_EXCEL:'Export Excel', + QUICK_SEARCH: 'Quick search', + QUERY_STANDARD: 'Standard', + QUERY_BUILDER_CONDITION_AND:'AND', + QUERY_BUILDER_CONDITION_OR:'OR', + QUERY_BUILDER_OPERATORS_EQUALS:'equal', + QUERY_BUILDER_OPERATORS_NOT_EQUALS:'not equal', + QUERY_BUILDER_OPERATORS_IN:'in', + QUERY_BUILDER_OPERATORS_NOT_IN:'not in', + QUERY_BUILDER_OPERATORS_LESS:'less', + QUERY_BUILDER_OPERATORS_LESS_OR_EQUAL:'less or equal', + QUERY_BUILDER_OPERATORS_GREATER:'greater', + QUERY_BUILDER_OPERATORS_GREATER_OR_EQUAL:'greater or equal', + QUERY_BUILDER_OPERATORS_BETWEEN:'between', + QUERY_BUILDER_OPERATORS_BEGIN_WITH:'begins with', + QUERY_BUILDER_OPERATORS_NOT_BEGIN_WITH:'doesn\'t begin with', + QUERY_BUILDER_OPERATORS_CONTAINS:'contains', + QUERY_BUILDER_OPERATORS_NOT_CONTAINS:'doesn\'t contain', + QUERY_BUILDER_OPERATORS_ENDS_WITH:'ends with', + QUERY_BUILDER_OPERATORS_NOT_ENDS_WITH:'doesn\'t end with', + QUERY_BUILDER_OPERATORS_IS_EMPTY:'is empty', + QUERY_BUILDER_OPERATORS_IS_NOT_EMPTY:'is not empty', + QUERY_BUILDER_OPERATORS_IS_NULL:'is null', + QUERY_BUILDER_OPERATORS_IS_NOT_NULL:'is not null', + QUERY_BUILDER_ERRORS_NO_FILTER:'No filter selected', + QUERY_BUILDER_ERRORS_EMPTY_GROUP:'The group is empty', + QUERY_BUILDER_ERRORS_RADIO_EMPTY:'No value selected', + QUERY_BUILDER_ERRORS_CHECKBOX_EMPTY:'No value selected', + QUERY_BUILDER_ERRORS_SELECT_EMPTY:'No value selected', + QUERY_BUILDER_ERRORS_STRING_EMPTY:'Empty value', + QUERY_BUILDER_ERRORS_STRING_EXCEED_MIN_LENGTH:'Must contain at least {0} characters', + QUERY_BUILDER_ERRORS_STRING_EXCEED_MAX_LENGTH:'Must not contain more than {0} characters', + QUERY_BUILDER_ERRORS_STRING_INVALID_FORMAT:'Invalid format ({0})', + QUERY_BUILDER_ERRORS_NUMBER_NAN:'Not a number', + QUERY_BUILDER_ERRORS_NUMBER_NOT_INTEGER:'Not an integer', + QUERY_BUILDER_ERRORS_NUMBER_NOT_DOUBLE:'Not a real number', + QUERY_BUILDER_ERRORS_NUMBER_EXCEED_MIN:'Must be greater than {0}', + QUERY_BUILDER_ERRORS_NUMBER_EXCEED_MAX:'Must be lower than {0}', + QUERY_BUILDER_ERRORS_NUMBER_WRONG_STEP:'Must be a multiple of {0}', + QUERY_BUILDER_ERRORS_DATETIME_EMPTY:'Empty value', + QUERY_BUILDER_ERRORS_DATETIME_INVALID:'Invalid date format ({0})', + QUERY_BUILDER_ERRORS_DATETIME_EXCEED_MIN:'Must be after {0}', + QUERY_BUILDER_ERRORS_DATETIME_EXCEED_MAX:'Must be before {0}', + QUERY_BUILDER_ERRORS_BOOLEAN_NOT_VALID:'Not a boolean', + QUERY_BUILDER_ERRORS_OPERATOR_NOT_MULTIPLE:'Operator {0} cannot accept multiple values', + QUERY_GROUP_AUTHOR:'Author', + QUERY_GROUP_PART_MASTER:'Part Master', + QUERY_GROUP_PART_REVISION:'Part Revision', + QUERY_GROUP_PRODUCT:'Product', + QUERY_GROUP_ATTRIBUTE_STRING:'String Attribute', + QUERY_GROUP_ATTRIBUTE_LONG_STRING:'Long string Attribute', + QUERY_GROUP_ATTRIBUTE_LOV:'List of values Attribute', + QUERY_GROUP_ATTRIBUTE_URL:'URL Attribute', + QUERY_GROUP_ATTRIBUTE_NUMBER:'Number Attribute', + QUERY_GROUP_ATTRIBUTE_DATE:'Date Attribute', + QUERY_GROUP_ATTRIBUTE_BOOLEAN:'Boolean Attribute', + QUERY_GROUP_CONTEXT:'Context', + QUERY_GROUP_PATH_DATA_ATTRIBUTE:'Deliverable Attribute', + QUERY_NAME:'Query name', + QUERY_SEARCH:'Build a query', + QUERY_SELECT:'Select', + QUERY_CONTEXT:'Context', + QUERY_WHERE:'Where', + QUERY_ORDER_BY:'Order by', + QUERY_GROUP_BY:'Group by', + QUERY_CLEAR:'clear', + QUERY_REMOVE_SAVED_QUERY:'Delete from queries list', + QUERY_CHOOSE_EXISTING_REQUEST:'Or use an existing one...', + QUERY_CONTEXT_ERROR:'Can\'t retrieve products', + QUERY_ATTRIBUTES_ERROR:'Can\'t retrieve attribute list', + QUERY_FILTER_STATUS_WIP:'Work in progress', + QUERY_FILTER_STATUS_RELEASED:'Released', + QUERY_FILTER_STATUS_OBSOLETE:'Obsolete', + QUERY_DEPTH:'Depth', + READ_ONLY: 'Read only', + REBASE: 'Rebase', + REBASE_AUTHOR: 'Rebase author', + REBASE_SUCCESS: 'The deliverable has been rebased', + REFERENCE_HELP: 'Document ID', + REFERENCE: 'ID', + REFRESH_TREE : 'Refresh tree', + REJECT_TASK: 'Reject task', + REJECT_VIDEO_INVITE: 'Reject', + RELATIVE: 'Relative', + RELAUCNH_ACTIVITY_SELECTOR: 'Relaunch activity if rejected', + RELEASE_DATE: 'Release date', + RELEASED_BY: 'Released by', + RELEASED: 'Released', + RELEASED_SHORT:'Latest released versions', + RELEASE: 'Release', + RELEASE_NOW: 'Release now', + RELEASE_SELECTION_QUESTION: 'Release selection?', + RELOAD:'Reload', + REMOTE_ACCEPT: 'Remote user has accepted the call. Waiting for connection ...', + REMOTE_BUSY: 'Remote user is busy. Call aborted ...', + REMOTE_HANGUP: 'Remote user hang up', + REMOTE_OFFLINE: 'Remote user is offline. Call ended ...', + REMOTE_REJECT: 'Remote user has rejected the call. Call ended ...', + REMOTE_STEAM_ADDED: 'Remote stream added', + REMOTE_STEAM_REMOVED: 'Remote stream removed', + REMOTE_TIMEOUT: 'Remote user didn\'t answer in time. Call aborted ...', + RENAME: 'Rename', + RENAME_WORKFLOW_COPY: 'Name of the copy', + REPLACE: 'Reset', + REQUESTS_NUMBER: 'Number of request', + REQUESTS: 'Requests', + REQUIRED_FIELD:'Please fill out this field', + RESET: 'Reset', + RESET_CAMERA: 'Reset camera', + RESULT:'Result', + RETAIN: 'Retain', + RETRY: 'Retry', + REVISION_DATE: 'Revision date', + REVISION_NOTE: 'Revision note', + REVISION_NOTE_PROMPT_CANCEL: 'Ignore', + REVISION_NOTE_PROMPT_LABEL: 'Do you want to add a revision note?', + REVISION_NOTE_PROMPT_OK: 'Add revision note', + RIGHT_CLICK: 'right click', + ROLE_NAME: 'Role name', + ROLE: 'Role', + ROLES: 'Roles', + ROOT_PART: 'Root part', + ROTATE: 'Rotate', + ROTATE_SCENE: 'Rotate the scene', + RUN:'Run', + SAVED: 'Saved', + SAVE: 'Save', + SCALE: 'Scale', + SCENE_VIEW: 'Scene view', + SCROLL_DOWN: 'scroll down', + SCROLL_UP: 'scroll up', + SEARCH_OPTIONS: 'Search options by number or name', + SEARCH_PART_BEGINNING: 'Search part beginning by', + SEARCH_PART_CALLED: 'Search part called', + SEARCH_PART_ENDING: 'Search part ending by ', + SEARCH_PARTS: 'Search parts', + SEARCH: 'Search', + SEARCHING: 'Searching...', + SEARCHING_FOR_PARTS: "Searching for parts, please do not close the window...", + SECONDS: 's', + SEE_IN_DOCUMENT_MANAGEMENT:'See the document in the document management module', + SEE_IN_PRODUCT_MANAGEMENT:'See the part in the product management module', + SELECT_FILE: 'Choose a file', + SELECT_FILES: 'Choose one or more file(s)', + SEND: 'Send', + SEQUENTIAL_ACTIVITY: 'In sequential', + SERIAL_NUMBER: 'Serial number', + SESSION_CONNECTING: 'Session connecting', + SESSION_OPENED: 'Session opened', + SHARED_DOCUMENTS_TITLE: 'Share document', + SHARED_PARTS_TITLE: 'Share part', + SHARE: 'Share', + SHORTCUTS_TITLE: 'Keyboard / mouse shortcuts', + SHOW_ALL: 'Show all', + SHOW_BY_PAGE: 'Show by page', + SHOW_EDITED_MESHES: 'Highlight edited meshes', + SHOW_SUBSTITUTES:'Show substitutes', + SIGN_TASK: 'Signing up', + SINGLE_FILE_RESTRICTION:'Only one file is allowed', + SPACE: 'space', + SQUARE_METER:'m²', + STANDARD_PART_ALL: 'All', + STANDARD_PART_FALSE: 'No', + STANDARD_PART: 'Standard part', + STANDARD_PART_TRUE: 'Yes', + START: 'Start', + START_IMPORT: 'Start import', + STATE_CHANGE: 'State change', + STATUS: 'Status', + STRUCTURE: 'Structure', + SUB_PARTS: 'Assembly', + SUM: 'Sum', + SUBSCRIBE_ITERATION_CHANGE: 'Subscribe to iteration change notifications', + SUBSCRIBE_STATE_CHANGE: 'Subscribe to document state change notifications', + TAGS_HELP: 'Tags list, comma separated', + TAGS: 'Tags', + TAGS_TO_ADD: 'Tags to add', + TAKE_SCREENSHOT: 'Take screenshot', + TASK_APPROVED_ON: 'Task done on', + TASK_CLOSED_ON: 'Task closed on', + TASK_NAME_PLACEHOLDER: 'New task', + TASK_REJECT_ON: 'Task reject on', + TASK_WORKER: 'Processed by', + TASKS_IN_PROGRESS: 'In progress', + TASKS: 'Tasks', + TEMPLATES: 'Templates', + TEMPLATE: 'Template', + TEXT: 'Text', + LONG_TEXT: 'Long text', + TITLE_HELP: 'Document title', + TITLE: 'Title', + TO:'To', + TOGGLE_ALL_LAYERS: 'Toggle all layers', + TRACKBALL_MODE: 'Trackball mode', + TRANSFORM_CONTROLS: 'Transformation', + TRUE: 'True', + TYPE: 'Type', + UDF_DEF:'Define function', + UNCHECK_ALL: 'Uncheck all', + UNDO_CHECKOUT: 'Cancel checkout', + UNDO_CHECKOUT_QUESTION: 'Undo checkout?', + UNFOLD_TASK: 'Edit task', + UNREACHABLE: 'Cannot invite user, user is unreachable', + UNSUBSCRIBE_ITERATION_CHANGE: 'Unsubscribe from iteration change notifications', + UNSUBSCRIBE_STATE_CHANGE: 'Unsubscribe from document state change notifications', + UNIT: 'each', + UPDATE_ACL_ERROR:'An error occurred while updating acl', + URL: 'URL', + USE_ACL: 'Use ACL ?', + USER_DEFINED_FUNCTION:'User defined function', + USER_MEDIA_FAILED: 'Cannot access to your video input. Is this a WebRTC capable browser?', + USERS_CONNECTED: 'users connected', + USERS: 'Users', + VALIDATION_FAILED_FOR: 'Validation failed for ', + _VALIDATION_PATTERN_DATE: '\\d{4}-\\d{2}-\\d{2}', + _VALIDATION_PATTERN_NUMBER: '^\\-?\\d+(\\.\\d+)?(([eE]([-+])?)?\\d+)?$', + _VALIDATION_PATTERN_TIME: '\\d{2}:\\d{2}', + VALUE: 'Value', + VALUES: 'Values', + VERIFICATION_NOTE: 'Verification note', + VERIFIED: 'Verified', + VERSION: 'Version', + VERSIONS: 'Versions', + VIDEO_INVITATION_SENT: 'Invitation has been sent to remote user, waiting for him/her to accept', + VIDEO_INVITE_ACCEPT: 'Accept', + VIDEO_INVITE_NOTIFICATION_TITLE: 'Video invite', + VIDEO_INVITE_REJECT: 'Reject', + VIDEO_INVITE_TEXT: 'is inviting you to a video chat session', + VIEW_MODE: 'View mode', + VISUALIZE_ASSEMBLY: 'Visualize assembly', + VISUALIZE_PRODUCT: 'Visualize product', + WARNINGS: 'Warnings:', + WAITING_REMOTE_VIDEO: 'Waiting for remote video.', + WAITING_USER_MEDIA: 'Waiting user media ... Please accept the prompt from your browser.', + USED_BY: 'Used by', + WITHDRAW_INVITATION: 'Withdraw invitation', + WITH_LINKS: 'With links', + WITHOUT_LINKS: 'Without links', + WORKFLOW_HISTORY: 'History', + WORKFLOW_LEGEND_COMPLETE: 'Complete', + WORKFLOW_LEGEND_INCOMPLETE: 'Incomplete', + WORKFLOW_LEGEND_INPROGRESS: 'In progress', + WORKFLOW_LEGEND_REJECTED: 'Rejected', + WORKFLOW_NAME_PLACEHOLDER: 'Workflow name', + WORKFLOW: 'Workflow', + WORKFLOW_COPY: 'Workflow copy', + WORKFLOWS: 'Workflows', + WORKSPACES_ADMINISTRATION: 'Workspaces administration', + WORKSPACE_MANAGEMENT: 'Workspace administration', + WORKSPACES: 'Workspaces', + WORKSPACE: 'Workspace', + YOU_CANNOT_CREATE_LINK_WITHOUT_TYPE: 'You cannot create a link without a type', + YOU_HAVE_BEEN_KICKED:'You have been kicked from the collaborative room', + LANGUAGES:{ + fr:'French', + en:'English' + } + }, + 'es': true, + 'fr': true +}); diff --git a/docdoku-web-front/app/js/localization/nls/document-management.js b/docdoku-web-front/app/js/localization/nls/document-management.js new file mode 100644 index 0000000000..febf16544a --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/document-management.js @@ -0,0 +1,8 @@ +/*global define*/ +define({ + root: { + CONFIRM_DELETE_DOCUMENTS_COLLECTIONS:'Do you want to delete selected documents collections?' + }, + 'fr': true, + 'es': true +}); diff --git a/docdoku-web-front/app/js/localization/nls/download.js b/docdoku-web-front/app/js/localization/nls/download.js new file mode 100644 index 0000000000..641211f817 --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/download.js @@ -0,0 +1,13 @@ +/*global define*/ +define({ + root: { + DOWNLOAD:'Download', + DPLM_CLIENT:'DPLM Client', + DPLM_CLIENT_INSTALL_MESSAGE:'DPLM Client requires you have Java 8 or above installed on your computer. To install Java or to check its installation follow : http://www.java.com', + CHOOSE_PLATFORM:'Choose your platform', + DPLM_CLIENT_ABOUT_QUESTION:'What is DPLM Client?', + DPLM_CLIENT_ABOUT_TEXT:'DPLM Client is a multi-platform client application which allows to exchange (upload / download) 2D and 3D files between your local workstation and the DocDokuPLM server. File format agnostic, it provides seamless integration with all the authoring tools of the market.' + }, + 'fr': true, + 'es': true +}); diff --git a/docdoku-web-front/app/js/localization/nls/es/common.js b/docdoku-web-front/app/js/localization/nls/es/common.js new file mode 100644 index 0000000000..e79669fbc6 --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/es/common.js @@ -0,0 +1,3 @@ +/*global define*/ +define({ +}); diff --git a/docdoku-web-front/app/js/localization/nls/fr/account-management.js b/docdoku-web-front/app/js/localization/nls/fr/account-management.js new file mode 100644 index 0000000000..55a632fd11 --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/fr/account-management.js @@ -0,0 +1,10 @@ +/*global define*/ +define({ + ACCOUNT_NAME : 'Prénom, nom', + ACCOUNT_EDITION:'Editez votre compte', + ACCOUNT_UPDATED:'Compte enregistré', + CHANGE_PASSWORD:'Changer le mot de passe', + LANG:'Langue', + NEED_PAGE_RELOAD_CHANGED_LANG:'Un rechargement de la page est nécéssaire pour prendre en compte le changement de langue', + TIMEZONE:'Fuseau horaire' +}); diff --git a/docdoku-web-front/app/js/localization/nls/fr/change-management.js b/docdoku-web-front/app/js/localization/nls/fr/change-management.js new file mode 100644 index 0000000000..244376c71c --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/fr/change-management.js @@ -0,0 +1,13 @@ +/*global define*/ +define({ + ADVICE_CREATE_WORKFLOW:'Vous pouvez ici créer des processus qui définiront le cycle de vie de vos documents ou de vos articles', + CONFIRM_DELETE_ISSUE:'Voulez-vous supprimer les anomalies sélectionnées', + CONFIRM_DELETE_ORDER:'Voulez-vous supprimer les ordres sélectionnés', + CONFIRM_DELETE_REQUEST:'Voulez-vous supprimer les demandes sélectionnées', + CONFIRM_DELETE_WORKFLOW:'Voulez-vous supprimer les processus sélectionnées', + ERROR_WORKFLOW_REFERENCE_MISSING:'Veuillez renseigner le nom de votre processus', + NEW_ROLES_NAME:'Entrer le nom du nouveau rôle', + WARNING_ANY_ROLE:'Veuillez créer des rôles avant de créer un processus', + WARNING_FINAL_STATE_MISSING:'Vous devriez renseigner le nom de l\'état final de votre processus', + WARNING_ACTIVITIES_MISSING:'Vous devriez créer au moins une activité et des tâches' +}); diff --git a/docdoku-web-front/app/js/localization/nls/fr/common.js b/docdoku-web-front/app/js/localization/nls/fr/common.js new file mode 100644 index 0000000000..c682a4cc7e --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/fr/common.js @@ -0,0 +1,686 @@ +/*global define*/ +define({ + ABOUT_DOCDOKUPLM: 'A propos de DocdokuPLM', + ABSOLUTE: 'Absolu', + ACCEPT_VIDEO_INVITE: 'Accepter', + ACCESS_RIGHTS: 'Droits d\'accès', + ACL: 'ACL', + ACTIVITY_STATE_PLACEHOLDER: 'Nouvel état', + ADD_ACTIVITY: 'Ajouter une activité', + ADD: 'Ajouter', + ADD_CALCULATION: 'Ajouter un calcul', + ADD_COMMENT: 'Ajouter un commentaire', + ADD_DOCUMENT: 'Ajouter un document', + ADD_ISSUE: 'Ajouter une anomalie', + ADD_NEW_TAG: 'Saisir un nouveau libellé', + ADD_PART: 'Ajouter un article', + ADD_REQUEST: 'Ajouter une requête', + ADD_REVISION_NOTE: 'Ajouter une note de révision', + ADD_TASK: 'Ajouter une tâche', + ADD_TEMPLATE_ATTRIBUTES:'Ajouter les attributs d\'exemplaire', + ADMINISTRATED: 'Administrés', + ADVANCED: 'Avancé', + ADVANCED_SEARCH: 'Recherche avancée', + AFFECTED_ELEMENT: 'Eléments affectés', + ALERT_ROLE_IN_USE: 'Ce rôle ne peut être supprimé car il est utilisé dans un processus', + ALL: 'Tous', + ALL_PRODUCTS: 'Tous les produits', + ALL_TASKS: 'Toutes', + AMOUNT: 'Quantité', + APPEND: 'Ajouter', + APPEND_LAYER: 'Créer un nouveau calque', + APPROVE_TASK: 'Marquer la tâche comme effectuée', + ASSIGNED_ROLE_PRE_FILL:'Assignations par défaut', + ASSIGNED_ROLE_REQUIRED:'Vous devez assigner au moins un utilisateur ou un groupe', + ASSIGNED_USERS:'Utilisateurs assignés', + ASSIGNED_USERS_PRE_FILL:'Utilisateurs assignés par défaut', + ASSIGNED_USERGROUPS:'Groupes assignés', + ASSIGNED_USERGROUPS_PRE_FILL:'Groupes assignés par défaut', + ATTACHED_FILES: 'Fichiers joints', + ASSIGNMENTS:'Assignations', + ATTRIBUTES: 'Attributs', + AUTHOR: 'Auteur', + AUTHOR_LOGIN: 'Identifiant de l\'auteur', + AUTHOR_NAME: 'Nom de l\'Auteur', + AUTHOR_DOCUMENT_HELP: 'Auteur du document ou utilisateur ayant travaillé sur le document', + AUTHOR_PART_HELP: 'Auteur de l\'article ou utilisateur ayant travaillé sur l\'article', + AUTO_CHECKIN:'Libération automatique des articles réservés pour l\'import', + AUTO_CHECKOUT:'Réservation automatique des articles non réservés', + AVG: 'Moyenne', + BACK:'Retour', + BASED_ON: 'Baseline de référence', + BASELINE: 'Baseline', + BASELINE_DELETION_ERROR:'Impossible de supprimer la baseline : ', + BASELINE_OF: 'Baseline de', + BASELINES: 'Baselines', + BEST_FIT_VIEW:'Meilleure vue', + BLOCKER_TEXT: '(Z, Q, S, D = Déplacer, ESPACE = Haut, CTRL = Bas, SOURIS = Regarder autour, ECHAP = Quitter)', + BLOCKER_TITLE: 'Cliquez pour lancer la visualisation', + BOM_VIEW: 'Visualiser structure produit', + BOOLEAN: 'Booléen', + BY: 'par', + CAD_FILE: 'Fichier CAO', + CAD_FILES: 'Fichiers CAO', + CALCULATIONS: 'Calculs', + CALL_TO_TITLE: 'Appel en cours', + CANCEL: 'Annuler', + CANCEL_CHECKOUT: 'Annuler la réservation', + CANCEL_PART_CREATION: 'Annuler la création de l\'article', + CANNOT_CREATE_PC: 'Impossible de créer la connexion, votre navigateur ne supporte pas le WebRTC.', + CANNOT_DELETE_FILE:'Impossible de supprimer le fichier : ', + CHANGE_ITEM_ASSIGNEE: 'Utilisateur assigné', + CHANGE_ITEM_CATEGORY: 'Catégorie', + CHANGE_ITEM_PRIORITY: 'Priorité', + CHANGES: 'Gestion des changements', + CHANGES_MANAGEMENT: 'Gestion des changements', + CHANNEL_NOT_READY_ERROR: 'Impossible de se connecter au serveur d\'appel, vérifiez que votre navigateur supporte les websocket, ou que votre FAI ne les bloque pas', + CHECK_ALL: 'Tout sélectionner', + CHECKED_IN: 'libéré', + CHECKIN: 'Libérer', + CHECKIN_DATE: 'Date de libération', + CHECKOUT_BY: 'Réservé par', + CHECKOUT_DATE: 'Date de réservation', + CHECKOUT: 'Réserver', + CHECKOUTS: 'Réservés', + CHOICES:'Choix', + CHOOSE_A_PRODUCT: 'Sélectionnez un produit', + CHOSEN_OPTIONALS:'Articles optionnels exclus', + CHOSEN_SUBSTITUTES:'Variantes choisies', + CLEAR: 'Effacer', + CLIPPING: 'Couper', + CLOSE: 'Fermer', + CLOSING_PEER_CON: 'Fermeture de la connexion ...', + CLOSURE_COMMENT: 'Commentaire', + COLLABORATIVE: 'Co-browsing', + COLLABORATIVE_INVITE: 'Vous êtes invité à rejoindre une salle', + COLLABORATIVE_KICKED: 'Vous n\'êtes plus dans la salle', + COLLABORATIVE_LEAD: 'Présentateur', + COLLABORATIVE_NO_MASTER: 'absent', + COLLABORATIVE_NOT_INVITED:'Vous n\'avez pas été invité à cette salle.', + COLLABORATIVE_WITHDRAW_INVITATION: 'Invitation annulée', + COMMENT: 'Commentaire', + COMPONENT_SELECTED: 'Article sélectionné', + CONFIG_SPEC: 'Config spec', + CONFIGURATION: 'Configuration', + CONFIGURATIONS: 'Configurations', + CONFIRM: 'Confirmer', + CONFIRM_IMPORT:'Confirmer l\'import ?', + CONFIRM_IMPORT_WITH_OPTION:'Confirmer l\'import avec les options suivantes :', + CONFIRM_PASSWORD: 'Confirmer le mot de passe', + CONNECTED: 'Connecté', + CONNECTING: 'Connexion ...', + CONTAINS_OBSOLETE_PARTS:'Contient des articles obsolètes', + CONTENT: 'Contenu', + CONTENT_HELP: 'Contenu du ou des fichier(s)', + CONTROLS_SHORTCUTS: 'Raccourcis clavier/souris', + COPY: 'Copier', + CONVERSION:'Conversion', + CONVERSION_STATUS:'Statut de la conversion', + CONVERSION_PENDING:'Conversion en cours', + CONVERSION_SUCCEED:'Conversion réussie', + CONVERSION_FAILED:'Conversion échouée', + COWORKERS: 'Collaborateurs', + CREATE_BASELINE: 'Créer une baseline', + CREATE: 'Créer', + CREATE_NEW_PART: 'Créer un nouvel article', + CREATE_NEW_PART_AS_SUBSTITUTE: 'Créer un nouvel article comme variante', + CREATE_NEW_VERSION: 'Créer une nouvelle version', + CREATE_PART: 'Créer l\'article', + CREATE_PART_SUBSTITUTE_TOOLTIP: 'Pour créer une variante, vous devez d\'abord sélectionner un lien d\'usage', + CREATE_PART_TOOLTIP: 'Pour créer un nouveau lien d\'usage, aucun élément ne doit être sélectionné', + CREATE_PRODUCT: 'Créer le produit', + CREATE_ROOM: 'Démarrer une revue de maquette', + CREATION_DATE: 'Date de création', + CREATION_ERROR: 'Une erreur s\'est produite lors de la création', + CURRENT_TAGS: 'Libellés associés', + CURRENT_WORKFLOW: 'Processus métier en cours', + CUTPLAN: 'Plan de coupe', + DATE: 'Date', + _DATE_FORMAT: 'DD-MM-YYYY HH:mm:ss', + _DATE_PICKER_DATE_FORMAT: 'YYYY-MM-DD', + _DATE_SHORT_FORMAT: 'DD-MM-YYYY', + DATE_SORT: 'date_sort', + DBL_CLICK: 'double clic', + DELETE_ACTIVITY: 'Supprimer l\'activité', + DELETE_FOLDER_QUESTION: 'Supprimer le dossier ?', + DELETE_LINKED_ITEM: 'Supprimer le lien', + DELETE_MARKER: 'Supprimer le marqueur', + DELETE_PRODUCT_CONFIRM: 'Etes-vous sûr de vouloir supprimer ce produit ?', + DELETE_PRODUCT_ERROR: 'Une erreur est survenue lors de la suppresion.', + DELETE_PRODUCT: 'Supprimer le produit', + DELETE_SELECTION_QUESTION: 'Supprimer la sélection ?', + DELETE_SELECTION: 'Supprimer la sélection', + DELETE: 'Supprimer', + DELETE_TAG_QUESTION: 'Supprimer le libellé ?', + DELETE_TASK: 'Supprimer la tâche', + DELETE_QUERY_QUESTION: 'Supprimer la requête', + DESCRIPTION: 'Description', + DEVICE_ERROR: 'Imposible de se connecter à votre matériel audio/vidéo. Veillez à activer le WebRTC pour votre navigateur, et de vérifier les exceptions.', + DISPLAY_COMMENTS: 'Afficher/Masquer les descriptions', + DOCUMENT: 'Document', + DOCUMENT_REVISION_NOTE_PROMPT_LABEL: 'Souhaitez-vous ajouter une note de révision aux documents qui n\'en ont pas ?', + DOCUMENT_S_DESCRIPTION: 'Description du document', + DOCUMENT_S_REFERENCE: 'Référence du document', + DOCUMENT_S_TITLE: 'Titre du document', + DOCUMENT_TEMPLATES: 'Modèle de document', + DOCUMENT_TYPE_HELP: 'Type du document', + DOCUMENT_VERSION_HELP: 'Version du document', + DOCUMENTS: 'Documents', + DOCUMENTS_COLLECTION: 'Collection de documents', + DOCUMENTS_COLLECTIONS: 'Collections de documents', + DOCUMENTS_MANAGEMENT: 'Gestion des documents', + DOWNLOAD_DPLM_CLIENT: 'Télécharger le client DPLM', + DOWNLOAD_ZIP:'Télécharger tous les fichiers dans un zip', + DROP_FILE_HERE: 'ou déposez le ici', + DROP_FILES_HERE: 'ou déposez les ici', + DUE_DATE: 'Date d\'échéance', + DUPLICATE: 'Dupliquer', + DUPLICATE_WORKFLOW: 'Dupliquer le processus métier', + EDIT_BASELINE: 'Editer la baseline', + EDIT: 'Editer', + EDIT_ISSUE: 'Editer l\'anomalie', + EDIT_LINKED_DOCUMENTS: 'Editer les liens', + EDIT_LINKED_DOCUMENT_COMMENT: 'Editer le commentaire', + EDIT_MILESTONE: 'Editer le jalon', + EDIT_ORDER: 'Editer l\'ordre de modification', + EDIT_PRODUCT: 'Editer le produit', + EDIT_PRODUCT_INSTANCE: 'Modifier l\'exemplaire', + EDIT_REQUEST: 'Editer la demande de modification', + EDITION_ERROR:'Une erreur est survenue pendant l\'édition', + EMAIL:'Email', + EMBED_SCENE: 'Exporter la scène', + EMPTY_CHOICES:'Impossible de créer une configuration sans choix', + EMPTY_REVISION_NOTE:'Pas de note de résivision. Ajoutez en une ou décochez cette option', + ENTER_NAV: 'Commencer la navigation', + ERROR: 'Erreur', + ERRORS: 'Erreurs : ', + ERROR_404:'Nous n\'avons pas pu trouver ce que vous cherchez ...', + ERROR_404_SORRY:'Désolé !', + EXISTING_TAGS: 'Libellé disponible', + EXIT: 'Quitter la revue de maquette', + EXPIRE_DATE: 'Date d\'expiration', + EXPLODE: 'Eclater', + EXPORT: 'Exporter', + EVERYTHING: 'Tout', + FALSE: 'Non', + FILE: 'Fichier', + FILE_DELETION_ERROR: 'Le fichier %{id} n\'a pu être supprimé', + FILE_UPLOADED: 'Le fichier a bien été envoyé', + FILES_UPLOADED: 'Les fichiers ont bien été envoyés', + FILES: 'Fichiers', + FILTER: 'Filtrer', + FINAL_STATE_PLACEHOLDER: 'Etat final', + FIRST_PAGE: 'Première page', + FIT_VIEW: 'Centrer les objets visibles', + FLYING_MODE: 'Mode libre', + FLY_TO: 'Atteindre', + FOLDER: 'Dossier', + FOLDERS: 'Dossiers', + FOLDER_S_NAME: 'Nom du dossier', + FOLD_TASK: 'Réduire la tâche', + FORBIDDEN: 'Interdit', + FORBIDDEN_MESSAGE: 'Vous n\'avez pas accès à cette ressource, vous devez vous connecter pour continuer', + FORM_HELPER: 'Laissez ces champs vides si vous ne voulez pas spécifier de mot de passe ou de date d\'expiration', + FREEZE_CURRENT_ITERATION: 'Figer l\'itération en cours', + FREEZE_AFTER_MODIFICATION: 'Geler les itérations après modifications', + FROM:'Depuis', + FULL_ACCESS: 'Accès complet', + FULL_SCREEN: 'Plein écran', + GENERAL: 'Général', + GENERATED_URL: 'Lien généré', + GIVE_HAND: 'Donner la main', + GO_TO_PAGE: 'Aller à la page ...', + GOTO_PARALLEL_MODE: 'Transformer l\'activité en parallèle', + GOTO_SEQUENTIAL_MODE: 'Transformer l\'activité en serie', + GRID_SWITCH: 'Afficher/Masquer la grille', + GROUPS: 'Groupes', + HAS_SUBSTITUTES: 'Possède des variantes', + HEAD_CHECKIN:'Derniers libérés', + HEAD_RELEASED:'Derniers finalisés', + HEAD_WIP:'Travail en cours', + HIDE_SUBSTITUTES:'Masquer les variantes', + HOME_PAGE: 'Accueil', + HOURS: 'h', + HISTORY: 'Historique', + ID_GENERATION: 'Génération de l\'id', + IGNORE: 'Ignorer', + IMPORT_ATTRIBUTE_FILE: 'Importer un fichier d\'attributs', + IMPORT_DELIVERABLE_DATA:"Importer des données d'exemplaires", + IMPORT_PART_DATA:'Importer des données d\'articles', + IMPORT_PENDING:'Import en cours', + IMPORT_SUCCEED:'Import réussi', + IMPORT_FAILED:'Echec de l\'import', + IMPORTER:'Importer un fichier', + INIT_VALUE: 'Valeur initiale', + IN_PROGRESS: 'En cours', + INITIATOR: 'Initiateur', + INSTANCE_SELECTED: 'Instance sélectionnée', + INSTRUCTIONS: 'Instructions', + INVERT_SOURCE_DESTINATION:'Inverser la source et la destination', + INVITE: 'Inviter des collaborateurs', + ISSUES: 'Anomalies', + IS_COMPONENT_OF:'Est un composant de :', + IS_SUBSTITUTE_IN:'Est une variante dans :', + IS_SUBSTITUTE: 'Est une variante', + ITERATION_CHANGE: 'Changement d\'iteration', + ITERATION: 'Itération', + KEYBOARD_SHORTCUTS: 'Raccourcis clavier', + LAST_ITERATION: 'Itération courante', + LAST_PAGE: 'Dernière page', + LATEST: 'Dernier', + LATEST_SHORT:'Dernières versions', + LAYERS: 'Calques', + LEAVE_NAV: 'Quitter la navigation', + LEFT_CLICK: 'clic gauche', + LIFECYCLE_STATE: 'Etat du cycle de vie', + LINK_DESCRIPTION: 'Description du lien', + LINKED_DOCUMENTS: 'Documents liés', + LINKS: 'Liens', + LIST_LOV:'Ensemble des listes de valeurs', + LOV:'Liste de valeurs', + LOADING:'Chargement', + LOCK:'Verrouiller', + LOCK_ATTRIBUTES: 'Geler la structure des attributs', + LOCKED: 'Verrouillé', + LOGIN:'Identifiant', + LOGOUT: 'Se déconnecter', + LOOK_AT: 'Regarder', + MANAGE_PRODUCT: 'Gestion des produits', + MANAGE_ROLES: 'Gestion des rôles', + MANDATORY: 'Obligatoire', + MARK_ALL_AS_VERIFIED: 'Marquer tout comme vérifié', + MARK_AS_VERIFIED: 'Marquer comme vérifié', + MARK_AS_OBSOLETE: 'Marquer ces articles comme obsolètes', + MARK_DOCUMENT_AS_OBSOLETE_QUESTION: 'Marquer le document sélectionné comme obsolète ?', + MARK_PART_AS_OBSOLETE_QUESTION: 'Marquer l\'article sélectionné comme obsolète ?', + MARKER_INFO: 'Afficher des informations sur la note', + MARKERS: 'Marqueurs', + MARKERS_OPACITY: 'Changer l\'opacité des marqueurs', + MARKERS_SIZE_MAX: 'Augmenter la taille des marqueurs', + MARKERS_SIZE_MIN: 'Diminuer la taille des marqueurs', + MASK_HELP: 'Définissez le format de la référence des documents/articles créés avec ce modèle.

                              En plus des caractères ordinaires, vous pouvez utiliser \'#\' pour tout chiffre valide et \'*\' pour n\'importe quel caractère.

                              Par exemple \'FAX_***_##\' accepte \'FAX-AZE-12\' comme entrée valide pour la référence.

                              Un masque sans caractère \'*\' ou \'#\' ne sera utilisable qu\'une fois', + MASK: 'Masque', + MEASURE: 'Mesure', + MILESTONE: 'Jalon', + MILESTONES: 'Jalons', + MODIFICATION_DATE: 'Date de modification', + MODIFICATION_NOTIFICATION_LIST_DESCRIPTION: 'Journal des modifications des sous-composants', + MOUSE_CONTROLS: 'Contrôles de la souris', + MOVE: 'Déplacer', + MOVE_BACKWARD: 'Reculer', + MOVE_DOWN: 'Descendre', + MOVE_FORWARD: 'Avancer', + MOVE_LEFT: 'Déplacer à droite', + MOVE_RIGHT: 'Déplacer à gauche', + MOVE_SCENE: 'Se déplacer dans la scène', + MOVE_UP: 'Monter', + MY_ACCOUNT: 'Mon compte', + MY_ORGANIZATION: 'Mon organisation', + MY_WORKSPACES: 'Mes espaces de travail', + NAME: 'Nom', + NAVIGATION: 'Navigation', + NB_TASKS_TO_COMPLETE: 'tâches à compléter', + NEW_BASELINE: 'Nouvelle baseline', + NEW_CONFIGURATION:'Nouvelle configuration', + NEW_DOCUMENT: 'Nouveau document', + NEW_DOCUMENTS_COLLECTION: 'Nouvelle collection de documents', + NEW_FOLDER: 'Nouveau dossier', + NEW_ISSUE: 'Nouveau rapport d\'anomalie', + NEW_LAYER: 'Nouveau calque', + NEW_LINK_TYPE: 'Créer un nouveau type', + NEW_MARKER: 'Créer un marqueur', + NEW_MILESTONE: 'Nouveau jalon', + NEW_ORDER: 'Nouvel ordre de modification', + NEW_PART: 'Nouvel article', + NEW_PART_TEMPLATE: 'Nouveau modèle d\'article', + NEW_PRODUCT_INSTANCE: 'Nouvel exemplaire', + NEW_PRODUCT: 'Nouveau produit', + NEW_REQUEST: 'Nouvelle demande de modification', + NEW_ROLE: 'Nouveau rôle', + NEW_TAG: 'Nouveau libellé', + NEW_TEMPLATE: 'Nouveau modèle de document', + NEW_UNIT:'Unité', + NEW_VERSION: 'Nouvelle version', + NEW_VERSION_OF: 'Nouvelle version de', + NEW_WORKFLOW: 'Nouveau processus métier', + NEXT_PAGE: 'Page suivante', + NO_CONVERSION: 'Pas de conversion', + NO_COWORKERS:'Aucun collaborateur', + NO_DATA: 'Aucune donnée dans la table', + NO_FILE_TO_IMPORT:'Pas de fichier à importer', + NO_FILTERED_DATA: 'Aucune donnée correspondante', + NO_LAYERS: 'Aucun calque', + NO_PARTICIPANT: 'Aucun participant. Invitez des utilisateurs depuis le menu Collaborateurs.', + NOTIFICATIONS: 'Notifications', + NOT_INVITED_COLLABORATIVE_ROOM:'Vous n\'êtes pas invité à rejoindre cette salle', + NOT_VERIFIED: 'Non vérifié', + NO_WEBGL: 'Le navigateur ne supporte pas WebGL ou n\'est pas activé.', + NOT_YET_REGISTERED_QUESTION:'Pas encore enregistré ?', + NOT_YET_REGISTERED_REGISTER:'Inscrivez-vous !', + NODE_ASSEMBLIES_VISITED:'Assemblages visités', + NODE_INSTANCES_VISITED:'Instances visitées', + NONE:'Aucun(e)', + NUMBER: 'Nombre', + OBSOLETE: 'Obsolète', + OBSOLETE_AUTHOR: 'Rendu obsolète par', + OBSOLETE_DATE: 'Obsolète depuis', + OFFICIAL_SITE:'Communauté PLM', + OFFLINE: 'Hors ligne', + OK: 'OK', + ON: 'le', + OR: 'ou', + ONLINE: 'En ligne', + OPEN_NEW_WINDOW:'Ouvrir dans une nouvelle fenêtre', + OPTIONAL:'Facultatif', + OPTIONAL_REMOVE:'Aucun', + OPTIONS: 'Options', + OR_ADD_PART: 'Ou ajouter un article existant', + ORBIT_MODE: 'Mode orbit', + ORDERS_NUMBER: 'Ordres associés', + ORDERS: 'Ordres', + OTHERS: 'Autres', + PARALLEL_ACTIVITY: 'En parallèle, nécessite', + PART_ATTRIBUTES: 'Attributs de l\'article', + PART_ATTRIBUTES_TEMPLATES: 'Modèles d\'attributs associés à l\'article', + PARTICIPANT: 'Participant', + PART_NAME: 'Nom', + PART_NOT_FOUND: 'L\'article de tête n\'a pas été trouvé', + PART_NUMBER_HELP: 'Numéro de l\'article', + PART_NUMBER: 'Numéro', + PARTS: 'Articles', + PART_REVISION_NOTE_PROMPT_LABEL: 'Souhaitez-vous ajouter une note de révision aux articles qui n\'en ont pas ?', + PART_SEARCH_HELP: 'texte', + PART_SUBSTITUTE: 'article variante', + PARTS_SUBSTITUTES: 'articles variantes', + PART_TEMPLATE: 'Modèle d\'article', + PART_TEMPLATES: 'Modèles', + PART_TYPE_HELP: 'Type de l\'article', + PART_VERSION_HELP: 'Version de l\'article', + PASSWORD: 'Mot de passe', + PASSWORD_NOT_CONFIRMED: 'Les champs mot de passe et confirmation doivent être identiques', + PATH: 'Dossier', + PATH_TO_PATH_LINKS:'Liens typés', + PATH_TO_PATH_SOURCE: 'Source', + PATH_TO_PATH_TARGET: 'Destination', + PATH_TO_PATH_SOURCE_LINK: 'Liens source', + PATH_TO_PATH_TARGET_LINK: 'Liens destination', + PENDING: 'Participant absent', + PERMALINK: 'Permalien', + PERMISSIVE_UPDATE: 'Mise à jour permissive', + PERMISSIVE_UPDATE_PATH_DATA:'Mise à jour permissive (seules les données d\'exemplaires modifiables seront considérées)', + PERMISSIVE_UPDATE_PART:'Mise à jour permissive (seuls les articles modifiables seront considérés)', + POSSIBLE_VALUE: 'valeurs possibles', + PREVIOUS_PAGE: 'Page précédente', + PRIVATE_SHARE_TITLE: 'Accès privé', + PRODUCT: 'Produit', + PRODUCT_ID: 'Référence', + PRODUCT_INSTANCE: 'Exemplaire', + PRODUCT_INSTANCES: 'Exemplaires', + PRODUCT_INSTANCE_DATA: 'Données de l\'exemplaire', + PRODUCT_INSTANCE_DATA_ATTRIBUTES: 'Attributs des données dans l\'exemplaire', + PRODUCT_INSTANCE_DATA_ATTRIBUTES_TEMPLATES: 'Modèles d\'attributs associés aux données dans l\'exemplaire', + PRODUCT_NAME: 'Nom de produit', + PRODUCTS_MANAGEMENT: 'Gestion des produits', + PRODUCTS: 'Produits', + PRODUCT_STRUCTURE: 'Structure du produit', + PROTECTED_RESOURCE : 'Cette resource est protégée par mot de passe', + PUBLIC_ACCESS_TITLE: 'Accès public au permalien', + PUBLIC_SHARE: 'Partage public', + PUBLISH: 'Publier', + QUERY_EXPORT_EXCEL_LOADING:'Export...', + QUERY_EXPORT_EXCEL:'Export Excel', + QUICK_SEARCH: 'Recherche rapide', + QUERY_STANDARD: 'Standard', + QUERY_BUILDER_CONDITION_AND:'ET', + QUERY_BUILDER_CONDITION_OR:'OU', + QUERY_BUILDER_OPERATORS_EQUALS:'égal', + QUERY_BUILDER_OPERATORS_NOT_EQUALS:'non égal', + QUERY_BUILDER_OPERATORS_IN:'dans', + QUERY_BUILDER_OPERATORS_NOT_IN:'pas dans', + QUERY_BUILDER_OPERATORS_LESS:'inférieur', + QUERY_BUILDER_OPERATORS_LESS_OR_EQUAL:'inférieur ou égal', + QUERY_BUILDER_OPERATORS_GREATER:'supérieur', + QUERY_BUILDER_OPERATORS_GREATER_OR_EQUAL:'supérieur ou égal', + QUERY_BUILDER_OPERATORS_BETWEEN:'entre', + QUERY_BUILDER_OPERATORS_BEGIN_WITH:'commence par', + QUERY_BUILDER_OPERATORS_NOT_BEGIN_WITH:'ne commence pas par', + QUERY_BUILDER_OPERATORS_CONTAINS:'contient', + QUERY_BUILDER_OPERATORS_NOT_CONTAINS:'ne contient pas', + QUERY_BUILDER_OPERATORS_ENDS_WITH:'finit par', + QUERY_BUILDER_OPERATORS_NOT_ENDS_WITH:'ne finit pas par', + QUERY_BUILDER_OPERATORS_IS_EMPTY:'est vide', + QUERY_BUILDER_OPERATORS_IS_NOT_EMPTY:'n\'est pas vide', + QUERY_BUILDER_OPERATORS_IS_NULL:'est nul', + QUERY_BUILDER_OPERATORS_IS_NOT_NULL:'n\'est pas nul', + QUERY_BUILDER_ERRORS_NO_FILTER:'Aucun filtre sélectionné', + QUERY_BUILDER_ERRORS_EMPTY_GROUP:'Le groupe est vide', + QUERY_BUILDER_ERRORS_RADIO_EMPTY:'Pas de valeur selectionnée', + QUERY_BUILDER_ERRORS_CHECKBOX_EMPTY:'Pas de valeur selectionnée', + QUERY_BUILDER_ERRORS_SELECT_EMPTY:'Pas de valeur selectionnée', + QUERY_BUILDER_ERRORS_STRING_EMPTY:'Valeur vide', + QUERY_BUILDER_ERRORS_STRING_EXCEED_MIN_LENGTH:'Doit contenir au moins {0} caractères', + QUERY_BUILDER_ERRORS_STRING_EXCEED_MAX_LENGTH:'Ne doit pas contenir plus de {0} caractères', + QUERY_BUILDER_ERRORS_STRING_INVALID_FORMAT:'Format invalide ({0})', + QUERY_BUILDER_ERRORS_NUMBER_NAN:'N\'est pas un nombre', + QUERY_BUILDER_ERRORS_NUMBER_NOT_INTEGER:'N\'est pas un entier', + QUERY_BUILDER_ERRORS_NUMBER_NOT_DOUBLE:'N\'est pas un nombre réel', + QUERY_BUILDER_ERRORS_NUMBER_EXCEED_MIN:'Doit être plus grand que {0}', + QUERY_BUILDER_ERRORS_NUMBER_EXCEED_MAX:'Doit être plus petit que {0}', + QUERY_BUILDER_ERRORS_NUMBER_WRONG_STEP:'Doit être un multiple de {0}', + QUERY_BUILDER_ERRORS_DATETIME_EMPTY:'Valeur vide', + QUERY_BUILDER_ERRORS_DATETIME_INVALID:'Fomat de date invalide ({0})', + QUERY_BUILDER_ERRORS_DATETIME_EXCEED_MIN:'Doit être après {0}', + QUERY_BUILDER_ERRORS_DATETIME_EXCEED_MAX:'Doit être avant {0}', + QUERY_BUILDER_ERRORS_BOOLEAN_NOT_VALID:'N\'est pas un booléen', + QUERY_BUILDER_ERRORS_OPERATOR_NOT_MULTIPLE:'L\'opérateur {0} ne peut utiliser plusieurs valeurs', + QUERY_GROUP_AUTHOR:'Auteur', + QUERY_GROUP_PART_MASTER:'Article', + QUERY_GROUP_PART_REVISION:'Revision d\'article', + QUERY_GROUP_PRODUCT:'Produit', + QUERY_GROUP_ATTRIBUTE_STRING:'Attribut de type chaine', + QUERY_GROUP_ATTRIBUTE_LONG_STRING:'Attribut de type longue chaine', + QUERY_GROUP_ATTRIBUTE_URL:'Attribut de type URL', + QUERY_GROUP_ATTRIBUTE_LOV:'Attribut de type Liste de valeurs', + QUERY_GROUP_ATTRIBUTE_NUMBER:'Attribut de type nombre', + QUERY_GROUP_ATTRIBUTE_DATE:'Attribut de type date', + QUERY_GROUP_ATTRIBUTE_BOOLEAN:'Attribut de type booléen', + QUERY_GROUP_CONTEXT:'Contexte', + QUERY_GROUP_PATH_DATA_ATTRIBUTE:'Attribut d\'exemplaire', + QUERY_NAME:'Nom de la requête', + QUERY_SEARCH:'Construire une requête', + QUERY_SELECT:'Champs', + QUERY_CONTEXT:'Contexte', + QUERY_WHERE:'Critères', + QUERY_ORDER_BY:'Ordonner par', + QUERY_GROUP_BY:'Grouper par', + QUERY_CLEAR:'vider', + QUERY_CHOOSE_EXISTING_REQUEST:'Ou utiliser une requête sauvegardée...', + QUERY_REMOVE_SAVED_QUERY:'Supprimer de la liste des requêtes', + QUERY_CONTEXT_ERROR:'Impossible de récuperer la liste des produits', + QUERY_ATTRIBUTES_ERROR:'Impossible de récuperer la liste des attributs', + QUERY_FILTER_STATUS_WIP:'Travail en cours', + QUERY_FILTER_STATUS_RELEASED:'Finalisé', + QUERY_FILTER_STATUS_OBSOLETE:'Obsolète', + QUERY_DEPTH:'Profondeur', + READ_ONLY: 'Lecture seule', + REBASE: 'Rebaser', + REBASE_AUTHOR: 'Auteur de l\'itération', + REBASE_SUCCESS: 'L\'exemplaire a bien été rebasé', + REFERENCE_HELP: 'ID du document', + REFERENCE: 'Référence', + REFRESH_TREE : 'Rafraîchir l\'arbre', + REJECT_TASK: 'Rejeter la tâche', + REJECT_VIDEO_INVITE: 'Rejeter', + RELAUCNH_ACTIVITY_SELECTOR: 'Retour à l\'activité en cas de rejet', + RELEASED: 'Finalisé', + RELEASED_SHORT:'Dernières versions finalisées', + RELEASE: 'Finaliser', + RELEASE_DATE: 'Finalisé depuis', + RELEASED_BY: 'Finalisé par', + RELEASE_NOW: 'Finaliser maintenant', + RELEASE_SELECTION_QUESTION: 'Finaliser la sélection ?', + RELOAD:'Recharger', + REMOTE_ACCEPT: 'L\'utilisateur distant a accepté l\'appel. En attente de connexion ...', + REMOTE_BUSY: 'L\'utilisateur distant est occupé. Appel terminé ...', + REMOTE_HANGUP: 'L\'utilisateur distant a raccroché', + REMOTE_OFFLINE: 'L\'utilisateur distant est hors-ligne. Appel terminé ...', + REMOTE_REJECT: 'L\'utilisateur distant a rejeté l\'appel. Appel terminé ...', + REMOTE_STEAM_ADDED: 'Flux vidéo de l\'utilisateur distant ajouté', + REMOTE_STEAM_REMOVED: 'Flux vidéo de l\'utilisateur distant enlevé', + REMOTE_TIMEOUT: 'L\'utilisateur distant n\'a pas répondu à temps. Appel terminé ...', + RENAME: 'Renommer', + RENAME_WORKFLOW_COPY: 'Nom de la copie', + REPLACE: 'Réinitialiser', + REQUESTS: 'Demandes', + REQUESTS_NUMBER: 'Demandes associées', + REQUIRED_FIELD:'Veuillez renseigner ce champ', + RESET: 'Remise à zero', + RESET_CAMERA: 'Repositionner la caméra', + RESULT:'Résultat', + RETAIN: 'Retenir', + RETRY: 'Re-essayer', + REVISION_DATE: 'Date de révision', + REVISION_NOTE: 'Note de révision', + REVISION_NOTE_PROMPT_CANCEL: 'Ignorer', + REVISION_NOTE_PROMPT_LABEL: 'Souhaitez-vous ajouter une note de révision ?', + REVISION_NOTE_PROMPT_OK: 'Ajouter la note de révision', + RIGHT_CLICK: 'clic droit', + ROLE_NAME: 'Nom du rôle', + ROLE: 'Rôle', + ROLES: 'Rôles', + ROOT_PART: 'Article de tête', + ROTATE: 'Rotation', + ROTATE_SCENE: 'Pivoter la scène', + RUN:'Lancer', + SAVED: 'Sauvegardé', + SAVE: 'Enregistrer', + SCALE: 'Echelle', + SCENE_VIEW: 'Scène', + SCROLL_DOWN: 'défilement bas', + SCROLL_UP: 'défilement haut', + SEARCH_OPTIONS: 'Options de recherche par numéro ou nom', + SEARCH_PART_BEGINNING: 'Article commencant par', + SEARCH_PART_CALLED: 'Rechercher l\'article', + SEARCH_PART_ENDING: 'Article finissant par', + SEARCH_PARTS: 'Recherche d\'articles', + SEARCH: 'Rechercher', + SEARCHING: 'Recherche en cours...', + SEARCHING_FOR_PARTS: "Recherche des articles en cours, veuillez ne pas fermer la fenêtre...", + SECONDS: 's', + SEE_IN_DOCUMENT_MANAGEMENT:'Voir le document dans la gestion des documents', + SEE_IN_PRODUCT_MANAGEMENT:'Voir l\'article dans la gestion des produits', + SELECT_FILE: 'Choisissez un fichier', + SELECT_FILES: 'Choisissez un ou plusieurs fichier(s)', + SEND: 'Envoyer', + SEQUENTIAL_ACTIVITY: 'En série', + SERIAL_NUMBER: 'Numéro de série', + SESSION_CONNECTING: 'Connexion de la session ...', + SESSION_OPENED: 'Session connectée', + SHARED_DOCUMENTS_TITLE: 'Publier le document', + SHARED_PARTS_TITLE: 'Publier l\'article', + SHARE: 'Partager', + SHORTCUTS_TITLE: 'Raccourcis clavier / souris', + SHOW_ALL: 'Tout voir', + SHOW_BY_PAGE: 'Voir par page', + SHOW_EDITED_MESHES: 'Surligner les objets modifiés', + SHOW_SUBSTITUTES:'Afficher les variantes', + SIGN_TASK: 'Signature', + SINGLE_FILE_RESTRICTION:'Seulement un fichier est autorisé.', + SPACE: 'espace', + STANDARD_PART_ALL: 'Tous', + STANDARD_PART: 'Article standard', + STANDARD_PART_FALSE: 'Non', + STANDARD_PART_TRUE: 'Oui', + START: 'Départ', + START_IMPORT: 'Lancer l\'import', + STATE_CHANGE: 'Changement d\'état', + STATUS: 'Statut', + STRUCTURE: 'Structure', + SUB_PARTS: 'Assemblage', + SUBSCRIBE_ITERATION_CHANGE: 'S\'abonner aux notifications de changement d\'itération', + SUBSCRIBE_STATE_CHANGE: 'S\'abonner aux notifications de changement d\'état', + SUM:'Somme', + TAGS_HELP: 'Liste des libéllés, séparés par une virgule', + TAGS: 'Libellés', + TAGS_TO_ADD: 'Libellés à ajouter', + TAKE_SCREENSHOT: 'Capture d\'écran', + TASK_APPROVED_ON: 'Tâche effectuée le', + TASK_CLOSED_ON: 'Tâche fermée le', + TASK_NAME_PLACEHOLDER: 'Nouvelle tâche', + TASK_REJECT_ON: 'Tâche rejetée le', + TASK_WORKER: 'Effectué par', + TASKS_IN_PROGRESS: 'En cours', + TASKS: 'Tâches', + TEMPLATE: 'Modèle', + TEMPLATES: 'Modèles', + TEXT: 'Texte', + LONG_TEXT: 'Texte long', + TITLE_HELP: 'Titre du document', + TITLE: 'Titre', + TO:'Au', + TOGGLE_ALL_LAYERS: 'Basculer la visibilité des calques', + TRACKBALL_MODE: 'Mode trackball', + TRANSFORM_CONTROLS: 'Transformation', + TRUE: 'Oui', + TYPE: 'Type', + UDF_DEF:'Fonction', + UNCHECK_ALL: 'Tout désélectionner', + UNDO_CHECKOUT: 'Annuler la réservation', + UNDO_CHECKOUT_QUESTION: 'Annuler la réservation ?', + UNFOLD_TASK: 'Editer la tâche', + UNREACHABLE: 'Impossible d\'envoyer l\'invitation, l\'utilisateur distant est hors-ligne', + UNSUBSCRIBE_ITERATION_CHANGE: 'Se désabonner des notifications de changement d\'itération', + UNSUBSCRIBE_STATE_CHANGE: 'Se désabonner des notifications de changement d\'état', + UNIT: 'unité', + UPDATE_ACL_ERROR:'Une erreur s\'est produite pendant la mise à jour des droits', + URL: 'URL', + USE_ACL: 'Utiliser les ACL (droits) ?', + USER_DEFINED_FUNCTION:'Fonction utilisateur', + USER_MEDIA_FAILED: 'Imposible d\'accéder à votre entrée vidéo. Votre navigateur supporte-il le WebRTC ?', + USERS_CONNECTED: 'utilisateurs connectés', + USERS: 'Utilisateurs', + VALIDATION_FAILED_FOR: 'Validation échouée pour ', + _VALIDATION_PATTERN_DATE: '\\d{4}-\\d{2}-\\d{2}', + _VALIDATION_PATTERN_NUMBER: '^\\-?\\d+(\\.\\d+)?(([eE]([-+])?)?\\d+)?$', + _VALIDATION_PATTERN_TIME: '\\d{2}:\\d{2}', + VALUE: 'Valeur', + VALUES: 'Valeurs', + VERIFICATION_NOTE: 'Note de vérification', + VERIFIED: 'Vérifié', + VERSION: 'Version', + VERSIONS: 'Versions', + VIDEO_INVITATION_SENT: 'L\'invitation a été envoyée à l\'utilisateur distant, en attente de l\'acceptation ...', + VIDEO_INVITE_ACCEPT: 'Accepter', + VIDEO_INVITE_NOTIFICATION_TITLE: 'Invitation Vidéo', + VIDEO_INVITE_REJECT: 'Refuser', + VIDEO_INVITE_TEXT: 'vous invite à démarrer une session vidéo.', + VIEW_MODE: 'Mode de vue', + VISUALIZE_ASSEMBLY: 'Visualiser l\'assemblage', + VISUALIZE_PRODUCT: 'Visualiser le produit', + WAITING_REMOTE_VIDEO: 'En attente de la vidéo de l\'utilisateur distant.', + WAITING_USER_MEDIA: 'En attente de l\'entrée vidéo ... Veuillez accepter la demande de votre navigateur.', + USED_BY: 'Cas d\'emploi', + WARNINGS:'Avertissements : ', + WITHDRAW_INVITATION: 'Retirer l\'invitation', + WITH_LINKS: 'Avec les liens', + WITHOUT_LINKS: 'Sans les liens', + WORKFLOW_HISTORY: 'Historique', + WORKFLOW_LEGEND_COMPLETE: 'Complet', + WORKFLOW_LEGEND_INCOMPLETE: 'Incomplet', + WORKFLOW_LEGEND_INPROGRESS: 'En cours', + WORKFLOW_LEGEND_REJECTED: 'Rejeté', + WORKFLOW_NAME_PLACEHOLDER: 'Nom du processus métier', + WORKFLOW: 'Processus', + WORKFLOW_COPY: 'Copie du processus', + WORKFLOWS: 'Processus', + WORKSPACE: 'Espace de travail', + WORKSPACES_ADMINISTRATION: 'Administration des espaces de travail', + WORKSPACE_MANAGEMENT: 'Administration de l\'espace', + WORKSPACES: 'Workspaces', + YOU_CANNOT_CREATE_LINK_WITHOUT_TYPE: 'Vous ne pouvez pas créer un lien sans type', + YOU_HAVE_BEEN_KICKED:'Vous avez été exclu de la salle collaborative', + LANGUAGES:{ + fr:'Français', + en:'Anglais' + } +}); diff --git a/docdoku-web-front/app/js/localization/nls/fr/document-management.js b/docdoku-web-front/app/js/localization/nls/fr/document-management.js new file mode 100644 index 0000000000..28977c2b1b --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/fr/document-management.js @@ -0,0 +1,4 @@ +/*global define*/ +define({ + CONFIRM_DELETE_DOCUMENTS_COLLECTIONS:'Voulez-vous supprimer les collections de documents sélectionnées ?' +}); diff --git a/docdoku-web-front/app/js/localization/nls/fr/download.js b/docdoku-web-front/app/js/localization/nls/fr/download.js new file mode 100644 index 0000000000..7d5f4d0120 --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/fr/download.js @@ -0,0 +1,9 @@ +/*global define*/ +define({ + DOWNLOAD:'Téléchargement', + DPLM_CLIENT:'DPLM Client', + DPLM_CLIENT_INSTALL_MESSAGE:'DPLM Client nécessite que vous ayez préalablement installé Java version 8 ou supérieur. Pour effectuer cette installation ou la vérifier visitez : http://www.java.com', + CHOOSE_PLATFORM:'Choisissez votre plateforme', + DPLM_CLIENT_ABOUT_QUESTION:'Qu\'est ce que DPLM Client ?', + DPLM_CLIENT_ABOUT_TEXT:'DPLM Client est une application cliente multiplateforme permettant d\'échanger des fichiers (upload / download) entre votre poste de travail et le serveur DocDokuPLM. Agnostique aux formats de fichiers, il permet une intégration transparente avec tous les outils de création du march2.' +}); diff --git a/docdoku-web-front/app/js/localization/nls/fr/index.js b/docdoku-web-front/app/js/localization/nls/fr/index.js new file mode 100644 index 0000000000..055b3b65be --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/fr/index.js @@ -0,0 +1,54 @@ +/*global define*/ +define({ + CONNECTION:'Connexion', + USER:'Utilisateur', + RECOVERY:'Mot de passe oublié ?', + RECOVER:'Changez votre mot de passe', + ENTER_NEW_PASSWORD:'Entrez votre nouveau mot de passe', + RECOVER_OK:'Le mot de passe a bien été changé, vous pouvez maintenant vous connecter', + MANAGE_DOCUMENTS:'Gérez vos documents', + DISCONNECTED:'Vous avez bien été déconnecté', + FAILED_LOGIN:'La connexion au PLM a échoué', + RECOVERY_REQUEST_SENT:'Un email contenant les instructions nécessaires au changement de votre mot de passe vous a été envoyé.', + USER_NOT_FOUND:'Utilisateur non trouvé', + LANGUAGE:'Langue', + TIMEZONE:'Fuseau horaire', + MANAGE_DOCUMENTS_STRINGS:[ + 'Gérez les changements avec le contrôle de version', + 'Ajoutez des libellés et des attributs personnalisés', + 'Créez des modèles de documents', + 'Définissez des liens entre les documents', + 'Organisez, naviguez, recherchez des documents' + ], + + MANAGE_PRODUCTS:'Gérez vos produits', + MANAGE_PRODUCTS_STRINGS:[ + 'Créez et gérez vos structures produit', + 'Définissez vos nomenclatures', + 'Visualisez les modélisations 3D de vos produits' + ], + + TRACK_CHANGES:'Tracez et organisez les changements', + TRACK_CHANGES_STRINGS:[ + 'Définissez vos processus métier', + 'Gérez les utilisateurs, leurs tâches', + 'Suivez la progression des travaux', + 'Soyez notifié des changements' + ], + + SOCIAL_FEATURES:'Fonctionnalités sociales', + SOCIAL_FEATURES_STRINGS:[ + 'Comunication temps réel', + 'Chat, conversation audio et vidéo', + 'Créez vos espaces de travail, vos groupes d\'utilisateurs', + 'Partagez la visualisation des articles' + ], + ENTER_ID:'Entrez votre identifiant', + USER_ID:'Identifiant utilisateur', + FIRSTNAME_NAME:'Prénom, nom', + REGISTRATION:'Enregistrement', + CREATE_ID:'Créez votre identifiant DocDokuPLM', + TERMS_OF_SERVICES:'Contrat de Service', + TERMS_OF_SERVICES_TEXT:'Vous vous enregistrez pour obtenir un compte gratuit, pour évaluer le logiciel. Il n\'y a par conséquent aucune garantie que votre compte ne soit pas désactivé un jour. Si vous souhaitez utiliser DocDokuPLM en production, contactez nous.' + +}); diff --git a/docdoku-web-front/app/js/localization/nls/fr/product-management.js b/docdoku-web-front/app/js/localization/nls/fr/product-management.js new file mode 100644 index 0000000000..0fc8a5972d --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/fr/product-management.js @@ -0,0 +1,19 @@ +/*global define*/ +define({ + CONFIRM_DELETE_BASELINE:'Voulez-vous supprimer les baselines sélectionnées ?', + CONFIRM_DELETE_PART:'Voulez-vous supprimer les articles sélectionnés ?', + CONFIRM_DELETE_PART_TEMPLATE:'Voulez-vous supprimer les modèles d\'articles sélectionnés ?', + CONFIRM_DELETE_PRODUCT:'Voulez-vous supprimer les produits sélectionnés ?', + CONFIRM_DELETE_PRODUCT_INSTANCE:'Voulez-vous supprimer les exemplaires de produits sélectionnés ?', + CONFIRM_DELETE_CONFIGURATION:'Voulez-vous supprimer les configurations sélectionnées ?', + CREATE_BASELINE_LATEST: 'Créer la baseline avec les derniers articles', + CREATE_BASELINE_RELEASED: 'Créer la baseline avec les derniers articles finalisés', + CREATE_PRODUCT_BEFORE_BASELINE:'Vous devez avoir créé au moins un produit pour créer des baselines', + CREATE_PART_BEFORE_PRODUCT:'Vous devez avoir créé au moins un article pour créer des produits', + CREATE_BASELINE_BEFORE_PRODUCT_INSTANCE:'Vous devez avoir créé au moins une baseline pour créer un exemplaire de produit', + CREATE_PRODUCT_BEFORE_PRODUCT_INSTANCE:'Vous devez avoir créé au moins un produit pour créer un exemplaire de produit', + PRODUCT_CREATED:'Le produit a été créé', + PRODUCT_INSTANCE_CREATED:'L\'exemplaire du produit a été créé', + BASELINE_CREATED:'La baseline a été créée', + CONFIGURATION_CREATED:'La configuration a été créée' +}); diff --git a/docdoku-web-front/app/js/localization/nls/fr/product-structure.js b/docdoku-web-front/app/js/localization/nls/fr/product-structure.js new file mode 100644 index 0000000000..59e5a7cea8 --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/fr/product-structure.js @@ -0,0 +1,7 @@ +/*global define*/ +define({ + CASCADE: 'Cascade', + CASCADE_RESULT: 'Résultat', + DONE: 'effectué(s)', + SELECTED: 'Sélection' +}); diff --git a/docdoku-web-front/app/js/localization/nls/fr/workspace-management.js b/docdoku-web-front/app/js/localization/nls/fr/workspace-management.js new file mode 100644 index 0000000000..ec3cfc0cbc --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/fr/workspace-management.js @@ -0,0 +1,46 @@ +/*global define*/ +define({ + ADMIN:'Administrateur', + ADD_USER:'Ajouter un utilisateur', + CHART_AXIS_DAYS_NUMBER:'Jours', + CHART_AXIS_DOCUMENTS_NUMBER:'Documents', + CHART_AXIS_PARTS_NUMBER:'Articles', + CREATE_GROUP:'Créer un groupe', + CREATE_WORKSPACE:'Créer un espace de travail', + CREATE_WORKSPACE_SUBTITLE:'Créez votre espace de travail', + CREATE_WORKSPACE_SIDE_TEXT:'Seul le créateur de l\'espace de travail dispose des droits administrateur. Ces droits l\'autorisent à éditer les propriétés de l\'espace de travail et à y ajouter ou supprimer des utilisateurs.', + CHECKED_OUT_DOCUMENTS:'Documents réservés', + CHECKED_OUT_PARTS:'Articles réservés', + DASHBOARD:'Tableau de bord', + DELETE_WORKSPACE_TEXT:'Cela ne peut pas être annulé, toutes ses données seront effacées.', + DELETE_WORKSPACE_QUESTION:'Êtes-vous sûr de vouloir supprimer cet espace de travail ?', + DISABLE_USER:'Désactiver', + DISK_USAGE:'Usage disque', + DISK_USAGE_TOTAL:'Usage disque total', + EDIT_WORKSPACE_SUBTITLE:'Editez votre espace de travail', + EMAIL:'Email', + ENTITIES:'Entités', + ENABLE_USER:'Activer', + FOLDER_LOCKED:'Geler la structure des répertoires (seul l\'administrateur peut modifier la structure dossier)', + LANG:'Langage', + LOGIN:'Identifiant', + MOVE_TO_GROUP:'Ajouter au groupe', + NO_ADMINISTRATED_WORKSPACES:'Aucun espace de travail administré', + NO_USER_IN_GROUP:'Aucun utilisateur dans ce groupe', + NO_USER_TO_MANAGE:'Aucun utilisateur à gérer', + READONLY:'Lecture seule', + REMOVE_FROM_GROUP:'Enlever du groupe', + ROOT_ADMIN:'Super utilisateur', + ROOT_ADMIN_MESSAGE:'Informations et statistiques sur tous les espaces de travail du serveur', + ADMIN_DASHBOARD:'Tableau de bord super utilisateur', + TOTAL:'Total', + ACTIVE_USERS:'Utilisateurs actifs', + INACTIVE_USERS:'Utilisateurs désactivés', + ACTIVE_GROUPS:'Groupes actifs', + INACTIVE_GROUPS:'Groupes désactivés', + WORKSPACES_ADMINISTRATION_TEXT:'Seul le créateur de l\'espace de travail dispose des droits administrateur. Ces droits l\'autorisent à éditer les propriétés de l\'espace de travail et à y ajouter ou supprimer des utilisateurs.', + WORKSPACE_DELETING_TITLE:'La suppression de l\'espace de travail est en cours', + WORKSPACE_DELETING_TEXT:'Un email de rapport sera envoyé à l\'administrateur à la fin de l\'opération.', + WORKSPACE_INDEXING:'L\'indexations des données de l\'espace de travail est en cours. Un email de rapport sera envoyé à l\'administrateur à la fin de l\'opération.', + INDEX_WORKSPACE:'Indexer les données', +}); diff --git a/docdoku-web-front/app/js/localization/nls/index.js b/docdoku-web-front/app/js/localization/nls/index.js new file mode 100644 index 0000000000..6297d4d7b2 --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/index.js @@ -0,0 +1,58 @@ +/*global define*/ +define({ + root: { + CONNECTION:'Connection', + USER:'User', + RECOVERY:'Forgot your password?', + RECOVER:'Recover your password', + ENTER_NEW_PASSWORD:'Enter your new password', + RECOVER_OK:'Your password has been updated, you can now sign in', + MANAGE_DOCUMENTS:'Manage your documents', + DISCONNECTED:'You have been disconnected', + FAILED_LOGIN:'DocdokuPLM failed to connect', + RECOVERY_REQUEST_SENT:'An email with the instructions necessary to change your password has been sent to you', + USER_NOT_FOUND:'User not found', + LANGUAGE:'Language', + TIMEZONE:'Time zone', + MANAGE_DOCUMENTS_STRINGS:[ + 'Manage changes with version control' , + 'Add tags and custom attributes' , + 'Create document templates' , + 'Create links between documents' , + 'Organize, browse, search documents' + ], + + MANAGE_PRODUCTS:'Manage your products', + MANAGE_PRODUCTS_STRINGS:[ + 'Create and manage product structures' , + 'Manage bill of materials' , + 'Visualize 3D models of your products' + ], + + TRACK_CHANGES:'Track and organize changes', + TRACK_CHANGES_STRINGS:[ + 'Define workflow processes' , + 'Manage users and tasks' , + 'Follow the work progress' , + 'Be notified on changes' + ], + + SOCIAL_FEATURES:'Social knowledge management', + SOCIAL_FEATURES_STRINGS:[ + 'Do realtime comunication' , + 'Chat, audio and video conference' , + 'Create workspaces and user groups' , + 'Share part visualizations' + ], + ENTER_ID:'Enter your ID', + USER_ID:'User ID', + FIRSTNAME_NAME:'First name, name', + REGISTRATION:'Registration', + CREATE_ID:'Create your DocDokuPLM ID', + TERMS_OF_SERVICES:'Terms of Service', + TERMS_OF_SERVICES_TEXT:'You\'re registering for a free, evaluation purpose account. There\'s hence no guarantee that your account could not be deactivated someday. If you wish to use DocDokuPLM for production, contact us.' + + }, + 'fr': true, + 'es': true +}); diff --git a/docdoku-web-front/app/js/localization/nls/product-management.js b/docdoku-web-front/app/js/localization/nls/product-management.js new file mode 100644 index 0000000000..9b242525dc --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/product-management.js @@ -0,0 +1,23 @@ +/*global define*/ +define({ + root: { + CONFIRM_DELETE_BASELINE:'Do you want to delete selected baselines?', + CONFIRM_DELETE_PART:'Do you want to delete selected parts?', + CONFIRM_DELETE_PART_TEMPLATE:'Do you want to delete selected parts templates?', + CONFIRM_DELETE_PRODUCT:'Do you want to delete selected products?', + CONFIRM_DELETE_CONFIGURATION:'Do you want to delete selected configurations?', + CONFIRM_DELETE_PRODUCT_INSTANCE:'Do you want to delete selected products instances?', + CREATE_BASELINE_LATEST: 'Create baseline with last part', + CREATE_BASELINE_RELEASED: 'Create baseline with last released part', + CREATE_PRODUCT_BEFORE_BASELINE:'You must have created at least one product to create a baseline', + CREATE_PART_BEFORE_PRODUCT:'You must have created at least one part to create a product', + CREATE_BASELINE_BEFORE_PRODUCT_INSTANCE:'You must have created at least one baseline to create a product instance', + CREATE_PRODUCT_BEFORE_PRODUCT_INSTANCE:'You must have created at least one product to create a product instance', + PRODUCT_CREATED:'Product created', + PRODUCT_INSTANCE_CREATED:'Product instance created', + BASELINE_CREATED:'Baseline created', + CONFIGURATION_CREATED:'Configuration created' + }, + fr: true, + es: true +}); diff --git a/docdoku-web-front/app/js/localization/nls/product-structure.js b/docdoku-web-front/app/js/localization/nls/product-structure.js new file mode 100644 index 0000000000..f2bc597473 --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/product-structure.js @@ -0,0 +1,11 @@ +/*global define*/ +define({ + root: { + CASCADE: 'Cascade', + CASCADE_RESULT: 'Result', + DONE: 'Done', + SELECTED: 'Selected' + }, + fr: true, + es: true +}); diff --git a/docdoku-web-front/app/js/localization/nls/workspace-management.js b/docdoku-web-front/app/js/localization/nls/workspace-management.js new file mode 100644 index 0000000000..15378af5f0 --- /dev/null +++ b/docdoku-web-front/app/js/localization/nls/workspace-management.js @@ -0,0 +1,43 @@ +/*global define*/ +define({ + root: { + ADMIN:'Administrator', + ADD_USER:'Add user', + CREATE_GROUP:'Create group', + CREATE_WORKSPACE:'Create workspace', + CREATE_WORKSPACE_SUBTITLE:'Create your workspace', + CREATE_WORKSPACE_SIDE_TEXT:'Only the creator of the workspace has administrator rights. These rights allow to edit the workspace properties and add and remove users.', + CHECKED_OUT_DOCUMENTS:'Checked out documents', + CHECKED_OUT_PARTS:'Checked out parts', + DASHBOARD:'Dashboard', + DELETE_WORKSPACE_TEXT:'This cannot be undone, all its data will be deleted.', + DELETE_WORKSPACE_QUESTION:'Are you sure to want to delete this workspace ?', + DISABLE_USER:'Disable', + DISK_USAGE:'Disk usage', + DISK_USAGE_TOTAL:'Total disk usage', + EMAIL:'Email', + ENTITIES:'Entities', + EDIT_WORKSPACE_SUBTITLE:'Edit your workspace', + ENABLE_USER:'Enable', + FOLDER_LOCKED:'Folder structure frozen', + LANG:'Language', + LOGIN:'Login', + MOVE_TO_GROUP:'Add to group', + NO_ADMINISTRATED_WORKSPACES:'No administrated workspace', + NO_USER_IN_GROUP:'No user in this group', + NO_USER_TO_MANAGE:'No user to manage', + READONLY:'Read only', + REMOVE_FROM_GROUP:'Remove from group', + ROOT_ADMIN:'Super administrator', + ROOT_ADMIN_MESSAGE:'Infos and stats on all workspace content and users of the server', + ADMIN_DASHBOARD:'Super administrator dashboard', + TOTAL:'Total', + WORKSPACES_ADMINISTRATION_TEXT:'Only the creator of the workspace has administrator rights. These rights allow to edit the workspace properties and add and remove users.', + WORKSPACE_DELETING_TITLE:'Workspace deletion on progress.', + WORKSPACE_DELETING_TEXT:'A report will be sent by mail to workspace administrator once finished.', + WORKSPACE_INDEXING:'Workspace indexation on progress. A report will be sent by mail to workspace administrator once finished.', + INDEX_WORKSPACE:'Index data' + }, + 'fr': true, + 'es': true +}); diff --git a/docdoku-web-front/app/js/modules/all.js b/docdoku-web-front/app/js/modules/all.js new file mode 100644 index 0000000000..848cc7f2e0 --- /dev/null +++ b/docdoku-web-front/app/js/modules/all.js @@ -0,0 +1,53 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/websocket/channel', + 'common-objects/websocket/channelListener', + 'common-objects/websocket/channelMessagesType', + 'modules/coworkers-access-module/app', + 'modules/user-popover-module/app', + 'modules/chat-module/app', + 'modules/webrtc-module/app' +], function (Backbone, Channel, ChannelListener, ChannelMessagesType, CoWorkersAccessModuleView, UserPopoverModule, chatListener, webRTCInvitationListener) { + 'use strict'; + + if(App.config.admin){ + return; + } + + var pageUnload = function () { + App.mainChannel.ws.onclose = function () { + }; + App.mainChannel.ws.close(); + }; + + var userStatusListener = new ChannelListener({ + + isApplicable: function (messageType) { + return messageType === ChannelMessagesType.USER_STATUS; + }, + + onMessage: function (message) { + Backbone.Events.trigger('UserStatusRequestDone', message); + }, + + onStatusChanged: function () { + + } + + }); + + window.addEventListener('beforeunload', pageUnload, false); + + App.mainChannel = new Channel(); + App.mainChannel.addChannelListener(userStatusListener); + App.mainChannel.addChannelListener(webRTCInvitationListener); + App.mainChannel.addChannelListener(chatListener); + var wsProtocol = window.location.protocol === 'https:' ? 'wss://':'ws://'; + App.mainChannel.init(wsProtocol + window.location.host + App.config.contextPath + '/ws'); + + return { + CoWorkersAccessModuleView: CoWorkersAccessModuleView + }; + +}); diff --git a/docdoku-web-front/app/js/modules/chat-module/app.js b/docdoku-web-front/app/js/modules/chat-module/app.js new file mode 100644 index 0000000000..3a755840b9 --- /dev/null +++ b/docdoku-web-front/app/js/modules/chat-module/app.js @@ -0,0 +1,61 @@ +/*global define,App*/ +define([ + 'backbone', + 'modules/chat-module/models/chat_message_model', + 'modules/chat-module/views/chat_module_view', + 'common-objects/websocket/channelListener', + 'common-objects/websocket/channelMessagesType' +], function (Backbone, ChatMessage, ChatModuleView, ChannelListener, ChannelMessagesType) { + 'use strict'; + var cmv = new ChatModuleView().render(); + + // triggered on remote user message sent + Backbone.Events.on('NewChatMessage', cmv.onNewChatMessage); + + // triggered on local user action + Backbone.Events.on('NewChatSession', cmv.onNewChatSession); + + // triggered on websocket status changed + Backbone.Events.on('ChatStatusChanged', cmv.onChatStatusChanged); + + // triggered on chat session requires focus + Backbone.Events.on('ChatSessionFocusRequired', cmv.onChatSessionFocusRequired); + + // triggered on local user action + Backbone.Events.on('NewWebRTCInvitation', cmv.onNewWebRTCInvitation); + + Backbone.Events.on('RemoveWebRTCInvitation', cmv.onRemoveWebRTCInvitation); + + var chatListener = new ChannelListener({ + + // what kind of messages are we listening to ? + isApplicable: function (messageType) { + return messageType === ChannelMessagesType.CHAT_MESSAGE || + messageType === ChannelMessagesType.CHAT_MESSAGE_ACK; + }, + + // onMessage handler + onMessage: function (message) { + + if (App.config.login !== message.sender && message.message.match(/^\/invite /)) { + var url = window.location.origin + App.config.contextPath + '/product-structure/' + message.message.substring(8); + message.message = '' + App.config.i18n.COLLABORATIVE_INVITE + ' : ' + url; + } else if (App.config.login !== message.sender && message.message.match(/^\/withdrawInvitation/)) { + message.message = App.config.i18n.COLLABORATIVE_WITHDRAW_INVITATION; + } + + Backbone.Events.trigger('NewChatMessage', message); + }, + + // onStatusChangedHandler + onStatusChanged: function (status) { + Backbone.Events.trigger('ChatStatusChanged', status); + } + + }); + + return chatListener; + +}); + + diff --git a/docdoku-web-front/app/js/modules/chat-module/collections/chat_message_collection.js b/docdoku-web-front/app/js/modules/chat-module/collections/chat_message_collection.js new file mode 100644 index 0000000000..5c989b0191 --- /dev/null +++ b/docdoku-web-front/app/js/modules/chat-module/collections/chat_message_collection.js @@ -0,0 +1,36 @@ +/*global define*/ +define( + ['backbone', 'modules/chat-module/models/chat_message_model'], + function (Backbone, ChatMessage) { + + 'use strict'; + + var ChatMessageCollection = Backbone.Collection.extend({ + + model: ChatMessage, + + getMessage: function () { + return this.get('message'); + }, + + getSender: function () { + return this.get('sender'); + }, + + getContext: function () { + return this.get('context'); + }, + + getRemoteUser: function () { + return this.get('remoteUser'); + }, + + getDate: function () { + return this.get('date'); + } + + }); + + return ChatMessageCollection; + + }); diff --git a/docdoku-web-front/app/js/modules/chat-module/models/chat_message_model.js b/docdoku-web-front/app/js/modules/chat-module/models/chat_message_model.js new file mode 100644 index 0000000000..443257d79f --- /dev/null +++ b/docdoku-web-front/app/js/modules/chat-module/models/chat_message_model.js @@ -0,0 +1,13 @@ +/*global define*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var ChatMessage = Backbone.Model.extend({ + + initialize: function () { + } + + }); + + return ChatMessage; + +}); diff --git a/docdoku-web-front/app/js/modules/chat-module/templates/chat_message_view.html b/docdoku-web-front/app/js/modules/chat-module/templates/chat_message_view.html new file mode 100644 index 0000000000..9f7b93379b --- /dev/null +++ b/docdoku-web-front/app/js/modules/chat-module/templates/chat_message_view.html @@ -0,0 +1,17 @@ +
                              + {{ chatSession.remoteUser }} | {{ chatSession.context }} + + +
                              +
                                +
                                +
                                +
                                + + +
                                +
                                diff --git a/docdoku-web-front/app/js/modules/chat-module/views/chat_message_view.js b/docdoku-web-front/app/js/modules/chat-module/views/chat_message_view.js new file mode 100644 index 0000000000..47fb3ec9d9 --- /dev/null +++ b/docdoku-web-front/app/js/modules/chat-module/views/chat_message_view.js @@ -0,0 +1,21 @@ +/*global define,_*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var ChatMessageView = Backbone.View.extend({ + + tagName: 'li', + + template: _.template( + '<%= chatMessage.sender %> : <%= chatMessage.message.replaceUrl() %>' + ), + + render: function () { + this.$el.html(this.template({chatMessage: this.model.attributes})); + return this; + } + + }); + + return ChatMessageView; + +}); diff --git a/docdoku-web-front/app/js/modules/chat-module/views/chat_module_view.js b/docdoku-web-front/app/js/modules/chat-module/views/chat_module_view.js new file mode 100644 index 0000000000..2c504fa961 --- /dev/null +++ b/docdoku-web-front/app/js/modules/chat-module/views/chat_module_view.js @@ -0,0 +1,107 @@ +/*global _,define*/ +define(['backbone', 'modules/chat-module/views/chat_session_view'], function (Backbone, ChatMessageSessionView) { + 'use strict'; + var ChatModuleView = Backbone.View.extend({ + + el: '#chat_module', + + initialize: function () { + _.bindAll(this); + this._chatSessionViews = []; + }, + + render: function () { + // nothing to do + return this; + }, + + onNewChatMessage: function (message) { + var view = this.getChatSessionView(message); + view.onNewChatMessage(message); + // set above others + Backbone.Events.trigger('ChatSessionFocusRequired', view); + }, + + onNewChatSession: function (chatSessionArgs) { + var csv = this.getChatSessionView(chatSessionArgs); + // set above others + Backbone.Events.trigger('ChatSessionFocusRequired', csv); + }, + + onChatStatusChanged: function () { + }, + + getChatSessionView: function (chatSessionArgs) { + + // find or create the chat session view + var sessionChatView = this.findOrCreateChatSessionViewForRemoteUser(chatSessionArgs); + + // set or update context and user + sessionChatView.setRemoteUser(chatSessionArgs.remoteUser).setContext(chatSessionArgs.context); + + // render if not already done + if (!sessionChatView._rendered) { + this.$el.append(sessionChatView.render().$el); + } else { + sessionChatView.refreshTitle().$el.show(); + } + // then return the session view + return sessionChatView; + + }, + + findOrCreateChatSessionViewForRemoteUser: function (chatSessionArgs) { + + // find the view of given remoteUser + var view = _(this._chatSessionViews).select(function (view) { + return view.remoteUser === chatSessionArgs.remoteUser; + })[0]; + + // create the view if it doesn't exist + if (!view) { + view = this.createNewChatSessionView(); + } + + // return the view + return view; + + }, + + createNewChatSessionView: function () { + + // create new a instance of ChatMessageSessionView + var view = new ChatMessageSessionView(); + + // push the view in memory + this._chatSessionViews.push(view); + + // return the view + return view; + }, + + onChatSessionFocusRequired: function (view) { + + _.each(this._chatSessionViews, function (csv) { + csv.removeFromTop(); + }); + view.setOnTop(); + }, + + onNewWebRTCInvitation: function (webRTCInvitation) { + var csv = this.getChatSessionView(webRTCInvitation.message); + csv.onWebRTCInvitation(webRTCInvitation); + }, + + onRemoveWebRTCInvitation: function (webRTCInvitation) { + var csv = this.getChatSessionView(webRTCInvitation.message); + if (csv && csv.webRTCInvitation !== null) { + clearTimeout(csv.webRTCInvitation.invitationTimeout); + csv.hideWebRTCInvitation(); + } + } + + }); + + return ChatModuleView; + +}); diff --git a/docdoku-web-front/app/js/modules/chat-module/views/chat_session_view.js b/docdoku-web-front/app/js/modules/chat-module/views/chat_session_view.js new file mode 100644 index 0000000000..6f68e4107b --- /dev/null +++ b/docdoku-web-front/app/js/modules/chat-module/views/chat_session_view.js @@ -0,0 +1,237 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!modules/chat-module/templates/chat_message_view.html', + 'modules/chat-module/collections/chat_message_collection', + 'modules/chat-module/views/chat_message_view', + 'common-objects/websocket/channelMessagesType' +], function (Backbone, Mustache, template, ChatMessageCollection, ChatMessageView, ChannelMessagesType) { + 'use strict'; + var ChatMessageSessionView = Backbone.View.extend({ + + events: { + 'submit form': 'onSubmitForm', + 'click .fa-times': 'onClose', + 'click .fa-video-camera': 'onVideoButtonClick', + 'click .chat_session_title': 'onGlobalClick', + 'click .chat_session_messages': 'onGlobalClick', + 'click input[name=chat_message_session_input]': 'onGlobalClick', + 'click a.accept-webrtc': 'onWebRTCAccept', + 'click a.reject-webrtc': 'onWebRTCReject' + }, + + className:'chat_session', + + initialize: function () { + + this.isOnTop = false; + + this._messagesViews = []; + + this.collection = new ChatMessageCollection(); + + this.collection.bind('add', this.add, this); + + _(this).bindAll('onSubmitForm', 'onClose', 'onGlobalClick', 'onVideoButtonClick'); + + }, + + add: function (chatMessage) { + + var that = this; + + var cmv = new ChatMessageView({ + model: chatMessage + }); + + this._messagesViews.push(cmv); + + if (this._rendered) { + this.$el.show(); + + // append new message + var $ul = this.$('ul.chat_session_messages'); + $ul.append(cmv.render().el); + $ul[0].scrollTop = $ul[0].scrollHeight; + + // stick to bottom if needed + var thatElHeight = that.$el.height(); + var offsetTop = that.$el.offset().top; + var windowHeight = window.innerHeight; + + if (windowHeight - thatElHeight - offsetTop < 15) { + this.$el.css({'bottom': '0', 'top': 'auto'}); + } + } + + }, + + refreshTitle: function () { + this.$('.chat_session_title').html(' ' + this.remoteUser + ' | ' + this.context); + return this; + }, + + onNewChatMessage: function (message) { + if (message.error) { + this.onError(message); + } else { + this.collection.push(message); + + if (message.sender !== App.config.login) { + Backbone.Events.trigger('NotificationSound'); + } + + } + return this; + }, + + setRemoteUser: function (remoteUser) { + this.remoteUser = remoteUser; + return this; + }, + + setContext: function (context) { + this.context = context; + return this; + }, + + render: function () { + + var that = this; + + this.$el.html(Mustache.render(template, { + chatSession: { + remoteUser: this.remoteUser, + context: this.context + }, + i18n: App.config.i18n + })); + + this._rendered = true; + this.delegateEvents(); + + this.$el.draggable({ + handle: 'div.chat_session_header', + containment: 'body', + position: 'absolute', + addClasses: false, + start: function () { + that.$el.css('bottom', 'auto'); + } + }); + + // override jquery style, better behavior + this.$el.css('position', 'absolute'); + + this.$WebRTCInvitation = this.$('.chat_webrtc_invite'); + + return this; + }, + + onSubmitForm: function (event) { + + if (!App.mainChannel.isReady()) { + this.$('ul.chat_session_messages').append('
                              • ' + App.config.i18n.ERROR + ' : ' + App.config.i18n.CHANNEL_NOT_READY_ERROR + '
                              • '); + event.stopPropagation(); + event.preventDefault(); + return false; + } + + var textInput = event.target.children[0]; + + if (!textInput.value.length) { + return false; + } + + // build the message + var message = { + type: ChannelMessagesType.CHAT_MESSAGE, + remoteUser: this.remoteUser, + message: textInput.value, + context: this.context, + sender: App.config.login + }; + + // send it to remote + App.mainChannel.sendJSON(message); + + // trigger message render on local + //Backbone.Events.trigger('NewChatMessage', message); + + textInput.value = ''; + + event.stopPropagation(); + event.preventDefault(); + return false; + + }, + + onClose: function () { + this.$el.hide(); + }, + + onError: function (message) { + if (this._rendered) { + this.$('ul.chat_session_messages').append('
                              • ' + App.config.i18n.ERROR + ' : ' + App.config.i18n[message.error] + '
                              • '); + this.$el.show(); + } + }, + + focusInput: function () { + this.$('input[name=chat_message_session_input]').focus(); + return this; + }, + + onGlobalClick: function () { + if (!this.isOnTop) { + Backbone.Events.trigger('ChatSessionFocusRequired', this); + } + }, + + onVideoButtonClick: function () { + Backbone.Events.trigger('NewOutgoingCall', {remoteUser: this.remoteUser, context: this.context}); + }, + + setOnTop: function () { + this.$el.addClass('chat_session_on_top'); + this.isOnTop = true; + this.focusInput(); + return this; + }, + + removeFromTop: function () { + this.$el.removeClass('chat_session_on_top'); + this.isOnTop = false; + return this; + }, + + onWebRTCInvitation: function (webRTCInvitation) { + this.webRTCInvitation = webRTCInvitation; + this.$WebRTCInvitation.show(); + }, + + onWebRTCAccept: function () { + if (this.webRTCInvitation) { + this.webRTCInvitation.accept(); + this.webRTCInvitation = null; + this.hideWebRTCInvitation(); + } + }, + + onWebRTCReject: function () { + if (this.webRTCInvitation) { + this.webRTCInvitation.reject(); + this.webRTCInvitation = null; + this.hideWebRTCInvitation(); + } + }, + + hideWebRTCInvitation: function () { + this.$WebRTCInvitation.hide(); + } + + }); + + return ChatMessageSessionView; +}); diff --git a/docdoku-web-front/app/js/modules/coworkers-access-module/app.js b/docdoku-web-front/app/js/modules/coworkers-access-module/app.js new file mode 100644 index 0000000000..2384beccf4 --- /dev/null +++ b/docdoku-web-front/app/js/modules/coworkers-access-module/app.js @@ -0,0 +1,19 @@ +/*global define,App*/ +define([ + 'backbone', + 'modules/coworkers-access-module/views/coworkers_access_module_view', + 'common-objects/websocket/channelMessagesType' +], +function (Backbone, CoWorkersAccessModuleView, ChannelMessagesType) { + 'use strict'; + function onUserStatusRequest(remoteUser) { + App.mainChannel.sendJSON({ + type: ChannelMessagesType.USER_STATUS, + remoteUser: remoteUser + }); + } + + Backbone.Events.on('UserStatusRequest', onUserStatusRequest); + + return CoWorkersAccessModuleView; +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/modules/coworkers-access-module/templates/coworker_item_template.html b/docdoku-web-front/app/js/modules/coworkers-access-module/templates/coworker_item_template.html new file mode 100644 index 0000000000..8609c6fea7 --- /dev/null +++ b/docdoku-web-front/app/js/modules/coworkers-access-module/templates/coworker_item_template.html @@ -0,0 +1,8 @@ + + + {{user}} + + + + {{#displayCobrowsingButton}}{{/displayCobrowsingButton}} + diff --git a/docdoku-web-front/app/js/modules/coworkers-access-module/views/coworkers_access_module_view.js b/docdoku-web-front/app/js/modules/coworkers-access-module/views/coworkers_access_module_view.js new file mode 100644 index 0000000000..753103eea1 --- /dev/null +++ b/docdoku-web-front/app/js/modules/coworkers-access-module/views/coworkers_access_module_view.js @@ -0,0 +1,75 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'common-objects/collections/reachable_users', + 'modules/coworkers-access-module/views/coworkers_item_view' +], function (Backbone, Users, CoWorkersItemView) { + 'use strict'; + var CoWorkersAccessModuleView = Backbone.View.extend({ + + el: '#coworkers_access_module', + + initialize: function () { + var that = this; + var users = new Users(); + this._coworkersItemViews = []; + + var $ul = this.$('#coworkers_access_module_entries'); + + users.fetch({reset: true, success: function () { + + var coWorkers = users.models.filter(function(user){ + return user.attributes.login !== App.config.login; + }); + + if(!coWorkers.length){ + var menuUrl = App.config.contextPath+'/workspace-management/'; + $ul.append('
                              •  '+App.config.i18n.NO_COWORKERS+'
                              • '); + $ul.append('
                              • '+App.config.i18n.WORKSPACES_ADMINISTRATION+'
                              • '); + return; + } + + _.each(coWorkers, function (user) { + if (user.attributes.login !== App.config.login) { + var cwiv = new CoWorkersItemView({ + model: user.attributes + }); + + that._coworkersItemViews.push(cwiv); + + $ul.append(cwiv.render().el); + } + }); + }}); + + this.$el.show(); + + this.$('#coworkers_access_module_toggler').click(function () { + if (!$ul.is(':visible')) { + that.refreshAvailabilities(); + } + }); + + Backbone.Events.on('collaboration:invite', this.collaborativeInvite, this); + + return this; + }, + + refreshAvailabilities: function () { + _.each(this._coworkersItemViews, function (view) { + view.refreshAvailability(); + }); + }, + + render: function () { + return this; + }, + + collaborativeInvite: function () { + this.$('#coworkers_access_module_toggler').click(); + this.$('.fa-globe').removeClass('corworker-action-disable').addClass('corworker-action'); + } + }); + + return CoWorkersAccessModuleView; +}); diff --git a/docdoku-web-front/app/js/modules/coworkers-access-module/views/coworkers_item_view.js b/docdoku-web-front/app/js/modules/coworkers-access-module/views/coworkers_item_view.js new file mode 100644 index 0000000000..7d1376ce3c --- /dev/null +++ b/docdoku-web-front/app/js/modules/coworkers-access-module/views/coworkers_item_view.js @@ -0,0 +1,69 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!modules/coworkers-access-module/templates/coworker_item_template.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var CoWorkersItemView = Backbone.View.extend({ + tagName: 'li', + + events: { + 'click .fa-video-camera': 'onVideoButtonClick', + 'click .fa-comments': 'onChatButtonClick', + 'click .fa-envelope': 'onMailButtonClick', + 'click .fa-globe': 'onCobrowsingButtonClick' + }, + + initialize: function () { + var data = { + user: this.model.login + }; + if (typeof(App) !== 'undefined' && App.sceneManager) { + data.displayCobrowsingButton = this.model.workspaceId === App.config.workspaceId; + } + this.template = Mustache.render(template, data); + _.bindAll(this); + return this; + }, + + render: function () { + this.$el.html(this.template); + return this; + }, + + refreshAvailability: function () { + var that = this; + // Listen for the status request done + Backbone.Events.on('UserStatusRequestDone', function (message) { + if (message.remoteUser === that.model.login && message.status !== null) { + if (message.status === 'OFFLINE') { + that.$('.fa-user').addClass('user-offline').removeClass('user-online').attr('title', App.config.i18n.OFFLINE); + } else if (message.status === 'ONLINE') { + that.$('.fa-user').addClass('user-online').removeClass('user-offline').attr('title', App.config.i18n.ONLINE); + } + } + }); + // trigger the status request + Backbone.Events.trigger('UserStatusRequest', that.model.login); + }, + + onVideoButtonClick: function () { + Backbone.Events.trigger('NewOutgoingCall', { remoteUser: this.model.login, context: App.config.workspaceId }); + }, + + onChatButtonClick: function () { + Backbone.Events.trigger('NewChatSession', { remoteUser: this.model.login, context: App.config.workspaceId }); + }, + + onMailButtonClick: function () { + window.location.href = encodeURI('mailto:' + this.model.email + '?subject=' + App.config.workspaceId); + }, + + onCobrowsingButtonClick: function () { + Backbone.Events.trigger('SendCollaborativeInvite', this.model.login); + } + }); + + return CoWorkersItemView; +}); diff --git a/docdoku-web-front/app/js/modules/user-popover-module/app.js b/docdoku-web-front/app/js/modules/user-popover-module/app.js new file mode 100644 index 0000000000..32f4ab326a --- /dev/null +++ b/docdoku-web-front/app/js/modules/user-popover-module/app.js @@ -0,0 +1,101 @@ +/*global $,define,App*/ +define(['backbone','common-objects/collections/users'], +function (Backbone, Users) { + 'use strict'; + + var statusHtml = { + OFFLINE: ' ' + App.config.i18n.OFFLINE, + ONLINE: ' ' + App.config.i18n.ONLINE + }; + + // popover content template + var tipContent = '
                                ' + + '' + + '
                                ' + + ' ' + + ' ' + + ' Mail ' + + '
                                '; + + var users = new Users(); + + $.fn.userPopover = function (userLogin, context, placement) { + + // don't show the popover if user clicks on his name + if (userLogin === App.config.login) { + return $(this).addClass('is-connected-user'); + } + + var popoverLink = $(this).popover({ + title: '', + html: true, + content: tipContent, + container: 'body', + trigger: 'manual', + placement: placement + }).click(function (e) { + + var that = this; + + // Fetch and display user data + users.fetch({reset: true, success: function () { + + // find user in collection + var user = users.findWhere({login: userLogin}); + + if (user) { + + $(that).popover('show').on('hidden', function(e) { + // Fix bug (modal closing on popover close https://github.com/twbs/bootstrap/issues/6942) + e.stopPropagation(); + }); + + // get the popover tip element + var $tip = $(that).data('popover').$tip; + $tip.addClass('reach-user-popover'); + $tip.toggleClass('above-modal-popover',$('.modal').length > 0); + + // Listen for the status request done + Backbone.Events.on('UserStatusRequestDone', function (message) { + if (message.remoteUser === userLogin && message.status !== null) { + $tip.find('.user-status').html(statusHtml[message.status]); + } + }); + + // trigger the status request + Backbone.Events.trigger('UserStatusRequest', user.get('login')); + + // set the popover title + $tip.find('.popover-title').html(user.get('name') + ' | ' + App.config.workspaceId + ' : ' + context); + + // handle webrtc button click event + $tip.find('.webRTC_invite_button').one('click', function () { + Backbone.Events.trigger('NewOutgoingCall', { remoteUser: user.get('login'), context: App.config.workspaceId + ' : ' + context}); + $(that).popover('hide'); + }); + + // handle chat button click event + $tip.find('.new_chat_session_button').one('click', function () { + Backbone.Events.trigger('NewChatSession', {remoteUser: user.get('login'), context: App.config.workspaceId + ' : ' + context}); + $(that).popover('hide'); + }); + + // handle mail button click event + var mailToString = encodeURI('mailto:' + user.get('email') + '?subject=' + App.config.workspaceId + ' : ' + context); + $tip.find('.mailto_button').attr('href', mailToString).one('click', function () { + $(that).popover('hide'); + }); + } + + }}); + + e.stopPropagation(); + e.preventDefault(); + return false; + + }); + + return popoverLink; + + }; +}); diff --git a/docdoku-web-front/app/js/modules/webrtc-module/adapters/webRTCAdapter.js b/docdoku-web-front/app/js/modules/webrtc-module/adapters/webRTCAdapter.js new file mode 100644 index 0000000000..46b01dd54e --- /dev/null +++ b/docdoku-web-front/app/js/modules/webrtc-module/adapters/webRTCAdapter.js @@ -0,0 +1,234 @@ +/*global define,mozRTCPeerConnection,mozRTCSessionDescription,mozRTCIceCandidate,webkitRTCPeerConnection,MediaStreamTrack,Promise*/ +define(function () { + + 'use strict'; + + var RTCPeerConnection = null; + var getUserMedia = null; + var attachMediaStream = null; + var reattachMediaStream = null; + var webrtcDetectedBrowser = null; + var webrtcDetectedVersion = null; + var webrtcMinimumVersion = null; + + function requestUserMedia(constraints) { + return new Promise(function (resolve, reject) { + getUserMedia(constraints, resolve, reject); + }); + } + + + if (navigator.mozGetUserMedia) { + webrtcDetectedBrowser = 'firefox'; + webrtcDetectedVersion = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); + webrtcMinimumVersion = 31; + RTCPeerConnection = function (pcConfig, pcConstraints) { + if (webrtcDetectedVersion < 38) { + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (server.hasOwnProperty('urls')) { + for (var j = 0; j < server.urls.length; j++) { + var newServer = {url: server.urls[j]}; + if (server.urls[j].indexOf('turn') === 0) { + newServer.username = server.username; + newServer.credential = server.credential; + } + newIceServers.push(newServer); + } + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + } + return new mozRTCPeerConnection(pcConfig, pcConstraints); + }; + window.RTCSessionDescription = mozRTCSessionDescription; + window.RTCIceCandidate = mozRTCIceCandidate; + getUserMedia = webrtcDetectedVersion < 38 ? function (c, onSuccess, onError) { + var constraintsToFF37 = function (c) { + if (typeof c !== 'object' || c.require) { + return c; + } + var require = []; + Object.keys(c).forEach(function (key) { + var r = c[key] = typeof c[key] === 'object' ? c[key] : {ideal: c[key]}; + if (r.exact !== undefined) { + r.min = r.max = r.exact; + delete r.exact; + } + if (r.min !== undefined || r.max !== undefined) { + require.push(key); + } + if (r.ideal !== undefined) { + c.advanced = c.advanced || []; + var oc = {}; + oc[key] = {min: r.ideal, max: r.ideal}; + c.advanced.push(oc); + delete r.ideal; + if (!Object.keys(r).length) { + delete c[key]; + } + } + }); + if (require.length) { + c.require = require; + } + return c; + }; + c.audio = constraintsToFF37(c.audio); + c.video = constraintsToFF37(c.video); + return navigator.mozGetUserMedia(c, onSuccess, onError); + } : navigator.mozGetUserMedia.bind(navigator); + navigator.getUserMedia = getUserMedia; + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: requestUserMedia}; + } + navigator.mediaDevices.enumerateDevices = navigator.mediaDevices.enumerateDevices || function () { + return new Promise(function (resolve) { + var infos = [{kind: 'audioinput', deviceId: 'default', label: '', groupId: ''}, { + kind: 'videoinput', + deviceId: 'default', + label: '', + groupId: '' + }]; + resolve(infos); + }); + }; + if (webrtcDetectedVersion < 41) { + var orgEnumerateDevices = navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); + navigator.mediaDevices.enumerateDevices = function () { + return orgEnumerateDevices().catch(function (e) { + if (e.name === 'NotFoundError') { + return []; + } + throw e; + }); + }; + } + attachMediaStream = function (element, stream) { + element.mozSrcObject = stream; + }; + reattachMediaStream = function (to, from) { + to.mozSrcObject = from.mozSrcObject; + }; + } else { + if (navigator.webkitGetUserMedia) { + webrtcDetectedBrowser = 'chrome'; + webrtcDetectedVersion = parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10); + webrtcMinimumVersion = 38; + RTCPeerConnection = function (pcConfig, pcConstraints) { + return new webkitRTCPeerConnection(pcConfig, pcConstraints); + }; + getUserMedia = function (c, onSuccess, onError) { + var constraintsToChrome = function (c) { + if (typeof c !== 'object' || c.mandatory || c.optional) { + return c; + } + var cc = {}; + Object.keys(c).forEach(function (key) { + if (key === 'require' || key === 'advanced') { + return; + } + var r = typeof c[key] === 'object' ? c[key] : {ideal: c[key]}; + if (r.exact !== undefined && typeof r.exact === 'number') { + r.min = r.max = r.exact; + } + var oldname = function (prefix, name) { + if (prefix) { + return prefix + name.charAt(0).toUpperCase() + name.slice(1); + } + return name === 'deviceId' ? 'sourceId' : name; + }; + if (r.ideal !== undefined) { + cc.optional = cc.optional || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[oldname('min', key)] = r.ideal; + cc.optional.push(oc); + oc = {}; + oc[oldname('max', key)] = r.ideal; + cc.optional.push(oc); + } else { + oc[oldname('', key)] = r.ideal; + cc.optional.push(oc); + } + } + if (r.exact !== undefined && typeof r.exact !== 'number') { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname('', key)] = r.exact; + } else { + ['min', 'max'].forEach(function (mix) { + if (r[mix] !== undefined) { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname(mix, key)] = r[mix]; + } + }); + } + }); + if (c.advanced) { + cc.optional = (cc.optional || []).concat(c.advanced); + } + return cc; + }; + c.audio = constraintsToChrome(c.audio); + c.video = constraintsToChrome(c.video); + return navigator.webkitGetUserMedia(c, onSuccess, onError); + }; + navigator.getUserMedia = getUserMedia; + attachMediaStream = function (element, stream) { + if (typeof element.srcObject !== 'undefined') { + element.srcObject = stream; + } else { + if (typeof element.mozSrcObject !== 'undefined') { + element.mozSrcObject = stream; + } else { + if (typeof element.src !== 'undefined') { + element.src = window.URL.createObjectURL(stream); + } else { + console.error('Error attaching stream to element.'); + } + } + } + }; + reattachMediaStream = function (to, from) { + to.src = from.src; + }; + if (!navigator.mediaDevices) { + navigator.mediaDevices = { + getUserMedia: requestUserMedia, + enumerateDevices: function () { + return new Promise(function (resolve) { + var kinds = {audio: 'audioinput', video: 'videoinput'}; + return MediaStreamTrack.getSources(function (devices) { + resolve(devices.map(function (device) { + return { + label: device.label, + kind: kinds[device.kind], + deviceId: device.id, + groupId: '' + }; + })); + }); + }); + } + }; + } + } else { + console.error('Browser does not appear to be WebRTC-capable'); + } + } + + return { + RTCPeerConnection: RTCPeerConnection, + getUserMedia: getUserMedia, + attachMediaStream: attachMediaStream, + reattachMediaStream: reattachMediaStream, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion + }; +}); diff --git a/docdoku-web-front/app/js/modules/webrtc-module/app.js b/docdoku-web-front/app/js/modules/webrtc-module/app.js new file mode 100644 index 0000000000..5376787a07 --- /dev/null +++ b/docdoku-web-front/app/js/modules/webrtc-module/app.js @@ -0,0 +1,205 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'buzz', + 'modules/webrtc-module/views/webrtc_module_view', + 'common-objects/websocket/channelListener', + 'common-objects/websocket/channelMessagesType', + 'common-objects/websocket/callState', + 'common-objects/websocket/rejectCallReason' +], function (Backbone, buzz, WebRTCModuleView, ChannelListener, ChannelMessagesType, CALL_STATE, REJECT_CALL_REASON) { + 'use strict'; + var WEBRTC_CONFIG = { + MS_TIMEOUT: 30000, + PLAY_SOUND: true + }; + + Backbone.Events.on('NotificationSound', function () { + new buzz.sound(App.config.contextPath + '/sounds/notification.ogg').play(); + }); + + Backbone.Events.on('IncomingCallSound', function () { + if (WEBRTC_CONFIG.PLAY_SOUND) { + new buzz.sound(App.config.contextPath + '/sounds/incoming-call.ogg').play(); + } + }); + + + // init the web rtc call manager + var webRTCModuleView = new WebRTCModuleView().setState(CALL_STATE.NO_CALL).render(); + + // Listen to call events + + // handle new webRtc session initiated by local user + // the room key is known : 'localUserLogin-remoteUserLogin' + Backbone.Events.on('NewOutgoingCall', webRTCModuleView.onNewOutgoingCall); + + // handle reject for local user + Backbone.Events.on('CallRejectedByLocalUser', webRTCModuleView.onCallRejectedByLocalUser); + + // Websocket Listener + var webRTCInvitationListener = new ChannelListener({ + + // messages listened + messagePattern: /^WEBRTC_.+/, + + webRtcSignalTypes: [ + ChannelMessagesType.WEBRTC_ANSWER, + ChannelMessagesType.WEBRTC_BYE, + ChannelMessagesType.WEBRTC_CANDIDATE, + ChannelMessagesType.WEBRTC_OFFER + ], + + isApplicable: function (messageType) { + return messageType.match(this.messagePattern) !== null || + _.indexOf(this.webRtcSignalTypes, messageType) > -1; + }, + + onMessage: function (message) { + + // error messages + if (message.error && webRTCModuleView.roomKey === message.roomKey) { + webRTCModuleView.onError(message); + return; + } + + // forwarded messages from the remote peer (web rtc view must be in an active call state) + // ensure the call state is either negotiating or running, and the room key is the right one + if (_.contains(this.webRtcSignalTypes, message.type) && + (webRTCModuleView.callState === CALL_STATE.NEGOTIATING || webRTCModuleView.callState === CALL_STATE.RUNNING) && + webRTCModuleView.roomKey === message.roomKey) { + + webRTCModuleView.processSignalingMessage(message); + return; + } + + // remote user has accepted the call, both users should be in the room + if (message.type === ChannelMessagesType.WEBRTC_ACCEPT && webRTCModuleView.roomKey === message.roomKey && webRTCModuleView.callState === CALL_STATE.OUTGOING) { + webRTCModuleView.onCallAcceptedByRemoteUser(message); + return; + } + + // remote user has rejected the call (the message contains the reason) + if (message.type === ChannelMessagesType.WEBRTC_REJECT && webRTCModuleView.roomKey === message.roomKey && webRTCModuleView.callState === CALL_STATE.OUTGOING) { + webRTCModuleView.onCallRejectedByRemoteUser(message); + return; + } + + // remote user hang up the call (local user must be in NEGOTIATING or RUNNING state) + if (message.type === ChannelMessagesType.WEBRTC_HANGUP && webRTCModuleView.roomKey === message.roomKey && (webRTCModuleView.callState === CALL_STATE.RUNNING || webRTCModuleView.callState === CALL_STATE.NEGOTIATING)) { + webRTCModuleView.onCallHangUpByRemoteUser(message); + return; + } + + // Receiving a WebRTC invitation + // NO CALL state : prompt the local user to accept / reject + // Other states : local user is busy. + + if (message.type === ChannelMessagesType.WEBRTC_INVITE) { + + + // TODO : remove this : + // webRTCModuleView.callState = CALL_STATE.RUNNING; + // TODO : ----- + + + // If local user is busy ... + if (webRTCModuleView.callState !== CALL_STATE.NO_CALL) { + + // tell the remote that the local user reject the call because he's busy + App.mainChannel.sendJSON({ + type: ChannelMessagesType.WEBRTC_REJECT, + roomKey: message.roomKey, + remoteUser: message.remoteUser, + reason: REJECT_CALL_REASON.BUSY + }); + + // TODO : keep track of this invite and notify local user of the call + + return; + } + + // Local user is in a NO_CALL state, let's prompt him. + else { + Backbone.Events.trigger('IncomingCallSound'); + // Set the call state to incoming, hook data on view + webRTCModuleView.setState(CALL_STATE.INCOMING); + webRTCModuleView.setRoomKey(message.roomKey); + webRTCModuleView.setRemoteUser(message.remoteUser); + webRTCModuleView.setContext(message.context); + + var webRTCInvitation = { + + message: message, + + accept: function () { + clearTimeout(this.invitationTimeout); + webRTCModuleView.onCallAcceptedByLocalUser(message); + }, + + reject: function () { + clearTimeout(this.invitationTimeout); + webRTCModuleView.onCallRejectedByLocalUser(message); + } + + }; + + webRTCInvitation.invitationTimeout = setTimeout(function () { + + // Timeout reached, remove the invitation + Backbone.Events.trigger('RemoveWebRTCInvitation', webRTCInvitation); + + // Send a reject (reason : timeout) if call remains incoming + if (webRTCModuleView.callState === CALL_STATE.INCOMING) { + webRTCModuleView.onCallTimeoutByLocalUser(message); + } + // stop local session + else { + webRTCModuleView.setState(CALL_STATE.NO_CALL); + webRTCModuleView.stop(); + } + + }, WEBRTC_CONFIG.MS_TIMEOUT); + + // trigger notification display in a chat session + Backbone.Events.trigger('NewWebRTCInvitation', webRTCInvitation); + } + + } + + // remove invitation if local user has accepted or rejected the invitation from an other socket (an other tab) + + if ((message.type === ChannelMessagesType.WEBRTC_ROOM_JOIN_EVENT || message.type === ChannelMessagesType.WEBRTC_ROOM_REJECT_EVENT) && + message.userLogin === App.config.login && + webRTCModuleView.callState === CALL_STATE.INCOMING && message.roomKey === webRTCModuleView.roomKey) { + + if (webRTCModuleView.remoteUser) { + + // hooked data needed + message.remoteUser = webRTCModuleView.remoteUser; + message.context = webRTCModuleView.context; + + Backbone.Events.trigger('RemoveWebRTCInvitation', {message: message}); + + if (message.type === ChannelMessagesType.WEBRTC_ROOM_REJECT_EVENT) { + webRTCModuleView.onCallRejectedByLocalUser(message); + } else { + webRTCModuleView.setState(CALL_STATE.NO_CALL); + webRTCModuleView.stop(); + } + + } + } + + }, + + onStatusChanged: function (status) { + webRTCModuleView.onWebRTCStatusChanged(status); + } + + }); + + return webRTCInvitationListener; + + +}); \ No newline at end of file diff --git a/docdoku-web-front/app/js/modules/webrtc-module/templates/webrtc_module_template.html b/docdoku-web-front/app/js/modules/webrtc-module/templates/webrtc_module_template.html new file mode 100644 index 0000000000..4b303accba --- /dev/null +++ b/docdoku-web-front/app/js/modules/webrtc-module/templates/webrtc_module_template.html @@ -0,0 +1,17 @@ +
                                +

                                + +

                                + + + + +

                                +
                                +
                                +
                                +
                                + + +
                                +
                                \ No newline at end of file diff --git a/docdoku-web-front/app/js/modules/webrtc-module/views/webrtc_module_view.js b/docdoku-web-front/app/js/modules/webrtc-module/views/webrtc_module_view.js new file mode 100644 index 0000000000..c6d87d7a5b --- /dev/null +++ b/docdoku-web-front/app/js/modules/webrtc-module/views/webrtc_module_view.js @@ -0,0 +1,565 @@ +/*global define,App,RTCSessionDescription,RTCIceCandidate,_*/ +define([ + 'backbone', + 'modules/webrtc-module/adapters/webRTCAdapter', + 'text!modules/webrtc-module/templates/webrtc_module_template.html', + 'common-objects/websocket/channelMessagesType', + 'common-objects/websocket/callState', + 'common-objects/websocket/rejectCallReason' +], +function (Backbone, webRTCAdapter, template, ChannelMessagesType, CALL_STATE, REJECT_CALL_REASON) { + + 'use strict'; + + function extractSdp(sdpLine, pattern) { + var result = sdpLine.match(pattern); + return (result && result.length === 2) ? result[1] : null; + } + + // Strip CN from sdp before CN constraints is ready. + function removeCN(sdpLines, mLineIndex) { + var mLineElements = sdpLines[mLineIndex].split(' '); + // Scan from end for the convenience of removing an item. + for (var i = sdpLines.length - 1; i >= 0; i--) { + var payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i); + if (payload) { + var cnPos = mLineElements.indexOf(payload); + if (cnPos !== -1) { + // Remove CN payload from m line. + mLineElements.splice(cnPos, 1); + } + // Remove CN line in sdp + sdpLines.splice(i, 1); + } + } + + sdpLines[mLineIndex] = mLineElements.join(' '); + return sdpLines; + } + + // Set the selected codec to the first in m line. + function setDefaultCodec(mLine, payload) { + var elements = mLine.split(' '); + var newLine = []; + var index = 0; + for (var i = 0; i < elements.length; i++) { + if (index === 3) { // Format of media starts from the fourth. + newLine[index++] = payload; // Put target payload to the first. + } + if (elements[i] !== payload) { + newLine[index++] = elements[i]; + } + } + return newLine.join(' '); + } + + // Set Opus as the default audio codec if it's present. + function preferOpus(sdp) { + var sdpLines = sdp.split('\r\n'); + + var mLineIndex = null; + // Search for m line. + for (var i = 0; i < sdpLines.length; i++) { + if (sdpLines[i].search('m=audio') !== -1) { + mLineIndex = i; + break; + } + } + if (mLineIndex === null) { + return sdp; + } + + // If Opus is available, set it as the default in m line. + for (var j = 0; j < sdpLines.length; j++) { + if (sdpLines[j].search('opus/48000') !== -1) { + var opusPayload = extractSdp(sdpLines[j], /:(\d+) opus\/48000/i); + if (opusPayload) { + sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], opusPayload); + } + break; + } + } + + // Remove CN in m line and sdp. + sdpLines = removeCN(sdpLines, mLineIndex); + + sdp = sdpLines.join('\r\n'); + return sdp; + } + + + + + var WebRTCModuleView = Backbone.View.extend({ + + el: '#webrtc_module', + + events: { + 'click #webrtc_minimize_btn': 'onMinimizeButtonClick', + 'click #webrtc_fullscreen_btn': 'onFullScreenButtonClick', + 'click #webrtc_restore_btn': 'onRestoreButtonClick', + 'click #webrtc_hangup_btn': 'onHangupButtonClick' + }, + + // dom elements + localVideo: null, + remoteVideo: null, + videoContainer: null, + localStream: null, + remoteStream: null, + + // peer connection + initiator: 0, + started: false, + + // state of current call + callState: null, + + mediaConstraints: { + 'mandatory': { + 'OfferToReceiveAudio': true, + 'OfferToReceiveVideo': true + }, + 'optional': [{'VoiceActivityDetection': false}] + }, + + isVideoMuted: false, + isAudioMuted: false, + + initialize: function () { + _.bindAll(this); + return this.el; + }, + + render: function () { + + this.$el.html(template); + + this.videoContainer = this.$('#webrtc_video_container')[0]; + this.localVideo = this.$('#webrtc_local_video')[0]; + this.remoteVideo = this.$('#webrtc_remote_video')[0]; + + this.makeDraggable(); + this.delegateEvents(); + return this; + + }, + + setState: function (callState) { + this.callState = callState; + return this; + }, + + onError: function (message) { + this.setStatus(App.config.i18n.ERROR + ' : ' + App.config.i18n[message.error]); + }, + + onMinimizeButtonClick: function () { + this.$el.removeClass('webrtc_shown').addClass('webrtc_minimized'); + }, + + onRestoreButtonClick: function () { + this.$el.removeClass('webrtc_minimized').addClass('webrtc_shown'); + }, + + onFullScreenButtonClick: function () { + this.videoContainer.webkitRequestFullScreen(); + this.$el.removeClass('webrtc_minimized').addClass('webrtc_shown'); + }, + + onHangupButtonClick: function () { + this.stop(); + this.exit(); + }, + + onRemoteHangup: function () { + this.setStatus(App.config.i18n.REMOTE_HANGUP); + this.stop(); + }, + + stop: function () { + + this.started = false; + this.isAudioMuted = false; + this.isVideoMuted = false; + this.initiator = 0; + + if (this.pc) { + this.pc.close(); + this.pc = null; + } + + if (this.localStream !== null && this.localStream.stop) { + this.localStream.stop(); + this.localStream = null; + } + + if (this.remoteStream !== null && this.remoteStream.stop) { + this.remoteStream.stop(); + this.remoteStream = null; + } + + this.localVideo.style.opacity = 0; + this.remoteVideo.style.opacity = 0; + + // say bye if state !== no call, then reset the call state to NO_CALL + if (this.callState !== CALL_STATE.NO_CALL) { + App.mainChannel.sendJSON({type: ChannelMessagesType.WEBRTC_BYE, roomKey: this.roomKey, remoteUser: App.config.login}); + this.setState(CALL_STATE.NO_CALL); + } + + }, + + exit: function () { + this.$el.removeClass('webrtc_shown').removeClass('webrtc_minimized'); + }, + + setRemoteUser: function (remoteUser) { + this.remoteUser = remoteUser; + }, + + setContext: function (context) { + this.context = context; + }, + + setRoomKey: function (roomKey) { + this.roomKey = roomKey; + }, + + setStatus: function (status) { + this.$('#webrtc_call_state').html(status); + }, + + setTitle: function (title) { + this.$('#webrtc_module_title').html(' ' + App.config.i18n.CALL_TO_TITLE + ' : ' + title); + }, + + makeDraggable: function () { + // local video is draggable + this.$('#webrtc_local_video').draggable({ + containment: 'div#webrtc_video_container', + position: 'absolute' + }).css('position', 'absolute'); + + }, + + onNewOutgoingCall: function (sessionArgs) { + + // store rtc session vars + this.setRemoteUser(sessionArgs.remoteUser); + this.setContext(sessionArgs.context); + this.setRoomKey(App.config.login + '-' + this.remoteUser); + this.setTitle(this.remoteUser + ' | ' + this.context); + + this.$el.show(); + this.$el.removeClass('webrtc_minimized').addClass('webrtc_shown'); + + if (!App.mainChannel.isReady()) { + this.setStatus(App.config.i18n.ERROR + ' : ' + App.config.i18n.CHANNEL_NOT_READY_ERROR); + this.stop(); + return; + } + + if (this.callState !== CALL_STATE.NO_CALL) { + // cannot initiate a new call in not a NO CALL state + return; + } + + // local user initiate the call + this.setState(CALL_STATE.OUTGOING); + this.setStatus(App.config.i18n.WAITING_USER_MEDIA); + this.initMedia(); + }, + + onCallAcceptedByLocalUser: function (message) { + + if (this.callState !== CALL_STATE.INCOMING) { + return; + } + + this.setState(CALL_STATE.NEGOTIATING); + + // remote user initiate the call + this.initiator = 1; + + this.setRemoteUser(message.remoteUser); + this.setContext(message.context); + this.setRoomKey(message.roomKey); + + // tell the remote user we accept the call. + // this will trigger a room.addUser + App.mainChannel.sendJSON({ + type: ChannelMessagesType.WEBRTC_ACCEPT, + roomKey: message.roomKey, + remoteUser: this.remoteUser + }); + + this.setTitle(this.remoteUser + ' | ' + this.context); + this.setStatus(App.config.i18n.WAITING_USER_MEDIA); + this.$el.addClass('webrtc_shown').show(); + this.initMedia(); + + }, + + onCallTimeoutByLocalUser: function (message) { + // tell the remote user we reject the call. + App.mainChannel.sendJSON({ + type: ChannelMessagesType.WEBRTC_REJECT, + roomKey: message.roomKey, + remoteUser: message.remoteUser, + reason: REJECT_CALL_REASON.TIMEOUT + }); + this.stop(); + }, + + onCallRejectedByLocalUser: function (message) { + + if (this.callState !== CALL_STATE.INCOMING) { + return; + } + + // tell the remote user we reject the call (reason : reject) + App.mainChannel.sendJSON({ + type: ChannelMessagesType.WEBRTC_REJECT, + roomKey: message.roomKey, + remoteUser: message.remoteUser, + reason: REJECT_CALL_REASON.REJECTED + }); + + this.stop(); + }, + + onCallAcceptedByRemoteUser: function () { + this.setState(CALL_STATE.NEGOTIATING); + this.setStatus(App.config.i18n.REMOTE_ACCEPT); + }, + + onCallRejectedByRemoteUser: function (message) { + + switch (message.reason) { + case REJECT_CALL_REASON.BUSY : + this.setStatus(App.config.i18n.REMOTE_BUSY); + break; + case REJECT_CALL_REASON.TIMEOUT : + this.setStatus(App.config.i18n.REMOTE_TIMEOUT); + break; + case REJECT_CALL_REASON.REJECTED : + this.setStatus(App.config.i18n.REMOTE_REJECT); + break; + case REJECT_CALL_REASON.OFFLINE : + this.setStatus(App.config.i18n.REMOTE_OFFLINE); + break; + default : + this.setStatus(App.config.i18n.REMOTE_REJECT); + break; + } + + this.stop(); + }, + + onCallHangUpByRemoteUser: function () { + this.setStatus(App.config.i18n.REMOTE_HANGUP); + this.stop(); + }, + + initMedia: function () { + try { + webRTCAdapter.getUserMedia({'audio': true, 'video': {'mandatory': {}, 'optional': []}}, this.onUserMediaSuccess, this.onUserMediaError); + } catch (e) { + this.setStatus(App.config.i18n.USER_MEDIA_FAILED); + } + }, + + onUserMediaSuccess: function (stream) { + + this.localStream = stream; + webRTCAdapter.attachMediaStream(this.localVideo, this.localStream); + this.localVideo.style.opacity = 1; + + // Caller creates PeerConnection. + if (this.initiator) { + this.maybeStart(); + } + else { + + if (!this.started) { + // send invite to remote user + App.mainChannel.sendJSON({ + type: ChannelMessagesType.WEBRTC_INVITE, + remoteUser: this.remoteUser, + context: this.context + }); + this.setStatus(App.config.i18n.VIDEO_INVITATION_SENT); + } + + } + + }, + + onUserMediaError: function () { + this.setStatus(App.config.i18n.ERROR + ' : ' + App.config.i18n.DEVICE_ERROR); + this.stop(); + }, + + maybeStart: function () { + + if (!this.started && this.localStream) { + this.setStatus(App.config.i18n.CONNECTING); + this.createPeerConnection(); + this.pc.addStream(this.localStream); + this.started = true; + if (this.initiator) { + this.doCall(); + } + } + + }, + + doCall: function () { + this.pc.createOffer(this.setLocalAndSendOfferMessage, function(){ + }, this.mediaConstraints); + }, + + doAnswer: function () { + this.pc.createAnswer(this.setLocalAndSendAnswerMessage, function(){ + }, this.mediaConstraints); + }, + + setLocalAndSendOfferMessage: function (sessionDescription) { + + sessionDescription.sdp = preferOpus(sessionDescription.sdp); + this.pc.setLocalDescription(sessionDescription); + + App.mainChannel.sendJSON({ + type:ChannelMessagesType.WEBRTC_OFFER, + sdp:sessionDescription.sdp, + roomKey:this.roomKey, + remoteUser:this.remoteUser + }); + + }, + setLocalAndSendAnswerMessage: function (sessionDescription) { + + sessionDescription.sdp = preferOpus(sessionDescription.sdp); + this.pc.setLocalDescription(sessionDescription); + + App.mainChannel.sendJSON({ + type:ChannelMessagesType.WEBRTC_ANSWER, + sdp:sessionDescription.sdp, + roomKey:this.roomKey, + remoteUser:this.remoteUser + }); + + }, + + onWebRTCStatusChanged: function () { + }, + + createPeerConnection: function () { + try { + // Create an RTCPeerConnection via the adapter + this.pc = new webRTCAdapter.RTCPeerConnection({'iceServers': [ + {'url': 'stun:stun.l.google.com:19302'} + ]}); + this.pc.onicecandidate = this.onIceCandidate.bind(this); + this.pc.onconnecting = this.onSessionConnecting.bind(this); + this.pc.onopen = this.onSessionOpened.bind(this); + this.pc.onaddstream = this.onRemoteStreamAdded.bind(this); + this.pc.onremovestream = this.onRemoteStreamRemoved.bind(this); + } catch (e) { + // Failed to create PeerConnection + this.setStatus(App.config.i18n.CANNOT_CREATE_PC); + } + }, + + processSignalingMessage: function (msg) { + + if (msg.type === ChannelMessagesType.WEBRTC_OFFER) { + + // Callee creates PeerConnection + if (!this.initiator && !this.started) { + this.maybeStart(); + } + + this.pc.setRemoteDescription(new RTCSessionDescription(msg),this.onRemoteDescriptionSet.bind(this), this.onError.bind(this)); + + } else if (msg.type === ChannelMessagesType.WEBRTC_ANSWER && this.started) { + + this.pc.setRemoteDescription(new RTCSessionDescription(msg), this.onRemoteDescriptionSet.bind(this), this.onError.bind(this)); + + } else if (msg.type === ChannelMessagesType.WEBRTC_CANDIDATE && this.started) { + + this.pc.addIceCandidate(new RTCIceCandidate({ + sdpMLineIndex: msg.label, + candidate: msg.candidate + })); + + } else if (msg.type === ChannelMessagesType.WEBRTC_BYE) { + this.onRemoteHangup(); + } + + }, + + onRemoteDescriptionSet:function(){ + this.doAnswer(); + }, + + onIceCandidate: function (event) { + if (event.candidate) { + App.mainChannel.sendJSON({ + type: ChannelMessagesType.WEBRTC_CANDIDATE, + roomKey: this.roomKey, + label: event.candidate.sdpMLineIndex, + id: event.candidate.sdpMid, + candidate: event.candidate.candidate, + remoteUser: this.remoteUser + }); + } + }, + + onSessionConnecting: function () { + this.setStatus(App.config.i18n.SESSION_CONNECTING); + }, + + onSessionOpened: function () { + this.setStatus(App.config.i18n.SESSION_OPENED); + this.setState(CALL_STATE.RUNNING); + }, + + onRemoteStreamAdded: function (event) { + this.setStatus(App.config.i18n.REMOTE_STEAM_ADDED); + this.remoteStream = event.stream; + webRTCAdapter.attachMediaStream(this.remoteVideo, this.remoteStream); + this.waitForRemoteVideo(); + }, + + onRemoteStreamRemoved: function () { + this.setStatus(App.config.i18n.REMOTE_STEAM_REMOVED); + }, + + waitForRemoteVideo: function () { + this.setStatus(App.config.i18n.WAITING_REMOTE_VIDEO); + var self = this; + try { + //Try the new representation of tracks in a stream in M26. + this.videoTracks = this.remoteStream.getVideoTracks(); + } catch (e) { + this.videoTracks = this.remoteStream.videoTracks; + } + + if (this.videoTracks.length === 0 || this.remoteVideo.readyState >= 2) { + this.remoteVideo.style.opacity = 1; + this.setStatus(App.config.i18n.CONNECTED); + this.setState(CALL_STATE.RUNNING); + } else { + setTimeout(function () { + self.waitForRemoteVideo(); + }, 100); + } + } + + }); + + return WebRTCModuleView; + +}); diff --git a/docdoku-web-front/app/js/utils/datatables.oSort.ext.js b/docdoku-web-front/app/js/utils/datatables.oSort.ext.js new file mode 100644 index 0000000000..4bffbfda26 --- /dev/null +++ b/docdoku-web-front/app/js/utils/datatables.oSort.ext.js @@ -0,0 +1,37 @@ +/*global $,define,App*/ +define(['datatables','moment'], function (DT,moment) { + 'use strict'; + + // sorting eu dates with current format and timezone + // allows string comparison + + var getComparableDate = function(date){ + return moment(date,App.config.i18n._DATE_FORMAT).toDate().getTime(); + }; + + var stripTags = function(input, allowed) { + allowed = (((allowed || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join(''); + var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, commentsAndPhpTags = /|<\?(?:php)?[\s\S]*?\?>/gi; + return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) { + return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''; + }); + }; + + $.fn.dataTableExt.oSort['date_sort-asc'] = function (x, y) { + return getComparableDate(x) - getComparableDate(y); + }; + + $.fn.dataTableExt.oSort['date_sort-desc'] = function (x, y) { + return getComparableDate(y) - getComparableDate(x); + }; + + $.fn.dataTableExt.oSort['strip_html-asc'] = function (x, y) { + + return stripTags(x).trim() > stripTags(y).trim() ? 1:-1; + }; + + $.fn.dataTableExt.oSort['strip_html-desc'] = function (x, y) { + return stripTags(x).trim() <= stripTags(y).trim() ? 1:-1; + }; + +}); diff --git a/docdoku-web-front/app/js/utils/effects.js b/docdoku-web-front/app/js/utils/effects.js new file mode 100644 index 0000000000..22b144b0c2 --- /dev/null +++ b/docdoku-web-front/app/js/utils/effects.js @@ -0,0 +1,24 @@ +/*global _,jQuery*/ +(function ($) { + 'use strict'; + $.fn.highlightEffect = function () { + $(this).effect('highlight', {color: '#999999'}, 1000); + }; + + $.fn.customResizable = function (args) { + var options = { + handles: 'e', + autoHide: true, + stop: function (e, ui) { + var parent = ui.element.parent(); + ui.element.css({ + width: ui.element.width() / parent.width() * 100 + '%', + height: '100%' + }); + } + }; + + _.extend(options, args); + $(this).resizable(options); + }; +})(jQuery); diff --git a/docdoku-web-front/app/js/utils/input-validity.js b/docdoku-web-front/app/js/utils/input-validity.js new file mode 100644 index 0000000000..a4ac7ffe5e --- /dev/null +++ b/docdoku-web-front/app/js/utils/input-validity.js @@ -0,0 +1,40 @@ +/*global jQuery*/ +(function ($) { + + 'use strict'; + + // Find a tab where's the first error found, and show it, returns true if the form is invalid, false otherwise + $.fn.invalidFormTabSwitcher = function(){ + var $tabPanes = this.find('.tab-pane'); + for(var i = 0 ; i < $tabPanes.length; i++){ + if($tabPanes[i].querySelectorAll(':invalid').length){ + this.find('li:eq('+i+') a').tab('show'); + return true; + } + } + return false; + }; + + // Changes the custom validity message + $.fn.customValidity = function (message) { + + var element = this; + + var onInvalid = function(e){ + e.target.setCustomValidity(''); + if (!e.target.validity.valid) { + e.target.setCustomValidity(message); + } + }; + var onInput = function(e){ + e.target.setCustomValidity(''); + }; + + for(var i = 0 ; i < element.length; i++){ + element[i].oninvalid = onInvalid; + element[i].oninput = onInput; + } + + }; + +})(jQuery); diff --git a/docdoku-web-front/app/js/utils/jquery.maskedinput-config.js b/docdoku-web-front/app/js/utils/jquery.maskedinput-config.js new file mode 100644 index 0000000000..b17717513e --- /dev/null +++ b/docdoku-web-front/app/js/utils/jquery.maskedinput-config.js @@ -0,0 +1,14 @@ +/*global jQuery*/ +(function ($) { + 'use strict'; + $.mask = { + // Override to handle our mask grammar + definitions: { + '#': '[0-9]', + '%': '[A-Za-z]', + '*': '[A-Za-z0-9]' + }, + dataName: 'rawMaskFn', + placeholder: '_' + }; +})(jQuery); diff --git a/docdoku-web-front/app/js/utils/popover.utils.js b/docdoku-web-front/app/js/utils/popover.utils.js new file mode 100644 index 0000000000..dc36f4bd01 --- /dev/null +++ b/docdoku-web-front/app/js/utils/popover.utils.js @@ -0,0 +1,20 @@ +/*global jQuery*/ +(function ($) { + + 'use strict'; + + function removePopovers(){ + $('.popover').remove(); + } + + function removePopoversIfClickOutside(e){ + var $elem = $(e.target); + if (!$elem.parents('.popover').length && !$elem.hasClass('popover') ) { + removePopovers(); + } + } + + addEventListener('mousewheel',removePopovers); + addEventListener('click',removePopoversIfClickOutside); + +})(jQuery); diff --git a/docdoku-web-front/app/js/utils/query-builder-options.js b/docdoku-web-front/app/js/utils/query-builder-options.js new file mode 100644 index 0000000000..f3b3d08e16 --- /dev/null +++ b/docdoku-web-front/app/js/utils/query-builder-options.js @@ -0,0 +1,315 @@ +/*global $,define,App*/ +define(function () { + + 'use strict'; + + /** + * Jquery Query builder doc : http://mistic100.github.io/jQuery-QueryBuilder + * Selectize Events doc : https://github.com/brianreavis/selectize.js/blob/master/docs/events.md + */ + + + $.fn.queryBuilder.defaults({ lang: { + 'add_rule': '', + 'add_group': '', + 'delete_rule': '', + 'delete_group': '', + 'conditions': { + 'AND': App.config.i18n.QUERY_BUILDER_CONDITION_AND, + 'OR': App.config.i18n.QUERY_BUILDER_CONDITION_OR + }, + 'operators': { + 'equal': App.config.i18n.QUERY_BUILDER_OPERATORS_EQUALS, + 'not_equal': App.config.i18n.QUERY_BUILDER_OPERATORS_NOT_EQUALS, + 'in': App.config.i18n.QUERY_BUILDER_OPERATORS_IN, + 'not_in': App.config.i18n.QUERY_BUILDER_OPERATORS_NOT_IN, + 'less': App.config.i18n.QUERY_BUILDER_OPERATORS_LESS, + 'less_or_equal': App.config.i18n.QUERY_BUILDER_OPERATORS_LESS_OR_EQUAL, + 'greater': App.config.i18n.QUERY_BUILDER_OPERATORS_GREATER, + 'greater_or_equal': App.config.i18n.QUERY_BUILDER_OPERATORS_GREATER_OR_EQUAL, + 'between': App.config.i18n.QUERY_BUILDER_OPERATORS_BETWEEN, + 'begins_with': App.config.i18n.QUERY_BUILDER_OPERATORS_BEGIN_WITH, + 'not_begins_with': App.config.i18n.QUERY_BUILDER_OPERATORS_NOT_BEGIN_WITH, + 'contains': App.config.i18n.QUERY_BUILDER_OPERATORS_CONTAINS, + 'not_contains': App.config.i18n.QUERY_BUILDER_OPERATORS_NOT_CONTAINS, + 'ends_with': App.config.i18n.QUERY_BUILDER_OPERATORS_ENDS_WITH, + 'not_ends_with': App.config.i18n.QUERY_BUILDER_OPERATORS_NOT_ENDS_WITH, + 'is_empty': App.config.i18n.QUERY_BUILDER_OPERATORS_IS_EMPTY, + 'is_not_empty': App.config.i18n.QUERY_BUILDER_OPERATORS_IS_NOT_EMPTY, + 'is_null': App.config.i18n.QUERY_BUILDER_OPERATORS_IS_NULL, + 'is_not_null': App.config.i18n.QUERY_BUILDER_OPERATORS_IS_NOT_NULL + }, + 'errors': { + 'no_filter': App.config.i18n.QUERY_BUILDER_ERRORS_NO_FILTER, + 'empty_group': App.config.i18n.QUERY_BUILDER_ERRORS_EMPTY_GROUP, + 'radio_empty': App.config.i18n.QUERY_BUILDER_ERRORS_RADIO_EMPTY, + 'checkbox_empty': App.config.i18n.QUERY_BUILDER_ERRORS_CHECKBOX_EMPTY, + 'select_empty': App.config.i18n.QUERY_BUILDER_ERRORS_SELECT_EMPTY, + 'string_empty': App.config.i18n.QUERY_BUILDER_ERRORS_STRING_EMPTY, + 'string_exceed_min_length': App.config.i18n.QUERY_BUILDER_ERRORS_STRING_EXCEED_MIN_LENGTH, + 'string_exceed_max_length': App.config.i18n.QUERY_BUILDER_ERRORS_STRING_EXCEED_MAX_LENGTH, + 'string_invalid_format': App.config.i18n.QUERY_BUILDER_ERRORS_STRING_INVALID_FORMAT, + 'number_nan': App.config.i18n.QUERY_BUILDER_ERRORS_NUMBER_NAN, + 'number_not_integer': App.config.i18n.QUERY_BUILDER_ERRORS_NUMBER_NOT_INTEGER, + 'number_not_double': App.config.i18n.QUERY_BUILDER_ERRORS_NUMBER_NOT_DOUBLE, + 'number_exceed_min': App.config.i18n.QUERY_BUILDER_ERRORS_NUMBER_EXCEED_MIN, + 'number_exceed_max': App.config.i18n.QUERY_BUILDER_ERRORS_NUMBER_EXCEED_MAX, + 'number_wrong_step': App.config.i18n.QUERY_BUILDER_ERRORS_NUMBER_WRONG_STEP, + 'datetime_empty': App.config.i18n.QUERY_BUILDER_ERRORS_DATETIME_EMPTY, + 'datetime_invalid': App.config.i18n.QUERY_BUILDER_ERRORS_DATETIME_INVALID, + 'datetime_exceed_min': App.config.i18n.QUERY_BUILDER_ERRORS_DATETIME_EXCEED_MIN, + 'datetime_exceed_max': App.config.i18n.QUERY_BUILDER_ERRORS_DATETIME_EXCEED_MAX, + 'boolean_not_valid': App.config.i18n.QUERY_BUILDER_ERRORS_BOOLEAN_NOT_VALID, + 'operator_not_multiple': App.config.i18n.QUERY_BUILDER_ERRORS_OPERATOR_NOT_MULTIPLE + } + }}); + + var dateInput = function(rule,inputName){ + return ''; + }; + + var stringOperators = ['equal', 'not_equal', 'contains', 'not_contains', 'begins_with', 'not_begins_with', 'ends_with', 'not_ends_with']; + var dateOperators = ['equal', 'not_equal', 'less', 'less_or_equal', 'greater', 'greater_or_equal', 'between']; + var lovOperators = ['equal', 'not_equal']; + var statusOperators = ['equal', 'not_equal']; + var booleanOperators = ['equal', 'not_equal']; + var tagOperators = ['equal']; + var numberOperators = ['equal', 'not_equal', 'less', 'less_or_equal', 'greater', 'greater_or_equal', 'between']; + + var filters = []; + + // Part Master + filters.push({ + id: 'pm.name', + label: App.config.i18n.PART_NAME, + type: 'string', + operators: stringOperators, + optgroup:App.config.i18n.QUERY_GROUP_PART_MASTER, + realType:'string' + }); + + filters.push({ + id: 'pm.number', + label: App.config.i18n.PART_NUMBER, + type: 'string', + operators: stringOperators, + optgroup:App.config.i18n.QUERY_GROUP_PART_MASTER, + realType:'partNumber' + }); + + filters.push({ + id: 'pm.type', + label: App.config.i18n.TYPE, + type: 'string', + operators: stringOperators, + optgroup:App.config.i18n.QUERY_GROUP_PART_MASTER, + realType:'string' + }); + + filters.push({ + id: 'pm.standardPart', + label: App.config.i18n.QUERY_STANDARD, + type: 'string', + operators: booleanOperators, + optgroup:App.config.i18n.QUERY_GROUP_PART_MASTER, + realType:'boolean', + input : 'select', + values : [ + {'true' : App.config.i18n.TRUE}, + {'false' : App.config.i18n.FALSE} + ] + }); + + filters.push({ + id: 'author.login', + label: App.config.i18n.AUTHOR_LOGIN, + type: 'string', + operators: stringOperators, + optgroup:App.config.i18n.QUERY_GROUP_AUTHOR, + realType:'string' + }); + + filters.push({ + id: 'author.name', + label: App.config.i18n.AUTHOR_NAME, + type: 'string', + operators: stringOperators, + optgroup:App.config.i18n.QUERY_GROUP_AUTHOR, + realType:'string' + }); + + // Part Revision + + filters.push({ + id: 'pr.version', + label: App.config.i18n.VERSION, + type: 'string', + operators: stringOperators, + optgroup:App.config.i18n.QUERY_GROUP_PART_REVISION, + realType:'string' + }); + + filters.push({ + id: 'pr.modificationDate', + label: App.config.i18n.MODIFICATION_DATE, + type: 'date', + input:dateInput, + operators: dateOperators, + optgroup:App.config.i18n.QUERY_GROUP_PART_REVISION, + realType:'date' + }); + + filters.push({ + id: 'pr.creationDate', + label: App.config.i18n.CREATION_DATE, + type: 'date', + input:dateInput, + operators: dateOperators, + optgroup:App.config.i18n.QUERY_GROUP_PART_REVISION, + realType:'date' + }); + + filters.push({ + id: 'pr.checkOutDate', + label: App.config.i18n.CHECKOUT_DATE, + type: 'date', + input:dateInput, + operators: dateOperators, + optgroup:App.config.i18n.QUERY_GROUP_PART_REVISION, + realType:'date' + }); + + filters.push({ + id: 'pr.checkInDate', + label: App.config.i18n.CHECKIN_DATE, + type: 'date', + input:dateInput, + operators: dateOperators, + optgroup:App.config.i18n.QUERY_GROUP_PART_REVISION, + realType:'date' + }); + + filters.push({ + id: 'pr.lifeCycleState', + label: App.config.i18n.LIFECYCLE_STATE, + type: 'string', + operators: stringOperators, + optgroup:App.config.i18n.QUERY_GROUP_PART_REVISION, + realType:'string' + }); + + filters.push({ + id: 'pr.status', + label: App.config.i18n.STATUS, + type: 'string', + operators: statusOperators, + optgroup:App.config.i18n.QUERY_GROUP_PART_REVISION, + realType:'string', + input : 'select', + values : [ + {'WIP':App.config.i18n.QUERY_FILTER_STATUS_WIP}, + {'RELEASED':App.config.i18n.QUERY_FILTER_STATUS_RELEASED}, + {'OBSOLETE':App.config.i18n.QUERY_FILTER_STATUS_OBSOLETE} + ] + }); + + filters.push({ + id: 'pr.linkedDocuments', + label: App.config.i18n.LINKED_DOCUMENTS, + type: 'string', + operators: stringOperators, + optgroup:App.config.i18n.QUERY_GROUP_PART_REVISION, + realType:'linkedDocuments' + }); + + var fields = []; + + var types = { + BOOLEAN: 'boolean', + DATE: 'date', + NUMBER: 'double', + TEXT: 'string', + LONG_TEXT: 'string', + URL: 'string', + LOV: 'lov' + }; + + var groups = [ + {id: 'pm', name: App.config.i18n.QUERY_GROUP_PART_MASTER}, + {id: 'pr', name: App.config.i18n.QUERY_GROUP_PART_REVISION}, + {id: 'pi', name: App.config.i18n.QUERY_GROUP_PRODUCT}, + {id: 'author', name: App.config.i18n.QUERY_GROUP_AUTHOR}, + {id: 'attr-TEXT', name: App.config.i18n.QUERY_GROUP_ATTRIBUTE_STRING}, + {id: 'attr-LONG_TEXT', name: App.config.i18n.QUERY_GROUP_ATTRIBUTE_LONG_STRING}, + {id: 'attr-URL', name: App.config.i18n.QUERY_GROUP_ATTRIBUTE_URL}, + {id: 'attr-LOV', name: App.config.i18n.QUERY_GROUP_ATTRIBUTE_LOV}, + {id: 'attr-NUMBER', name: App.config.i18n.QUERY_GROUP_ATTRIBUTE_NUMBER}, + {id: 'attr-DATE', name: App.config.i18n.QUERY_GROUP_ATTRIBUTE_DATE}, + {id: 'attr-BOOLEAN', name: App.config.i18n.QUERY_GROUP_ATTRIBUTE_BOOLEAN}, + {id: 'pd-attrs', name: App.config.i18n.QUERY_GROUP_PATH_DATA_ATTRIBUTE}, + {id: 'ctx', name: App.config.i18n.QUERY_GROUP_CONTEXT} + ]; + + filters.map(function(filter){ + fields.push({ + name:filter.label, + value:filter.id, + group:filter.id.substring(0,filter.id.indexOf('.')) + }); + }); + + var contextFields = []; + + contextFields.push({ + name:App.config.i18n.QUERY_DEPTH, + value:'ctx.depth', + group:'ctx' + }); + + contextFields.push({ + value: 'ctx.serialNumber', + name: App.config.i18n.SERIAL_NUMBER, + group: 'ctx' + }); + + contextFields.push({ + value: 'ctx.productId', + name: App.config.i18n.PRODUCT_NAME, + group: 'ctx' + }); + + contextFields.push({ + value: 'ctx.amount', + name: App.config.i18n.AMOUNT, + group: 'ctx' + }); + + contextFields.push({ + value: 'ctx.p2p.source', + name: App.config.i18n.PATH_TO_PATH_SOURCE_LINK, + group: 'ctx' + }); + + contextFields.push({ + value: 'ctx.p2p.target', + name: App.config.i18n.PATH_TO_PATH_TARGET_LINK, + group: 'ctx' + }); + + return { + dateInput: dateInput, + filters : filters, + fields : fields, + groups : groups, + types : types, + contextFields : contextFields, + + stringOperators: stringOperators, + dateOperators : dateOperators, + lovOperators : lovOperators, + booleanOperators : booleanOperators, + tagOperators: tagOperators, + numberOperators: numberOperators + }; + +}); diff --git a/docdoku-web-front/app/js/utils/url-utils.js b/docdoku-web-front/app/js/utils/url-utils.js new file mode 100644 index 0000000000..be0f176ec9 --- /dev/null +++ b/docdoku-web-front/app/js/utils/url-utils.js @@ -0,0 +1,18 @@ +define(function(){ + 'use strict'; + return { + getParameterByName:function(name) { + var url = window.location.href; + name = name.replace(/[\[\]]/g, '\\$&'); + var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'), + results = regex.exec(url); + if (!results) { + return null; + } + if (!results[2]){ + return ''; + } + return decodeURIComponent(results[2].replace(/\+/g, ' ')); + } + }; +}); diff --git a/docdoku-web-front/app/js/utils/utils.prototype.js b/docdoku-web-front/app/js/utils/utils.prototype.js new file mode 100644 index 0000000000..357ccf99b4 --- /dev/null +++ b/docdoku-web-front/app/js/utils/utils.prototype.js @@ -0,0 +1,10 @@ +String.prototype.replaceUrl = function () { + 'use strict'; + var exp = /(\b(https?|ftp|file):\/\/[\-A-Z0-9+&@#\/%?=~_|!:,.;]*[\-A-Z0-9+&@#\/%=~_|])/ig; + return this.replace(exp, '$1'); +}; + +String.prototype.nl2br = function () { + 'use strict'; + return this.replace(/\n/g, '
                                '); +}; diff --git a/docdoku-web-front/app/less/account-management/content.less b/docdoku-web-front/app/less/account-management/content.less new file mode 100644 index 0000000000..850df25597 --- /dev/null +++ b/docdoku-web-front/app/less/account-management/content.less @@ -0,0 +1,9 @@ +#account-management-content { + width: 85%; + height: 100%; + overflow: auto; + position: absolute; + right: 0; + top: 0; + z-index: 0; +} diff --git a/docdoku-web-front/app/less/account-management/menu.less b/docdoku-web-front/app/less/account-management/menu.less new file mode 100644 index 0000000000..814f2d2956 --- /dev/null +++ b/docdoku-web-front/app/less/account-management/menu.less @@ -0,0 +1,186 @@ +#account-management-menu { + + position: relative; + /*-webkit-box-shadow: 0px 1px 3px #9b9b9b; + box-shadow: 0px 1px 3px #9b9b9b;*/ + float: left; + background-color: #f5f5f5; + height: 100%; + min-height: 100%; + width: 15%; + min-width: 15%; + max-width: 55%; + border: none; + border-radius: none; + border-right: 1px solid rgba(63, 95, 153, 0.24); + padding: 0; + margin: 0; + z-index: 1; + + .nav { + margin: 10px 0 5% 6px; + padding: 0; + height: 95%; + height: -moz-calc(~"95% - 68px"); + height: -webkit-calc(~"95% - 68px"); + height: calc(~"95% - 68px"); + overflow: auto; + } + + .well { + background: none; + box-shadow: none; + border: none; + } + + .nav-header { + font-size: 12px; + padding: 3px 10px; + display: block; + font-weight: bold; + line-height: 18px; + color: #999999; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-transform: uppercase; + } + + .nav-list-entry { + position: relative; + padding: 0 40px 0 6px; + height: 26px; + display: block; + + &.move-part-into { + border: 1px dashed #8fbc8f; + height: 25px; + margin-top: -1px; + padding-left: 5px; + } + + .icon { + float: left; + } + + > a { + overflow: hidden; + text-overflow: ellipsis; + line-height: 26px; + white-space: nowrap; + width: 100%; + float: left; + + .status { + float: left; + } + } + + > .btn-group { + right: 0; + position: absolute; + } + + .dropdown-toggle { + border-radius: 0; + margin: 0 0 0 8px; + display: none; + border: none; + background: none; + -webkit-box-shadow: none; + box-shadow: none; + padding: 3px 12px; + .caret { + border-top-color: #000000; + opacity: .5; + } + } + + .dropdown-toggle:hover { + background: #CCC; + .caret { + opacity: 1; + } + } + + .dropdown-menu { + border-radius: 0; + padding: 0 0 0 0; + margin: -1px 1px 0 0; + right: 0; + left: auto; + max-width: 250px; + + > li { + + width: 100%; + padding: 0; + + > a { + font-size: 13px; + padding: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + } + + } + li.new-folder .icon { + .plm-icon-menu-folder-new; + margin: 4px 6px 4px 6px; + } + li.edit .icon { + .plm-icon-menu-edit; + margin: 4px 6px 4px 6px; + } + li.delete .icon { + .plm-icon-menu-delete; + margin: 4px 6px 4px 6px; + } + } + + .nav-checkedOut-number-item { + margin-top: -22px; + } + } + + .nav-list-entry:hover { + > a { + text-decoration: none; + } + background-color: #EEE; + .dropdown-toggle { + display: block; + } + } + + .nav-list-entry.active, .nav-list-entry.active > a { + /*text-shadow: 0 -1px 0 @grayLight;*/ + color: #FFF; + background-color: @plm-secondary-color; + .badge { + background-color: #fff; + color: @plm-secondary-color; + } + } + + .items { + overflow: visible; + list-style-type: none; + margin-left: 6px; + padding: 0; + } + + .ui-resizable-handle { + + opacity: 0; + position: absolute; + bottom: 65px; + right: 0; + top: 0; + z-index: 1000; + width: 8px; + cursor: e-resize; + display: block !important; + + } +} diff --git a/docdoku-web-front/app/less/account-management/style.less b/docdoku-web-front/app/less/account-management/style.less new file mode 100644 index 0000000000..8a1f6b3244 --- /dev/null +++ b/docdoku-web-front/app/less/account-management/style.less @@ -0,0 +1,4 @@ +@import "../common/style"; +@import "../modules/style"; +@import "menu"; +@import "content"; diff --git a/docdoku-web-front/app/less/change-management/content.less b/docdoku-web-front/app/less/change-management/content.less new file mode 100644 index 0000000000..7b19e80818 --- /dev/null +++ b/docdoku-web-front/app/less/change-management/content.less @@ -0,0 +1,15 @@ +#change-management-content { + width: 85%; + height: 100%; + overflow: auto; + position: absolute; + right: 0; + top: 0; + z-index: 0; + /*-webkit-box-shadow: 0px 1px 3px #9b9b9b; + box-shadow: 0px 1px 3px #9b9b9b;*/ + input { + height: 22px; + margin: 8px; + } +} diff --git a/docdoku-web-front/app/less/change-management/link.less b/docdoku-web-front/app/less/change-management/link.less new file mode 100644 index 0000000000..093c4ca27a --- /dev/null +++ b/docdoku-web-front/app/less/change-management/link.less @@ -0,0 +1,3 @@ +.affected-links { + min-height: 130px; +} diff --git a/docdoku-web-front/app/less/change-management/menu.less b/docdoku-web-front/app/less/change-management/menu.less new file mode 100644 index 0000000000..ffdbf58b24 --- /dev/null +++ b/docdoku-web-front/app/less/change-management/menu.less @@ -0,0 +1,238 @@ +#change-management-menu { + position: relative; + /* -webkit-box-shadow: 0px 1px 3px #9b9b9b; + box-shadow: 0px 1px 3px #9b9b9b;*/ + float: left; + background-color: #f5f5f5; + height: 100%; + min-height: 100%; + width: 15%; + min-width: 15%; + max-width: 55%; + border: none; + border-radius: none; + border-right: 1px solid rgba(63, 95, 153, 0.24); + padding: 0; + margin: 0; + z-index: 1; + + .nav { + margin: 10px 0 5% 6px; + padding: 0; + height: 95%; + overflow: auto; + } + + .well { + background: none; + box-shadow: none; + border: none; + } + + .nav-header { + font-size: 12px; + padding: 3px 10px; + } + + .nav-list-entry { + position: relative; + padding: 0 40px 0 6px; + height: 26px; + display: block; + + &.move-doc-into { + border: 1px dashed #8fbc8f; + height: 25px; + margin-top: -1px; + padding-left: 5px; + } + + .icon { + float: left; + } + + > a { + overflow: hidden; + text-overflow: ellipsis; + line-height: 26px; + white-space: nowrap; + width: 100%; + float: left; + + .status { + float: left; + } + } + + > .btn-group { + right: 0; + position: absolute; + } + + .dropdown-toggle { + border-radius: 0; + margin: 0 0 0 8px; + display: none; + border: none; + background: none; + box-shadow: none; + + .caret { + border-top-color: #000000; + opacity: .5; + } + } + + .dropdown-toggle:hover { + background: #CCC; + .caret { + opacity: 1; + } + } + + .dropdown-menu { + border-radius: 0; + padding: 0 10px 0 0; + min-width: auto; + margin: -1px 0px 0 0; + right: 0; + left: auto; + + a:hover { + text-shadow: 0 1px 0 white; + } + + .new-folder .icon { + .plm-icon-menu-folder-new; + margin: 4px 6px 4px 0; + } + .edit .icon { + .plm-icon-menu-edit; + margin: 4px 6px 4px 0; + } + .delete .icon { + .plm-icon-menu-delete; + margin: 4px 6px 4px 0; + } + + } + } + + .nav-list-entry:hover { + > a { + text-decoration: none; + } + background-color: #EEE; + .dropdown-toggle { + display: block; + } + } + + .nav-list-entry.active, .nav-list-entry.active > a { + /*text-shadow: 0 -1px 0 @grayLight;*/ + color: #FFF; + background-color: @plm-secondary-color; + } + + .items { + padding: 0; + overflow: visible; + list-style-type: none; + margin-left: 6px; + } + + .ui-resizable-handle { + + opacity: 0; + position: absolute; + bottom: 65px; + right: 0; + top: 0; + z-index: 1000; + width: 8px; + cursor: e-resize; + display: block !important; + + } +} + +#folder-nav { + *zoom: 1; + &:before, + &:after { + display: table; + content: ""; + // Fixes Opera/contenteditable bug: + // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952 + line-height: 0; + } + &:after { + clear: both; + } + + .folder { + .status { + .plm-icon-nav-folder-status-closed; + &:hover { + opacity: 1; + } + } + .icon { + .plm-icon-nav-folder; + } + } + + .folder.open > div > a { + > .status { + .plm-icon-nav-folder-status-opened; + &:hover { + opacity: 1; + } + } + + > .icon { + .plm-icon-nav-folder-opened; + } + } + + .folder.home > .header > a .icon { + .plm-icon-nav-folder-home; + } + + .items { + border-left: 1px solid rgba(155, 155, 155, 0.35); + overflow: visible; + list-style-type: none; + margin-left: 11px; + padding: 0; + } + > .items { + margin-left: 6px; + border-left: 1px solid rgba(155, 155, 155, 0); + } +} + +#tags-nav { + *zoom: 1; + &:before, + &:after { + display: table; + content: ""; + // Fixes Opera/contenteditable bug: + // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952 + line-height: 0; + } + &:after { + clear: both; + } +} + +#product_container { + a.product-management { + i.fa-briefcase { + float: left; + font-size: 12px; + margin: 5px 5px 0 0; + } + } +} diff --git a/docdoku-web-front/app/less/change-management/style.less b/docdoku-web-front/app/less/change-management/style.less new file mode 100644 index 0000000000..56cbcc1f0f --- /dev/null +++ b/docdoku-web-front/app/less/change-management/style.less @@ -0,0 +1,44 @@ +@import "../common/style"; +@import "../common/tags"; +@import "../modules/style"; +@import "menu"; +@import "content"; +@import "workflow_models"; +@import "link"; +@import (inline) "../../bower_components/selectize/dist/css/selectize.default.css"; + +#iteration-attributes { + margin-left: -15px; +} + +[id^="editable-list-add-item-view"] { + margin-top: -10px; + margin-bottom: 10px; +} + +[id^="editable-list-add-item-view"] i { + font-size: 13px; + margin-top: 3px; + margin-left: 5px; +} + +.editable-list li { + margin: 4px 4px 10px 4px; +} + +td.document-master-subscriptions { + i { + color: orange; + cursor: pointer; + } +} + +td.document-master-share { + i { + cursor: pointer; + &:hover { + color: #333; + } + } +} + diff --git a/docdoku-web-front/app/less/change-management/workflow_models.less b/docdoku-web-front/app/less/change-management/workflow_models.less new file mode 100644 index 0000000000..781f8f93c8 --- /dev/null +++ b/docdoku-web-front/app/less/change-management/workflow_models.less @@ -0,0 +1,412 @@ +input#workflow-name { + font-size: 15px; + font-weight: bold; +} + +input#workflow-name:invalid { + background-color: #ffffff; +} + +button#copy-workflow { + float: right; + font-size: 18px; +} + +div#modal-copy-workflow { + i.fa-files-o { + margin: 4px 6px 0px 0px; + font-size: 18px; + float: left; + } +} + +div#workflow-editor { + padding: 15px 5px; + + ::-webkit-input-placeholder, ::-moz-placeholder { + text-shadow: none; + } + + div#start, div#end { + margin: 2px 8px; + /* border-radius: 4px;*/ + border: 2px solid #999; + height: 68px; + background-color: #999; + font-size: 20px; + color: #5A5A5A; + + i { + width: 18px; + display: block; + margin: 8px auto; + } + + input { + text-align: center; + font-weight: bold; + font-size: 18px; + width: 58px; + margin: 0; + display: block; + } + + input#final-state { + width: 175px; + } + } + + button#add-activity { + display: block; + padding: 0px; + margin: 2px 8px; + width: 38px; + height: 72px; + border: 2px solid #999; + background-color: #999; + font-size: 20px; + color: #5A5A5A; + /* + border-radius: 4px; + */ + } + + button { + box-shadow: none; + background: none; + border: none; + } + + ul#activity-list { + display: box; + display: -moz-box; + display: -webkit-box; + margin: 0; + list-style: none; + + li.activity-section-placeholder { + border: 1px dashed red; + width: 50px; + height: 50px; + display: block; + } + + li.activity-section { + text-align: center; + + div.activity { + display: inline-block; + margin: 0px 8px; + + div.activity-topbar { + position: relative; + border: 2px solid #999; + margin: 0px 0px 8px 0px; + text-align: left; + background-color: #999; + //border-radius: 4px; + cursor: move; + + button.switch-activity { + float: left; + margin: 2px; + width: 32px; + height: 32px; + background-repeat: no-repeat; + background-size: contain; + background-position: 0; + } + + span.activity-type { + margin: 2px; + line-height: 34px; + color: white; + } + + button.delete-activity { + top: 0px; + right: 0px; + position: absolute; + width: 32px; + height: 32px; + color: #5A5A5A; + font-size: 16px; + } + + input.activity-state { + margin: 0px auto; + //border-radius: 4px; + text-align: center; + padding: 4px 0px; + display: block; + width: 99%; + font-size: 18px; + font-weight: bold; + } + + div.relaunchActivitySelector-wrapper { + height: 32px; + + i { + float: left; + margin: 10px 2px 2px 8px; + } + select.relaunchActivitySelector { + width: 80%; + float: left; + margin: 2px; + background: transparent; + border: none; + outline: none; + color: white; + option { + background: #999; + } + } + } + } + + button.add-task { + /*box-shadow: 0px 0px 2px #3F5F99;*/ + background-color: #659FD8; + display: block; + margin: 0px auto 0px auto; + padding: 10px 0px; + width: 38px; + height: 38px; + font-size: 20px; + color: #25598F; + // border-radius: 4px; + } + + ul.task-list { + padding: 0px; + margin: 0px; + list-style: none; + + li.task-section { + padding: 0px 8px 8px 0px; + + div.task { + border: 1px solid #ccc; + text-align: left; + background-color: white; + /*box-shadow: 0px 0px 2px #3F5F99;*/ + // border-radius: 4px; + + &.fold { + background-color: #659FD8; + + div.task-topbar { + background-color: transparent; + + button.minimize-task { + display: none; + } + + i.fa-bars { + float: left; + width: 32px; + padding: 10px 0 8px 0; + cursor: move; + color: #25598F; + font-size: 17px; + text-align: center; + } + + input.task-name { + display: none; + } + + p.task-name { + height: 34px; + min-width: 90px; + font-weight: bold; + line-height: 32px; + overflow: hidden; + display: inline-block; + cursor: pointer; + box-shadow: none; + color: white; + padding: 2px 6px 2px 0; + margin: 0; + } + } + + div.task-content { + display: none; + } + } + + &.unfold { + + i.fa-bars { + display: none; + } + + button.minimize-task { + height: 38px; + width: 32px; + float: left; + color: #25598F; + padding: 0; + font-size: 15px; + } + + p.task-name { + display: none; + } + + input.task-name { + height: 24px; + width: 146px; + font-weight: bold; + margin: 2px 0 2px 2px; + border-color: rgba(0, 0, 0, 0.45); + background-color: rgba(255, 255, 255, 0.9); + } + } + + div.task-topbar { + height: 38px; + text-align: left; + background-color: #659FD8; + // border-radius: 4px 4px 0px 0px; + + input.task-name { + height: 24px; + font-weight: bold; + margin: 2px 0px 2px 2px; + } + } + + div.task-content { + > label { + font-size: 12px; + font-weight: bold; + color: #659FD8; + line-height: 25px; + margin: 4px 4px 0px 4px; + } + + > textarea { + margin: 0px 0px 4px 4px; + } + + > input { + width: 188px; + padding: 3px 0 3px 0; + margin: 0 4px 4px 4px; + border: 1px solid #cccccc; + /* -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px;*/ + } + + > select { + width: 190px; + margin: 0 4px 4px 4px; + } + + .add-role, + .new-role { + width: 22px; + height: 22px; + margin-right: 4px; + text-align: center; + } + + .new-role, .role-select { + display: inline-block; + } + .add-role, .role-input { + display: none; + } + + &.new-role { + .new-role, .role-select { + display: none; + } + .add-role, .role-input { + display: inline-block; + } + } + } + + button.delete-task { + padding: 0; + float: right; + width: 32px; + height: 38px; + color: #25598F; + font-size: 15px; + } + } + } + } + + &.SEQUENTIAL { + div.activity-topbar { + + input, label { + &.tasksToComplete { + display: none; + } + } + + button.switch-activity { + background-image: url("../images/workflow/sequential.png"); + } + } + + ul.task-list { + display: -webkit-inline-box; + display: -moz-inline-box; + } + + button.add-task { + display: inline-block; + vertical-align: top; + margin-left: -4px; + } + } + + &.PARALLEL { + div.activity-topbar { + + label.tasksToComplete { + color: white; + width: 65px; + display: inline-block; + margin: 4px 0px 2px 2px; + line-height: 13px; + } + + input.tasksToComplete { + vertical-align: top; + height: 15px; + margin: 5px 0px 2px 0px; + width: 30px; + } + + button.switch-activity { + -webkit-transform: rotate(90deg); + background-image: url("../images/workflow/parallel.png"); + } + } + + ul.task-list { + + li.task-section { + padding: 0px 0px 8px 0px; + } + + p.task-name { + max-width: 139px !important; + } + } + } + } + } + } +} diff --git a/docdoku-web-front/app/less/common/acl.less b/docdoku-web-front/app/less/common/acl.less new file mode 100644 index 0000000000..5b9ba4b7f5 --- /dev/null +++ b/docdoku-web-front/app/less/common/acl.less @@ -0,0 +1,76 @@ +.acl-info, .document-acl-info, .part-acl-info, .template-acl-info, .workflow-acl-info { + i { + &.read-only { + color: #ffb600; + } + + &.full-access { + color: #388052; + } + } +} + +.acl-item, .membership-item { + &.well { + padding: 6px; + margin-bottom: 6px; + + .acl-key { + box-shadow: none; + outline: none; + font-weight: bold; + width: 150px; + border: none; + background: transparent; + } + + .acl-radios { + display: inline-block; + + input[type=radio] { + display: none; + + + label { + margin-left: 10px; + display: inline-block; + opacity: 0.35; + &:hover { + opacity: 0.8; + } + } + + + label > i { + display: inline-block; + vertical-align: middle; + font-size: 13px; + } + + &[disabled="disabled"] + label { + opacity: 0; + } + + &:checked + label { + opacity: 1; + } + + &[value="FORBIDDEN"] + label > i { + color: #ff675b; + } + &[value="READ_ONLY"] + label > i { + color: #ffb600; + } + &[value="FULL_ACCESS"] + label > i { + color: #388052; + } + } + + } + } + +} + +.use-acl { + .acl-switch { + + } +} diff --git a/docdoku-web-front/app/less/common/action_buttons.less b/docdoku-web-front/app/less/common/action_buttons.less new file mode 100644 index 0000000000..876cfe6ef3 --- /dev/null +++ b/docdoku-web-front/app/less/common/action_buttons.less @@ -0,0 +1,12 @@ +.action-checkin-checkout { + text-align: center; + margin: 15px 0; + + .action-checkin, .action-checkout, .action-undocheckout { + cursor: pointer; + + } + .fa { + padding-right: 10px; + } +} diff --git a/docdoku-web-front/app/less/common/actions.less b/docdoku-web-front/app/less/common/actions.less new file mode 100644 index 0000000000..c02c4df332 --- /dev/null +++ b/docdoku-web-front/app/less/common/actions.less @@ -0,0 +1,135 @@ +.actions { + min-height: 48px; + border-radius: 0; + /* -webkit-box-shadow: 0 0px 3px #9b9b9b; + -moz-box-shadow: 0 0px 3px #9b9b9b; + box-shadow: 0 0px 3px #9b9b9b;*/ + + margin: 0; + padding: 0 20px; + border: none; + border-bottom: 1px solid rgba(63, 95, 153, 0.24); + + > .btn { + height: 32px; + margin: 8px 8px 8px 8px; + } + + .btn-group { + margin: 8px; + } + .btn-group .btn { + float: none; + } + .new-document .icon { + .plm-icon-action-document-new; + } + .new-template .icon { + .plm-icon-action-template-new; + } + .new-product .icon { + .plm-icon-action-product-new; + } + .new-configuration .icon { + .plm-icon-action-configuration-new; + } + .udf .icon { + .plm-icon-action-udf; + } + .new-part .icon { + .plm-icon-action-part-new; + } + .new-workflow .icon { + .plm-icon-action-workflow-new; + } + .new-issue .icon { + .plm-icon-action-issue-new; + } + .new-request .icon { + .plm-icon-action-request-new; + } + .new-order .icon { + .plm-icon-action-order-new; + } + .new-milestone .icon { + .plm-icon-action-milestone-new; + } + .new-product-instance .icon { + .plm-icon-action-new-product-intances; + } + .roles .icon { + .plm-icon-action-workflow-roles; + } + .list-lov .icon { + .plm-icon-action-lov; + } + + .delete { + display: none; + .icon { + .plm-icon-action-delete; + } + } + + .duplicate { + display: none; + .icon { + .plm-icon-action-duplicate; + } + } + + .new-baseline { + display: none; + .icon { + .plm-icon-action-new-baseline; + } + } + + .new-version { + display: none; + .icon { + .plm-icon-action-new-version; + } + } + .new-release { + display: none; + .icon { + .plm-icon-action-new-release; + } + } + .mark-as-obsolete { + display: none; + .icon { + .plm-icon-action-mark-as-obsolete; + } + } + .edit-acl { + display: none; + .icon { + .plm-icon-action-acl; + } + } + .checkout-group { + display: none; + } + .checkout .icon { + .plm-icon-action-checkout; + } + .undocheckout .icon { + .plm-icon-action-undocheckout; + } + .checkin .icon { + .plm-icon-action-checkin; + } + .tags { + display: none; + } + .tags .icon { + .plm-icon-action-tags; + } + + .import .icon { + .plm-icon-action-import; + } + +} diff --git a/docdoku-web-front/app/less/common/alert.less b/docdoku-web-front/app/less/common/alert.less new file mode 100644 index 0000000000..034e8ac4db --- /dev/null +++ b/docdoku-web-front/app/less/common/alert.less @@ -0,0 +1,43 @@ +.Alert { + margin: 0; + &-closeBtn { + + } + &-title { + display: inline-block; + + .alert-info & { + &:before { + display: inline; + content: "Info ! "; + font-weight: bold; + padding-right: 48px; + font-size: 18px; + } + } + + .alert-warning & { + &:before { + display: inline; + content: "Warning ! "; + font-weight: bold; + padding-right: 10px; + font-size: 18px; + } + } + + .alert-error & { + &:before { + display: inline; + content: "Error ! "; + font-weight: bold; + padding-right: 38px; + font-size: 18px; + } + } + } + &-message { + display: inline-block; + margin: 0; + } +} diff --git a/docdoku-web-front/app/less/common/attributes.less b/docdoku-web-front/app/less/common/attributes.less new file mode 100644 index 0000000000..01743a8486 --- /dev/null +++ b/docdoku-web-front/app/less/common/attributes.less @@ -0,0 +1,165 @@ +.attributes-edit { + .add { + margin-bottom: 12px; + } + .sortable-handler { + opacity: 0.2; + cursor: move; + } + + .fa.fa-bars.sortable-handler.invisible { + visibility: hidden; + } + + .fa.fa-bars.sortable-handler.shift-margin { + margin-right: 15px; + } + + .fa-times { + float: right; + font-size: 15px; + line-height: 18px; + color: black; + text-shadow: 0 1px 0 white; + opacity: 0.2; + + &:hover { + text-decoration: none; + color: #F00; + opacity: 1; + } + } + + .fa.fa-times.invisible { + visibility: hidden; + } + + .list-item { + padding: 6px; + margin-bottom: 6px; + + > * { + display: inline-block; + vertical-align: middle; + margin: 0 2px; + > .value { + max-width: 153px; + } + > select { + max-width: 134px; + } + > .name { + width: auto; + max-width: 109px; + } + } + &.highlight { + border: 1px dashed #0088cc; + } + } + .controls.boolean { + > label { + height: 12px; + > input { + &:checked ~ .false { + display: none; + } + &:not(:checked) ~ .true { + display: none; + } + } + } + input[type="checkbox"].input-xlarge.value { + height: 18px; + } + } + + i.fa + div.controls > .input-xlarge.name { + max-width: 100px; + } + + .controls { + input[type="date"] { + display: -webkit-inline-box; + max-width: 153px; + } + + &.shift-margin { + margin-left: 3px; + } + + input[disabled="disabled"].input-xlarge.name { + margin-right: 9px; + max-width: 120px; + padding-left: 5px; + } + + select.lovItemValues { + max-width: 167px; + width: auto; + } + + } + + .controls.listOfValues { + width: 124px; + margin-left: 12px; + } + + .attribute-link { + word-break: break-all; + } + + div.type { + width: 126px; + max-width: 139px; + text-transform: capitalize; + margin: 0 1px 0 0; + } + + input[disabled] { + padding: 4px 0; + border: none; + background: none; + box-shadow: none; + + &.name { + font-weight: bold; + } + } +} + +div.attribute-edit { + @leftWidth: 80px; + @inputWidth: 170px; + @booleanWidth: 80px; + display: inline-block; + select { + width: @leftWidth; + } + + input { + width: @inputWidth; + + } + input.boolean { + width: @booleanWidth; + } + + div { + display: inline-block; + } + div.stroke { + + color: red; + + div.type { + width: @leftWidth; + } + div { + text-decoration: line-through; + width: @inputWidth; + } + + } +} diff --git a/docdoku-web-front/app/less/common/bootstrap_combobox.less b/docdoku-web-front/app/less/common/bootstrap_combobox.less new file mode 100644 index 0000000000..fa04f72f07 --- /dev/null +++ b/docdoku-web-front/app/less/common/bootstrap_combobox.less @@ -0,0 +1,55 @@ +.form-search, +.form-inline { + .combobox-container { + display: inline-block; + margin-bottom: 0; + vertical-align: top; + .input-group-addon { + width: auto; + } + } +} + +.combobox-selected .caret { + display: none; +} + +/* :not doesn't work in IE8 */ +.combobox-container:not(.combobox-selected) .glyphicon-remove { + display: none; +} + +.typeahead-long { + max-height: 300px; + overflow-y: auto; +} + +.control-group.error .combobox-container { + .add-on { + color: #B94A48; + border-color: #B94A48; + } + .caret { + border-top-color: #B94A48; + } +} + +.control-group.warning .combobox-container { + .add-on { + color: #C09853; + border-color: #C09853; + } + .caret { + border-top-color: #C09853; + } +} + +.control-group.success .combobox-container { + .add-on { + color: #468847; + border-color: #468847; + } + .caret { + border-top-color: #468847; + } +} diff --git a/docdoku-web-front/app/less/common/bootstrap_switch.less b/docdoku-web-front/app/less/common/bootstrap_switch.less new file mode 100644 index 0000000000..144360c423 --- /dev/null +++ b/docdoku-web-front/app/less/common/bootstrap_switch.less @@ -0,0 +1,291 @@ +.has-switch { + display: inline-block; + cursor: pointer; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + border: 1px solid; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + position: relative; + text-align: left; + overflow: hidden; + line-height: 8px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + min-width: 100px; +} + +.has-switch.switch-mini { + min-width: 72px; +} + +.has-switch.switch-small { + min-width: 80px; +} + +.has-switch.switch-large { + min-width: 120px; +} + +.has-switch.deactivate { + opacity: 0.5; + filter: alpha(opacity=50); + cursor: default !important; +} + +.has-switch.deactivate label, +.has-switch.deactivate span { + cursor: default !important; +} + +.has-switch > div { + display: inline-block; + width: 150%; + position: relative; + top: 0; +} + +.has-switch > div.switch-animate { + -webkit-transition: left 0.5s; + -moz-transition: left 0.5s; + -o-transition: left 0.5s; + transition: left 0.5s; +} + +.has-switch > div.switch-off { + left: -50%; +} + +.has-switch > div.switch-on { + left: 0%; +} + +.has-switch input[type=checkbox] { + display: none; +} + +.has-switch span, +.has-switch label { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + cursor: pointer; + position: relative; + display: inline-block; + height: 100%; + padding-bottom: 4px; + padding-top: 4px; + font-size: 14px; + line-height: 20px; +} + +.has-switch span.switch-mini, +.has-switch label.switch-mini { + padding-bottom: 4px; + padding-top: 4px; + font-size: 10px; + line-height: 9px; +} + +.has-switch span.switch-small, +.has-switch label.switch-small { + padding-bottom: 3px; + padding-top: 3px; + font-size: 12px; + line-height: 18px; +} + +.has-switch span.switch-large, +.has-switch label.switch-large { + padding-bottom: 9px; + padding-top: 9px; + font-size: 16px; + line-height: normal; +} + +.has-switch label { + text-align: center; + margin-top: -1px; + margin-bottom: -1px; + z-index: 100; + width: 34%; + border-left: 1px solid #bbbbbb; + border-right: 1px solid #bbbbbb; + color: #ffffff; + text-shadow: none; + background: #f5f5f5; +} + +.has-switch label:hover, +.has-switch label:active, +.has-switch label.active, +.has-switch label.disabled, +.has-switch label[disabled] { + color: #ffffff; + background-color: #e6e6e6; + *background-color: #d9d9d9; +} + +.has-switch label:active, +.has-switch label.active { + background-color: #cccccc \9; +} + +.has-switch label i { + color: #000; + text-shadow: 0 1px 0 #fff; + line-height: 18px; + pointer-events: none; +} + +.has-switch span { + text-align: center; + z-index: 1; + width: 33%; +} + +.has-switch span.switch-left { + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + border-top-left-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + border-bottom-left-radius: 4px; +} + +.has-switch span.switch-right { + color: #333333; + text-shadow:none; + background: #f0f0f0; +} + +.has-switch span.switch-right:hover, +.has-switch span.switch-right:active, +.has-switch span.switch-right.active, +.has-switch span.switch-right.disabled, +.has-switch span.switch-right[disabled] { + color: #333333; + background-color: #ffffff; + *background-color: #f2f2f2; +} + +.has-switch span.switch-right:active, +.has-switch span.switch-right.active { + background-color: #e6e6e6 \9; +} + +.has-switch span.switch-primary, +.has-switch span.switch-left { + color: #ffffff; + text-shadow: none; + background: #005fcc; +} + +.has-switch span.switch-primary:hover, +.has-switch span.switch-left:hover, +.has-switch span.switch-primary:active, +.has-switch span.switch-left:active, +.has-switch span.switch-primary.active, +.has-switch span.switch-left.active, +.has-switch span.switch-primary.disabled, +.has-switch span.switch-left.disabled, +.has-switch span.switch-primary[disabled], +.has-switch span.switch-left[disabled] { + color: #ffffff; + background-color: #0088cc; + *background-color: #0077b3; +} + +.has-switch span.switch-primary:active, +.has-switch span.switch-left:active, +.has-switch span.switch-primary.active, +.has-switch span.switch-left.active { + background-color: #006699 \9; +} + +.has-switch span.switch-info { + color: #ffffff; + text-shadow: none; + background: #41a7c5; +} + +.has-switch span.switch-info:hover, +.has-switch span.switch-info:active, +.has-switch span.switch-info.active, +.has-switch span.switch-info.disabled, +.has-switch span.switch-info[disabled] { + color: #ffffff; + background-color: #5bc0de; + *background-color: #46b8da; +} + +.has-switch span.switch-info:active, +.has-switch span.switch-info.active { + background-color: #31b0d5 \9; +} + +.has-switch span.switch-success { + color: #ffffff; + text-shadow: none; + background: #58b058; +} + +.has-switch span.switch-success:hover, +.has-switch span.switch-success:active, +.has-switch span.switch-success.active, +.has-switch span.switch-success.disabled, +.has-switch span.switch-success[disabled] { + color: #ffffff; + background-color: #62c462; + *background-color: #4fbd4f; +} + +.has-switch span.switch-success:active, +.has-switch span.switch-success.active { + background-color: #42b142 \9; +} + +.has-switch span.switch-warning { + color: #ffffff; + text-shadow:none; + background: #f9a123; +} + +.has-switch span.switch-warning:hover, +.has-switch span.switch-warning:active, +.has-switch span.switch-warning.active, +.has-switch span.switch-warning.disabled, +.has-switch span.switch-warning[disabled] { + color: #ffffff; + background-color: #fbb450; + *background-color: #faa937; +} + +.has-switch span.switch-warning:active, +.has-switch span.switch-warning.active { + background-color: #fa9f1e \9; +} + +.has-switch span.switch-danger { + color: #ffffff; + text-shadow: none; + background: #d14641; +} + +.has-switch span.switch-danger:hover, +.has-switch span.switch-danger:active, +.has-switch span.switch-danger.active, +.has-switch span.switch-danger.disabled, +.has-switch span.switch-danger[disabled] { + color: #ffffff; + background-color: #ee5f5b; + *background-color: #ec4844; +} + +.has-switch span.switch-danger:active, +.has-switch span.switch-danger.active { + background-color: #e9322d \9; +} diff --git a/docdoku-web-front/app/less/common/button.less b/docdoku-web-front/app/less/common/button.less new file mode 100644 index 0000000000..76870d114d --- /dev/null +++ b/docdoku-web-front/app/less/common/button.less @@ -0,0 +1,108 @@ +// Override bootsrap switch colors + +.has-switch.custom-switch span.switch-custom-off { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background: #6785aa; + border-color: #7a99c1 #7a99c1 #4c72a3; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); +} + +.has-switch.custom-switch span.switch-custom-off:hover, +.has-switch.custom-switch span.switch-custom-off:active, +.has-switch.custom-switch span.switch-custom-off.active, +.has-switch.custom-switch span.switch-custom-off.disabled, +.has-switch.custom-switch span.switch-custom-off[disabled] { + color: #ffffff; + background-color: #7a99c1; + *background-color: #698cb9; +} + +.has-switch.custom-switch span.switch-custom-off:active, +.has-switch.custom-switch span.switch-custom-off.active { + background-color: #577eb1 \9; +} + +.has-switch.custom-switch span.switch-custom-on { + color: #ffffff; + text-shadow: none; + background: @plm-secondary-color; +} + +.has-switch.custom-switch span.switch-custom-on:hover, +.has-switch.custom-switch span.switch-custom-on:active, +.has-switch.custom-switch span.switch-custom-on.active, +.has-switch.custom-switch span.switch-custom-on.disabled, +.has-switch.custom-switch span.switch-custom-on[disabled] { + color: #ffffff; + background-color: #2c4361; + *background-color: #24374f; +} + +.has-switch.custom-switch span.switch-custom-on:active, +.has-switch.custom-switch span.switch-custom-on.active { + background-color: #1c2b3e \9; +} + +.btn{ + + padding: 4px 12px; + font-size: 13px; + + &.btn-primary{ + background: @plm-primary-color; + &:hover, &:active, &.active, &.disabled, &[disabled] { + background: @plm-primary-hover-color; + } + } + + &.btn-custom { + color: white; + background: #3a87ad; + text-shadow: none; + &:hover, &:active, &.active, &.disabled, &[disabled] { + background: #336b8f; + } + } + + &.btn-default{ + background: #F5F5F5; + &:hover, &:active, &.active, &.disabled, &[disabled] { + background: #ededed; + } + } + + &.btn-danger{ + background: #bd362f; + &:hover, &:active, &.active, &.disabled, &[disabled] { + background: #ab352e; + } + } + + &.btn-success{ + background: #51a351; + &:hover, &:active, &.active, &.disabled, &[disabled] { + background: #478d47; + } + } + &.btn-warning{ + background: #f89406; + &:hover, &:active, &.active, &.disabled, &[disabled] { + background: #e28206; + } + } + &.btn-info{ + background: #49afcd; + &:hover, &:active, &.active, &.disabled, &[disabled] { + background: #48a8c5; + } + } +} +.actions { + + .btn-group > .btn { + font-size: 18px; + height: 32px; + padding: 2px 12px; + } +} diff --git a/docdoku-web-front/app/less/common/config_spec.less b/docdoku-web-front/app/less/common/config_spec.less new file mode 100644 index 0000000000..5ef9741308 --- /dev/null +++ b/docdoku-web-front/app/less/common/config_spec.less @@ -0,0 +1,57 @@ +.ConfigSpecSelector { + position: absolute; + bottom: 0; + width: 100%; + min-height: 65px; + background-color: @plm-secondary-color; + /*background-image: -moz-linear-gradient(top, #7a99c1, #2c4361); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#7a99c1), to(#2c4361)); + background-image: -webkit-linear-gradient(top, #7a99c1, #2c4361); + background-image: -o-linear-gradient(top, #7a99c1, #2c4361); + background-image: linear-gradient(to bottom, #7a99c1, #2c4361); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5b779b', endColorstr='#ff7a99c1', GradientType=0);*/ + &-title { + float: left; + margin-left: 15px; + margin-top: 7px; + color: white; + font-weight: bold; + text-shadow: 0px 1px 1px black; + } + + &-menu { + display: inline-block; + float: right; + margin-right: 5%; + margin-top: 5px; + + .btn { + width: 23px; + height: 23px; + padding: 0px; + + &:hover { + outline: thin dotted #333; + } + + .icon { + .plm-icon-action; + margin: 0px; + line-height: 22px; + } + } + } + + &-list { + width: calc(~"95% - 15px"); + margin-top: 3px; + margin-left: 15px; + margin-right: 5%; + padding-left: 0px; + padding-top: 0px; + border: none; + font-size: 13px; + cursor: pointer; + } +} diff --git a/docdoku-web-front/app/less/common/content.less b/docdoku-web-front/app/less/common/content.less new file mode 100644 index 0000000000..ef2e9ecbb7 --- /dev/null +++ b/docdoku-web-front/app/less/common/content.less @@ -0,0 +1,72 @@ +#content { + z-index: 0; + top: 41px; + margin: 0; + padding: 0; + bottom: 0; + position: absolute; + left: 0; + right: 0; + #content-loader-indicator { + position: absolute; + top: 40%; + left: 50%; + margin-left: -32px; + margin-top: -32px; + width: 64px; + height: 64px; + border-radius: 64px; + background: rgba(255, 255, 255, 1); + transform-origin: 32px 32px; + animation: loaderAnimation 1s linear infinite; + -webkit-animation: loaderAnimation 1s linear infinite; + -moz-animation: loaderAnimation 1s linear infinite; + -o-animation: loaderAnimation 1s linear infinite; + } + .dataTables_wrapper .dataTables_filter { + float: left; + } + table { + border: none; + } +} + +@keyframes loaderAnimation { + 0% { + transform: scale(0, 0); + } + 100% { + transform: scale(1, 1); + opacity: 0; + } +} + +@-webkit-keyframes loaderAnimation { + 0% { + transform: scale(0, 0); + } + 100% { + transform: scale(1, 1); + opacity: 0; + } +} + +@-moz-keyframes loaderAnimation { + 0% { + transform: scale(0, 0); + } + 100% { + transform: scale(1, 1); + opacity: 0; + } +} + +@-o-keyframes loaderAnimation { + 0% { + transform: scale(0, 0); + } + 100% { + transform: scale(1, 1); + opacity: 0; + } +} diff --git a/docdoku-web-front/app/less/common/conversion.less b/docdoku-web-front/app/less/common/conversion.less new file mode 100644 index 0000000000..1f7b181437 --- /dev/null +++ b/docdoku-web-front/app/less/common/conversion.less @@ -0,0 +1,18 @@ +.conversion-status { + + .conversion-status-actions { + float: right; + margin-right: 10px; + } + .fa { + margin-right: 4px; + &.success { + color: green; + } + + &.fail { + color: #ee5f5b; + } + } + +} diff --git a/docdoku-web-front/app/less/common/dropdowns.less b/docdoku-web-front/app/less/common/dropdowns.less new file mode 100644 index 0000000000..82e7f22fe9 --- /dev/null +++ b/docdoku-web-front/app/less/common/dropdowns.less @@ -0,0 +1,42 @@ +.dropdown-menu { + + min-width: 140px; + + li > a { + line-height: 18px; + height: 18px; + padding: 3px 16px; + &:hover { + color: #fff; + background-color: #147be8; + background-image: -moz-linear-gradient(top, #147be8, #147be8); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#147be8), to(#147be8)); + background-image: -webkit-linear-gradient(top, #147be8, #147be8); + background-image: -o-linear-gradient(top, #147be8, #147be8); + background-image: linear-gradient(to bottom, #147be8, #147be8); + } + } + .divider { + margin: 5px 1px; + } + + &.large-entries { + li > a { + line-height: 30px; + height: 30px; + &:hover { + color: #111; + background: #eee; + } + } + } + +} + +.dropdown-menu > .disabled { + cursor: not-allowed; +} + +.dropdown-menu > .disabled > a { + pointer-events: none; +} diff --git a/docdoku-web-front/app/less/common/elements.less b/docdoku-web-front/app/less/common/elements.less new file mode 100644 index 0000000000..93f3d69ebd --- /dev/null +++ b/docdoku-web-front/app/less/common/elements.less @@ -0,0 +1,167 @@ +.gradient(@color: #F5F5F5, @start: #EEE, @stop: #FFF) { + background: @color; + background: -webkit-gradient(linear, + left bottom, + left top, + color-stop(0, @start), + color-stop(1, @stop)); + background: -ms-linear-gradient(bottom, + @start, + @stop); + background: -moz-linear-gradient(center bottom, + @start 0%, + @stop 100%); + background: -o-linear-gradient(@stop, + @start); + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",@stop,@start)); +} + +.bw-gradient(@color: #F5F5F5, @start: 0, @stop: 255) { + background: @color; + background: -webkit-gradient(linear, + left bottom, + left top, + color-stop(0, rgb(@start,@start,@start)), + color-stop(1, rgb(@stop,@stop,@stop))); + background: -ms-linear-gradient(bottom, + rgb(@start,@start,@start) 0%, + rgb(@stop,@stop,@stop) 100%); + background: -moz-linear-gradient(center bottom, + rgb(@start,@start,@start) 0%, + rgb(@stop,@stop,@stop) 100%); + background: -o-linear-gradient(rgb(@stop,@stop,@stop), + rgb(@start,@start,@start)); + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)", rgb(@stop,@stop,@stop), rgb(@start,@start,@start))); +} + +.bordered(@top-color: #EEE, @right-color: #EEE, @bottom-color: #EEE, @left-color: #EEE) { + border-top: solid 1px @top-color; + border-left: solid 1px @left-color; + border-right: solid 1px @right-color; + border-bottom: solid 1px @bottom-color; +} + +.drop-shadow(@x-axis: 0, @y-axis: 1px, @blur: 2px, @alpha: 0.1) { + -webkit-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); + -moz-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); + box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); +} + +.rounded(@radius: 2px) { + -webkit-border-radius: @radius; + -moz-border-radius: @radius; + border-radius: @radius; +} + +.border-radius(@topright: 0, @bottomright: 0, @bottomleft: 0, @topleft: 0) { + -webkit-border-top-right-radius: @topright; + -webkit-border-bottom-right-radius: @bottomright; + -webkit-border-bottom-left-radius: @bottomleft; + -webkit-border-top-left-radius: @topleft; + -moz-border-radius-topright: @topright; + -moz-border-radius-bottomright: @bottomright; + -moz-border-radius-bottomleft: @bottomleft; + -moz-border-radius-topleft: @topleft; + border-top-right-radius: @topright; + border-bottom-right-radius: @bottomright; + border-bottom-left-radius: @bottomleft; + border-top-left-radius: @topleft; + .background-clip(padding-box); +} + +.opacity(@opacity: 0.5) { + -moz-opacity: @opacity; + -khtml-opacity: @opacity; + -webkit-opacity: @opacity; + opacity: @opacity; + @opperc: @opacity * 100; + -ms-filter: ~"progid:DXImageTransform.Microsoft.Alpha(opacity=@{opperc})"; + filter: ~"alpha(opacity=@{opperc})"; +} + +.transition-duration(@duration: 0.2s) { + -moz-transition-duration: @duration; + -webkit-transition-duration: @duration; + -o-transition-duration: @duration; + transition-duration: @duration; +} + +.transform(...) { + -webkit-transform: @arguments; + -moz-transform: @arguments; + -o-transform: @arguments; + -ms-transform: @arguments; + transform: @arguments; +} + +.rotation(@deg:5deg) { + .transform(rotate(@deg)); +} + +.scale(@ratio:1.5) { + .transform(scale(@ratio)); +} + +.transition(@duration:0.2s, @ease:ease-out) { + -webkit-transition: all @duration @ease; + -moz-transition: all @duration @ease; + -o-transition: all @duration @ease; + transition: all @duration @ease; +} + +.inner-shadow(@horizontal:0, @vertical:1px, @blur:2px, @alpha: 0.4) { + -webkit-box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha); + -moz-box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha); + box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha); +} + +.box-shadow(@arguments) { + -webkit-box-shadow: @arguments; + -moz-box-shadow: @arguments; + box-shadow: @arguments; +} + +.box-sizing(@sizing: border-box) { + -ms-box-sizing: @sizing; + -moz-box-sizing: @sizing; + -webkit-box-sizing: @sizing; + box-sizing: @sizing; +} + +.user-select(@argument: none) { + -webkit-user-select: @argument; + -moz-user-select: @argument; + -ms-user-select: @argument; + user-select: @argument; +} + +.columns(@colwidth: 250px, @colcount: 0, @colgap: 50px, @columnRuleColor: #EEE, @columnRuleStyle: solid, @columnRuleWidth: 1px) { + -moz-column-width: @colwidth; + -moz-column-count: @colcount; + -moz-column-gap: @colgap; + -moz-column-rule-color: @columnRuleColor; + -moz-column-rule-style: @columnRuleStyle; + -moz-column-rule-width: @columnRuleWidth; + -webkit-column-width: @colwidth; + -webkit-column-count: @colcount; + -webkit-column-gap: @colgap; + -webkit-column-rule-color: @columnRuleColor; + -webkit-column-rule-style: @columnRuleStyle; + -webkit-column-rule-width: @columnRuleWidth; + column-width: @colwidth; + column-count: @colcount; + column-gap: @colgap; + column-rule-color: @columnRuleColor; + column-rule-style: @columnRuleStyle; + column-rule-width: @columnRuleWidth; +} + +.translate(@x:0, @y:0) { + .transform(translate(@x, @y)); +} + +.background-clip(@argument: padding-box) { + -moz-background-clip: @argument; + -webkit-background-clip: @argument; + background-clip: @argument; +} diff --git a/docdoku-web-front/app/less/common/files.less b/docdoku-web-front/app/less/common/files.less new file mode 100644 index 0000000000..146afcfd3e --- /dev/null +++ b/docdoku-web-front/app/less/common/files.less @@ -0,0 +1,152 @@ +div.attachedFiles { + + div.progress .bar { + color: #333; + } + + .notifications { + margin-top: 10px; + } + + h4 { + font-size: 13px; + } + + &.idle { + + div.progress { + width: 0%; + display: none; + } + + button.cancel-upload-btn { + display: none; + } + + p.upload-file-shortname { + display: none; + } + } + + &.uploading { + + input.upload-btn { + display: none; + } + + div.filedroparea { + display: none; + } + } + + form.upload-form { + position: relative; + margin: 20px 0 0 0; + } + + input.upload-btn { + cursor: pointer; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + line-height: normal; + opacity: 0; + } + + span.droppable { + margin: 0 0 0 18px; + } + + .filedroparea { + + position: relative; + font-weight: bold; + padding: 20px; + margin: 10px 0 0 0; + color: #555; + border: 2px dashed #555; + border-radius: 7px; + + cursor: pointer; + } + .filedroparea.hover { + color: #555; + border-color: #9acd32; + border-style: solid; + box-shadow: inset 0 1px 4px #888; + } + + .filedroparea input { + cursor: pointer; + } + + input[type="checkbox"].file-check { + margin: 0 0 3px 0; + } + + li { + + margin: 4px; + + ul.file-list { + margin: 0; + } + + .stroke { + text-decoration: line-through; + color: red; + } + + .stroke, a.stroke, a.stroke { + text-decoration: line-through; + color: red; + } + + .edit-name { + cursor: pointer; + &:hover { + color: #0088cc; + } + } + + &.edition { + .filename-edit-form { + font-size: 13px; + display: inline-block; + i { + cursor: pointer; + } + .validate-name { + color: #33ae24; + } + .cancel-name { + color: #ff5622; + } + } + .edit-name, .fileName { + display: none; + } + } + + .filename-edit-form { + display: none; + } + .edit-name, .fileName { + display: inline-block; + } + + input[name=filename] { + line-height: 18px; + height: 19px; + font-size: 13px; + padding: 0; + border: none; + } + } + + input.file-delete { + margin: 5px; + } +} diff --git a/docdoku-web-front/app/less/common/footer.less b/docdoku-web-front/app/less/common/footer.less new file mode 100644 index 0000000000..62ac3439f7 --- /dev/null +++ b/docdoku-web-front/app/less/common/footer.less @@ -0,0 +1,15 @@ +#footer { + border: none; + text-align: center; + clear: both; + margin-top: 20px; + margin-bottom: 20px; + + p { + font-size: 0.8em; + } + + a { + color: #0061c0; + } +} diff --git a/docdoku-web-front/app/less/common/form.less b/docdoku-web-front/app/less/common/form.less new file mode 100644 index 0000000000..c006316800 --- /dev/null +++ b/docdoku-web-front/app/less/common/form.less @@ -0,0 +1,15 @@ +.form-horizontal { + .control-label { + padding-top: 0; + } +} +textarea:focus, input[type="text"]:focus, input[type="password"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, input[type="number"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="color"]:focus, .uneditable-input:focus { + box-shadow: none; +} +form.inline{ + display: inline; +} + +form.inline.hide{ + display: none; +} diff --git a/docdoku-web-front/app/less/common/general.less b/docdoku-web-front/app/less/common/general.less new file mode 100644 index 0000000000..ccf7e80a8a --- /dev/null +++ b/docdoku-web-front/app/less/common/general.less @@ -0,0 +1,81 @@ +html, body { + height: 100%; + width: 100%; // required for firefox + background-color:@background-color; + margin: 0; + padding: 0; + font-size: 13px; + color:#323A45; +} + +a:hover { + cursor: pointer; +} + +input:focus:invalid, +textarea:focus:invalid, +select:focus:invalid { + color: @errorText; + background-color: @errorBackground; +} + +[class^="icon-"] { + background-image: none; +} + +[class^="fa-"] { + background-image: none; +} + +a.is-connected-user { + text-decoration: none; + cursor: default; +} + +.fa-user.user-online, +.fa-globe.participant-online, +.fa-graduation-cap.master { + color: #4ca300; +} + +.fa-user.user-offline { + color: #708090; +} + +.fa-graduation-cap.no-master, +.fa-globe.participant-offline { + color: #A30400; +} + +.hidden { + display: none; + visibility: hidden; +} + +#header .dropdown-menu > li > a:hover, +#header .dropdown-menu > li > a:focus, +#header .dropdown-submenu:hover > a, +#header .dropdown-submenu:focus > a { + background: @plm-secondary-color; +} + +#header .dropdown-menu > .active > a, +#header .dropdown-menu > .active > a:hover, +#header .dropdown-menu > .active > a:focus { + background: @plm-secondary-color; +} + +#header .dropdown-menu > .disabled > a, +#header .dropdown-menu > .disabled > a:hover, +#header .dropdown-menu > .disabled > a:focus { + color: #999999; +} + +.margin{ + margin: 20px; +} + +#header .offline-menu a { + color: white; + text-shadow: none; +} diff --git a/docdoku-web-front/app/less/common/header.less b/docdoku-web-front/app/less/common/header.less new file mode 100644 index 0000000000..eed19ae631 --- /dev/null +++ b/docdoku-web-front/app/less/common/header.less @@ -0,0 +1,252 @@ +#header { + &.Header { + position: absolute; + right: 0; + left: 0; + z-index: 1030; + min-height: 40px; + // margin-bottom: 0; + border-width: 0; + opacity: 0; + -webkit-transition: opacity .15s cubic-bezier(.17,.67,.83,.67); + -moz-transition: opacity .15s cubic-bezier(.17,.67,.83,.67); + -ms-transition: opacity .15s cubic-bezier(.17,.67,.83,.67); + -o-transition: opacity .15s cubic-bezier(.17,.67,.83,.67); + transition: opacity .15s cubic-bezier(.17,.67,.83,.67); + &.loaded { + opacity: 1; + //box-shadow:0px 1px 3px #9b9b9b; + } + } + + .Header { + &-collapseBtn { + position: absolute; + z-index: 101; + right: 5px; + + &.btn-navbar { + /* + .gradient(#2b426a, #1a273f, #375487); + background-image: linear-gradient(top, #1a273f, #375487); + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*/ + background-color: #3f5f99; + + } + } + &-content { + z-index: 100; + padding: 0 10px; + } + } + + .Brand { + position: relative; + z-index: 10; + float: left; + display: block; + margin-left: -20px; + padding: 0px 20px 0px; + font-size: 20px; + font-weight: 200; + color: #ffffff; + text-shadow: 0 1px 0 #3f5f99; + + &-logo { + float: left; + display: inline-block; + height: 30px; + width: 30px; + margin: 5px; + } + + &-name { + display: inline-block; + padding-top: 11px; + padding-bottom: 11px; + line-height: 18px; + } + } + + .HeaderContent { + background: @plm-primary-color; + + /* .gradient(#334d7c, #213251, #3f5f99); + background-image: linear-gradient(top, #213251, #3f5f99);*/ + border-bottom: 1px solid #0f1726; + } + + .HeaderFluidContent { + padding: 0; + } + + .nav { + > .dropdown { + > .dropdown-toggle { + color: rgba(255, 255, 255, 1); + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + } + } + } + + .HeaderMenu, .BreadcrumbMenu { + position: relative; + left: 0; + display: block; + &-item { + .dropdown-toggle:hover { + background-color: @plm-primary-hover-color; + } + + &.open { + .dropdown-toggle { + background-color: transparent; + &:hover { + background-color: @plm-primary-hover-color; + } + } + } + + i { + margin-top: 1px; + } + } + } + + .BreadcrumbMenuList { + padding: 6px 0; + line-height: 22px; + + &-sectionTitle { + padding-left: 8px; + font-weight: bold; + } + + &-hr { + margin: 5px 0; + } + + i.fa-check { + margin-left: -14px; + } + } + + .HeaderDropdownMenu { + > li { + + &.dropdown-section-title { + padding-left: 8px; + } + + &.dropdown-hr hr { + margin: 5px 0; + } + + i.fa-check { + margin-left: -14px; + } + } + } +} + +@media (max-width: @navbarCollapseWidth) { + #header { + .nav-collapse { + .dropdown-menu { + a { + color: white; + } + } + + + div { + clear: both; + } + } + + .HeaderMenu, .BreadcrumbMenu { + &-item { + .caret { + display: none; + } + } + } + + .BreadcrumbMenu { + margin: 0px; + &-item { + > a { + padding: 9px 15px; + background-color: transparent; + line-height: 20px; + } + &:first-child, &:last-child { + > a { + border: 1px solid transparent; + border-radius: 3px; + } + } + } + } + + .BreadcrumbMenuItem { + &-link { + &:before, &:after { + display: none; + } + } + } + + .BreadcrumbMenuList { + color: white; + &-sectionTitle { + display: none; + } + + &-hr { + display: none; + } + } + + .BreadcrumbSubMenuItem { + font-weight: normal; + + &-title { + display: inline-block; + float: left; + width: calc(~"100% - 155px"); + height: 18px; + padding: 9px 15px; + color: white; + line-height: 18px; + + i { + color: rgba(255, 255, 255, 0.5); + } + } + + } + + .BreadcrumbSubMenuList { + display: inline-block; + li { + float: left; + list-style-type: none; + + a { + width: 13px; + height: 13px; + padding: 10px; + margin: 0; + } + } + } + } +} + +@media (max-width: 767px) { + #header { + &.Header { + margin: 0; + } + } +} diff --git a/docdoku-web-front/app/less/common/icons.less b/docdoku-web-front/app/less/common/icons.less new file mode 100644 index 0000000000..c371b2189e --- /dev/null +++ b/docdoku-web-front/app/less/common/icons.less @@ -0,0 +1,289 @@ +.plm-icon-nav { + background: no-repeat center; + display: inline-block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; +} + +.plm-icon-menu { + background: no-repeat center; + display: inline-block; + height: 22px; + width: 22px; + margin: 0 8px 0 0; +} + +.plm-icon-action { + background: no-repeat center; + display: inline-block; + height: 22px; + margin: 0 8px; + width: 22px; +} + +// Nav + +.plm-icon-nav-documents { + .plm-icon-nav; + background-image: url("../images/icon-nav-documents.png"); +} + +.plm-icon-nav-folder { + .plm-icon-nav; + background-image: url("../images/icon-nav-folder.png"); +} + +.plm-icon-nav-folder-opened { + .plm-icon-nav; + background-image: url("../images/icon-nav-folder-opened.png"); +} + +.plm-icon-nav-folder-home { + .plm-icon-nav; + background-image: url("../images/icon-nav-user-home.png"); +} + +.plm-icon-nav-products { + .plm-icon-nav; + background-image: url("../images/icon-nav-product.png"); +} + +.plm-icon-nav-configuration { + .plm-icon-nav; + background-image: url("../images/icon-nav-configuration.png"); +} + +.plm-icon-nav-baselines { + .plm-icon-nav; + background-image: url("../images/icon-nav-baselines.png"); +} + +.plm-icon-nav-product-instances { + .plm-icon-nav; + background-image: url("../images/icon-nav-product-instances.png"); +} + +.plm-icon-nav-parts { + .plm-icon-nav; + background-image: url("../images/icon-nav-part.png"); +} + +.plm-icon-nav-folder-status-closed { + .plm-icon-nav; + width: 12px; + background-image: url("../images/icon-nav-bullet-right.png"); + opacity: 0.6; +} + +.plm-icon-nav-folder-status-opened { + .plm-icon-nav; + width: 12px; + background-image: url("../images/icon-nav-bullet-down.png"); + opacity: 0.6; +} + +.plm-icon-nav-tags { + .plm-icon-nav; + background-image: url("../images/icon-nav-tags.png"); +} + +.plm-icon-nav-tag { + .plm-icon-nav; + background-image: url("../images/icon-nav-tag.png"); +} + +.plm-icon-nav-workflows { + .plm-icon-nav; + background-image: url("../images/icon-nav-workflows.png"); +} + +.plm-icon-nav-milestones { + .plm-icon-nav; + background-image: url("../images/icon-nav-milestones.png"); +} + +.plm-icon-nav-issues { + .plm-icon-nav; + background-image: url("../images/icon-nav-issues.png"); +} + +.plm-icon-nav-orders { + .plm-icon-nav; + background-image: url("../images/icon-nav-orders.png"); +} + +.plm-icon-nav-requests { + .plm-icon-nav; + background-image: url("../images/icon-nav-requests.png"); +} + +.plm-icon-nav-templates { + .plm-icon-nav; + background-image: url("../images/icon-nav-templates.png"); +} + +.plm-icon-nav-checkedouts { + .plm-icon-nav; + background-image: url("../images/icon-nav-checkedouts.png"); +} + +.plm-icon-nav-tasks { + .plm-icon-nav; + background-image: url("../images/icon-nav-tasks.png"); +} + +.plm-icon-nav-search { + .plm-icon-nav; + background-image: url("../images/icon-nav-search.png"); +} + +// Actions + +.plm-icon-action-delete { + .plm-icon-action; + background-image: url("../images/icon-action-delete.png"); +} + +.plm-icon-action-duplicate { + .plm-icon-action; + background-image: url("../images/icon-action-duplicate.png"); +} + +.plm-icon-action-new-baseline { + .plm-icon-action; + background-image: url("../images/icon-action-new-baseline.png"); +} + +.plm-icon-action-new-product-intances { + .plm-icon-action; + background-image: url("../images/icon-action-new-product-intances.png"); +} + +.plm-icon-action-document-new { + .plm-icon-action; + background-image: url("../images/icon-action-document-new.png"); +} + +.plm-icon-action-template-new { + .plm-icon-action; + background-image: url("../images/icon-action-template-new.png"); +} + +.plm-icon-action-product-new { + .plm-icon-action; + background-image: url("../images/icon-action-product-new.png"); +} + +.plm-icon-action-configuration-new { + .plm-icon-action; + background-image: url("../images/icon-action-configuration-new.png"); +} + +.plm-icon-action-udf { + .plm-icon-action; + background-image: url("../images/icon-action-udf.png"); +} + +.plm-icon-action-part-new { + .plm-icon-action; + background-image: url("../images/icon-action-part-new.png"); +} + +.plm-icon-action-checkout { + .plm-icon-action; + background-image: url("../images/icon-action-document-checkout.png"); +} + +.plm-icon-action-undocheckout { + .plm-icon-action; + background-image: url("../images/icon-action-document-undocheckout.png"); +} + +.plm-icon-action-checkin { + .plm-icon-action; + background-image: url("../images/icon-action-document-checkin.png"); +} + +.plm-icon-action-tags { + .plm-icon-action; + background-image: url("../images/icon-nav-tag.png"); +} + +.plm-icon-action-workflow-new { + .plm-icon-action; + background-image: url("../images/icon-action-workflow-new.png"); +} + +.plm-icon-action-workflow-roles { + .plm-icon-action; + background-image: url("../images/icon-action-workflow-roles.png"); +} + +.plm-icon-action-new-version { + .plm-icon-action; + background-image: url("../images/icon-action-document-new-version.png"); +} + +.plm-icon-action-new-release { + .plm-icon-action; + background-image: url("../images/icon-action-document-new-release.png"); +} + +.plm-icon-action-mark-as-obsolete { + .plm-icon-action; + background-image: url("../images/icon-action-mark-as-obsolete.png"); +} + +.plm-icon-action-acl { + .plm-icon-action; + background-image: url("../images/icon-action-document-acl.png"); +} + +.plm-icon-action-milestone-new { + .plm-icon-action; + background-image: url("../images/icon-action-milestone-new.png"); +} + +.plm-icon-action-issue-new { + .plm-icon-action; + background-image: url("../images/icon-action-issue-new.png"); +} + +.plm-icon-action-request-new { + .plm-icon-action; + background-image: url("../images/icon-action-request-new.png"); +} + +.plm-icon-action-order-new { + .plm-icon-action; + background-image: url("../images/icon-action-order-new.png"); +} + +.plm-icon-action-lov { + .plm-icon-action; + background-image: url("../images/icon-action-lov.png"); +} + +.plm-icon-action-import { + .plm-icon-action; + background-image: url("../images/icon-action-import.png"); +} + +// Menu + +.plm-icon-menu-edit { + .plm-icon-menu; + background-image: url("../images/icon-menu-edit.png"); +} + +.plm-icon-menu-delete { + .plm-icon-menu; + background-image: url("../images/icon-menu-edit-delete.png"); +} + +.plm-icon-menu-folder-new { + .plm-icon-menu; + background-image: url("../images/icon-menu-folder-new.png"); +} + diff --git a/docdoku-web-front/app/less/common/import.less b/docdoku-web-front/app/less/common/import.less new file mode 100644 index 0000000000..83fb86e9e5 --- /dev/null +++ b/docdoku-web-front/app/less/common/import.less @@ -0,0 +1,29 @@ +.import-status { + p { + margin: 0; + } + margin: 18px 0 0 4px; + + .import-status-actions { + float: right; + margin-right: 10px; + } + .fa { + margin-right: 4px; + &.success { + color: green; + } + + &.fail { + color: #ee5f5b; + } + } + p.error { + color: #ee5f5b; + } + p.warning { + color: #a45b13; + } + padding-top: 6px; + +} diff --git a/docdoku-web-front/app/less/common/iteration.less b/docdoku-web-front/app/less/common/iteration.less new file mode 100644 index 0000000000..d99aa260e7 --- /dev/null +++ b/docdoku-web-front/app/less/common/iteration.less @@ -0,0 +1,4 @@ +#switch-iteration { + float: left; + margin-bottom: 0px; +} diff --git a/docdoku-web-front/app/less/common/lifecycle.less b/docdoku-web-front/app/less/common/lifecycle.less new file mode 100644 index 0000000000..4e82b11336 --- /dev/null +++ b/docdoku-web-front/app/less/common/lifecycle.less @@ -0,0 +1,199 @@ +.LifecycleModalTab { + &-historyContent { + a:not(.active) { + color: #333; + } + } +} + +.LifecycleModalLegend { + padding: 10px 0px; + &-line { + margin-right: 10px; + + &:before { + content: ""; + display: inline-block; + width: 5px; + height: 5px; + margin: 0px 2px; + border: 2px solid #000; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + border-radius: 50%; + + .incomplete& { + background: #aaaaaa; + } + + .complete& { + background: #5bb75b; + } + + .inProgress& { + background: royalblue; + } + + .rejected& { + background: #ee5f5b;; + } + } + + } +} + +.LifecycleActivitiesWrapper { + height: 100%; +} + +#lifecycle-activities { + + display: inline-flex; + white-space: nowrap; + padding: 0; + margin: 0; + + .activity { + display: inline-block; + float: left; + margin: 0 18px 0 0; + padding: 0 18px; + background: white; + + &.complete { + border: 3px solid #5bb75b; + } + + &.in_progress { + border: 3px dashed royalblue; + } + + &.rejected { + border: 3px solid #ee5f5b; + } + + &.incomplete { + + } + + .tasks { + + .task { + + margin-right: 5px; + padding: 9px 19px; + + &.not_started { + border: 1px dotted #aaaaaa; + } + + &.in_progress { + border: 2px dashed royalblue; + } + + &.approved { + border: 2px solid #5bb75b; + } + + &.rejected { + border: 2px solid #ee5f5b; + } + + i { + cursor: pointer; + margin: 0 0 0 5px; + opacity: 0.5; + + &:hover { + opacity: 1; + text-decoration: none; + } + + } + + i.fa-times:hover { + color: #F00; + } + + i.fa-check:hover { + color: #33ae24; + } + + h5 { + margin: 0; + + &.mark_task { + white-space: normal; + } + } + + p { + word-wrap: break-word; + white-space: pre-line; + } + + div.task-comment { + + margin-top: 10px; + display: none; + border-top: 1px solid #aaaaaa; + + &.toggled { + display: block; + } + + p { + margin: 10px 0; + } + + } + + .closure-comment { + + margin-top: 10px; + display: none; + border-top: 1px solid #aaaaaa; + + &.toggled { + display: block; + } + + h5 { + margin: 10px 0; + } + + .closure-comment-form { + > div { + margin: 10px 0; + + > input.input-medium { + width: 188px; + } + } + + .task-signing { + i.clear-signing { + display: none; + } + i.save-signing { + display: none; + } + } + + .lifecycle-activities-canvas { + border: 1px solid #b5b5b5; + cursor: crosshair; + + &:active { + cursor: crosshair; + } + } + + } + } + + } + + } + } +} diff --git a/docdoku-web-front/app/less/common/linked_documents.less b/docdoku-web-front/app/less/common/linked_documents.less new file mode 100644 index 0000000000..57e438723c --- /dev/null +++ b/docdoku-web-front/app/less/common/linked_documents.less @@ -0,0 +1,127 @@ +div.linked-items-view { + label.reference-typeahead-label { + font-weight: bold; + } + + button#toggle-links-edit-mode { + float: right; + } + + div.change-items-legend { + padding: 10px 0px; + + span { + margin-right: 10px; + + &:before { + content: ""; + display: inline-block; + width: 5px; + height: 5px; + margin: 0px 2px; + border: 2px solid #000; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + border-radius: 50%; + } + + &.change-items-low:before { + background: #5cb85c; + } + + &.change-items-medium:before { + background: #5bc0de; + } + + &.change-items-high:before { + background: #f0ad4e; + } + + &.change-items-emergency:before { + background: #d9534f; + } + } + } + + ul.linked-items { + margin: 12px 0; + list-style: none; + + li.linked-item { + padding: 6px 4px 2px 4px; + margin: 0 12px 12px 0; + display: inline-block; + //background-image: repeating-linear-gradient(45deg, transparent, transparent 5px, #f5f5f5 15px, #f5f5f5 25px); + + &.priorityColor-LOW { + background-color: #5cb85c; + color: white; + } + &.priorityColor-MEDIUM { + background-color: #5bc0de; + color: white; + } + &.priorityColor-HIGH { + background-color: #f0ad4e; + color: white; + } + &.priorityColor-EMERGENCY { + background-color: #d9534f; + color: white; + } + + .delete-linked-item { + font-size: 15px; + color: #333; + opacity: 0.2; + + &:hover { + text-decoration: none; + opacity: 0.8; + color: #333; + } + } + .edit-linked-item-comment { + color: #333; + opacity: 0.8; + } + + .reference { + padding: 0 6px; + font-weight: bold; + margin: 0; + display: inline-block; + } + + div[name="comment"] { + display: none; + } + span.documentTitle { + font-weight: bold; + } + span.comment { + display: block; + font-style: italic; + color: #333; + opacity: 0.8; + } + + i.delete-comment { + color: #ff5622; + } + + i.validate-comment { + color: #33ae24; + } + + &.edition { + div[name="comment"] { + display: block; + } + span.comment { + display: none; + } + } + } + } +} diff --git a/docdoku-web-front/app/less/common/lov.less b/docdoku-web-front/app/less/common/lov.less new file mode 100644 index 0000000000..58edc5a83a --- /dev/null +++ b/docdoku-web-front/app/less/common/lov.less @@ -0,0 +1,155 @@ +.lovItem { + + .sortable-handler { + opacity: 0.2; + cursor: move; + display: inline-block; + vertical-align: top; + margin-top: 8px; + margin-right: 5px; + } + + .fa-times { + color: black; + opacity: 0.2; + display: inline-block; + vertical-align: text-top; + margin-right: 5px; + + &:hover { + text-decoration: none; + color: #F00; + opacity: 1; + } + } + + .expandIcon { + float: right; + margin-top: 5px; + cursor: pointer; + } + + .deleteLovItem { + display: inline-block; + vertical-align: bottom; + } + + .deleteLovItemPossibleValue { + display: inline-block; + vertical-align: top; + margin-top: 6px; + } + + .lovValueDiv { + display: inline-block; + vertical-align: top; + margin-top: 2px; + } + + .lovItemNameTitle { + vertical-align: middle; + } + + .lovValueTitle { + vertical-align: top; + } + + .lovValueDiv { + margin-left: 20px; + } + + div.lovValues { + display: none; + } + + input.lovItemNameInput { + display: none; + } + + button.addLOVValue { + display: none; + } + + span.lovNumberOfValue { + vertical-align: top; + } + + span.lovItemName { + vertical-align: middle; + margin-bottom: 2px; + font-weight: bold; + display: inline-block; + text-overflow: ellipsis; + overflow: hidden; + width: 50%; + } + + &.edition { + div.lovValues { + display: inline-block; + vertical-align: top; + margin-left: 5px; + } + + span.lovItemName { + display: none; + } + + input.lovItemNameInput { + display: inline-block; + margin: 5px; + } + + span.lovNumberOfValue { + display: none; + } + + span.lovNumberOfValueTitle { + display: none + } + + i.expandIcon { + transform: rotate(180deg); + } + + button.addLOVValue { + display: block; + margin-left: 24px; + } + + .lovValueDiv { + margin: 5px; + margin-left: 30px; + } + + .deleteLovItem { + display: inline-block; + vertical-align: text-top; + } + + .expandIcon { + margin-top: 12px; + } + + &.isOldItem { + span.lovItemName { + vertical-align: middle; + margin-bottom: 2px; + font-weight: bold; + display: inline-block; + text-overflow: ellipsis; + overflow: hidden; + width: 24%; + } + + .lovValueDiv { + margin: -3px; + margin-left: 30px; + } + + .expandIcon { + margin-top: 5px; + } + } + } +} diff --git a/docdoku-web-front/app/less/common/mixins.less b/docdoku-web-front/app/less/common/mixins.less new file mode 100644 index 0000000000..46b97b142f --- /dev/null +++ b/docdoku-web-front/app/less/common/mixins.less @@ -0,0 +1,39 @@ +.display(@value) when (@value = box) { + display: -moz-box; + display: -webkit-box; + display: box; +} + +.display(@value) when not (@value = box) { + display: @value; +} + +.box-orient(@value) { + -moz-box-orient: @value; + -webkit-box-orient: @value; + box-orient: @value; +} + +.box-flex(@value) { + -moz-box-flex: @value; + -webkit-box-flex: @value; + box-flex: @value; +} + +.column-width(@value) { + -moz-column-width: @value; + -webkit-column-width: @value; + column-width: @value; +} + +.column-count(@value) { + -moz-column-count: @value; + -webkit-column-count: @value; + column-count: @value; +} + +.column-rule(@value) { + -moz-column-rule: @value; + -webkit-column-rule: @value; + column-rule: @value; +} diff --git a/docdoku-web-front/app/less/common/modal.less b/docdoku-web-front/app/less/common/modal.less new file mode 100644 index 0000000000..6c5b9556cd --- /dev/null +++ b/docdoku-web-front/app/less/common/modal.less @@ -0,0 +1,52 @@ +.modal-backdrop { + position: absolute; +} + +.modal { + position: absolute; + max-height: none; + overflow: visible; + .modal-body { + max-height: 421px; + overflow: auto; + .tab-content { + overflow: visible; + } + } + + &.no-overflow { + .modal-body { + overflow: visible; + } + } +} + +div.modal-header { + i { + float: left; + margin: 0 8px 0 0 !important; + line-height: 28px; + font-size: 20px; + } + + span.icon { + float: left; + margin: 0 8px 0 0 !important; + } +} + +.document-modal { + + .modal-body { + height: 421px; + } + + .pager { + margin: 0; + } + + .fa-pencil { + cursor: pointer; + } + +} diff --git a/docdoku-web-front/app/less/common/modal_base.less b/docdoku-web-front/app/less/common/modal_base.less new file mode 100644 index 0000000000..98dbcf496e --- /dev/null +++ b/docdoku-web-front/app/less/common/modal_base.less @@ -0,0 +1,55 @@ +.Modal { + position: absolute; + max-height: none; + &-header { + + } + &-body { + height: 421px; + max-height: 421px; + overflow: auto; + + .no-overflow & { + overflow: visible; + } + } + &-footer { + + } +} + +.ModalHeader { + &-icon { + float: left; + margin: 0 8px 0 0 !important; + } + + i { + line-height: 28px; + font-size: 20px; + } +} + +.ModalBody { + &-alert { + + } + &-tabs { + height: 100%; + } +} + +.ModalTabs { + &-nav { + + } + &-form { + height: calc(~"100% - 64px"); + } +} + +.ModalTabsContent, +.ModalTabsPane { + height: 100%; + overflow: auto; +} diff --git a/docdoku-web-front/app/less/common/modification_notifications.less b/docdoku-web-front/app/less/common/modification_notifications.less new file mode 100644 index 0000000000..7c1f46c110 --- /dev/null +++ b/docdoku-web-front/app/less/common/modification_notifications.less @@ -0,0 +1,40 @@ +div#iteration-modification-notifications { + + div.modification-notification-group { + margin-top: 10px; + padding: 10px; + padding-bottom: 0px; + } + + div.group-title { + font-weight: bold; + } + + div.modification-notification { + padding: 10px; + padding-right: 0px; + } + + i.fa-circle { + float: left; + margin-top: 3px; + color: #62697B; + } + + div.modification-notification-details { + margin-left: 20px; + } + + div.group-comment input#group-acknowledgement-comment { + width: calc(~"100% - 179px"); + } + + div.comment input#acknowledgement-comment { + width: calc(~"100% - 152px"); + } + + div.comment-italic { + font-style: italic; + } + +} diff --git a/docdoku-web-front/app/less/common/not_found.less b/docdoku-web-front/app/less/common/not_found.less new file mode 100644 index 0000000000..b90f89068d --- /dev/null +++ b/docdoku-web-front/app/less/common/not_found.less @@ -0,0 +1,17 @@ +#not-found-view{ + text-align: center; + margin: 42px; + + h1{ + + } + h2{ + + } + h3{ + + } + p{ + + } +} diff --git a/docdoku-web-front/app/less/common/part_assembly.less b/docdoku-web-front/app/less/common/part_assembly.less new file mode 100644 index 0000000000..e7ebae966f --- /dev/null +++ b/docdoku-web-front/app/less/common/part_assembly.less @@ -0,0 +1,248 @@ +#iteration-components { + + min-height: 250px; + + input#create-part-revision-as-part-usage-link, + input#create-part-revision-as-part-substitute-link { + float: left; + } + + input#part-usage-link-type-ahead, + input#part-substitute-link-type-ahead { + float: right; + } + + .create-part-usage-link-menu { + display: block; + } + .create-part-substitute-link-menu { + display: none; + } + &.component-selected { + .create-part-usage-link-menu { + display: none; + } + .create-part-substitute-link-menu { + display: block; + } + } + + .components { + margin-top: 20px; + } + + .component { + + border: 2px solid transparent; + border-radius: 2px; + position: relative; + float: left; + padding: 9px; + width: 95%; + margin-bottom: 4px; + + &.part-substitute-link { + width: 86%; + display: inline-block; + border: 1px dashed #a1a0a0; + background-color: rgba(237, 237, 237, 0.61); + margin: 0 22px; + } + + &:hover { + background-color: rgba(193, 193, 193, 0.61); + cursor: pointer; + } + + &.selected { + opacity: 1; + background-color: rgba(193, 193, 193, 0.61); + border: 2px dashed #ACACAC; + box-shadow: none; + } + + a.part-substitute-links-count { + display: table; + margin: 8px 0 0 22px; + } + + a.remove, a.removeSub { + + margin: 5px; + float: left; + font-size: 20px; + font-weight: bold; + line-height: 18px; + color: #000000; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.2; + + &:hover { + text-decoration: none; + color: #F00; + opacity: 1; + cursor: hand; + } + } + + input { + + &[name="number"] { + width: 160px; + } + + &[name="name"] { + width: 160px; + } + + &[name="amount"] { + margin-top: 8px; + width: 61px; + } + + &[name="reference-description"] { + margin: 8px 0 0 22px; + width: 160px; + } + + &[name="optional" ] { + margin: -1px 10px 0 5px; + } + + } + + .selectize-control { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + width: 61px; + margin-top: 14px; + > div { + padding: 4px; + } + .selectize-input:after { + right: 6px; + } + } + + label { + &[name="optional"] { + display: inline; + vertical-align: middle; + padding-right: 15px; + } + } + + > .toggle-cad-instances { + font-size: 18px; + cursor: pointer; + opacity: 0.5; + position: absolute; + bottom: 8px; + right: 12px; + + &:hover { + opacity: 1; + } + } + + .add-cad-instance, .remove-cad-instance { + margin: 0 0 0 10px; + opacity: 0.4; + &:hover { + cursor: pointer; + opacity: 1; + } + } + + &.cad-instances-opened { + > .cad-instances { + display: inline-table; + } + > .toggle-cad-instances { + transform: rotate(180deg); + } + } + + > .part-substitute-links { + display: none; + } + + &.substitutes-opened { + > .part-substitute-links { + display: block; + } + } + + > .cad-instances { + + margin: 10px 0 0 21px; + clear: both; + display: none; + + .cad-instance { + cursor: pointer; + h5 { + font-weight: normal; + cursor: not-allowed; + } + float: left; + clear: both; + .coord-group { + float: left; + width: 45%; + input.coord { + width: 22%; + } + select { + width: 90%; + } + .delete-cad-instance { + margin-top: 42px; + float: left; + cursor: pointer; + opacity: 0.5; + &:hover { + opacity: 1; + } + } + } + .coord-group-remove { + width: 10%; + margin-top: 4px; + } + + &:only-child { + .delete-cad-instance { + display: none; + } + } + } + } + + } + + .tooltip { + position: relative; + color: rgba(0, 0, 0, 0.4); + margin-right: 5px; + font-size: 14px; + text-decoration: none; + opacity: 1; + } + + .tooltip:hover:after { + background: #333; + background: rgba(0, 0, 0, .8); + border-radius: 5px; + bottom: 26px; + color: #fff; + content: attr(data-tooltip); + left: 20%; + padding: 5px 15px; + position: absolute; + z-index: 98; + width: 220px; + } + +} diff --git a/docdoku-web-front/app/less/common/part_modal.less b/docdoku-web-front/app/less/common/part_modal.less new file mode 100644 index 0000000000..c8f1ccd45e --- /dev/null +++ b/docdoku-web-front/app/less/common/part_modal.less @@ -0,0 +1,44 @@ +#part-modal { + + .checkbox.lock { + display: none; + } + + #tab-part-links { + min-height: 280px; + } + #tab-part-files { + .conversion-status { + margin: 18px 0 0 4px; + button.reload { + float: right; + + } + } + } + + #tab-part-attributes { + p { + font-weight: bold; + } + } + + .pager { + margin: 0; + } + + .substitute-parts { + margin: 18px 20px 0px 21px; + } + .display-substitute-part { + margin-left: 6px; + color: rgba(103, 103, 103, 0.5); + + } + + .subParts-CADInstance, .data-sub-part { + display: none; + + } + +} diff --git a/docdoku-web-front/app/less/common/popover.less b/docdoku-web-front/app/less/common/popover.less new file mode 100644 index 0000000000..a88ad87e99 --- /dev/null +++ b/docdoku-web-front/app/less/common/popover.less @@ -0,0 +1,46 @@ +.popover { + &.left { + margin: -27px 0 0 -18px + } + &.right { + margin: -27px 0 0 18px + } + &.above-modal-popover { + z-index: 1060; + } + &.reach-user-popover { + + background: #f5f5f5; + + h3.popover-title { + font-weight: bold; + background: #3F5F99; + background-image: -moz-linear-gradient(top, #3F5F99, #213251); + background-image: -webkit-linear-gradient(top, #3F5F99, #213251); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + height: 18px; + line-height: 18px; + text-shadow: none; + color: white; + } + + div.popover-content { + text-shadow: none; + color: #333; + } + + &.small { + width: 20px; + height: 50px; + + h3.popover-title { + display: none; + } + } + + } +} + + diff --git a/docdoku-web-front/app/less/common/product_creation.less b/docdoku-web-front/app/less/common/product_creation.less new file mode 100644 index 0000000000..61f94a8008 --- /dev/null +++ b/docdoku-web-front/app/less/common/product_creation.less @@ -0,0 +1,5 @@ +#product_creation_modal { + .modal-body { + overflow: inherit; + } +} diff --git a/docdoku-web-front/app/less/common/prompt.less b/docdoku-web-front/app/less/common/prompt.less new file mode 100644 index 0000000000..568e8901c8 --- /dev/null +++ b/docdoku-web-front/app/less/common/prompt.less @@ -0,0 +1,9 @@ +#prompt_modal { + .control-label { + float: left; + width: 160px; + padding-top: 5px; + padding-right: 10px; + text-align: right; + } +} diff --git a/docdoku-web-front/app/less/common/registration.less b/docdoku-web-front/app/less/common/registration.less new file mode 100644 index 0000000000..ace4eae975 --- /dev/null +++ b/docdoku-web-front/app/less/common/registration.less @@ -0,0 +1,9 @@ +#registration_link_container { + text-shadow: 0px -1px 0px black; + color: #CCC; + margin-top: 10px; +} + +#registration_link_container a { + display: inline; +} diff --git a/docdoku-web-front/app/less/common/responsive.less b/docdoku-web-front/app/less/common/responsive.less new file mode 100755 index 0000000000..44755ea988 --- /dev/null +++ b/docdoku-web-front/app/less/common/responsive.less @@ -0,0 +1,52 @@ +/*! + * Bootstrap Responsive v2.2.2 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ + +// Responsive +// For phone and tablet devices +// ------------------------------------------------------------- + +// IE10 Metro responsive +// Required for Windows 8 Metro split-screen snapping with IE10 +// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/ + +@-ms-viewport { + width: device-width; +} + +// REPEAT VARIABLES & MIXINS +// ------------------------- +// Required since we compile the responsive stuff separately + +@import "variables"; +// Modify this for custom colors, font-sizes, etc +@import "mixins"; + +// RESPONSIVE CLASSES +// ------------------ + +@import "responsive_utilities"; + +// MEDIA QUERIES +// ------------------ + +// Large desktops +@import "responsive_1200px_min"; + +// Tablets to regular desktops +@import "responsive_768px_979px"; + +// Phones to portrait tablets and narrow desktops +@import "responsive_767px_max"; + +// RESPONSIVE NAVBAR +// ------------------ + +// From 979px and below, show a button to toggle navbar contents +@import "responsive_navbar"; diff --git a/docdoku-web-front/app/less/common/responsive_1200px_min.less b/docdoku-web-front/app/less/common/responsive_1200px_min.less new file mode 100755 index 0000000000..337ed6ec30 --- /dev/null +++ b/docdoku-web-front/app/less/common/responsive_1200px_min.less @@ -0,0 +1,11 @@ +// +// Responsive: Large desktop and up +// -------------------------------------------------- + +@media (min-width: 1200px) { + + .row-fluid .thumbnails { + margin-left: 0; + } + +} diff --git a/docdoku-web-front/app/less/common/responsive_767px_max.less b/docdoku-web-front/app/less/common/responsive_767px_max.less new file mode 100755 index 0000000000..f77ee18f60 --- /dev/null +++ b/docdoku-web-front/app/less/common/responsive_767px_max.less @@ -0,0 +1,204 @@ +// +// Responsive: Landscape phone to desktop/tablet +// -------------------------------------------------- + +@media (max-width: 767px) { + + // Padding to set content in a bit + body { + padding: 0; + } + + // Negative indent the now static "fixed" navbar + .navbar-fixed-top, + .navbar-fixed-bottom, + .navbar-static-top { + margin: 0; + } + + // Remove padding on container given explicit padding set on body + .container-fluid { + padding: 0; + } + + // TYPOGRAPHY + // ---------- + // Reset horizontal dl + .dl-horizontal { + dt { + float: none; + clear: none; + width: auto; + text-align: left; + } + dd { + margin-left: 0; + } + } + + // GRID & CONTAINERS + // ----------------- + // Remove width from containers + .container { + width: auto; + } + + // Fluid rows + .row-fluid { + width: 100%; + } + + // Undo negative margin on rows and thumbnails + .row, + .thumbnails { + margin-left: 0; + } + + .thumbnails > li { + float: none; + margin-left: 0; // Reset the default margin for all li elements when no .span* classes are present + } + + // Make all grid-sized elements block level again + [class*="span"], + .uneditable-input[class*="span"], // Makes uneditable inputs full-width when using grid sizing + .row-fluid [class*="span"] { + float: none; + display: block; + width: 100%; + margin-left: 0; + .box-sizing(border-box); + } + + .span12, + .row-fluid .span12 { + width: 100%; + .box-sizing(border-box); + } + + .row-fluid [class*="offset"]:first-child { + margin-left: 0; + } + + // FORM FIELDS + // ----------- + // Make span* classes full width + .input-large, + .input-xlarge, + .input-xxlarge, + input[class*="span"], + select[class*="span"], + textarea[class*="span"], + .uneditable-input { + .input-block-level(); + } + + // But don't let it screw up prepend/append inputs + .input-prepend input, + .input-append input, + .input-prepend input[class*="span"], + .input-append input[class*="span"] { + display: inline-block; // redeclare so they don't wrap to new lines + width: auto; + } + + .controls-row [class*="span"] + [class*="span"] { + margin-left: 0; + } + + // Modals + .modal { + position: fixed; + top: 20px; + left: 20px; + right: 20px; + width: auto; + margin: 0; + &.fade { + top: -100px; + } + &.fade.in { + top: 20px; + } + } + +} + +// UP TO LANDSCAPE PHONE +// --------------------- + +@media (max-width: 480px) { + + // Smooth out the collapsing/expanding nav + .nav-collapse { + -webkit-transform: translate3d(0, 0, 0); // activate the GPU + } + + // Block level the page header small tag for readability + .page-header h1 small { + display: block; + line-height: @baseLineHeight; + } + + // Update checkboxes for iOS + input[type="checkbox"], + input[type="radio"] { + border: 1px solid #ccc; + } + + // Remove the horizontal form styles + .form-horizontal { + .control-label { + float: none; + width: auto; + padding-top: 0; + text-align: left; + } + // Move over all input controls and content + .controls { + margin-left: 0; + } + // Move the options list down to align with labels + .control-list { + padding-top: 0; // has to be padding because margin collaspes + } + // Move over buttons in .form-actions to align with .controls + .form-actions { + padding-left: 10px; + padding-right: 10px; + } + } + + // Medias + // Reset float and spacing to stack + .media .pull-left, + .media .pull-right { + float: none; + display: block; + margin-bottom: 10px; + } + + // Remove side margins since we stack instead of indent + .media-object { + margin-right: 0; + margin-left: 0; + } + + // Modals + .modal { + top: 10px; + left: 10px; + right: 10px; + } + + .modal-header .close { + padding: 10px; + margin: -10px; + } + + // Carousel + .carousel-caption { + position: static; + } + +} diff --git a/docdoku-web-front/app/less/common/responsive_768px_979px.less b/docdoku-web-front/app/less/common/responsive_768px_979px.less new file mode 100755 index 0000000000..0a5f8571c0 --- /dev/null +++ b/docdoku-web-front/app/less/common/responsive_768px_979px.less @@ -0,0 +1,18 @@ +// +// Responsive: Tablet to desktop +// -------------------------------------------------- + +@media (min-width: 768px) and (max-width: 979px) { + + // Fixed grid + #grid > .core(@gridColumnWidth768, @gridGutterWidth768); + + // Fluid grid + #grid > .fluid(@fluidGridColumnWidth768, @fluidGridGutterWidth768); + + // Input grid + #grid > .input(@gridColumnWidth768, @gridGutterWidth768); + + // No need to reset .thumbnails here since it's the same @gridGutterWidth + +} diff --git a/docdoku-web-front/app/less/common/responsive_modal.less b/docdoku-web-front/app/less/common/responsive_modal.less new file mode 100644 index 0000000000..6781591702 --- /dev/null +++ b/docdoku-web-front/app/less/common/responsive_modal.less @@ -0,0 +1,16 @@ +// +//Responsive : Modal +// + +@media (max-height: 1200px) { + .modal .modal-body{ + max-height:600px; + } +} + +@media (max-height: 768px) { + + .modal .modal-body{ + max-height:421px; + } +} diff --git a/docdoku-web-front/app/less/common/responsive_navbar.less b/docdoku-web-front/app/less/common/responsive_navbar.less new file mode 100755 index 0000000000..5b996d8735 --- /dev/null +++ b/docdoku-web-front/app/less/common/responsive_navbar.less @@ -0,0 +1,225 @@ +// +// Responsive: Navbar +// -------------------------------------------------- + +// TABLETS AND BELOW +// ----------------- +@media (max-width: @navbarCollapseWidth) { + + // UNFIX THE TOPBAR + // ---------------- + // Remove any padding from the body + body { + padding-top: 0; + } + + // Unfix the navbars + .navbar-fixed-top, + .navbar-fixed-bottom { + position: static; + } + + .navbar-fixed-top { + margin-bottom: @baseLineHeight; + } + + .navbar-fixed-bottom { + margin-top: @baseLineHeight; + } + + .navbar-fixed-top .navbar-inner, + .navbar-fixed-bottom .navbar-inner { + padding: 5px; + } + + .navbar .container { + width: auto; + padding: 0; + } + + // Account for brand name + .navbar .brand { + padding-left: 10px; + padding-right: 10px; + margin: 0 0 0 -5px; + } + + // COLLAPSIBLE NAVBAR + // ------------------ + // Nav collapse clears brand + .nav-collapse { + clear: both; + } + + // Block-level the nav + .nav-collapse .nav { + float: none; + margin: 0 0 (@baseLineHeight / 2); + } + + .nav-collapse .nav > li { + float: none; + } + + .nav-collapse .nav > li > a { + margin-bottom: 2px; + } + + .nav-collapse .nav > .divider-vertical { + display: none; + } + + .nav-collapse .nav .nav-header { + color: @navbarText; + text-shadow: none; + } + + // Nav and dropdown links in navbar + .nav-collapse .nav > li > a, + .nav-collapse .dropdown-menu a { + padding: 9px 15px; + font-weight: bold; + color: white; + border-radius: 3px; + } + + // Buttons + .nav-collapse .btn { + padding: 4px 10px 4px; + font-weight: normal; + border-radius: 4px; + } + + .nav-collapse .dropdown-menu li + li a { + margin-bottom: 2px; + } + + .nav-collapse .nav > li > a:hover, + .nav-collapse .dropdown-menu a:hover { + background-color: @navbarBackground; + } + + .navbar-inverse .nav-collapse .nav > li > a, + .navbar-inverse .nav-collapse .dropdown-menu a { + color: @navbarInverseLinkColor; + } + + .navbar-inverse .nav-collapse .nav > li > a:hover, + .navbar-inverse .nav-collapse .dropdown-menu a:hover { + background-color: @navbarInverseBackground; + } + + // Buttons in the navbar + .nav-collapse.in .btn-group { + margin-top: 5px; + padding: 0; + } + + // Dropdowns in the navbar + .nav-collapse .dropdown-menu { + position: static; + top: auto; + left: auto; + float: none; + display: none; + max-width: none; + margin: 0 15px; + padding: 0; + background-color: transparent; + border: none; + border-radius: 0; + .box-shadow(none); + } + + .nav-collapse .open > .dropdown-menu { + display: block; + } + + .nav-collapse .dropdown-menu:before, + .nav-collapse .dropdown-menu:after { + display: none; + } + + .nav-collapse .dropdown-menu .divider { + display: none; + } + + .nav-collapse .nav > li > .dropdown-menu { + &:before, + &:after { + display: none; + } + } + + // Forms in navbar + .nav-collapse .navbar-form, + .nav-collapse .navbar-search { + float: none; + padding: (@baseLineHeight / 2) 15px; + margin: (@baseLineHeight / 2) 0; + border-top: 1px solid @navbarBackground; + border-bottom: 1px solid @navbarBackground; + //.box-shadow(~"inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1)"); + } + + .navbar-inverse .nav-collapse .navbar-form, + .navbar-inverse .nav-collapse .navbar-search { + border-top-color: @navbarInverseBackground; + border-bottom-color: @navbarInverseBackground; + } + + // Pull right (secondary) nav content + .navbar .nav-collapse .nav.pull-right { + float: none; + margin-left: 0; + } + + // Hide everything in the navbar save .brand and toggle button */ + .nav-collapse, + .nav-collapse.collapse { + overflow: hidden; + height: 0; + } + + // Navbar button + .navbar .btn-navbar { + display: block; + } + + // STATIC NAVBAR + // ------------- + .navbar-static .navbar-inner { + padding-left: 10px; + padding-right: 10px; + } + + // Negative breadcrumb-menu design + body #header ul.breadcrumb-menu > li:first-child > a { + padding: 9px 15px !important; + border: none; + border-radius: 3px !important; + background: none; + + &:before, &:after { + display: none; + } + + .caret { + display: inline-block !important; + } + } + +} + +// DEFAULT DESKTOP +// --------------- + +@media (min-width: @navbarCollapseDesktopWidth) { + + // Required to make the collapsing navbar work on regular desktops + .nav-collapse.collapse { + height: auto !important; + overflow: visible !important; + } + +} diff --git a/docdoku-web-front/app/less/common/responsive_utilities.less b/docdoku-web-front/app/less/common/responsive_utilities.less new file mode 100755 index 0000000000..a9ef2298aa --- /dev/null +++ b/docdoku-web-front/app/less/common/responsive_utilities.less @@ -0,0 +1,80 @@ +// +// Responsive: Utility classes +// -------------------------------------------------- + +// Hide from screenreaders and browsers +// Credit: HTML5 Boilerplate +.hidden { + display: none; + visibility: hidden; +} + +// Visibility utilities + +// For desktops +.visible-phone { + display: none !important; +} + +.visible-tablet { + display: none !important; +} + +.hidden-phone { +} + +.hidden-tablet { +} + +.hidden-desktop { + display: none !important; +} + +.visible-desktop { + display: inherit !important; +} + +// Tablets & small desktops only +@media (min-width: 768px) and (max-width: 979px) { + // Hide everything else + .hidden-desktop { + display: inherit !important; + } + + .visible-desktop { + display: none !important; + } + + // Show + .visible-tablet { + display: inherit !important; + } + + // Hide + .hidden-tablet { + display: none !important; + } +} + +// Phones only +@media (max-width: 767px) { + // Hide everything else + .hidden-desktop { + display: inherit !important; + } + + .visible-desktop { + display: none !important; + } + + // Show + .visible-phone { + display: inherit !important; + } + + // Use inherit to restore previous behavior + // Hide + .hidden-phone { + display: none !important; + } +} diff --git a/docdoku-web-front/app/less/common/roles.less b/docdoku-web-front/app/less/common/roles.less new file mode 100644 index 0000000000..755f5afd04 --- /dev/null +++ b/docdoku-web-front/app/less/common/roles.less @@ -0,0 +1,65 @@ +#form-roles { + + .well.roles-item { + + padding: 6px; + margin-bottom: 6px; + + a { + margin: 0 3px; + font-size: 15px; + line-height: 18px; + color: black; + text-shadow: 0 1px 0 white; + opacity: 0.2; + + &:hover { + text-decoration: none; + color: #9d261d; + opacity: 1; + } + + } + + input { + margin: 0 3px; + } + select { + margin: 0 3px; + } + } +} + +.role-mapping { + + .well.roles-item { + + padding: 6px; + margin-bottom: 6px; + + a { + display: none; + } + + input { + margin: 0 3px; + } + + select { + margin: 0 3px; + } + + .required-message{ + display: none; + color:#9c5d5d; + margin:0 0 0 2px; + } + + &.invalid{ + border-color: #9c5d5d; + .required-message{ + display: block; + } + } + } +} diff --git a/docdoku-web-front/app/less/common/scrollbar.less b/docdoku-web-front/app/less/common/scrollbar.less new file mode 100644 index 0000000000..91ae515396 --- /dev/null +++ b/docdoku-web-front/app/less/common/scrollbar.less @@ -0,0 +1,34 @@ +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment { + height: 5px; + width: 5px; + display: block; + background-color: #e2e7ef; +} + +::-webkit-scrollbar-track-piece { + background-color: #e2e7ef; +} + +::-webkit-scrollbar-thumb { + height: 50px; + border: none; + -webkit-border-radius: 2px; + background-color: rgba(100, 100, 100, 0.4); +} + +.post:hover::-webkit-scrollbar-thumb, .photoset-photos:hover::-webkit-scrollbar-thumb, body::-webkit-scrollbar-thumb { + background-color: rgba(150, 150, 150, 0.6); +} + +*::-webkit-scrollbar-thumb:hover { + background-color: rgba(100, 100, 100, 0.7) !important; +} + +*::-webkit-scrollbar-thumb:active { + background-color: rgba(70, 70, 70, 0.8) !important; +} diff --git a/docdoku-web-front/app/less/common/style.less b/docdoku-web-front/app/less/common/style.less new file mode 100644 index 0000000000..72199806f4 --- /dev/null +++ b/docdoku-web-front/app/less/common/style.less @@ -0,0 +1,50 @@ +// Imports only +@import "../../bower_components/bootstrap/less/variables.less"; +@import "elements"; +@import (inline) "../../bower_components/bootstrap/docs/assets/css/bootstrap.css"; +@import (inline) "../../bower_components/bootstrap/docs/assets/css/bootstrap-responsive.css"; +@import "../../bower_components/fontawesome/less/font-awesome"; +@import (inline) "../../bower_components/datatables/media/css/jquery.dataTables.css"; +@import (inline) "../../bower_components/bootstrap-datepicker/dist/css/bootstrap-datepicker.standalone.css"; +@import "general"; +@import "scrollbar"; +@import "variables"; +@import "mixins"; +@import "icons"; +@import "modal"; +@import "modal_base"; +// Refactor of modal +@import "header"; +@import "content"; +@import "bootstrap_switch"; +@import "bootstrap_combobox"; +@import "button"; +@import "footer"; +@import "popover"; +@import "table"; +@import "actions"; +@import "form"; +@import "product_creation"; +@import "attributes"; +@import "files"; +@import "dropdowns"; +@import "linked_documents"; +@import "used_by"; +@import "modification_notifications"; +@import "part_modal"; +@import "part_assembly"; +@import "registration"; +@import "roles"; +@import "lifecycle"; +@import "prompt"; +@import "iteration"; +@import "acl"; +@import "alert"; +@import "udf"; +@import "conversion"; +@import "import"; +@import "lov"; +@import "user_defined_function"; +@import "action_buttons"; +@import "not_found"; +@import "responsive_modal"; diff --git a/docdoku-web-front/app/less/common/table.less b/docdoku-web-front/app/less/common/table.less new file mode 100644 index 0000000000..bff90a19c3 --- /dev/null +++ b/docdoku-web-front/app/less/common/table.less @@ -0,0 +1,129 @@ +#content { + table { + border-radius: 3px; + //box-shadow: 0px 1px 5px #9b9b9b; + border: 1px solid rgba(63, 95, 153, 0.24); + margin: 20px auto; + width: 96% !important; + thead { + background-color: @plm-secondary-color; + /* background-image: -moz-linear-gradient(top, #7a99c1, #2c4361); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#7a99c1), to(#2c4361)); + background-image: -webkit-linear-gradient(top, #7a99c1, #2c4361); + background-image: -o-linear-gradient(top, #7a99c1, #2c4361); + background-image: linear-gradient(to bottom, #7a99c1, #2c4361); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff7a99c1', endColorstr='#ff2c4361', GradientType=0);*/ + color: white; + text-shadow: #555 0px -1px 0px; + tr { + height: 40px; + font-size: 13px; + th { + border-bottom: none; + vertical-align: middle; + padding: 0 .5em; + } + th:first-child { + border-radius: 3px 0 0 0; + } + th:last-child { + border-radius: 0 3px 0 0; + } + } + } + tbody { + color: #62697B; + font-size: 12px; + tr { + height: 35px; + td { + padding: 0 .5em; + vertical-align: middle; + &.reference, &.part_number, &.product_id, &.configuration_id, &.author_login { + cursor: pointer; + font-weight: bold; + i { + font-size: 14px; + margin-right: 4px; + text-align: center; + display: inline-block; + width: 14px; + } + } + &.reference:hover, &.part_number:hover, &.product_id:hover { + color: #333; + } + } + td:first-child { + horizontal-align: center; + } + } + } + &.table-striped tbody tr td { + background: transparent; + } + &.table-striped tbody tr:nth-child(odd) { + background: #f5f5f5; + &:hover { + background: #eee; + } + } + &.table-striped tbody tr:nth-child(even) { + background: white; + &:hover { + background: #eee; + } + } + + } +} + +.dataTables_wrapper { + + .dataTables_filter { + margin: 10px 0 0 2%; + label { + color: #62697B; + input { + margin: 8px; + height: 14px !important; + background: #f5f5f5; + } + } + } + + table.dataTable { + + margin: 0 auto 20px auto !important; + + thead th, thead td { + padding-right: 14px !important; + cursor: pointer; + &:active { + outline: none; + } + + &.sorting { + background: none; + &:hover { + background: url('../images/sort_both.png') no-repeat center right; + text-decoration: underline; + } + } + + &.sorting_asc { + background: url('../images/sort_asc.png') no-repeat center right; + text-decoration: underline; + } + + &.sorting_desc { + background: url('../images/sort_desc.png') no-repeat center right; + text-decoration: underline; + } + + } + + } + +} diff --git a/docdoku-web-front/app/less/common/tags.less b/docdoku-web-front/app/less/common/tags.less new file mode 100644 index 0000000000..1bb0e84e0e --- /dev/null +++ b/docdoku-web-front/app/less/common/tags.less @@ -0,0 +1,28 @@ +.master-tags-list li, +.existing-tags-list li, +.tags-to-add-list li { + padding: 6px 9px 2px 6px; + margin-right: 6px; + + a { + font-size: 15px; + color: black; + text-shadow: 0 1px 0 white; + opacity: 0.2; + + &:hover { + text-decoration: none; + color: #F00; + opacity: 1; + } + } + + span { + color: #08C; + margin-left: 3px; + } +} + +.existing-tags-list li:hover { + cursor: pointer; +} diff --git a/docdoku-web-front/app/less/common/udf.less b/docdoku-web-front/app/less/common/udf.less new file mode 100644 index 0000000000..8133e657eb --- /dev/null +++ b/docdoku-web-front/app/less/common/udf.less @@ -0,0 +1,7 @@ +#user_defined_function_modal { + + textarea { + width: 280px; + height: 100px; + } +} diff --git a/docdoku-web-front/app/less/common/used_by.less b/docdoku-web-front/app/less/common/used_by.less new file mode 100644 index 0000000000..f5536c4602 --- /dev/null +++ b/docdoku-web-front/app/less/common/used_by.less @@ -0,0 +1,29 @@ +div.group-title { + font-weight: bold; +} + +div.used-by-items-view { + + ul.used-by-items { + margin: 12px 0; + list-style: none; + + li.used-by-item { + padding: 6px 4px 2px 4px; + margin: 0 12px 12px 0; + display: inline-block; + + .reference { + padding: 0 6px; + font-weight: bold; + margin: 0; + display: inline-block; + } + .reference-path { + padding: 0 6px; + font-weight: normal; + margin: 0; + } + } + } +} diff --git a/docdoku-web-front/app/less/common/user_defined_function.less b/docdoku-web-front/app/less/common/user_defined_function.less new file mode 100644 index 0000000000..9fdc519392 --- /dev/null +++ b/docdoku-web-front/app/less/common/user_defined_function.less @@ -0,0 +1,19 @@ +#user_defined_function_modal { + + .run-udf { + display: none; + } + + .calculation { + + select { + width: 111px; + display: inline; + } + margin-bottom: 10px; + .result { + margin-top: 10px; + } + } + +} diff --git a/docdoku-web-front/app/less/common/variables.less b/docdoku-web-front/app/less/common/variables.less new file mode 100644 index 0000000000..9c32b06f1d --- /dev/null +++ b/docdoku-web-front/app/less/common/variables.less @@ -0,0 +1,33 @@ +// Navbar +@navbarBackground: #213251; +@navbarBackgroundHighlight: lighten(@navbarBackground, 20%); + +@navbarLinkColorActive: rgba(255, 255, 255, 0.8); +@navbarLinkBackgroundActive: transparent; + +@navBackground: #F3F5FB; +@navbarBrandColor: #ffffff; + +// Actionbar +@actionBarBackground: #F4F4F4; + +// Font Size +@baseFontSize: 13px; +@baseLineHeight: 18px; + +/* +// Default colors +@plm-primary-color: #1A658A; +@plm-primary-hover-color: #255578; +@plm-secondary-color: #3a87ad; +@background-color: #E3E8EA; +@menu-color: #f5f5f5; +*/ + +@plm-primary-color: #1A658A; +@plm-primary-hover-color: #255578; +@plm-secondary-color: #3a87ad; +@background-color: #E3E8EA; +@menu-color: #f5f5f5; + + diff --git a/docdoku-web-front/app/less/document-management/content.less b/docdoku-web-front/app/less/document-management/content.less new file mode 100644 index 0000000000..5cb7282d53 --- /dev/null +++ b/docdoku-web-front/app/less/document-management/content.less @@ -0,0 +1,34 @@ +#document-management-content { + width: 85%; + height: 100%; + overflow: auto; + position: absolute; + right: 0; + top: 0; + z-index: 0; + /*-webkit-box-shadow: 0px 1px 3px #9b9b9b; + box-shadow: 0px 1px 3px #9b9b9b;*/ + input { + height: 22px; + margin: 8px; + } + + td.reference { + i.fa-pencil { + color: #f89406; + } + + i.fa-lock { + color: #ee5f5b; + } + + i.fa-check { + color: #51a351; + } + + i.fa-exclamation { + color: #ee5f5b; + } + + } +} diff --git a/docdoku-web-front/app/less/document-management/documents.less b/docdoku-web-front/app/less/document-management/documents.less new file mode 100644 index 0000000000..085d34e452 --- /dev/null +++ b/docdoku-web-front/app/less/document-management/documents.less @@ -0,0 +1,34 @@ +#document-management-content { + + td { + a.dochandle { + font-size: 14px; + color: #aaa; + text-shadow: 0 1px 0 white; + + &:hover { + color: #555; + cursor: move; + text-decoration: none; + } + } + + &.doc-ref > a { + color: #62697B; + &:hover { + text-decoration: none; + color: #333; + } + } + } + + tr.moving { + a.dochandle { + color: #555; + } + + td.doc-ref > a { + color: #333; + } + } +} diff --git a/docdoku-web-front/app/less/document-management/menu.less b/docdoku-web-front/app/less/document-management/menu.less new file mode 100644 index 0000000000..b0f1c85310 --- /dev/null +++ b/docdoku-web-front/app/less/document-management/menu.less @@ -0,0 +1,267 @@ +#document-management-menu { + + position: relative; + /*-webkit-box-shadow: 0px 1px 3px #9b9b9b; + box-shadow: 0px 1px 3px #9b9b9b;*/ + float: left; + background-color: #f5f5f5; + height: 100%; + min-height: 100%; + width: 15%; + min-width: 15%; + max-width: 55%; + border: none; + border-radius: none; + border-right: 1px solid rgba(63, 95, 153, 0.24); + padding: 0; + margin: 0; + z-index: 1; + + .nav { + margin: 10px 0 5% 6px; + padding: 0; + height: 95%; + height: -moz-calc(~"95% - 68px"); + height: -webkit-calc(~"95% - 68px"); + height: calc(~"95% - 68px"); + overflow: auto; + } + + .well { + background: none; + box-shadow: none; + border: none; + } + + .nav-header { + font-size: 12px; + padding: 3px 10px; + display: block; + font-weight: bold; + line-height: 18px; + color: #999999; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-transform: uppercase; + } + + .nav-list-entry { + position: relative; + padding: 0 40px 0 6px; + height: 26px; + display: block; + + &.move-doc-into { + border: 1px dashed #8fbc8f; + height: 25px; + margin-top: -1px; + padding-left: 5px; + } + + .icon { + float: left; + } + + > a { + overflow: hidden; + text-overflow: ellipsis; + line-height: 26px; + white-space: nowrap; + width: 100%; + float: left; + + .status { + float: left; + } + } + + > .btn-group { + right: 0; + position: absolute; + } + + .dropdown-toggle { + border-radius: 0; + margin: 0 0 0 8px; + display: none; + border: none; + background: none; + -webkit-box-shadow: none; + box-shadow: none; + padding: 3px 12px; + .caret { + border-top-color: #000000; + opacity: .5; + } + } + + .dropdown-toggle:hover { + background: #CCC; + .caret { + opacity: 1; + } + } + + .dropdown-menu { + border-radius: 0; + padding: 0 0 0 0; + margin: -1px 1px 0 0; + right: 0; + left: auto; + max-width: 250px; + + > li { + + width: 100%; + padding: 0; + + > a { + font-size: 13px; + padding: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + } + + } + li.new-folder .icon { + .plm-icon-menu-folder-new; + margin: 4px 6px 4px 6px; + } + li.edit .icon { + .plm-icon-menu-edit; + margin: 4px 6px 4px 6px; + } + li.delete .icon { + .plm-icon-menu-delete; + margin: 4px 6px 4px 6px; + } + } + + .nav-checkedOut-number-item { + margin-top: -22px; + } + } + + .nav-list-entry:hover { + > a { + text-decoration: none; + } + background-color: #EEE; + .dropdown-toggle { + display: block; + } + } + + .nav-list-entry.active, .nav-list-entry.active > a { + /*text-shadow: 0 -1px 0 @grayLight;*/ + color: #FFF; + background-color: @plm-secondary-color; + .badge { + background-color: #fff; + color: @plm-secondary-color; + } + } + + .items { + overflow: visible; + list-style-type: none; + margin-left: 6px; + padding: 0; + } + + .ui-resizable-handle { + + opacity: 0; + position: absolute; + bottom: 65px; + right: 0; + top: 0; + z-index: 1000; + width: 8px; + cursor: e-resize; + display: block !important; + + } +} + +#folder-nav { + *zoom: 1; + &:before, + &:after { + display: table; + content: ""; + // Fixes Opera/contenteditable bug: + // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952 + line-height: 0; + } + &:after { + clear: both; + } + + .folder { + .status { + .plm-icon-nav-folder-status-closed; + &:hover { + opacity: 1; + } + } + .icon { + .plm-icon-nav-folder; + } + } + + .folder.open > div > a { + > .status { + .plm-icon-nav-folder-status-opened; + &:hover { + opacity: 1; + } + } + + > .icon { + .plm-icon-nav-folder-opened; + } + } + + .folder.home > .header > a .icon { + .plm-icon-nav-folder-home; + } + + .items { + border-left: 1px solid rgba(155, 155, 155, 0.35); + overflow: visible; + list-style-type: none; + margin-left: 11px; + padding: 0; + } + > .items { + margin-left: 6px; + border-left: 1px solid rgba(155, 155, 155, 0); + } +} + +#tags-nav { + *zoom: 1; + &:before, + &:after { + display: table; + content: ""; + // Fixes Opera/contenteditable bug: + // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952 + line-height: 0; + } + &:after { + clear: both; + } +} + +#product_container { + a.product-management { + i.fa-briefcase { + float: left; + font-size: 12px; + margin: 5px 5px 0 0; + } + } +} diff --git a/docdoku-web-front/app/less/document-management/search.less b/docdoku-web-front/app/less/document-management/search.less new file mode 100644 index 0000000000..a2cc03ef2e --- /dev/null +++ b/docdoku-web-front/app/less/document-management/search.less @@ -0,0 +1,45 @@ +#document-search-form { + + margin: 0 8px; + display: inline-block; + width: 261px; + + .search-document-input { + width: 192px; + padding-right: 30px; + border-radius: 6px; + } + + .advanced-search-button { + margin-left: -66px; + cursor: pointer; + opacity: 0.4; + } + .advanced-search-button:hover { + opacity: 0.6; + } + + .fa-search { + margin-left: 5px; + border-radius: 0 6px 6px 0; + padding: 8px 12px; + } + +} + +#advanced_search_form { + + .creationDates, .modificationDates { + + input { + width: auto; + margin-bottom: 5px; + } + + } + + #search-add-attributes { + margin-bottom: 18px; + } + +} diff --git a/docdoku-web-front/app/less/document-management/style.less b/docdoku-web-front/app/less/document-management/style.less new file mode 100644 index 0000000000..7d7fa6a6ee --- /dev/null +++ b/docdoku-web-front/app/less/document-management/style.less @@ -0,0 +1,55 @@ +@import "../common/style"; +@import "../common/tags"; +@import "../common/config_spec"; +@import "../modules/style"; +@import "menu"; +@import "content"; +@import "documents"; +@import "search"; +@import "task"; +@import (inline) "../../bower_components/selectize/dist/css/selectize.default.css"; + +#iteration-attributes { + margin-left: -15px; +} + +[id^="editable-list-add-item-view"] { + margin-top: -10px; + margin-bottom: 10px; +} + +[id^="editable-list-add-item-view"] i { + font-size: 13px; + margin-top: 3px; + margin-left: 5px; +} + +.editable-list li { + margin: 4px 4px 10px 4px; +} + +td.document-master-subscriptions { + i { + color: orange; + cursor: pointer; + } +} + +td.document-master-share { + i { + cursor: pointer; + &:hover { + color: #333; + } + } +} + +td.document-attached-files { + i { + cursor: pointer; + &:hover { + color: #333; + opacity: 1; + } + } +} diff --git a/docdoku-web-front/app/less/document-management/task.less b/docdoku-web-front/app/less/document-management/task.less new file mode 100644 index 0000000000..2ab6b59cb8 --- /dev/null +++ b/docdoku-web-front/app/less/document-management/task.less @@ -0,0 +1,3 @@ +div.status-filter { + margin: 16px 0 0 32px; +} diff --git a/docdoku-web-front/app/less/document-management/templates.less b/docdoku-web-front/app/less/document-management/templates.less new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docdoku-web-front/app/less/documents/document-revision.less b/docdoku-web-front/app/less/documents/document-revision.less new file mode 100644 index 0000000000..361a94bdc1 --- /dev/null +++ b/docdoku-web-front/app/less/documents/document-revision.less @@ -0,0 +1,54 @@ +#content{ + + .document-revision{ + + margin: 42px; + padding: 10px 20px; + background-color: #fff; + border: 1px solid #ccc ; + + table{ + width:100% !important; + margin-top: 0; + } + + .accordion-heading { + position: relative; + } + + .accordion-heading a.download { + position: absolute; + left: 8px; + top: 8px; + } + .accordion-heading a.download-pdf { + position: absolute; + left: 30px; + top: 8px; + } + .accordion-heading a.external-download { + position: absolute; + left: 26px; + top: 8px; + } + .accordion-heading .accordion-toggle { + padding: 8px 50px; + } + input[readonly].autoselect { + width: 40%; + cursor: text; + margin-left: 10px; + } + .pdfviewer { + width: 100%; + height: 1000px; + } + + #assembly-frame{ + width: 100%; + height: 600px; + border: none; + } + + } +} diff --git a/docdoku-web-front/app/less/documents/style.less b/docdoku-web-front/app/less/documents/style.less new file mode 100644 index 0000000000..42e200c887 --- /dev/null +++ b/docdoku-web-front/app/less/documents/style.less @@ -0,0 +1,2 @@ +@import "../common/style"; +@import "document-revision"; diff --git a/docdoku-web-front/app/less/download/content.less b/docdoku-web-front/app/less/download/content.less new file mode 100644 index 0000000000..37727a7574 --- /dev/null +++ b/docdoku-web-front/app/less/download/content.less @@ -0,0 +1,39 @@ +#download-content { + width: 85%; + height: 100%; + overflow: auto; + position: absolute; + right: 0; + top: 0; + z-index: 0; + +} + +.download-dplm-platform-list li { + list-style: none; +} +.download-dplm-platform-list li a { + display: inline-block; + margin: 5px; + padding: 2px 0 3px 10px; + border: @plm-primary-color; + border-radius: 20px; + background-color: @plm-secondary-color; + color: #ffffff; +} +.download-dplm-platform-list li a:hover { + color: rgba(255, 255, 255, 0.8); + text-decoration: initial; +} +.download-dplm-platform-list li a img { + margin-right: 4px; + border-radius: 50%; + background: white; +} +.download-dplm-platform-list li a span { + display: inline-block; + width: 120px; +} +.download-dplm-platform-list li a i { + margin: 0 8px 0 0; +} diff --git a/docdoku-web-front/app/less/download/style.less b/docdoku-web-front/app/less/download/style.less new file mode 100644 index 0000000000..aec675abba --- /dev/null +++ b/docdoku-web-front/app/less/download/style.less @@ -0,0 +1,3 @@ +@import "../common/style"; +@import "../modules/style"; +@import "content"; diff --git a/docdoku-web-front/app/less/main/login.less b/docdoku-web-front/app/less/main/login.less new file mode 100644 index 0000000000..63e01ba28a --- /dev/null +++ b/docdoku-web-front/app/less/main/login.less @@ -0,0 +1,275 @@ +#content { + width: 96%; + margin: 30px auto 0 auto; + text-align: center; +} +#content > * { + text-align: left; +} +#general_presentation { + display: flex; + flex-wrap: wrap; + width: 100%; + max-width: 848px; + margin: 0 auto 30px auto; + padding: 0; + text-align: center; + vertical-align: bottom; + background: -webkit-radial-gradient(center top, #e2e7ef 0%, #abbacd 100%); + background: -moz-radial-gradient(center top, #e2e7ef 0%, #abbacd 100%); + background: -ms-radial-gradient(center top, #e2e7ef 0%, #abbacd 100%); + background: -o-radial-gradient(center top, #e2e7ef 0%, #abbacd 100%); + background: radial-gradient(center top, #e2e7ef 0%, #abbacd 100%); + border: 1px solid #ccc; +} +#demo-scene { + order: 2; + flex: 1 0 200px; + box-sizing: border-box; + height: 354px; + margin: 0; + line-height: 51px; + canvas { + height: 332px; + } +} + +#form_container { + flex: 1 0 200px; + box-sizing: border-box; + + background-color: white; + line-height: 51px; + h3 { + text-align: center; + font-size: 20px; + color: #434C64; + i { + margin-right: 10px; + margin-top: -2px; + } + } + + + &.put-right{ + order:3; + float: right; + margin: 0; + padding: 20px 44px; + .notifications{ + .alert { + line-height: normal; + h4{ + display: none; + } + margin-bottom: 12px; + max-width: 218px; + .Alert-message { + max-width: 206px; + } + } + } + } + + + &.put-above{ + order: 1; + flex: 1 0 100%; + padding: 0 20px; + .notifications{ + .alert { + line-height: normal; + h4{ + display: none; + } + margin-bottom: 12px; + max-width: 498px; + .Alert-message { + max-width: 486px; + } + } + } + } + + .form-inputs{ + display: flex; + flex-wrap: wrap; + >p{ + flex: 1 0 200px; + box-sizing: border-box; + /* background: #e0ddd5; */ + color: #171e42; + padding: 0; + margin-left: 10px; + margin-top: 0px; + } + } +} +#login_form.connecting{ + #login_form-login_button{ + visibility: hidden; + } +} +#login_form, +#recovery_form { + + margin-top: 15px; + margin-bottom: 0px; + text-align: left; + label { + color: #5B6B80; + } + input[type="text"], + input[type="password"]{ + width: 250px; + } +} +#recovery_form{ + h4 { + font-size: 16px; + color: #434C64; + } +} +#account_creation_form{ + text-align: left; + h3{ + text-align: left; + } + h4 { + font-size: 16px; + color: #434C64; + } + .terms-of-service{ + float: right; + width: 200px; + line-height: 16px; + } + .account-form-buttons{ + input,a{ + margin-top: 30px; + margin-right: 10px; + } + } +} +.form_button_container { + float: right; + margin-bottom: 15px; +} + +@media (max-width: 720px) { + #form_container, + #recovery_form_container { + float: none; + padding-right: 0; + padding-left: 0; + width: 100%; + } + #login_form, + #recovery_form { + text-align: center; + } + .form_button_container { + float: none; + } + #demo-scene { + width: 100%; + } +} +#detail_presentation { + margin: 0px auto; + max-width: 1520px; + .well { + margin-bottom: 0; + float: left; + position: relative; + border: none; + border-radius: 0; + background: none; + height: 196px; + width: 280px; + box-shadow: none; + border-top: 1px solid #cccccc; + padding: 19px 50px; + + h2 { + line-height: 18px; + color: #0088cc; + font-size: 18px; + margin-top: 0px; + } + h3 { + line-height: 12px; + font-size: 12px; + margin-top: 0; + margin-bottom: 5px; + color: #999; + } + ul { + margin-bottom: 0; + list-style: none; + margin-left: 10px; + li { + line-height: 13px; + font-size: 13px; + margin-top: 8px; + margin-bottom: 5px; + color: #666; + font-weight: normal; + em { + font-weight: 500; + font-style: normal; + } + } + } + p { + margin: 15px 0 0 0; + color: #666; + font-style: italic; + } + > div { + position: absolute; + bottom: 10px; + font-weight: bold; + width: inherit; + } + } +} + +@media (max-width: 1583px) { + #detail_presentation { + max-width: 760px; + } +} +@media (max-width: 878px) { + #detail_presentation { + max-width: 600px; + } + #detail_presentation .well { + padding: 19px 10px; + } +} +@media (max-width: 624px) { + #detail_presentation { + max-width: 300px; + } + #detail_presentation .well { + padding: 19px 10px; + height: auto; + } +} + + +#footer { + border: none; + text-align: center; + clear: both; + margin-top: 20px; + margin-bottom: 20px; + p { + text-align: center; + font-size: 0.8em; + } + a { + color: #0061c0; + } +} diff --git a/docdoku-web-front/app/less/main/style.less b/docdoku-web-front/app/less/main/style.less new file mode 100644 index 0000000000..03340869a9 --- /dev/null +++ b/docdoku-web-front/app/less/main/style.less @@ -0,0 +1,2 @@ +@import "../common/style"; +@import "login"; diff --git a/docdoku-web-front/app/less/modules/chat_module.less b/docdoku-web-front/app/less/modules/chat_module.less new file mode 100644 index 0000000000..9c490ce874 --- /dev/null +++ b/docdoku-web-front/app/less/modules/chat_module.less @@ -0,0 +1,106 @@ +#chat_module { + position: absolute; + bottom: 1%; + right: 1%; + height: 0; + width: 0; + + &.chat_module_hidden { + height: 100%; + background: red; + width: 2px; + } + + > .chat_session { + resize:both; + background: whiteSmoke; + position: absolute; + width: 310px; + z-index: 1200; + bottom: 0; + right: 0; + border-radius: 4px; + border: 1px solid #ccc; + + &.chat_session_on_top { + z-index: 1201; + } + + > .chat_session_header { + background: @plm-primary-color; + padding: 5px; + color: white; + cursor: move; + border-radius: 4px 4px 0 0; + } + + > .chat_session_header > span.chat_session_title { + font-weight: bold; + width: 100%; + margin-bottom: 10px; + } + + > .chat_session_header > .fa-times { + position: absolute; + top: 6px; + right: 7px; + cursor: pointer; + } + + + > .chat_session_header > .fa-video-camera { + position: absolute; + top: 6px; + right: 27px; + cursor: pointer; + } + + > ul.chat_session_messages { + width: 100%; + margin: 10px 0 0 0; + max-height: 300px; + overflow: hidden; + overflow-y: auto; + border-bottom: 1px solid #CCC; + + > li { + list-style: none; + text-overflow: ellipsis; + list-style: none; + word-wrap: break-word; + margin: 0 5px; + &.chat_message_error { + color: #B94A48; + } + } + } + + > div.chat_webrtc_invite { + display: none; + padding: 5px; + border-bottom: 1px solid #CCC; + } + + > div.chat_webrtc_invite > span { + float: left; + width: 100%; + margin-bottom: 6px; + } + + > div.chat_webrtc_invite > .btn { + margin-right: 6px; + } + + > div.chat_session_reply { + padding: 5px; + > .chat_session_reply_form { + margin: 0; + > input { + margin: 0; + } + } + } + + } + +} diff --git a/docdoku-web-front/app/less/modules/coworkers_access_module.less b/docdoku-web-front/app/less/modules/coworkers_access_module.less new file mode 100644 index 0000000000..5c465edb8c --- /dev/null +++ b/docdoku-web-front/app/less/modules/coworkers_access_module.less @@ -0,0 +1,32 @@ +#coworkers_access_module_entries { + min-width: 200px; +} + +#coworkers_access_module_entries span { + float: left; +} + +#coworkers_access_module_entries .fa-user { + margin: 4px 4px 0 -10px; + float: left; +} + +#coworkers_access_module_entries .corworker-action { + opacity: 0.5; + float: right; + margin: 4px 0 0 0; + padding-right: 3px; +} + +#coworkers_access_module_entries .corworker-action-disable { + opacity: 0.2; + float: right; + margin: 4px 0 0 0; + padding-right: 3px; +} + +#coworkers_access_module_entries > li > a > i.corworker-action:hover { + opacity: 1; + color: white; + text-shadow: 0 -1px 0 #aaa; +} diff --git a/docdoku-web-front/app/less/modules/style.less b/docdoku-web-front/app/less/modules/style.less new file mode 100644 index 0000000000..0d4eaec9fa --- /dev/null +++ b/docdoku-web-front/app/less/modules/style.less @@ -0,0 +1,3 @@ +@import 'chat_module'; +@import 'coworkers_access_module'; +@import 'webrtc_module'; diff --git a/docdoku-web-front/app/less/modules/webrtc_module.less b/docdoku-web-front/app/less/modules/webrtc_module.less new file mode 100644 index 0000000000..0ca10ab257 --- /dev/null +++ b/docdoku-web-front/app/less/modules/webrtc_module.less @@ -0,0 +1,139 @@ +#webrtc_module { + background: rgba(0, 0, 0, 0.8); + /*box-shadow: 0 0 5px #9B9B9B;*/ + position: absolute; + width: 0; + height: 0; + z-index: 1200; + top: 10%; + right: 0; + border-radius: 6px 0 0 6px; + -webkit-transition: width 0.5s ease; + -moz-transition: width 0.5s ease; + -o-transition: width 0.5s ease; + -ms-transition: width 0.5s ease; + transition: width 0.5s ease; + overflow: hidden; +} + +#webrtc_module.webrtc_shown { + width: 70%; + height: 80%; +} + +#webrtc_module.webrtc_minimized { + top: 6%; + width: 10%; + height: 19%; +} + +#webrtc_module > #webrtc_module_header { + margin: 0; + padding: 5px; + border-radius: 6px 0 0 0; +} + +#webrtc_module > #webrtc_module_header > h3#webrtc_module_title { + margin: 0; + color: white; + text-align: center; +} + +#webrtc_module > #webrtc_module_header > h3#webrtc_module_controls { + font-size: 16px; + position: absolute; + color: white; + top: 0; + cursor: pointer; + left: 15px; +} + +#webrtc_module > #webrtc_module_header > h3#webrtc_module_controls > i { + margin-right: 10px; + float: right; + opacity: 0.6; +} + +#webrtc_module > #webrtc_module_header > h3#webrtc_module_controls > i:hover { + opacity: 0.8; +} + +#webrtc_module > #webrtc_module_body { + text-align: center; + height: 100%; +} + +#webrtc_module > #webrtc_module_body > #webrtc_call_state { + height: 6%; + color: white; +} + +#webrtc_module.webrtc_minimized > #webrtc_module_body > #webrtc_call_state, +#webrtc_module.webrtc_minimized > #webrtc_module_body > #webrtc_video_container > #webrtc_local_video, +#webrtc_module.webrtc_minimized > #webrtc_module_header > h3#webrtc_module_title, +#webrtc_module.webrtc_minimized > #webrtc_module_header > h3#webrtc_module_controls > #webrtc_minimize_btn, +#webrtc_module.webrtc_shown > #webrtc_module_header > h3#webrtc_module_controls > #webrtc_restore_btn { + display: none; +} + +#webrtc_module.webrtc_minimized > #webrtc_module_header { + height: 12%; +} + +#webrtc_module.webrtc_minimized > #webrtc_module_body { + height: 93%; +} + +#webrtc_module > #webrtc_module_body > #webrtc_video_container { + width: 85%; + height: 80%; + position: relative; + margin: 0 auto; + padding: 0; + background-color: rgba(0, 0, 0, 0.6); + border-radius: 6px; + /*box-shadow: 0 0 3px #666;*/ + overflow: hidden; +} + +#webrtc_module > #webrtc_module_body > #webrtc_video_container > video.webrtc_loading { + background-image: url(../images/loader.gif); + background-position: center center; + background-repeat: no-repeat; +} + +#webrtc_module > #webrtc_module_body > #webrtc_video_container > #webrtc_remote_video { + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.4); + border-radius: 6px; + -webkit-transition: all 0.5s ease; + -moz-transition: all 0.5s ease; + -o-transition: all 0.5s ease; + -ms-transition: all 0.5s ease; + transition: all 0.5s ease; + opacity: 0; +} + +#webrtc_module > #webrtc_module_body > #webrtc_video_container > #webrtc_local_video { + position: absolute; + z-index: 2; + bottom: 5px; + left: 5px; + width: 20%; + height: 25%; + background-color: rgba(0, 0, 0, 0.6); + /*box-shadow: 0 0 3px #666;*/ + cursor: move; + border-radius: 6px; + opacity: 0; + -webkit-transition: opacity 0.5s ease; + -moz-transition: opacity 0.5s ease; + -o-transition: opacity 0.5s ease; + -ms-transition: opacity 0.5s ease; + transition: opacity 0.5s ease; + -webkit-transform: scale(-1, 1); + -moz-transform: scale(-1, 1); + -webkit-backface-visibility: hidden; + -moz-backface-visibility: hidden; +} diff --git a/docdoku-web-front/app/less/parts/part-revision.less b/docdoku-web-front/app/less/parts/part-revision.less new file mode 100644 index 0000000000..f73e559008 --- /dev/null +++ b/docdoku-web-front/app/less/parts/part-revision.less @@ -0,0 +1,64 @@ +#content{ + + .part-revision{ + + margin: 42px; + padding: 10px 20px; + background-color: #fff; + border: 1px solid #ccc ; + + table{ + width:100% !important; + margin-top: 0; + } + + .accordion-heading { + position: relative; + } + + .accordion-heading a.download { + position: absolute; + left: 8px; + top: 8px; + } + .accordion-heading a.download-pdf { + position: absolute; + left: 30px; + top: 8px; + } + .accordion-heading a.external-download { + position: absolute; + left: 26px; + top: 8px; + } + .accordion-heading .accordion-toggle { + padding: 8px 50px; + } + input[readonly].autoselect { + width: 40%; + cursor: text; + margin-left: 10px; + } + .pdfviewer { + width: 100%; + height: 1000px; + } + + #assembly-frame{ + width: 100%; + height: 600px; + border: none; + } + + #cad-file-view{ + #cad-file{ + margin: 8px 0; + background: -moz-radial-gradient(center top, #e2e7ef 0%, #abbacd 100%); + background: -webkit-radial-gradient(center top, #e2e7ef 0%, #abbacd 100%); + border: 1px solid #f5f5f5; + } + } + + + } +} diff --git a/docdoku-web-front/app/less/parts/style.less b/docdoku-web-front/app/less/parts/style.less new file mode 100644 index 0000000000..0e3433128f --- /dev/null +++ b/docdoku-web-front/app/less/parts/style.less @@ -0,0 +1,2 @@ +@import "../common/style"; +@import "part-revision"; diff --git a/docdoku-web-front/app/less/product-management/baselines.less b/docdoku-web-front/app/less/product-management/baselines.less new file mode 100644 index 0000000000..e00def936d --- /dev/null +++ b/docdoku-web-front/app/less/product-management/baselines.less @@ -0,0 +1,94 @@ +ul.baselines-list { + + margin: 0; + + li.baseline-item { + + margin: 4px; + list-style: none; + + input[type=checkbox] { + margin: 0 3px 3px 0; + } + + a { + &.stroke { + text-decoration: line-through; + color: red; + } + } + + } + +} + +ul.baselined-parts { + + margin: 2px 4px; + + li.baselined-part-item { + list-style: none; + + label { + display: inline-block; + line-height: 13px; + input[type=radio] { + margin: 0 4px; + } + } + + } +} + +.baselines-filter-bar { + float: left; + display: inline-block; + padding: 8px 0 0px 0; + margin: 0 8px; + label { + display: inline-block; + } + .typeahead { + width: 218px; + } +} + +.choices-list { + .choices { + + .not-retained { + opacity: 0.6; + } + + .fa-long-arrow-right { + color: #888888; + font-size: 12px; + } + + .baseline-choice-item { + list-style: none; + + label { + input[type=checkbox] { + float: left; + margin-left: -20px; + } + } + + .baseline-choice-details { + font-size: 12px; + font-style: italic; + display: block; + margin: -5px 0 5px 20px; + } + + } + } +} + +.optional { + .part-link { + color: #888888; + } +} + diff --git a/docdoku-web-front/app/less/product-management/content.less b/docdoku-web-front/app/less/product-management/content.less new file mode 100644 index 0000000000..195fa6513c --- /dev/null +++ b/docdoku-web-front/app/less/product-management/content.less @@ -0,0 +1,79 @@ +#product-management-content { + width: 85%; + height: 100%; + overflow: auto; + position: absolute; + right: 0; + top: 0; + z-index: 0; + + /*-webkit-box-shadow: 0px 1px 3px #9b9b9b; + box-shadow: 0px 1px 3px #9b9b9b;*/ + + td { + a.parthandle { + font-size: 14px; + color: #aaa; + text-shadow: 0 1px 0 white; + + &:hover { + color: #555; + cursor: move; + text-decoration: none; + } + } + + &.part_number > a { + color: #62697B; + &:hover { + text-decoration: none; + color: #333; + } + } + } + + tr.moving { + a.parthandle { + color: #555; + } + + td.part_number > a { + color: #333; + } + } + + td.modification_notification, td.part-revision-share, td.part-attached-files { + i { + cursor: pointer; + &:hover { + color: #333; + } + } + } + + td.modification_notification { + i.fa-exclamation { + color: #ee5f5b; + } + } + + td.part_number { + i.fa-pencil { + color: #f89406; + } + + i.fa-lock { + color: #ee5f5b; + } + + i.fa-check { + color: #51a351; + } + } + +} + +.modal-subtitle { + font-style: italic; + line-height: 20px; +} diff --git a/docdoku-web-front/app/less/product-management/menu.less b/docdoku-web-front/app/less/product-management/menu.less new file mode 100644 index 0000000000..362215b1d2 --- /dev/null +++ b/docdoku-web-front/app/less/product-management/menu.less @@ -0,0 +1,187 @@ +#product-management-menu { + + position: relative; + /* -webkit-box-shadow: 0px 1px 3px #9b9b9b; + box-shadow: 0px 1px 3px #9b9b9b;*/ + float: left; + background-color: #f5f5f5; + height: 100%; + min-height: 100%; + width: 15%; + min-width: 15%; + max-width: 55%; + border: none; + border-radius: none; + border-right: 1px solid rgba(63, 95, 153, 0.24); + padding: 0; + margin: 0; + z-index: 1; + + .nav { + margin: 10px 0 5% 6px; + padding: 0; + height: 95%; + height: -moz-calc(~"95% - 68px"); + height: -webkit-calc(~"95% - 68px"); + height: calc(~"95% - 68px"); + overflow: auto; + } + + .well { + background: none; + box-shadow: none; + border: none; + } + + .nav-header { + font-size: 12px; + padding: 3px 10px; + display: block; + font-weight: bold; + line-height: 18px; + color: #999999; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-transform: uppercase; + } + + .nav-list-entry { + position: relative; + padding: 0 40px 0 6px; + height: 26px; + display: block; + + &.move-part-into { + border: 1px dashed #8fbc8f; + height: 25px; + margin-top: -1px; + padding-left: 5px; + } + + .icon { + float: left; + } + + > a { + overflow: hidden; + text-overflow: ellipsis; + line-height: 26px; + white-space: nowrap; + width: 100%; + float: left; + + .status { + float: left; + } + } + + > .btn-group { + right: 0; + position: absolute; + } + + .dropdown-toggle { + border-radius: 0; + margin: 0 0 0 8px; + display: none; + border: none; + background: none; + -webkit-box-shadow: none; + box-shadow: none; + padding: 3px 12px; + .caret { + border-top-color: #000000; + opacity: .5; + } + } + + .dropdown-toggle:hover { + background: #CCC; + .caret { + opacity: 1; + } + } + + .dropdown-menu { + border-radius: 0; + padding: 0 0 0 0; + margin: -1px 1px 0 0; + right: 0; + left: auto; + max-width: 250px; + + > li { + + width: 100%; + padding: 0; + + > a { + font-size: 13px; + padding: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + } + + } + li.new-folder .icon { + .plm-icon-menu-folder-new; + margin: 4px 6px 4px 6px; + } + li.edit .icon { + .plm-icon-menu-edit; + margin: 4px 6px 4px 6px; + } + li.delete .icon { + .plm-icon-menu-delete; + margin: 4px 6px 4px 6px; + } + } + + .nav-checkedOut-number-item { + margin-top: -22px; + } + } + + .nav-list-entry:hover { + > a { + text-decoration: none; + } + background-color: #EEE; + .dropdown-toggle { + display: block; + } + } + + .nav-list-entry.active, .nav-list-entry.active > a { + /*text-shadow: 0 -1px 0 @grayLight;*/ + color: #FFF; + background-color: @plm-secondary-color; + .badge { + background-color: #fff; + color: #999; + } + } + + .items { + overflow: visible; + list-style-type: none; + margin-left: 6px; + padding: 0; + } + + .ui-resizable-handle { + + opacity: 0; + position: absolute; + bottom: 65px; + right: 0; + top: 0; + z-index: 1000; + width: 8px; + cursor: e-resize; + display: block !important; + + } + +} diff --git a/docdoku-web-front/app/less/product-management/page_controls.less b/docdoku-web-front/app/less/product-management/page_controls.less new file mode 100644 index 0000000000..d06842bb84 --- /dev/null +++ b/docdoku-web-front/app/less/product-management/page_controls.less @@ -0,0 +1,11 @@ +#product-management-content { + .page-controls { + display: none; + z-index: 2; + height: 0; + position: relative; + top: 12px; + right: 23px; + float: right; + } +} diff --git a/docdoku-web-front/app/less/product-management/product_instances.less b/docdoku-web-front/app/less/product-management/product_instances.less new file mode 100644 index 0000000000..b0480222e2 --- /dev/null +++ b/docdoku-web-front/app/less/product-management/product_instances.less @@ -0,0 +1,67 @@ +#product_instance_modal { + + .pager { + margin: 0; + } +} + +ul.product-instances-list { + + margin: 0; + + li.product-instances-item { + + margin: 4px; + list-style: none; + + input[type=checkbox] { + margin: 0 3px 3px 0; + } + + a { + &.stroke { + text-decoration: line-through; + color: red; + } + } + + } + +} + +ul.product-instances-parts { + margin: 0; + li.product-instances-part-item { + list-style: none; + .well { + margin: 4px; + padding: 2px; + input { + margin: 0; + } + input[type=checkbox] { + margin: 2px 0 0 29px; + } + } + } +} + +.product-instances-filter-bar { + float: left; + display: inline-block; + padding: 8px 0 0px 0; + margin: 0 8px; + + label { + display: inline-block; + } + .typeahead { + width: 218px; + } +} + +.has-path-to-path-link, .has-path-data, .has-attached-files { + i.fa-asterisk, i.fa-exchange, i.fa-paperclip { + cursor: pointer; + } +} diff --git a/docdoku-web-front/app/less/product-management/query_builder.less b/docdoku-web-front/app/less/product-management/query_builder.less new file mode 100644 index 0000000000..3bc82050d3 --- /dev/null +++ b/docdoku-web-front/app/less/product-management/query_builder.less @@ -0,0 +1,148 @@ +.query-builder .rules-group-container { + border-color: #EEE; + background-color: rgba(255, 255, 255, 0.9); +} + +.saveSwitch.switch.switch-small { + z-index: 0; +} + +.export-csv.switch.switch-small, .export-xls.switch.switch-small { + z-index: 0; +} + +.save-button { + margin-right: 10px; +} + +.search-button { + margin-left: 10px; +} + +.clear-select-badge { + margin-left: 5px; +} + +.delete-selected-query { + margin-left: 10px; +} + +select.form-control.query-list { + margin-bottom: 0px; +} + +.export-excel-button { + margin-left: 10px; +} + +.clear-where-badge { + margin-left: 5px; +} + +.clear-order-by-badge { + margin-left: 5px; +} + +.clear-group-by-badge { + margin-left: 5px; +} + +.query-context-block { + margin-top: 10px; + .query-context-input { + width: 80%; + } +} + +.query-select-block { + margin-top: 10px; + .query-select-input { + width: 80%; + } +} + +.query-where-block { + margin-top: 10px; + .query-where-builder { + width: 80%; + } +} + +.order-by-block { + margin-top: 10px; + .order-by-label { + + } + + .order-by-input { + width: 80%; + } +} + +.group-by-block { + margin-top: 10px; + .group-by-label { + } + + .group-by-input { + width: 80%; + } +} + +.query-export-block { + display: inline-block; + margin-top: 10px; + height: 30px; + + .export-csv-label { + text-align: center; + float: left; + margin-right: 15px; + margin-top: 5px; + } + + .export-csv, .export-xls { + display: inline-block; + float: left; + margin-right: 15px; + margin-top: 1px; + } +} + +.query-actions-block { + margin-top: 20px; + clear: both; +} + +#where { + .btn-group { + margin: 0 !important; + } +} + +#query-builder-view { + display: none; +} + +.displayQueryBuilder { + #query-builder-view { + display: block; + width: 96%; + margin-left: auto; + margin-right: auto; + padding-bottom: 20px; + } +} + +#query-table { + h4 { + display: block; + width: 96%; + margin-left: auto; + margin-right: auto; + } + + span.well { + padding: 6px; + } +} diff --git a/docdoku-web-front/app/less/product-management/search.less b/docdoku-web-front/app/less/product-management/search.less new file mode 100644 index 0000000000..2178d3a544 --- /dev/null +++ b/docdoku-web-front/app/less/product-management/search.less @@ -0,0 +1,46 @@ +#part-search-form { + margin: 0 8px; + display: inline-block; + width: 291px; + + .search-part-input { + width: 192px; + height: 22px; + margin: 8px; + padding-right: 30px; + border-radius: 6px; + } + + .advanced-search-button { + margin-left: -66px; + cursor: pointer; + opacity: 0.4; + } + .advanced-search-button:hover { + opacity: 0.6; + } + + .fa-search { + margin-left: 5px; + border-radius: 0 6px 6px 0; + padding: 8px 12px; + } + +} + +#advanced_search_form { + + .creationDates, .modificationDates { + + input { + width: auto; + margin-bottom: 5px; + } + + } + + #search-add-attributes { + margin-bottom: 18px; + } + +} diff --git a/docdoku-web-front/app/less/product-management/style.less b/docdoku-web-front/app/less/product-management/style.less new file mode 100644 index 0000000000..95dca3904f --- /dev/null +++ b/docdoku-web-front/app/less/product-management/style.less @@ -0,0 +1,12 @@ +@import "../common/style"; +@import "../common/tags"; +@import "../modules/style"; +@import "menu"; +@import "content"; +@import "page_controls"; +@import "baselines"; +@import "product_instances"; +@import "search"; +@import (inline) "../../bower_components/jQuery-QueryBuilder/dist/css/query-builder.default.css"; +@import (inline) "../../bower_components/selectize/dist/css/selectize.default.css"; +@import "query_builder"; diff --git a/docdoku-web-front/app/less/product-structure/actions_bar.less b/docdoku-web-front/app/less/product-structure/actions_bar.less new file mode 100644 index 0000000000..947ec42fec --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/actions_bar.less @@ -0,0 +1,8 @@ +#export_scene_btn { + height: auto; + display: none; +} + +#fullscreen_scene_btn { + height: auto; +} diff --git a/docdoku-web-front/app/less/product-structure/app.less b/docdoku-web-front/app/less/product-structure/app.less new file mode 100644 index 0000000000..4a99f696df --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/app.less @@ -0,0 +1,63 @@ +#product-content { + + .mode-controls { + display: inline-block; + } + + .dmu-controls, .bom-controls, #side_controls_container, #scene_container, #bom_table_container { + display: none; + } + + .product-instance { + display: inline-block; + } + + .path_to_path_link { + display: inline-block; + } + + &.bom-mode { + #bom_table_container { + display: block; + } + .bom-controls { + display: inline-block; + } + } + &.scene-mode { + #side_controls_container, #scene_container { + display: block; + } + .dmu-controls { + display: inline; + } + .actions .cascade-checkout-group .btn-group button.checkout span.icon { + display: inline-block; + } + } + + #side_controls_container { + .clear-measures-btn { + display: none; + + &.display { + display: inline-block; + float: right; + margin-top: -2px; + } + } + } + + .actions span.cascade-checkout-group { + display: none; + } + + .cascade-checkout-group .btn-group { + span.caret { + height: 15px; + } + + display: inline; + } + +} diff --git a/docdoku-web-front/app/less/product-structure/bom_table.less b/docdoku-web-front/app/less/product-structure/bom_table.less new file mode 100644 index 0000000000..b0fea49d5c --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/bom_table.less @@ -0,0 +1,4 @@ +#bom_table input { + height: 22px; + margin: 8px; +} \ No newline at end of file diff --git a/docdoku-web-front/app/less/product-structure/collaborative.less b/docdoku-web-front/app/less/product-structure/collaborative.less new file mode 100644 index 0000000000..a56ab3a766 --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/collaborative.less @@ -0,0 +1,68 @@ +#collaborative_view { + .CollaborativeRoom { + border-radius: 3px; + /*box-shadow: 0px 1px 5px #9b9b9b;*/ + max-width: 100%; + + &-title { + min-height: 18px; + margin: 0; + background: @plm-primary-color; + /* .gradient(#5b779b,#7a99c1,#2c4361); + background-image: linear-gradient(to bottom, #7a99c1, #2c4361); + background-repeat: repeat-x;*/ + color: white; + font-size: 1em; + text-shadow: #555555 0px -1px 0px; + } + + &-exitBtn { + float: right; + margin: 2px 5px; + &:hover { + color: @grayLight; + cursor: pointer; + } + } + + &-wrapper { + height: 100px; + overflow: auto; + margin: 0 0 10px 0; + } + } + + .CollaborativeRoomParticipant { + padding: 2px 5px 0 5px; + + &:hover { + background-color: rgba(118, 188, 255, 0.48); + } + + &.master { + + } + + &.pending { + + } + + .fa-chevron-right, + .fa-chevron-left { + font-size: 0.8em; + vertical-align: baseline; + line-height: 18px; + color: grey; + } + + .collaborative_give_hand, + .collaborative_kick, + .collaborative_withdraw_invitation { + color: @linkColor; + &:hover { + color: @linkColorHover; + cursor: pointer; + } + } + } +} diff --git a/docdoku-web-front/app/less/product-structure/content.less b/docdoku-web-front/app/less/product-structure/content.less new file mode 100644 index 0000000000..cca5c5f7c8 --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/content.less @@ -0,0 +1,159 @@ +.clear { + clear: both; + height: 0; + overflow: hidden; // IE 7 +} + +#product-content { + width: 85%; + height: 100%; + overflow-y: auto; + //background: white; + position: absolute; + right: 0; + top: 0; + z-index: 0; + /* + -webkit-box-shadow: 0px 1px 3px #9b9b9b; + box-shadow: 0px 1px 3px #9b9b9b;*/ + + button.active { + color: white; + } + + #dmu_container { + position: absolute; + top: 48px; + bottom: 0; + left: 0; + right: 0; + margin: 0; + padding: 0.5%; + .crashMessage { + position: relative; + top: 40%; + display: block; + text-align: center; + font-size: 18px; + text-shadow: 1px 1px 1px #fff; + color: #555; + } + } + + #side_controls_container { + top: 0; + right: 0; + background: #f5f5f5; + + /* -webkit-box-shadow: 0px 2px 3px #9b9b9b; + box-shadow: 0px 2px 3px #9b9b9b;*/ + position: absolute; + width: 15%; + height: 100%; + margin: 0; + padding: 0 1%; + z-index: 1; + border: none; + border-radius: none; + overflow: auto; + + button { + margin-bottom: 3px; + } + + .nav-header { + padding: 4px 0 0 0; + border-top: 1px solid rgba(155, 155, 155, 0.35); + margin-bottom: 4px; + } + + div.side_control_group { + margin: 0 0 10px 0; + position: relative; + color: #62697B; + } + + div.cut_plan_control { + + .axis-buttons { + float: left; + } + .plan-switch { + float: right; + } + + input#slider-cut-plan { + margin-top: 8px; + } + + &.disabled { + .btn-group, input#slider-cut-plan { + -webkit-transition: opacity .4s ease; + -moz-transition: opacity .4s ease; + transition: opacity .4s ease; + opacity: 0.5; + } + } + + } + + input#slider-explode, + input#slider-clipping, + input#slider-cut-plan { + width: 98%; + -webkit-appearance: none; + -webkit-border-radius: 0px; + outline: none; + /*box-shadow: 0 0 3px #9b9b9b;*/ + background: #f9f9f9; + cursor: pointer; + &:hover { + background: #eee; + } + } + + input#slider-explode::-webkit-slider-thumb, + input#slider-cut-plan::-webkit-slider-thumb, + input#slider-clipping::-webkit-slider-thumb { + -webkit-appearance: none; + background-image: linear-gradient(to bottom, #7a99c1, #2c4361); + width: 10px; + height: 18px; + } + + } + + #bom_table_container { + display: none; + } + + td.modification_notification, td.part-revision-share, td.part-attached-files { + i { + cursor: pointer; + &:hover { + color: #333; + } + } + } + + td.modification_notification { + i.fa-exclamation { + color: #ee5f5b; + } + } + + td.part_number { + i.fa-pencil { + color: #f89406; + } + + i.fa-lock { + color: #ee5f5b; + } + + i.fa-check { + color: #51a351; + } + } + +} diff --git a/docdoku-web-front/app/less/product-structure/dat.less b/docdoku-web-front/app/less/product-structure/dat.less new file mode 100644 index 0000000000..337ce0b52c --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/dat.less @@ -0,0 +1,80 @@ +.dg.main { + display: none; +} + +body.debug { + .dg { + color: #333; + font: 12px "Helvetica Neue", Helvetica, Arial, sans-serif; + text-shadow: none; + + &.main { + z-index: 8; + display: block; + position: absolute; + top: 55px; + right: 15%; + + .close-button { + line-height: 32px; + height: 32px; + background-color: rgba(255, 255, 255, 0.3); + -webkit-transition: all 200ms cubic-bezier(0.52, 0, 0.48, 1); + -moz-transition: all 200ms cubic-bezier(0.52, 0, 0.48, 1); + -ms-transition: all 200ms cubic-bezier(0.52, 0, 0.48, 1); + -o-transition: all 200ms cubic-bezier(0.52, 0, 0.48, 1); + transition: all 200ms cubic-bezier(0.52, 0, 0.48, 1); + + &:hover { + background-color: rgba(255, 255, 255, 0.8); + } + } + } + + li:not(.folder) { + background: rgba(255, 255, 255, 0.2); + } + + ul:not(.closed) li:not(.folder) { + border-bottom: 1px solid rgba(0, 0, 0, 0.2) + } + + .c { + .slider { + background: rgba(255, 255, 255, 0.3); + } + + .slider-fg { + background-color: #5b779b; + background-image: -moz-linear-gradient(top, #7a99c1, #2c4361); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#7a99c1), to(#2c4361)); + background-image: -webkit-linear-gradient(top, #7a99c1, #2c4361); + background-image: -o-linear-gradient(top, #7a99c1, #2c4361); + background-image: linear-gradient(to bottom, #7a99c1, #2c4361); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff7a99c1', endColorstr='#ff2c4361', GradientType=0); + } + + input[type=text] { + background: rgba(255, 255, 255, 0.3); + outline: none; + + &:focus { + background: rgba(255, 255, 255, 0.3); + color: #fff; + } + } + } + + .cr { + &:hover { + background: rgba(255, 255, 255, 0.8); + } + + &.number input[type=text] { + color: #333; + line-height: 12px; + } + } + } +} diff --git a/docdoku-web-front/app/less/product-structure/export_modal.less b/docdoku-web-front/app/less/product-structure/export_modal.less new file mode 100644 index 0000000000..25ec06aeab --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/export_modal.less @@ -0,0 +1,4 @@ +#exportSceneModal textarea { + height: 30%; + width: 97%; +} diff --git a/docdoku-web-front/app/less/product-structure/frame.less b/docdoku-web-front/app/less/product-structure/frame.less new file mode 100644 index 0000000000..75b9f1926b --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/frame.less @@ -0,0 +1,23 @@ +#frameWorkspace { + display: block; + margin: 0px; + padding: 0px; + width: 100%; + height: 100%; + overflow: hidden; + #progress_bar_container { + position: absolute; + left: 10px; + bottom: 10px; + } + #container { + margin: 0; + padding: 0; + height: 100%; + width: 100%; + canvas { + height: 100%; + width: 100%; + } + } +} diff --git a/docdoku-web-front/app/less/product-structure/jquery_treeview.less b/docdoku-web-front/app/less/product-structure/jquery_treeview.less new file mode 100755 index 0000000000..561b43510c --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/jquery_treeview.less @@ -0,0 +1,129 @@ +.treeview, .treeview ul { + margin: 0; + list-style: none; +} + +#product_nav_list ul li input[type=checkbox].selectable-part-checkbox { + margin-top: -5px; + margin-left: 5px; +} + +.treeview ul { + background-color: white; + margin-top: 4px; +} + +.treeview .hitarea { + background: url(../images/treeview-default.gif) -64px -25px no-repeat; + height: 16px; + width: 16px; + margin-left: -16px; + float: left; + cursor: pointer; +} + +/* fix for IE6 */ +* html .hitarea { + display: inline; + float: none; +} + +.treeview li { + margin: 0; + padding: 3px 0pt 3px 16px; +} + +.treeview a.selected { + background-color: #eee; +} + +#treecontrol { + margin: 1em 0; + display: none; +} + +.treeview .hover { + color: red; + cursor: pointer; +} + +.treeview li { + background: url(../images/treeview-default-line.gif) 0 0 no-repeat; +} + +.treeview li.collapsable, .treeview li.expandable { + background-position: 0 -176px; +} + +.treeview .expandable-hitarea { + background-position: -80px -3px; +} + +.treeview li.last { + background-position: 0 -1766px +} + +.treeview li.lastCollapsable, .treeview li.lastExpandable { + background-image: url(../images/treeview-default.gif); +} + +.treeview li.lastCollapsable { + background-position: 0 -111px +} + +.treeview li.lastExpandable { + background-position: -32px -67px +} + +.treeview div.lastCollapsable-hitarea, .treeview div.lastExpandable-hitarea { + background-position: 0; +} + +.treeview li input.load-3D { + display: none; + + label { + float: left; + margin: -3px 0 0 2px; + display: inline; + i.fa.toggle-3D:before { + content: "\f204"; + } + } + + &:checked { + + label { + i.fa.toggle-3D:before { + content: "\f205"; + } + } + } + +} + +.treeview { + .description { + display: none; + } + + a { + max-width: 90%; + display: inline-block; + cursor: pointer; + } + + &.displayComment { + + .description { + display: table; + color: #434C64; + font-style: italic; + font-weight: 200 !important; + font-size: smaller; + margin-top: 0px; + margin-left: 17px; + line-height: 12px; + max-width: 90%; + } + } + +} diff --git a/docdoku-web-front/app/less/product-structure/layers.less b/docdoku-web-front/app/less/product-structure/layers.less new file mode 100644 index 0000000000..f8819e0647 --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/layers.less @@ -0,0 +1,151 @@ +#manageMarker { + width: 100px; + margin: 0px auto; +} + +#layer-wrapper { + + nav { + max-height: 202px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + /*box-shadow: 0px 0px 3px #9b9b9b;*/ + width: 100%; + margin-top: 10px; + + ul { + + text-align: left; + color: #434C64; + margin: 0; + padding: 0; + background-color: white; + -webkit-font-smoothing: antialiased; + font-smoothing: antialiased; + + li { + + border-bottom: 1px solid #ccc; + color: #999; + margin: 0px; + position: relative; + height: 18px; + line-height: 18px; + padding: 5px 45px 5px 40px; + list-style-type: none; + background: #fff; + + &:nth-child(odd) { + background: @tableBackgroundAccent; + } + + &:hover { + background: #eee; + .fa-times { + opacity: 0.6; + } + } + + &:last-child { + border-bottom: none; + } + + &.editingName { + input.edit { + display: block; + } + p { + display: none; + } + } + + &.editingMarkers { + i.end { + opacity: 1; + } + } + + &.shown { + color: #434C64; + i.start { + opacity: 1; + } + + } + + i { + cursor: pointer; + opacity: 0.5; + position: absolute; + font-size: 20px; + + &.start { + left: 10px; + top: 3px; + } + + &.end { + right: 5px; + top: 6px; + } + + &.fa-times { + right: 25px; + top: 4px; + opacity: 0; + font-weight: normal; + } + + } + + input.edit { + position: relative; + margin: 0; + width: 100%; + height: 18px; + font-family: inherit; + border: 0; + outline: none; + color: inherit; + border: 1px solid #999; + /*box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);*/ + -webkit-box-sizing: border-box; + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + font-smoothing: antialiased; + display: none; + font-size: 13px; + line-height: 16px; + } + + p { + cursor: pointer; + margin: 0px; + padding: 0px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + line-height: 18px; + } + + .color { + position: absolute; + left: 0px; + top: 0px; + width: 8px; + height: 100%; + overflow: hidden; + input[type="color"] { + opacity: 0; + width: 8px; + padding: 0; + margin: 0; + } + } + + } + + } + } + +} diff --git a/docdoku-web-front/app/less/product-structure/menu.less b/docdoku-web-front/app/less/product-structure/menu.less new file mode 100644 index 0000000000..a707c5da3c --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/menu.less @@ -0,0 +1,189 @@ +#product-menu { + position: relative; + /*-webkit-box-shadow: 0px 1px 3px #9b9b9b; + box-shadow: 0px 1px 3px #9b9b9b;*/ + float: left; + background-color: #f5f5f5; + height: 100%; + min-height: 100%; + width: 15%; + min-width: 15%; + max-width: 55%; + border: none; + border-radius: 0; + border-right: 1px solid rgba(63, 95, 153, 0.24); + padding: 0; + margin: 0; + z-index: 1; + + -webkit-transition: background-color .4s ease; + -moz-transition: background-color .4s ease; + transition: background-color .4s ease; + + &.bom-mode { + i.fa.toggle-3D { + display: none; + } + } + &.scene-mode { + i.fa.toggle-3D { + display: inline; + } + } + + .nav.well { + margin: 10px 0 0 6px; + padding: 0; + overflow: auto; + background: none; + box-shadow: none; + border: none; + .nav-header { + font-size: 12px; + padding: 3px 10px; + display: block; + font-weight: bold; + line-height: 18px; + color: #999999; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-transform: uppercase; + } + } + + &.alpha { + background-color: rgba(245, 245, 245, 0.7); + } + + #product-menu-inner { + height: 100%; + + #nav_list_action_bar { + + i { + background-image: none; + font-size: 15px; + } + + #nav_list_controls { + + #nav_list_search { + margin: 0; + position: relative; + height: 45px; + + input { + position: absolute; + top: 0; + left: 15px; + right: 15px; + + width: -moz-calc(~"100% - 105px"); + width: -webkit-calc(~"100% - 105px"); + width: calc(~"100% - 105px"); + padding-right: 30px; + + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + -webkit-appearance: none; + text-overflow: ellipsis; + height: 21px; + } + + span { + position: absolute; + top: 6px; + bottom: 0; + right: 60px; + height: auto; + color: #999; + font-size: 15px; + + .popover-content { + padding: 0; + } + + .popover { + width: 300px; + } + } + + #nav_list_search_button { + position: absolute; + top: 0; + right: 15px; + padding: 8px 12px; + border-bottom-left-radius: 0px; + border-top-left-radius: 0px; + } + + } + + } + + #nav_list_controls_help_table { + border: none; + box-shadow: none; + margin: 0; + width: 100%; + + tr { + border-bottom: 1px solid #ebebeb; + } + + tr:last-child { + border-bottom: none; + } + } + } + #product_nav_list_container { + top: 0; + width: 100%; + overflow-y: auto; + bottom: 0; + position: absolute; + -webkit-overflow-scrolling: touch; + } + + } + + .isNode { + font-weight: 600; + } + + .ui-resizable-handle { + + opacity: 0; + position: absolute; + bottom: 65px; + right: 0; + top: 0; + z-index: 1000; + width: 8px; + cursor: e-resize; + display: block !important; + + } + + #config_spec_container { + select { + margin: 0 16px 4px 16px; + width: calc(~"100% - 32px"); + border: none; + border-radius: 0; + border-bottom: 1px solid #ccc; + } + .description { + font-size: 12px; + font-style: italic; + margin: 0 16px; + display: inline-block; + } + #diverge-selector { + margin-left: 14px; + } + } + .fa-asterisk { + cursor: pointer; + } + +} diff --git a/docdoku-web-front/app/less/product-structure/part_metadata.less b/docdoku-web-front/app/less/product-structure/part_metadata.less new file mode 100644 index 0000000000..548ae1b48b --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/part_metadata.less @@ -0,0 +1,22 @@ +.part_metadata_container { + dl { + color: #62697B; + dt { + float: left; + font-weight: bold; + clear: both; + } + + dd { + float: right; + margin-right: 5px; + } + + } + .selected-part { + white-space: nowrap; + } +} + + + diff --git a/docdoku-web-front/app/less/product-structure/path_to_path_link.less b/docdoku-web-front/app/less/product-structure/path_to_path_link.less new file mode 100644 index 0000000000..3f2d7be136 --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/path_to_path_link.less @@ -0,0 +1,37 @@ +#path-to-path-links { + .well { + padding: 10px; + } + + .path-to-path-link-content { + + div.control-group { + margin-bottom: 0px; + + &.type { + margin-top: 10px; + margin-bottom: 10px; + } + } + + label.control-label { + width: 110px; + } + + div.controls { + margin-left: 130px; + padding-right: 30px; + } + + textarea.path-to-path-description { + max-width: 346px; + width: 100%; + margin-right: 10px; + } + + .save-button { + float: right; + margin-top: 20px; + } + } +} diff --git a/docdoku-web-front/app/less/product-structure/product_instance_data.less b/docdoku-web-front/app/less/product-structure/product_instance_data.less new file mode 100644 index 0000000000..a7a1d37ba7 --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/product_instance_data.less @@ -0,0 +1,22 @@ +.path-description { + padding-bottom: 10px; +} + +.fa-long-arrow-right { + padding-right: 5px; + padding-left: 5px; +} + +.description-input { + margin-left: 10px; +} + +.new-iteration { + margin-top: 5%; +} + +.product-instance-data-modal { + #tab-link { + min-height: 280px; + } +} diff --git a/docdoku-web-front/app/less/product-structure/product_nav_list.less b/docdoku-web-front/app/less/product-structure/product_nav_list.less new file mode 100644 index 0000000000..0709326aa6 --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/product_nav_list.less @@ -0,0 +1,199 @@ +#product_nav_list { + + min-width: 500px; + + &.displayComment { + .fa-quote-right { + opacity: 1; + } + } + + .fa-quote-right { + color: green; + opacity: 0.5; + } + .fa-refresh { + color: green; + } + + .fa-arrows-h { + padding-right: 5px; + } + + .fa-question { + padding-right: 5px; + } + + .fa-random { + padding-right: 5px; + } + + #product_title { + + background: none; + font-weight: bold; + cursor: pointer; + line-height: 18px; + color: #434C64; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + padding-top: 6px; + + img.loader { + display: none; + height: 13px; + } + + i { + margin-top: 3px; + margin-left: 5px; + + } + + .product_title { + + padding-left: 5px; + padding-right: 2px; + + &.active { + background-color: #CFD7E4; + } + + } + } + + .fa-cogs { + font-size: 15px; + } + .fa-cog { + font-size: 10px; + } + + ul { + background-color: transparent; + margin-top: 0; + + li { + margin-left: 0px; + width: 100%; + + a { + display: inline-block; + cursor: pointer; + + label { + color: #434C64; + font-size: 12px; + text-shadow: 0px 1px 0px white; + } + + i { + background-image: none; + } + + &:hover { + text-decoration: none; + } + + label { + margin: 0; + + &:hover { + background-color: #CFD7E4; + } + + } + } + + &.resultPath { + > a > label { + background: #fc6; + } + } + + &.active { + > a > label { + text-shadow: 0px 1px 0px white; + color: #434C64; + background-color: #CFD7E4; + } + } + + input[type=checkbox] { + padding-left: 5px; + margin: -5px 0 0 0; + } + + .checkbox { + padding-left: 5px; + } + + &.nav-header { + margin-top: 0px; + > label { + color: #999; + font-weight: bold; + font-size: 11px; + } + } + + } + + i.fa-file { + margin-left: 5px; + opacity: 0.4; + cursor: pointer; + + &:hover { + opacity: 0.8; + + + i.fa-lock, + + i.fa-pencil, + + i.fa-eye, + + i.fa-check, + + i.fa-key { + margin-left: -2px; + opacity: 1; + } + } + + } + + i.fa-pencil { + color: #f89406; + } + + i.fa-lock { + color: #ee5f5b; + } + + i.fa-check { + color: #51a351; + } + + i.fa-exclamation { + color: #ee5f5b; + } + + i.fa-key { + color: red; + } + + i.fa-lock, + i.fa-pencil, + i.fa-eye, + i.fa-check, + i.fa-key { + &:hover { + cursor: pointer; + } + } + i.fa-lock, + i.fa-key { + + ul { + display: none; + } + } + + } + +} diff --git a/docdoku-web-front/app/less/product-structure/progress_bar.less b/docdoku-web-front/app/less/product-structure/progress_bar.less new file mode 100644 index 0000000000..80f4c97cb2 --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/progress_bar.less @@ -0,0 +1,19 @@ +#progress_bar_container { + + background-image: -webkit-linear-gradient(left, #f5f5f5 0%, #f9f9f9 100%); + background-image: -o-linear-gradient(left, #f5f5f5 0%, #f9f9f9 100%); + background-image: linear-gradient(to right, #f5f5f5 0%, #f9f9f9 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=1); + + margin: 15px 0 0 0; + float: right; + width: 15%; + height: 20px; + + .bar { + background-color: #7A99C1; + width: 0; + } + +} diff --git a/docdoku-web-front/app/less/product-structure/scene.less b/docdoku-web-front/app/less/product-structure/scene.less new file mode 100644 index 0000000000..410e9d6829 --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/scene.less @@ -0,0 +1,63 @@ +#scene_container { + float: left; + position: relative; + width: 82.5%; + height: 100%; + + &.markersCreationMode, &.measureMode { + cursor: crosshair; + } + + #container { + + height: 100%; + width: 100%; + /*box-shadow: 1px 1px 5px #9b9b9b;*/ + border: 1px solid #f5f5f5; + outline: none; + position: relative; + + background: -moz-radial-gradient(center top, #e2e7ef 0%, #abbacd 100%); + background: -webkit-radial-gradient(center top, #e2e7ef 0%, #abbacd 100%); + + &:focus { + box-shadow: 1px 1px 5px #333; + } + + #blocker { + width: 100%; + height: 100%; + position: absolute; + background-color: rgba(0, 0, 0, 0.5); + display: none; + + #instructions { + width: 100%; + height: 100%; + display: -webkit-box; + display: -moz-box; + display: box; + -webkit-box-orient: horizontal; + -moz-box-orient: horizontal; + box-orient: horizontal; + -webkit-box-pack: center; + -moz-box-pack: center; + box-pack: center; + -webkit-box-align: center; + -moz-box-align: center; + box-align: center; + color: #ffffff; + text-align: center; + cursor: pointer; + + span.large { + font-size: 40px; + } + + } + + } + + } + +} diff --git a/docdoku-web-front/app/less/product-structure/shortcuts.less b/docdoku-web-front/app/less/product-structure/shortcuts.less new file mode 100644 index 0000000000..a389da7239 --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/shortcuts.less @@ -0,0 +1,47 @@ +#shortcuts { + margin-bottom: 5px; + a { + color: #62697B; + font-size: 12px; + } +} + +#controlsInfosModal { + + dl.keyboard-ps-controls { + margin: 5px 0; + font-size: 12px; + + dt { + display: inline-block; + margin: 0; + padding: 3px 6px; + min-width: 10px; + text-align: center; + font-family: Monaco, "Liberation Mono", Courier, monospace; + background: #555555; + color: #EEE; + border-radius: 2px; + text-shadow: 1px 1px 0 #000; + + em { + padding: 0 4px; + color: #999; + font-style: normal; + font-weight: normal; + font-size: 11px; + font-family: Helvetica, arial, freesans, clean, sans-serif; + text-shadow: none; + } + + } + + dd { + display: inline; + margin: 0 0 0 5px; + color: #666; + } + + } + +} \ No newline at end of file diff --git a/docdoku-web-front/app/less/product-structure/stats.less b/docdoku-web-front/app/less/product-structure/stats.less new file mode 100644 index 0000000000..9bada510aa --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/stats.less @@ -0,0 +1,47 @@ +#statsWin { + position: absolute; + right: 0px; + bottom: 0px; + overflow: hidden; + transition: width 0.5s, height 0.5s; + -moz-transition: width 0.5s, height 0.5s; + -webkit-transition: width 0.5s ease, height 0.5s ease; + -o-transition: width 0.5s, height 0.5s; + + i#statsArrow { + float: left; + top: 0px; + left: 0px; + color: white; + margin-right: 2px; + font-size: 11px; + } + + &.statsWinMinimized { + width: 15px; + height: 13px; + + > #statsArrow { + -moz-transform: rotate(135deg); + -webkit-transform: rotate(135deg); + -o-transform: rotate(135deg); + margin-left: 0px; + margin-top: 0px; + } + + } + + &.statsWinMaximized { + width: 80px; + height: 48px; + + > #statsArrow { + -moz-transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); + -o-transform: rotate(-45deg); + margin-left: 2px; + margin-top: 2px; + } + + } +} diff --git a/docdoku-web-front/app/less/product-structure/style.less b/docdoku-web-front/app/less/product-structure/style.less new file mode 100644 index 0000000000..a1aabf36c0 --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/style.less @@ -0,0 +1,26 @@ +//imports only +@import "../common/style"; +@import "../common/config_spec"; +@import "../common/tags"; +@import "../modules/style"; +@import (inline) "../../bower_components/selectize/dist/css/selectize.default.css"; +@import "app"; +@import "jquery_treeview"; +@import "menu"; +@import "product_nav_list"; +@import "content"; +@import "actions_bar"; +@import "part_metadata"; +@import "shortcuts"; +@import "layers"; +@import "stats"; +@import "frame"; +@import "scene"; +@import "view_buttons"; +@import "bom_table"; +@import "export_modal"; +@import "progress_bar"; +@import "dat"; +@import "collaborative"; +@import "product_instance_data"; +@import "path_to_path_link"; diff --git a/docdoku-web-front/app/less/product-structure/style_frame.less b/docdoku-web-front/app/less/product-structure/style_frame.less new file mode 100644 index 0000000000..f349b4314a --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/style_frame.less @@ -0,0 +1,16 @@ +//imports only +@import "../common/style"; +@import "menu"; +@import "product_nav_list"; +@import "content"; +@import "actions_bar"; +@import "part_metadata"; +@import "shortcuts"; +@import "layers"; +@import "stats"; +@import "frame"; +@import "scene"; +@import "view_buttons"; +@import "bom_table"; +@import "export_modal"; +@import "progress_bar"; \ No newline at end of file diff --git a/docdoku-web-front/app/less/product-structure/view_buttons.less b/docdoku-web-front/app/less/product-structure/view_buttons.less new file mode 100644 index 0000000000..f7ddf1ca65 --- /dev/null +++ b/docdoku-web-front/app/less/product-structure/view_buttons.less @@ -0,0 +1,23 @@ +#view_buttons { + width: 75px; + margin: 0px auto; + + .camera-mode-label { + font-weight: bold; + font-size: 12px; + + &:nth-child(2) { + padding-left: 3px; + } + + [class^="icon-"] { + font-size: 6px; + font-style: italic; + } + + [class^="fa-"] { + font-size: 6px; + font-style: italic; + } + } +} diff --git a/docdoku-web-front/app/less/visualization/style.less b/docdoku-web-front/app/less/visualization/style.less new file mode 100644 index 0000000000..b41f26b64f --- /dev/null +++ b/docdoku-web-front/app/less/visualization/style.less @@ -0,0 +1,16 @@ +//imports only +@import "../common/style"; +@import "../product-structure/menu"; +@import "../product-structure/product_nav_list"; +@import "../product-structure/content"; +@import "../product-structure/actions_bar"; +@import "../product-structure/part_metadata"; +@import "../product-structure/shortcuts"; +@import "../product-structure/layers"; +@import "../product-structure/stats"; +@import "../product-structure/frame"; +@import "../product-structure/scene"; +@import "../product-structure/view_buttons"; +@import "../product-structure/bom_table"; +@import "../product-structure/export_modal"; +@import "../product-structure/progress_bar"; diff --git a/docdoku-web-front/app/less/workspace-management/charts.less b/docdoku-web-front/app/less/workspace-management/charts.less new file mode 100644 index 0000000000..c535a26d17 --- /dev/null +++ b/docdoku-web-front/app/less/workspace-management/charts.less @@ -0,0 +1,37 @@ +.workspace-chart-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; + align-items: stretch; + align-content: flex-start; + + .well-large.workspace-chart { + margin: 3px; + padding:0 8px 4px 10px; + width: 46%; + border:1px solid rgba(63, 95, 153, 0.24); + background-color: white; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + + &.large-chart{ + width: 92%; + } + + #admin_disk_usage_chart, + #disk_usage_chart, + #admin_users_chart, + #entities_chart, + #users_chart, + #cod_chart, + #cop_chart,{ + height: 400px; + svg{ + height: 100%; + width: 100%; + } + } + } +} diff --git a/docdoku-web-front/app/less/workspace-management/content.less b/docdoku-web-front/app/less/workspace-management/content.less new file mode 100644 index 0000000000..5859988c52 --- /dev/null +++ b/docdoku-web-front/app/less/workspace-management/content.less @@ -0,0 +1,9 @@ +#workspace-management-content { + width: 85%; + height: 100%; + overflow: auto; + position: absolute; + right: 0; + top: 0; + z-index: 0; +} diff --git a/docdoku-web-front/app/less/workspace-management/menu.less b/docdoku-web-front/app/less/workspace-management/menu.less new file mode 100644 index 0000000000..0c025b671f --- /dev/null +++ b/docdoku-web-front/app/less/workspace-management/menu.less @@ -0,0 +1,186 @@ +#workspace-management-menu { + + position: relative; + /*-webkit-box-shadow: 0px 1px 3px #9b9b9b; + box-shadow: 0px 1px 3px #9b9b9b;*/ + float: left; + background-color: #f5f5f5; + height: 100%; + min-height: 100%; + width: 15%; + min-width: 15%; + max-width: 55%; + border: none; + border-radius: none; + border-right: 1px solid rgba(63, 95, 153, 0.24); + padding: 0; + margin: 0; + z-index: 1; + + .nav { + margin: 10px 0 5% 6px; + padding: 0; + height: 95%; + height: -moz-calc(~"95% - 68px"); + height: -webkit-calc(~"95% - 68px"); + height: calc(~"95% - 68px"); + overflow: auto; + } + + .well { + background: none; + box-shadow: none; + border: none; + } + + .nav-header { + font-size: 12px; + padding: 3px 10px; + display: block; + font-weight: bold; + line-height: 18px; + color: #999999; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-transform: uppercase; + } + + .nav-list-entry { + position: relative; + padding: 0 40px 0 6px; + height: 26px; + display: block; + + &.move-part-into { + border: 1px dashed #8fbc8f; + height: 25px; + margin-top: -1px; + padding-left: 5px; + } + + .icon { + float: left; + } + + > a { + overflow: hidden; + text-overflow: ellipsis; + line-height: 26px; + white-space: nowrap; + width: 100%; + float: left; + + .status { + float: left; + } + } + + > .btn-group { + right: 0; + position: absolute; + } + + .dropdown-toggle { + border-radius: 0; + margin: 0 0 0 8px; + display: none; + border: none; + background: none; + -webkit-box-shadow: none; + box-shadow: none; + padding: 3px 12px; + .caret { + border-top-color: #000000; + opacity: .5; + } + } + + .dropdown-toggle:hover { + background: #CCC; + .caret { + opacity: 1; + } + } + + .dropdown-menu { + border-radius: 0; + padding: 0 0 0 0; + margin: -1px 1px 0 0; + right: 0; + left: auto; + max-width: 250px; + + > li { + + width: 100%; + padding: 0; + + > a { + font-size: 13px; + padding: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + } + + } + li.new-folder .icon { + .plm-icon-menu-folder-new; + margin: 4px 6px 4px 6px; + } + li.edit .icon { + .plm-icon-menu-edit; + margin: 4px 6px 4px 6px; + } + li.delete .icon { + .plm-icon-menu-delete; + margin: 4px 6px 4px 6px; + } + } + + .nav-checkedOut-number-item { + margin-top: -22px; + } + } + + .nav-list-entry:hover { + > a { + text-decoration: none; + } + background-color: #EEE; + .dropdown-toggle { + display: block; + } + } + + .nav-list-entry.active, .nav-list-entry.active > a { + /*text-shadow: 0 -1px 0 @grayLight;*/ + color: #FFF; + background-color: @plm-secondary-color; + .badge { + background-color: #fff; + color: @plm-secondary-color; + } + } + + .items { + overflow: visible; + list-style-type: none; + margin-left: 6px; + padding: 0; + } + + .ui-resizable-handle { + + opacity: 0; + position: absolute; + bottom: 65px; + right: 0; + top: 0; + z-index: 1000; + width: 8px; + cursor: e-resize; + display: block !important; + + } +} diff --git a/docdoku-web-front/app/less/workspace-management/style.less b/docdoku-web-front/app/less/workspace-management/style.less new file mode 100644 index 0000000000..ba59e12933 --- /dev/null +++ b/docdoku-web-front/app/less/workspace-management/style.less @@ -0,0 +1,8 @@ +@import "../common/style"; +@import "../modules/style"; +@import "menu"; +@import "content"; +@import "workspace-home"; +@import "workspace-users"; +@import (inline) "../../js/lib/charts/nv3d/src/nv.d3.css"; +@import "charts.less"; diff --git a/docdoku-web-front/app/less/workspace-management/workspace-home.less b/docdoku-web-front/app/less/workspace-management/workspace-home.less new file mode 100644 index 0000000000..b26849be45 --- /dev/null +++ b/docdoku-web-front/app/less/workspace-management/workspace-home.less @@ -0,0 +1,20 @@ +.home-workspace-list-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; + align-items: stretch; + align-content: flex-start; + + .well-large.home-workspace { + margin: 3px; + padding:0 8px 4px 10px; + width: 24%; + border:1px solid rgba(63, 95, 153, 0.24); + background-color: white; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + } + +} diff --git a/docdoku-web-front/app/less/workspace-management/workspace-users.less b/docdoku-web-front/app/less/workspace-management/workspace-users.less new file mode 100644 index 0000000000..6f3f97133a --- /dev/null +++ b/docdoku-web-front/app/less/workspace-management/workspace-users.less @@ -0,0 +1,50 @@ +.workspace-group-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; + align-items: stretch; + align-content: flex-start; + + .well-large.workspace-group { + margin: 3px; + padding:0 8px 4px 10px; + border:1px solid rgba(63, 95, 153, 0.24); + background-color: white; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + + position: relative; + + .group-readonly-switch{ + position: absolute; + top: 12px; + right: 20px; + } + } + + .well-large.workspace-group.half { + width: 46%; + } + +} +.readonly-switch{ + display: block; white-space: nowrap; + label{ + padding-left: 12px; + padding-right: 12px; + } +} +.actions { + .move-users-container { + position: relative; + .dropdown-menu { + width: 100%; + text-align: left; + } + } +} +.no-membership td:not(:last-child){ + text-decoration: line-through; +} diff --git a/docdoku-web-front/app/main/js/app.js b/docdoku-web-front/app/main/js/app.js new file mode 100644 index 0000000000..3fd6210a92 --- /dev/null +++ b/docdoku-web-front/app/main/js/app.js @@ -0,0 +1,54 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/app.html', + 'views/login-form', + 'views/recovery-form', + 'views/recover-form', + 'views/account-creation-form', + 'views/login-scene' +], function (Backbone, Mustache, template, LoginFormView, RecoveryFormView, RecoverFormView, AccountCreationFormView, LoginSceneView ) { + 'use strict'; + + var AppView = Backbone.View.extend({ + + el: '#content', + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + contextPath:App.config.contextPath + })).show(); + + this.sceneView = new LoginSceneView({el:this.$('#demo-scene')[0]}); + this.$FormContainer = this.$('#form_container'); + this.showLoginForm(); + return this; + }, + + showRecoveryForm:function(){ + this.$FormContainer.html(new RecoveryFormView().render().$el); + this.$FormContainer.attr('class','put-right'); + }, + + showRecoverForm:function(uuid){ + this.$FormContainer.html(new RecoverFormView().render(uuid).$el); + this.$FormContainer.attr('class','put-right'); + }, + + showLoginForm:function(){ + this.$FormContainer.html(new LoginFormView().render().$el); + this.$FormContainer.attr('class','put-right'); + }, + + showAccountCreationForm:function(){ + this.$FormContainer.html(new AccountCreationFormView().render().$el); + this.$FormContainer.attr('class','put-above'); + } + + + }); + + return AppView; +}); diff --git a/docdoku-web-front/app/main/js/router.js b/docdoku-web-front/app/main/js/router.js new file mode 100644 index 0000000000..7e0894f84b --- /dev/null +++ b/docdoku-web-front/app/main/js/router.js @@ -0,0 +1,35 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/common/singleton_decorator' +], +function (Backbone, singletonDecorator) { + 'use strict'; + var Router = Backbone.Router.extend({ + routes: { + '': 'login', + 'create-account': 'createAccount', + 'recovery': 'recovery', + 'recover/:uuid': 'recover', + }, + + login:function(){ + App.appView.showLoginForm(); + }, + + createAccount:function(){ + App.appView.showAccountCreationForm(); + }, + + recovery:function(){ + App.appView.showRecoveryForm(); + }, + + recover:function(uuid){ + App.appView.showRecoverForm(uuid); + } + + }); + + return singletonDecorator(Router); +}); diff --git a/docdoku-web-front/app/main/js/templates/account-creation-form.html b/docdoku-web-front/app/main/js/templates/account-creation-form.html new file mode 100644 index 0000000000..be4fb412e7 --- /dev/null +++ b/docdoku-web-front/app/main/js/templates/account-creation-form.html @@ -0,0 +1,91 @@ +

                                {{i18n.REGISTRATION}}

                                + +
                                +

                                {{i18n.TERMS_OF_SERVICES}}

                                + {{i18n.TERMS_OF_SERVICES_TEXT}} +
                                + +

                                {{i18n.CREATE_ID}}

                                + +
                                +
                                +

                                + + +

                                +

                                + + +

                                +

                                + + +

                                +

                                + + +

                                +

                                + + +

                                +
                                +
                                +

                                + + +

                                + +

                                + + +

                                + +
                                diff --git a/docdoku-web-front/app/main/js/templates/app.html b/docdoku-web-front/app/main/js/templates/app.html new file mode 100644 index 0000000000..0fadfa7f09 --- /dev/null +++ b/docdoku-web-front/app/main/js/templates/app.html @@ -0,0 +1,45 @@ +
                                +
                                +
                                +
                                + +
                                +
                                +

                                {{i18n.MANAGE_DOCUMENTS}}

                                +
                                  + {{#i18n.MANAGE_DOCUMENTS_STRINGS}} +
                                • {{.}}
                                • + {{/i18n.MANAGE_DOCUMENTS_STRINGS}} +
                                +
                                +
                                +

                                {{i18n.MANAGE_PRODUCTS}}

                                +
                                  + {{#i18n.MANAGE_PRODUCTS_STRINGS}} +
                                • {{.}}
                                • + {{/i18n.MANAGE_PRODUCTS_STRINGS}} +
                                +
                                +
                                +

                                {{i18n.TRACK_CHANGES}}

                                +
                                  + {{#i18n.TRACK_CHANGES_STRINGS}} +
                                • {{.}}
                                • + {{/i18n.TRACK_CHANGES_STRINGS}} +
                                +
                                + +
                                +

                                {{i18n.SOCIAL_FEATURES}}

                                +
                                  + {{#i18n.SOCIAL_FEATURES_STRINGS}} +
                                • {{.}}
                                • + {{/i18n.SOCIAL_FEATURES_STRINGS}} +
                                +
                                + +
                                + + diff --git a/docdoku-web-front/app/main/js/templates/login-form.html b/docdoku-web-front/app/main/js/templates/login-form.html new file mode 100644 index 0000000000..fbdba5e8f1 --- /dev/null +++ b/docdoku-web-front/app/main/js/templates/login-form.html @@ -0,0 +1,36 @@ + + +

                                {{i18n.CONNECTION}}

                                +
                                +

                                + + +

                                + +

                                + + +

                                + +

                                + +

                                + +

                                {{i18n.RECOVERY}}

                                + diff --git a/docdoku-web-front/app/main/js/templates/recover-form.html b/docdoku-web-front/app/main/js/templates/recover-form.html new file mode 100644 index 0000000000..b7ae4e18ff --- /dev/null +++ b/docdoku-web-front/app/main/js/templates/recover-form.html @@ -0,0 +1,39 @@ +

                                {{i18n.RECOVER}}

                                +
                                +
                                +
                                +

                                {{i18n.ENTER_NEW_PASSWORD}}

                                + +

                                + + +

                                + +

                                + + +

                                + +

                                + +

                                +
                                +

                                {{i18n.BACK}}

                                + diff --git a/docdoku-web-front/app/main/js/templates/recovery-form.html b/docdoku-web-front/app/main/js/templates/recovery-form.html new file mode 100644 index 0000000000..307dd99399 --- /dev/null +++ b/docdoku-web-front/app/main/js/templates/recovery-form.html @@ -0,0 +1,26 @@ +

                                {{i18n.RECOVERY}}

                                +
                                +
                                +

                                {{i18n.ENTER_ID}}

                                +

                                + + + +

                                + +

                                + +

                                + +

                                {{i18n.BACK}}

                                + diff --git a/docdoku-web-front/app/main/js/views/account-creation-form.js b/docdoku-web-front/app/main/js/views/account-creation-form.js new file mode 100644 index 0000000000..32dba4bea6 --- /dev/null +++ b/docdoku-web-front/app/main/js/views/account-creation-form.js @@ -0,0 +1,97 @@ +/*global App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/account-creation-form.html', + 'common-objects/views/alert', + 'common-objects/models/timezone', + 'common-objects/models/language' +], function (Backbone, Mustache, template, AlertView, TimeZone, Language) { + 'use strict'; + + var AccountCreationFormView = Backbone.View.extend({ + + tagName:'form', + id:'account_creation_form', + events:{ + 'submit':'onAccountCreationFormSubmit' + }, + + render: function () { + + + var _this = this; + + TimeZone.getTimeZones() + .then(function(timeZones){ + _this.timeZones = timeZones; + }) + .then(Language.getLanguages) + .then(function(languages){ + _this.languages = languages; + }) + .then(function(){ + _this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + account:App.config.account, + timeZones: _this.timeZones, + languages: _this.languages + })); + _this.$notifications = _this.$('.notifications'); + _this.$confirmPassword = _this.$('#account_creation_form-confirmPassword'); + _this.$password = _this.$('#account_creation_form-password'); + _this.$('#account_creation_form-language option').each(function(){ + $(this).text(App.config.i18n.LANGUAGES[$(this).val()]); + }); + _this.$('#account_creation_form-timeZone option').each(function(){ + // Fixed type error. Assume CET is in list. + // TODO : detect browser timezone for auto selection + $(this).attr('selected', $(this).val() === 'CET'); + }); + }); + + return this; + }, + + onAccountCreationFormSubmit:function(e){ + + this.$notifications.empty(); + + if(this.$password.val() !== this.$confirmPassword.val()){ + this.$notifications.append(new AlertView({ + type: 'error', + message: App.config.i18n.PASSWORD_NOT_CONFIRMED + }).render().$el); + e.preventDefault(); + return false; + } + + $.ajax({ + type: 'POST', + url: App.config.contextPath + '/api/accounts/create', + data: JSON.stringify({ + login:this.$('#account_creation_form-login').val(), + name:this.$('#account_creation_form-name').val(), + email:this.$('#account_creation_form-email').val(), + language:this.$('#account_creation_form-language').val(), + timeZone:this.$('#account_creation_form-timeZone').val(), + newPassword:this.$('#account_creation_form-password').val() + }), + contentType: 'application/json; charset=utf-8' + }).then(function(/*account, status, xhr*/){ + window.location.href =App.config.contextPath + '/workspace-management/?accountCreated=true'; + }, this.onAccountCreationFailed.bind(this)); + e.preventDefault(); + return false; + }, + + onAccountCreationFailed:function(err){ + this.$notifications.append(new AlertView({ + type: 'error', + message: err.responseText + }).render().$el); + } + }); + + return AccountCreationFormView; +}); diff --git a/docdoku-web-front/app/main/js/views/login-form.js b/docdoku-web-front/app/main/js/views/login-form.js new file mode 100644 index 0000000000..eb16282dd7 --- /dev/null +++ b/docdoku-web-front/app/main/js/views/login-form.js @@ -0,0 +1,78 @@ +/*global App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/login-form.html', + 'common-objects/views/alert', + 'urlUtils' +], function (Backbone, Mustache, template, AlertView, UrlUtils) { + 'use strict'; + + var LoginFormView = Backbone.View.extend({ + + tagName:'form', + id:'login_form', + events:{ + 'submit':'onLoginFormSubmit' + }, + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + contextPath:App.config.contextPath + })); + this.$notifications = this.$('.notifications'); + this.showMessageAtStart(); + return this; + }, + + showMessageAtStart:function(){ + var logout = UrlUtils.getParameterByName('logout'); + if(logout){ + this.$notifications.append(new AlertView({ + type: 'info', + message: App.config.i18n.DISCONNECTED + }).render().$el); + } + var denied = UrlUtils.getParameterByName('denied'); + if(denied){ + this.$notifications.append(new AlertView({ + type: 'error', + message: App.config.i18n.FORBIDDEN_MESSAGE + }).render().$el); + } + }, + + onLoginFormSubmit:function(e){ + delete localStorage.jwt; + this.$notifications.empty(); + this.$el.addClass('connecting'); + $.ajax({ + type: 'POST', + url: App.config.contextPath + '/api/auth/login', + data: JSON.stringify({ + login:this.$('#login_form-login').val(), + password:this.$('#login_form-password').val() + }), + contentType: 'application/json; charset=utf-8' + }).then(function(account, status, xhr ){ + var jwt = xhr.getResponseHeader('jwt'); + localStorage.jwt = jwt; + var originURL = UrlUtils.getParameterByName('originURL'); + window.location.href = originURL ? decodeURIComponent(originURL):App.config.contextPath + '/workspace-management/'; + }, this.onLoginFailed.bind(this)); + e.preventDefault(); + return false; + }, + + onLoginFailed:function(){ + this.$el.removeClass('connecting'); + this.$notifications.append(new AlertView({ + type: 'error', + message: App.config.i18n.FAILED_LOGIN + }).render().$el); + } + }); + + return LoginFormView; +}); diff --git a/docdoku-web-front/app/main/js/views/login-scene.js b/docdoku-web-front/app/main/js/views/login-scene.js new file mode 100644 index 0000000000..42695f7cdd --- /dev/null +++ b/docdoku-web-front/app/main/js/views/login-scene.js @@ -0,0 +1,129 @@ +/*global define,THREE,App,TWEEN,requestAnimationFrame*/ +define([ + 'backbone' +], function (Backbone) { + + 'use strict'; + + function hasWebGL() { + try { + return !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); + } catch( e ) { + return false; + } + } + + var errorMessage = window.WebGLRenderingContext ? [ + 'Your graphics card does not seem to support WebGL.
                                ', + 'Find out how to get it here.' + ].join( '\n' ) : [ + 'Your browser does not seem to support WebGL.
                                ', + 'Find out how to get it here.' + ].join( '\n' ); + + + var LoginSceneView = Backbone.View.extend({ + + initialize:function(){ + + var container = this.el; + + if ( ! hasWebGL() ){ + container.innerHTML = errorMessage; + return this; + } + // standard global variables + var scene, camera, renderer, controls; + + // custom global variables + var model; + + // FUNCTIONS + function update(){ + if(model){ + model.rotation.set(0.45, model.rotation.y+0.005, model.rotation.z+0.005); + } + controls.update(); + } + + function animateCamera(){ + camera.position.copy(App.SceneOptions.startCameraPosition); + new TWEEN.Tween(camera.position) + .to(App.SceneOptions.endCameraPosition, 1000) + .interpolation(TWEEN.Interpolation.CatmullRom) + .easing(TWEEN.Easing.Sinusoidal.InOut) + .start(); + } + + function render(){ + renderer.render( scene, camera ); + } + + function animate(){ + requestAnimationFrame(animate); + update(); + TWEEN.update(); + render(); + } + + function addModelToScene( geometry ){ + var material = new THREE.MeshLambertMaterial( { color: 0xffffff, shading: THREE.FlatShading, overdraw: true } ); + model = new THREE.Mesh( geometry, material ); + model.scale.set(1,1,1); + model.position.set(0, 0, 0); + model.rotation.set(0.45, 0, 1.55); + scene.add( model ); + geometry.computeBoundingBox(); + controls.target.copy(model.geometry.boundingBox.center()); + animate(); + animateCamera(); + } + + function handleResize() { + camera.aspect = container.clientWidth / container.clientHeight; + camera.updateProjectionMatrix(); + renderer.setSize(container.clientWidth , container.clientHeight); + controls.handleResize(); + } + + // SCENE + scene = new THREE.Scene(); + + // CAMERA + camera = new THREE.PerspectiveCamera( 45, container.clientWidth/container.clientHeight,0.1, 5000); + scene.add(camera); + camera.position.set(0,250,200); + + // RENDERER + renderer = new THREE.WebGLRenderer( {antialias:true, alpha:true} ); + renderer.setSize( container.clientWidth, container.clientHeight); + + // CONTROLS + controls = new THREE.TrackballControls( camera, container ); + + // LIGHT + var light = new THREE.PointLight(0xffffff); + light.position.set(-100,200,100); + scene.add(light); + var light2 = new THREE.PointLight(0xffffff); + light2.position.set(100,-200,-100); + scene.add(light2); + + // SKYBOX/FOG + scene.fog = new THREE.FogExp2( 0x9999ff, 0.00025 ); + + var binaryLoader = new THREE.BinaryLoader(); + binaryLoader.load( App.config.contextPath + '/images/pba.json', addModelToScene); + + var ambientLight = new THREE.AmbientLight(0x111111); + scene.add(ambientLight); + + container.appendChild( renderer.domElement ); + window.addEventListener('resize', handleResize, false); + + this.handleResize = handleResize; + } + }); + + return LoginSceneView; +}); diff --git a/docdoku-web-front/app/main/js/views/recover-form.js b/docdoku-web-front/app/main/js/views/recover-form.js new file mode 100644 index 0000000000..b7a01bf1e0 --- /dev/null +++ b/docdoku-web-front/app/main/js/views/recover-form.js @@ -0,0 +1,74 @@ +/*global App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/recover-form.html', + 'common-objects/views/alert' +], function (Backbone, Mustache, template, AlertView) { + 'use strict'; + + var RecoverFormView = Backbone.View.extend({ + + tagName:'form', + id:'recovery_form', + events:{ + 'submit':'onRecoverFormSubmit' + }, + + render: function (uuid) { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + contextPath:App.config.contextPath + })); + this.uuid = uuid; + this.$notifications = this.$('.notifications'); + this.$password = this.$('#recover_form-password'); + this.$confirmPassword = this.$('#recover_form-confirmPassword'); + + return this; + }, + + + onRecoverFormSubmit:function(e){ + this.$notifications.empty(); + + if(this.$password.val() !== this.$confirmPassword.val()){ + this.$notifications.append(new AlertView({ + type: 'error', + message: App.config.i18n.PASSWORD_NOT_CONFIRMED + }).render().$el); + e.preventDefault(); + return false; + } + + $.ajax({ + type: 'POST', + url: App.config.contextPath + '/api/auth/recover', + data: JSON.stringify({ + uuid:this.uuid, + newPassword:this.$password.val() + }), + contentType: 'application/json; charset=utf-8' + }).then(this.onRecoverSent.bind(this), this.onError.bind(this)); + e.preventDefault(); + + return false; + }, + + onRecoverSent:function(){ + this.$('.form-zone').after('

                                '+App.config.i18n.RECOVER_OK+'

                                '); + this.$('.form-zone').hide(); + }, + + onError:function(err){ + this.$notifications.append(new AlertView({ + type: 'error', + message: err.responseText + }).render().$el); + + } + + }); + + return RecoverFormView; +}); diff --git a/docdoku-web-front/app/main/js/views/recovery-form.js b/docdoku-web-front/app/main/js/views/recovery-form.js new file mode 100644 index 0000000000..0088df83bf --- /dev/null +++ b/docdoku-web-front/app/main/js/views/recovery-form.js @@ -0,0 +1,68 @@ +/*global App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/recovery-form.html', + 'common-objects/views/alert' +], function (Backbone, Mustache, template, AlertView) { + 'use strict'; + + var RecoveryFormView = Backbone.View.extend({ + + tagName:'form', + id:'recovery_form', + events:{ + 'submit':'onRecoveryFormSubmit' + }, + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + contextPath:App.config.contextPath + })); + this.$notifications = this.$('.notifications'); + this.$('#recovery_form-login').prop('disabled', false); + this.$('.form_button_container').show(); + return this; + }, + + + onRecoveryFormSubmit:function(e){ + this.$notifications.empty(); + var $loginInput = this.$('#recovery_form-login'); + var login = $loginInput.val(); + $loginInput.prop('disabled', true); + this.$('.form_button_container').hide(); + $.ajax({ + type: 'POST', + url: App.config.contextPath + '/api/auth/recovery', + data: JSON.stringify({ + login:login + }), + contentType: 'application/json; charset=utf-8' + }).then(this.onRecoverySent.bind(this), this.onError.bind(this)); + e.preventDefault(); + return false; + }, + + onRecoverySent:function(){ + this.$notifications.append(new AlertView({ + type: 'success', + message: App.config.i18n.RECOVERY_REQUEST_SENT + }).render().$el); + }, + + onError:function(err){ + this.$notifications.append(new AlertView({ + type: 'error', + message: err.responseText + }).render().$el); + + this.$('#recovery_form-login').prop('disabled', false); + this.$('.form_button_container').show(); + } + + }); + + return RecoveryFormView; +}); diff --git a/docdoku-web-front/app/main/main.js b/docdoku-web-front/app/main/main.js new file mode 100644 index 0000000000..b89b2de3af --- /dev/null +++ b/docdoku-web-front/app/main/main.js @@ -0,0 +1,105 @@ +/*global _,require,window*/ + +var App = { + debug:false, + config:{ + locale: window.localStorage.getItem('locale') || 'en' + }, + SceneOptions: { + zoomSpeed: 3, + rotateSpeed: 3.0, + panSpeed: 0.3, + cameraNear: 1, + cameraFar: 5E4, + defaultCameraPosition: {x: 0, y:0, z:0}, + startCameraPosition: {x: 100, y: 2500, z: 2500}, + endCameraPosition: {x: 0, y: 250, z: 250}, + defaultTargetPosition: {x: 0, y: 0, z: 0} + } +}; + +App.log=function(message){ + 'use strict'; + if(App.debug){ + window.console.log(message); + } +}; + +require.config({ + + baseUrl: 'main/js', + + shim: { + jqueryUI: { deps: ['jquery'], exports: 'jQuery' }, + bootstrap: { deps: ['jquery', 'jqueryUI'], exports: 'jQuery' }, + backbone: { deps: ['underscore', 'jquery'], exports: 'Backbone'}, + trackballcontrols:{deps:['threecore'],exports:'THREE'}, + binaryloader:{deps:['threecore'],exports:'THREE'} + }, + + paths: { + jquery: '../../bower_components/jquery/jquery', + jqueryUI: '../../bower_components/jqueryui/ui/jquery-ui', + backbone: '../../bower_components/backbone/backbone', + underscore: '../../bower_components/underscore/underscore', + mustache: '../../bower_components/mustache/mustache', + text: '../../bower_components/requirejs-text/text', + i18n: '../../bower_components/requirejs-i18n/i18n', + bootstrap: '../../bower_components/bootstrap/docs/assets/js/bootstrap', + 'common-objects': '../../js/common-objects', + localization: '../../js/localization', + threecore:'../../bower_components/threejs/build/three', + tween:'../../bower_components/tweenjs/src/Tween', + trackballcontrols:'../../js/dmu/controls/TrackballControls', + binaryloader:'../../js/dmu/loaders/BinaryLoader', + urlUtils:'../../js/utils/url-utils' + + }, + + deps: [ + 'jquery', + 'underscore', + 'bootstrap', + 'jqueryUI', + 'threecore', + 'tween', + 'trackballcontrols', + 'binaryloader' + ], + config: { + i18n: { + locale: (function(){ + 'use strict'; + try{ + return App.config.locale; + }catch(ex){ + return 'en'; + } + })() + } + } +}); + +require(['common-objects/contextResolver','i18n!localization/nls/common','i18n!localization/nls/index'], + function (ContextResolver, commonStrings, indexStrings) { + 'use strict'; + + App.config.i18n = _.extend(commonStrings, indexStrings); + ContextResolver.resolveServerProperties() + .then(function(){ + ContextResolver.resolveAccount().then(function(){ + window.location.href = App.config.contextPath + '/workspace-management/'; + }, null); + }) + .then(function buildView(){ + require(['backbone','app','router','common-objects/views/header'],function(Backbone, AppView, Router,HeaderView){ + App.appView = new AppView(); + App.headerView = new HeaderView(); + App.appView.render(); + App.headerView.render(); + App.router = Router.getInstance(); + Backbone.history.start(); + }); + }); + }); + diff --git a/docdoku-web-front/app/organization-management/main.css b/docdoku-web-front/app/organization-management/main.css new file mode 100644 index 0000000000..6c55c40541 --- /dev/null +++ b/docdoku-web-front/app/organization-management/main.css @@ -0,0 +1,12335 @@ +/*--------------------------------------------------- + LESS Elements 0.9 + --------------------------------------------------- + A set of useful LESS mixins + More info at: http://lesselements.com + ---------------------------------------------------*/ +/*! + * Bootstrap v2.3.2 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ + +.clearfix { + *zoom: 1; +} + +.clearfix:before, +.clearfix:after { + display: table; + line-height: 0; + content: ""; +} + +.clearfix:after { + clear: both; +} + +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.input-block-level { + display: block; + width: 100%; + min-height: 30px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section { + display: block; +} + +audio, +canvas, +video { + display: inline-block; + *display: inline; + *zoom: 1; +} + +audio:not([controls]) { + display: none; +} + +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +a:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +a:hover, +a:active { + outline: 0; +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +img { + width: auto\9; + height: auto; + max-width: 100%; + vertical-align: middle; + border: 0; + -ms-interpolation-mode: bicubic; +} + +#map_canvas img, +.google-maps img { + max-width: none; +} + +button, +input, +select, +textarea { + margin: 0; + font-size: 100%; + vertical-align: middle; +} + +button, +input { + *overflow: visible; + line-height: normal; +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} + +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + cursor: pointer; + -webkit-appearance: button; +} + +label, +select, +button, +input[type="button"], +input[type="reset"], +input[type="submit"], +input[type="radio"], +input[type="checkbox"] { + cursor: pointer; +} + +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} + +input[type="search"]::-webkit-search-decoration, +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; +} + +textarea { + overflow: auto; + vertical-align: top; +} + +@media print { + * { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + .ir a:after, + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + @page { + margin: 0.5cm; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } +} + +body { + margin: 0; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 20px; + color: #333333; + background-color: #ffffff; +} + +a { + color: #0088cc; + text-decoration: none; +} + +a:hover, +a:focus { + color: #005580; + text-decoration: underline; +} + +.img-rounded { + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +.img-polaroid { + padding: 4px; + background-color: #fff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.img-circle { + -webkit-border-radius: 500px; + -moz-border-radius: 500px; + border-radius: 500px; +} + +.row { + margin-left: -20px; + *zoom: 1; +} + +.row:before, +.row:after { + display: table; + line-height: 0; + content: ""; +} + +.row:after { + clear: both; +} + +[class*="span"] { + float: left; + min-height: 1px; + margin-left: 20px; +} + +.container, +.navbar-static-top .container, +.navbar-fixed-top .container, +.navbar-fixed-bottom .container { + width: 940px; +} + +.span12 { + width: 940px; +} + +.span11 { + width: 860px; +} + +.span10 { + width: 780px; +} + +.span9 { + width: 700px; +} + +.span8 { + width: 620px; +} + +.span7 { + width: 540px; +} + +.span6 { + width: 460px; +} + +.span5 { + width: 380px; +} + +.span4 { + width: 300px; +} + +.span3 { + width: 220px; +} + +.span2 { + width: 140px; +} + +.span1 { + width: 60px; +} + +.offset12 { + margin-left: 980px; +} + +.offset11 { + margin-left: 900px; +} + +.offset10 { + margin-left: 820px; +} + +.offset9 { + margin-left: 740px; +} + +.offset8 { + margin-left: 660px; +} + +.offset7 { + margin-left: 580px; +} + +.offset6 { + margin-left: 500px; +} + +.offset5 { + margin-left: 420px; +} + +.offset4 { + margin-left: 340px; +} + +.offset3 { + margin-left: 260px; +} + +.offset2 { + margin-left: 180px; +} + +.offset1 { + margin-left: 100px; +} + +.row-fluid { + width: 100%; + *zoom: 1; +} + +.row-fluid:before, +.row-fluid:after { + display: table; + line-height: 0; + content: ""; +} + +.row-fluid:after { + clear: both; +} + +.row-fluid [class*="span"] { + display: block; + float: left; + width: 100%; + min-height: 30px; + margin-left: 2.127659574468085%; + *margin-left: 2.074468085106383%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.row-fluid [class*="span"]:first-child { + margin-left: 0; +} + +.row-fluid .controls-row [class*="span"] + [class*="span"] { + margin-left: 2.127659574468085%; +} + +.row-fluid .span12 { + width: 100%; + *width: 99.94680851063829%; +} + +.row-fluid .span11 { + width: 91.48936170212765%; + *width: 91.43617021276594%; +} + +.row-fluid .span10 { + width: 82.97872340425532%; + *width: 82.92553191489361%; +} + +.row-fluid .span9 { + width: 74.46808510638297%; + *width: 74.41489361702126%; +} + +.row-fluid .span8 { + width: 65.95744680851064%; + *width: 65.90425531914893%; +} + +.row-fluid .span7 { + width: 57.44680851063829%; + *width: 57.39361702127659%; +} + +.row-fluid .span6 { + width: 48.93617021276595%; + *width: 48.88297872340425%; +} + +.row-fluid .span5 { + width: 40.42553191489362%; + *width: 40.37234042553192%; +} + +.row-fluid .span4 { + width: 31.914893617021278%; + *width: 31.861702127659576%; +} + +.row-fluid .span3 { + width: 23.404255319148934%; + *width: 23.351063829787233%; +} + +.row-fluid .span2 { + width: 14.893617021276595%; + *width: 14.840425531914894%; +} + +.row-fluid .span1 { + width: 6.382978723404255%; + *width: 6.329787234042553%; +} + +.row-fluid .offset12 { + margin-left: 104.25531914893617%; + *margin-left: 104.14893617021275%; +} + +.row-fluid .offset12:first-child { + margin-left: 102.12765957446808%; + *margin-left: 102.02127659574467%; +} + +.row-fluid .offset11 { + margin-left: 95.74468085106382%; + *margin-left: 95.6382978723404%; +} + +.row-fluid .offset11:first-child { + margin-left: 93.61702127659574%; + *margin-left: 93.51063829787232%; +} + +.row-fluid .offset10 { + margin-left: 87.23404255319149%; + *margin-left: 87.12765957446807%; +} + +.row-fluid .offset10:first-child { + margin-left: 85.1063829787234%; + *margin-left: 84.99999999999999%; +} + +.row-fluid .offset9 { + margin-left: 78.72340425531914%; + *margin-left: 78.61702127659572%; +} + +.row-fluid .offset9:first-child { + margin-left: 76.59574468085106%; + *margin-left: 76.48936170212764%; +} + +.row-fluid .offset8 { + margin-left: 70.2127659574468%; + *margin-left: 70.10638297872339%; +} + +.row-fluid .offset8:first-child { + margin-left: 68.08510638297872%; + *margin-left: 67.9787234042553%; +} + +.row-fluid .offset7 { + margin-left: 61.70212765957446%; + *margin-left: 61.59574468085106%; +} + +.row-fluid .offset7:first-child { + margin-left: 59.574468085106375%; + *margin-left: 59.46808510638297%; +} + +.row-fluid .offset6 { + margin-left: 53.191489361702125%; + *margin-left: 53.085106382978715%; +} + +.row-fluid .offset6:first-child { + margin-left: 51.063829787234035%; + *margin-left: 50.95744680851063%; +} + +.row-fluid .offset5 { + margin-left: 44.68085106382979%; + *margin-left: 44.57446808510638%; +} + +.row-fluid .offset5:first-child { + margin-left: 42.5531914893617%; + *margin-left: 42.4468085106383%; +} + +.row-fluid .offset4 { + margin-left: 36.170212765957444%; + *margin-left: 36.06382978723405%; +} + +.row-fluid .offset4:first-child { + margin-left: 34.04255319148936%; + *margin-left: 33.93617021276596%; +} + +.row-fluid .offset3 { + margin-left: 27.659574468085104%; + *margin-left: 27.5531914893617%; +} + +.row-fluid .offset3:first-child { + margin-left: 25.53191489361702%; + *margin-left: 25.425531914893618%; +} + +.row-fluid .offset2 { + margin-left: 19.148936170212764%; + *margin-left: 19.04255319148936%; +} + +.row-fluid .offset2:first-child { + margin-left: 17.02127659574468%; + *margin-left: 16.914893617021278%; +} + +.row-fluid .offset1 { + margin-left: 10.638297872340425%; + *margin-left: 10.53191489361702%; +} + +.row-fluid .offset1:first-child { + margin-left: 8.51063829787234%; + *margin-left: 8.404255319148938%; +} + +[class*="span"].hide, +.row-fluid [class*="span"].hide { + display: none; +} + +[class*="span"].pull-right, +.row-fluid [class*="span"].pull-right { + float: right; +} + +.container { + margin-right: auto; + margin-left: auto; + *zoom: 1; +} + +.container:before, +.container:after { + display: table; + line-height: 0; + content: ""; +} + +.container:after { + clear: both; +} + +.container-fluid { + padding-right: 20px; + padding-left: 20px; + *zoom: 1; +} + +.container-fluid:before, +.container-fluid:after { + display: table; + line-height: 0; + content: ""; +} + +.container-fluid:after { + clear: both; +} + +p { + margin: 0 0 10px; +} + +.lead { + margin-bottom: 20px; + font-size: 21px; + font-weight: 200; + line-height: 30px; +} + +small { + font-size: 85%; +} + +strong { + font-weight: bold; +} + +em { + font-style: italic; +} + +cite { + font-style: normal; +} + +.muted { + color: #999999; +} + +a.muted:hover, +a.muted:focus { + color: #808080; +} + +.text-warning { + color: #c09853; +} + +a.text-warning:hover, +a.text-warning:focus { + color: #a47e3c; +} + +.text-error { + color: #b94a48; +} + +a.text-error:hover, +a.text-error:focus { + color: #953b39; +} + +.text-info { + color: #3a87ad; +} + +a.text-info:hover, +a.text-info:focus { + color: #2d6987; +} + +.text-success { + color: #468847; +} + +a.text-success:hover, +a.text-success:focus { + color: #356635; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.text-center { + text-align: center; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 10px 0; + font-family: inherit; + font-weight: bold; + line-height: 20px; + color: inherit; + text-rendering: optimizelegibility; +} + +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small { + font-weight: normal; + line-height: 1; + color: #999999; +} + +h1, +h2, +h3 { + line-height: 40px; +} + +h1 { + font-size: 38.5px; +} + +h2 { + font-size: 31.5px; +} + +h3 { + font-size: 24.5px; +} + +h4 { + font-size: 17.5px; +} + +h5 { + font-size: 14px; +} + +h6 { + font-size: 11.9px; +} + +h1 small { + font-size: 24.5px; +} + +h2 small { + font-size: 17.5px; +} + +h3 small { + font-size: 14px; +} + +h4 small { + font-size: 14px; +} + +.page-header { + padding-bottom: 9px; + margin: 20px 0 30px; + border-bottom: 1px solid #eeeeee; +} + +ul, +ol { + padding: 0; + margin: 0 0 10px 25px; +} + +ul ul, +ul ol, +ol ol, +ol ul { + margin-bottom: 0; +} + +li { + line-height: 20px; +} + +ul.unstyled, +ol.unstyled { + margin-left: 0; + list-style: none; +} + +ul.inline, +ol.inline { + margin-left: 0; + list-style: none; +} + +ul.inline > li, +ol.inline > li { + display: inline-block; + *display: inline; + padding-right: 5px; + padding-left: 5px; + *zoom: 1; +} + +dl { + margin-bottom: 20px; +} + +dt, +dd { + line-height: 20px; +} + +dt { + font-weight: bold; +} + +dd { + margin-left: 10px; +} + +.dl-horizontal { + *zoom: 1; +} + +.dl-horizontal:before, +.dl-horizontal:after { + display: table; + line-height: 0; + content: ""; +} + +.dl-horizontal:after { + clear: both; +} + +.dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dl-horizontal dd { + margin-left: 180px; +} + +hr { + margin: 20px 0; + border: 0; + border-top: 1px solid #eeeeee; + border-bottom: 1px solid #ffffff; +} + +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #999999; +} + +abbr.initialism { + font-size: 90%; + text-transform: uppercase; +} + +blockquote { + padding: 0 0 0 15px; + margin: 0 0 20px; + border-left: 5px solid #eeeeee; +} + +blockquote p { + margin-bottom: 0; + font-size: 17.5px; + font-weight: 300; + line-height: 1.25; +} + +blockquote small { + display: block; + line-height: 20px; + color: #999999; +} + +blockquote small:before { + content: '\2014 \00A0'; +} + +blockquote.pull-right { + float: right; + padding-right: 15px; + padding-left: 0; + border-right: 5px solid #eeeeee; + border-left: 0; +} + +blockquote.pull-right p, +blockquote.pull-right small { + text-align: right; +} + +blockquote.pull-right small:before { + content: ''; +} + +blockquote.pull-right small:after { + content: '\00A0 \2014'; +} + +q:before, +q:after, +blockquote:before, +blockquote:after { + content: ""; +} + +address { + display: block; + margin-bottom: 20px; + font-style: normal; + line-height: 20px; +} + +code, +pre { + padding: 0 3px 2px; + font-family: Monaco, Menlo, Consolas, "Courier New", monospace; + font-size: 12px; + color: #333333; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +code { + padding: 2px 4px; + color: #d14; + white-space: nowrap; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; +} + +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 20px; + word-break: break-all; + word-wrap: break-word; + white-space: pre; + white-space: pre-wrap; + background-color: #f5f5f5; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.15); + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +pre.prettyprint { + margin-bottom: 20px; +} + +pre code { + padding: 0; + color: inherit; + white-space: pre; + white-space: pre-wrap; + background-color: transparent; + border: 0; +} + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} + +form { + margin: 0 0 20px; +} + +fieldset { + padding: 0; + margin: 0; + border: 0; +} + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: 40px; + color: #333333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} + +legend small { + font-size: 15px; + color: #999999; +} + +label, +input, +button, +select, +textarea { + font-size: 14px; + font-weight: normal; + line-height: 20px; +} + +input, +button, +select, +textarea { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +label { + display: block; + margin-bottom: 5px; +} + +select, +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + display: inline-block; + height: 20px; + padding: 4px 6px; + margin-bottom: 10px; + font-size: 14px; + line-height: 20px; + color: #555555; + vertical-align: middle; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +input, +textarea, +.uneditable-input { + width: 206px; +} + +textarea { + height: auto; +} + +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + background-color: #ffffff; + border: 1px solid #cccccc; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; + -moz-transition: border linear 0.2s, box-shadow linear 0.2s; + -o-transition: border linear 0.2s, box-shadow linear 0.2s; + transition: border linear 0.2s, box-shadow linear 0.2s; +} + +textarea:focus, +input[type="text"]:focus, +input[type="password"]:focus, +input[type="datetime"]:focus, +input[type="datetime-local"]:focus, +input[type="date"]:focus, +input[type="month"]:focus, +input[type="time"]:focus, +input[type="week"]:focus, +input[type="number"]:focus, +input[type="email"]:focus, +input[type="url"]:focus, +input[type="search"]:focus, +input[type="tel"]:focus, +input[type="color"]:focus, +.uneditable-input:focus { + border-color: rgba(82, 168, 236, 0.8); + outline: 0; + outline: thin dotted \9; + /* IE6-9 */ + + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); +} + +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + *margin-top: 0; + line-height: normal; +} + +input[type="file"], +input[type="image"], +input[type="submit"], +input[type="reset"], +input[type="button"], +input[type="radio"], +input[type="checkbox"] { + width: auto; +} + +select, +input[type="file"] { + height: 30px; + /* In IE7, the height of the select element cannot be changed by height, only font-size */ + + *margin-top: 4px; + /* For IE7, add top margin to align select with labels */ + + line-height: 30px; +} + +select { + width: 220px; + background-color: #ffffff; + border: 1px solid #cccccc; +} + +select[multiple], +select[size] { + height: auto; +} + +select:focus, +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +.uneditable-input, +.uneditable-textarea { + color: #999999; + cursor: not-allowed; + background-color: #fcfcfc; + border-color: #cccccc; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); +} + +.uneditable-input { + overflow: hidden; + white-space: nowrap; +} + +.uneditable-textarea { + width: auto; + height: auto; +} + +input:-moz-placeholder, +textarea:-moz-placeholder { + color: #999999; +} + +input:-ms-input-placeholder, +textarea:-ms-input-placeholder { + color: #999999; +} + +input::-webkit-input-placeholder, +textarea::-webkit-input-placeholder { + color: #999999; +} + +.radio, +.checkbox { + min-height: 20px; + padding-left: 20px; +} + +.radio input[type="radio"], +.checkbox input[type="checkbox"] { + float: left; + margin-left: -20px; +} + +.controls > .radio:first-child, +.controls > .checkbox:first-child { + padding-top: 5px; +} + +.radio.inline, +.checkbox.inline { + display: inline-block; + padding-top: 5px; + margin-bottom: 0; + vertical-align: middle; +} + +.radio.inline + .radio.inline, +.checkbox.inline + .checkbox.inline { + margin-left: 10px; +} + +.input-mini { + width: 60px; +} + +.input-small { + width: 90px; +} + +.input-medium { + width: 150px; +} + +.input-large { + width: 210px; +} + +.input-xlarge { + width: 270px; +} + +.input-xxlarge { + width: 530px; +} + +input[class*="span"], +select[class*="span"], +textarea[class*="span"], +.uneditable-input[class*="span"], +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"] { + float: none; + margin-left: 0; +} + +.input-append input[class*="span"], +.input-append .uneditable-input[class*="span"], +.input-prepend input[class*="span"], +.input-prepend .uneditable-input[class*="span"], +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"], +.row-fluid .input-prepend [class*="span"], +.row-fluid .input-append [class*="span"] { + display: inline-block; +} + +input, +textarea, +.uneditable-input { + margin-left: 0; +} + +.controls-row [class*="span"] + [class*="span"] { + margin-left: 20px; +} + +input.span12, +textarea.span12, +.uneditable-input.span12 { + width: 926px; +} + +input.span11, +textarea.span11, +.uneditable-input.span11 { + width: 846px; +} + +input.span10, +textarea.span10, +.uneditable-input.span10 { + width: 766px; +} + +input.span9, +textarea.span9, +.uneditable-input.span9 { + width: 686px; +} + +input.span8, +textarea.span8, +.uneditable-input.span8 { + width: 606px; +} + +input.span7, +textarea.span7, +.uneditable-input.span7 { + width: 526px; +} + +input.span6, +textarea.span6, +.uneditable-input.span6 { + width: 446px; +} + +input.span5, +textarea.span5, +.uneditable-input.span5 { + width: 366px; +} + +input.span4, +textarea.span4, +.uneditable-input.span4 { + width: 286px; +} + +input.span3, +textarea.span3, +.uneditable-input.span3 { + width: 206px; +} + +input.span2, +textarea.span2, +.uneditable-input.span2 { + width: 126px; +} + +input.span1, +textarea.span1, +.uneditable-input.span1 { + width: 46px; +} + +.controls-row { + *zoom: 1; +} + +.controls-row:before, +.controls-row:after { + display: table; + line-height: 0; + content: ""; +} + +.controls-row:after { + clear: both; +} + +.controls-row [class*="span"], +.row-fluid .controls-row [class*="span"] { + float: left; +} + +.controls-row .checkbox[class*="span"], +.controls-row .radio[class*="span"] { + padding-top: 5px; +} + +input[disabled], +select[disabled], +textarea[disabled], +input[readonly], +select[readonly], +textarea[readonly] { + cursor: not-allowed; + background-color: #eeeeee; +} + +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"][readonly], +input[type="checkbox"][readonly] { + background-color: transparent; +} + +.control-group.warning .control-label, +.control-group.warning .help-block, +.control-group.warning .help-inline { + color: #c09853; +} + +.control-group.warning .checkbox, +.control-group.warning .radio, +.control-group.warning input, +.control-group.warning select, +.control-group.warning textarea { + color: #c09853; +} + +.control-group.warning input, +.control-group.warning select, +.control-group.warning textarea { + border-color: #c09853; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.control-group.warning input:focus, +.control-group.warning select:focus, +.control-group.warning textarea:focus { + border-color: #a47e3c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; +} + +.control-group.warning .input-prepend .add-on, +.control-group.warning .input-append .add-on { + color: #c09853; + background-color: #fcf8e3; + border-color: #c09853; +} + +.control-group.error .control-label, +.control-group.error .help-block, +.control-group.error .help-inline { + color: #b94a48; +} + +.control-group.error .checkbox, +.control-group.error .radio, +.control-group.error input, +.control-group.error select, +.control-group.error textarea { + color: #b94a48; +} + +.control-group.error input, +.control-group.error select, +.control-group.error textarea { + border-color: #b94a48; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.control-group.error input:focus, +.control-group.error select:focus, +.control-group.error textarea:focus { + border-color: #953b39; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; +} + +.control-group.error .input-prepend .add-on, +.control-group.error .input-append .add-on { + color: #b94a48; + background-color: #f2dede; + border-color: #b94a48; +} + +.control-group.success .control-label, +.control-group.success .help-block, +.control-group.success .help-inline { + color: #468847; +} + +.control-group.success .checkbox, +.control-group.success .radio, +.control-group.success input, +.control-group.success select, +.control-group.success textarea { + color: #468847; +} + +.control-group.success input, +.control-group.success select, +.control-group.success textarea { + border-color: #468847; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.control-group.success input:focus, +.control-group.success select:focus, +.control-group.success textarea:focus { + border-color: #356635; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; +} + +.control-group.success .input-prepend .add-on, +.control-group.success .input-append .add-on { + color: #468847; + background-color: #dff0d8; + border-color: #468847; +} + +.control-group.info .control-label, +.control-group.info .help-block, +.control-group.info .help-inline { + color: #3a87ad; +} + +.control-group.info .checkbox, +.control-group.info .radio, +.control-group.info input, +.control-group.info select, +.control-group.info textarea { + color: #3a87ad; +} + +.control-group.info input, +.control-group.info select, +.control-group.info textarea { + border-color: #3a87ad; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.control-group.info input:focus, +.control-group.info select:focus, +.control-group.info textarea:focus { + border-color: #2d6987; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; +} + +.control-group.info .input-prepend .add-on, +.control-group.info .input-append .add-on { + color: #3a87ad; + background-color: #d9edf7; + border-color: #3a87ad; +} + +input:focus:invalid, +textarea:focus:invalid, +select:focus:invalid { + color: #b94a48; + border-color: #ee5f5b; +} + +input:focus:invalid:focus, +textarea:focus:invalid:focus, +select:focus:invalid:focus { + border-color: #e9322d; + -webkit-box-shadow: 0 0 6px #f8b9b7; + -moz-box-shadow: 0 0 6px #f8b9b7; + box-shadow: 0 0 6px #f8b9b7; +} + +.form-actions { + padding: 19px 20px 20px; + margin-top: 20px; + margin-bottom: 20px; + background-color: #f5f5f5; + border-top: 1px solid #e5e5e5; + *zoom: 1; +} + +.form-actions:before, +.form-actions:after { + display: table; + line-height: 0; + content: ""; +} + +.form-actions:after { + clear: both; +} + +.help-block, +.help-inline { + color: #595959; +} + +.help-block { + display: block; + margin-bottom: 10px; +} + +.help-inline { + display: inline-block; + *display: inline; + padding-left: 5px; + vertical-align: middle; + *zoom: 1; +} + +.input-append, +.input-prepend { + display: inline-block; + margin-bottom: 10px; + font-size: 0; + white-space: nowrap; + vertical-align: middle; +} + +.input-append input, +.input-prepend input, +.input-append select, +.input-prepend select, +.input-append .uneditable-input, +.input-prepend .uneditable-input, +.input-append .dropdown-menu, +.input-prepend .dropdown-menu, +.input-append .popover, +.input-prepend .popover { + font-size: 14px; +} + +.input-append input, +.input-prepend input, +.input-append select, +.input-prepend select, +.input-append .uneditable-input, +.input-prepend .uneditable-input { + position: relative; + margin-bottom: 0; + *margin-left: 0; + vertical-align: top; + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.input-append input:focus, +.input-prepend input:focus, +.input-append select:focus, +.input-prepend select:focus, +.input-append .uneditable-input:focus, +.input-prepend .uneditable-input:focus { + z-index: 2; +} + +.input-append .add-on, +.input-prepend .add-on { + display: inline-block; + width: auto; + height: 20px; + min-width: 16px; + padding: 4px 5px; + font-size: 14px; + font-weight: normal; + line-height: 20px; + text-align: center; + text-shadow: 0 1px 0 #ffffff; + background-color: #eeeeee; + border: 1px solid #ccc; +} + +.input-append .add-on, +.input-prepend .add-on, +.input-append .btn, +.input-prepend .btn, +.input-append .btn-group > .dropdown-toggle, +.input-prepend .btn-group > .dropdown-toggle { + vertical-align: top; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.input-append .active, +.input-prepend .active { + background-color: #a9dba9; + border-color: #46a546; +} + +.input-prepend .add-on, +.input-prepend .btn { + margin-right: -1px; +} + +.input-prepend .add-on:first-child, +.input-prepend .btn:first-child { + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; +} + +.input-append input, +.input-append select, +.input-append .uneditable-input { + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; +} + +.input-append input + .btn-group .btn:last-child, +.input-append select + .btn-group .btn:last-child, +.input-append .uneditable-input + .btn-group .btn:last-child { + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.input-append .add-on, +.input-append .btn, +.input-append .btn-group { + margin-left: -1px; +} + +.input-append .add-on:last-child, +.input-append .btn:last-child, +.input-append .btn-group:last-child > .dropdown-toggle { + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.input-prepend.input-append input, +.input-prepend.input-append select, +.input-prepend.input-append .uneditable-input { + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.input-prepend.input-append input + .btn-group .btn, +.input-prepend.input-append select + .btn-group .btn, +.input-prepend.input-append .uneditable-input + .btn-group .btn { + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.input-prepend.input-append .add-on:first-child, +.input-prepend.input-append .btn:first-child { + margin-right: -1px; + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; +} + +.input-prepend.input-append .add-on:last-child, +.input-prepend.input-append .btn:last-child { + margin-left: -1px; + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.input-prepend.input-append .btn-group:first-child { + margin-left: 0; +} + +input.search-query { + padding-right: 14px; + padding-right: 4px \9; + padding-left: 14px; + padding-left: 4px \9; + /* IE7-8 doesn't have border-radius, so don't indent the padding */ + + margin-bottom: 0; + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + border-radius: 15px; +} + +/* Allow for input prepend/append in search forms */ + +.form-search .input-append .search-query, +.form-search .input-prepend .search-query { + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.form-search .input-append .search-query { + -webkit-border-radius: 14px 0 0 14px; + -moz-border-radius: 14px 0 0 14px; + border-radius: 14px 0 0 14px; +} + +.form-search .input-append .btn { + -webkit-border-radius: 0 14px 14px 0; + -moz-border-radius: 0 14px 14px 0; + border-radius: 0 14px 14px 0; +} + +.form-search .input-prepend .search-query { + -webkit-border-radius: 0 14px 14px 0; + -moz-border-radius: 0 14px 14px 0; + border-radius: 0 14px 14px 0; +} + +.form-search .input-prepend .btn { + -webkit-border-radius: 14px 0 0 14px; + -moz-border-radius: 14px 0 0 14px; + border-radius: 14px 0 0 14px; +} + +.form-search input, +.form-inline input, +.form-horizontal input, +.form-search textarea, +.form-inline textarea, +.form-horizontal textarea, +.form-search select, +.form-inline select, +.form-horizontal select, +.form-search .help-inline, +.form-inline .help-inline, +.form-horizontal .help-inline, +.form-search .uneditable-input, +.form-inline .uneditable-input, +.form-horizontal .uneditable-input, +.form-search .input-prepend, +.form-inline .input-prepend, +.form-horizontal .input-prepend, +.form-search .input-append, +.form-inline .input-append, +.form-horizontal .input-append { + display: inline-block; + *display: inline; + margin-bottom: 0; + vertical-align: middle; + *zoom: 1; +} + +.form-search .hide, +.form-inline .hide, +.form-horizontal .hide { + display: none; +} + +.form-search label, +.form-inline label, +.form-search .btn-group, +.form-inline .btn-group { + display: inline-block; +} + +.form-search .input-append, +.form-inline .input-append, +.form-search .input-prepend, +.form-inline .input-prepend { + margin-bottom: 0; +} + +.form-search .radio, +.form-search .checkbox, +.form-inline .radio, +.form-inline .checkbox { + padding-left: 0; + margin-bottom: 0; + vertical-align: middle; +} + +.form-search .radio input[type="radio"], +.form-search .checkbox input[type="checkbox"], +.form-inline .radio input[type="radio"], +.form-inline .checkbox input[type="checkbox"] { + float: left; + margin-right: 3px; + margin-left: 0; +} + +.control-group { + margin-bottom: 10px; +} + +legend + .control-group { + margin-top: 20px; + -webkit-margin-top-collapse: separate; +} + +.form-horizontal .control-group { + margin-bottom: 20px; + *zoom: 1; +} + +.form-horizontal .control-group:before, +.form-horizontal .control-group:after { + display: table; + line-height: 0; + content: ""; +} + +.form-horizontal .control-group:after { + clear: both; +} + +.form-horizontal .control-label { + float: left; + width: 160px; + padding-top: 5px; + text-align: right; +} + +.form-horizontal .controls { + *display: inline-block; + *padding-left: 20px; + margin-left: 180px; + *margin-left: 0; +} + +.form-horizontal .controls:first-child { + *padding-left: 180px; +} + +.form-horizontal .help-block { + margin-bottom: 0; +} + +.form-horizontal input + .help-block, +.form-horizontal select + .help-block, +.form-horizontal textarea + .help-block, +.form-horizontal .uneditable-input + .help-block, +.form-horizontal .input-prepend + .help-block, +.form-horizontal .input-append + .help-block { + margin-top: 10px; +} + +.form-horizontal .form-actions { + padding-left: 180px; +} + +table { + max-width: 100%; + background-color: transparent; + border-collapse: collapse; + border-spacing: 0; +} + +.table { + width: 100%; + margin-bottom: 20px; +} + +.table th, +.table td { + padding: 8px; + line-height: 20px; + text-align: left; + vertical-align: top; + border-top: 1px solid #dddddd; +} + +.table th { + font-weight: bold; +} + +.table thead th { + vertical-align: bottom; +} + +.table caption + thead tr:first-child th, +.table caption + thead tr:first-child td, +.table colgroup + thead tr:first-child th, +.table colgroup + thead tr:first-child td, +.table thead:first-child tr:first-child th, +.table thead:first-child tr:first-child td { + border-top: 0; +} + +.table tbody + tbody { + border-top: 2px solid #dddddd; +} + +.table .table { + background-color: #ffffff; +} + +.table-condensed th, +.table-condensed td { + padding: 4px 5px; +} + +.table-bordered { + border: 1px solid #dddddd; + border-collapse: separate; + *border-collapse: collapse; + border-left: 0; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.table-bordered th, +.table-bordered td { + border-left: 1px solid #dddddd; +} + +.table-bordered caption + thead tr:first-child th, +.table-bordered caption + tbody tr:first-child th, +.table-bordered caption + tbody tr:first-child td, +.table-bordered colgroup + thead tr:first-child th, +.table-bordered colgroup + tbody tr:first-child th, +.table-bordered colgroup + tbody tr:first-child td, +.table-bordered thead:first-child tr:first-child th, +.table-bordered tbody:first-child tr:first-child th, +.table-bordered tbody:first-child tr:first-child td { + border-top: 0; +} + +.table-bordered thead:first-child tr:first-child > th:first-child, +.table-bordered tbody:first-child tr:first-child > td:first-child, +.table-bordered tbody:first-child tr:first-child > th:first-child { + -webkit-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; +} + +.table-bordered thead:first-child tr:first-child > th:last-child, +.table-bordered tbody:first-child tr:first-child > td:last-child, +.table-bordered tbody:first-child tr:first-child > th:last-child { + -webkit-border-top-right-radius: 4px; + border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; +} + +.table-bordered thead:last-child tr:last-child > th:first-child, +.table-bordered tbody:last-child tr:last-child > td:first-child, +.table-bordered tbody:last-child tr:last-child > th:first-child, +.table-bordered tfoot:last-child tr:last-child > td:first-child, +.table-bordered tfoot:last-child tr:last-child > th:first-child { + -webkit-border-bottom-left-radius: 4px; + border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; +} + +.table-bordered thead:last-child tr:last-child > th:last-child, +.table-bordered tbody:last-child tr:last-child > td:last-child, +.table-bordered tbody:last-child tr:last-child > th:last-child, +.table-bordered tfoot:last-child tr:last-child > td:last-child, +.table-bordered tfoot:last-child tr:last-child > th:last-child { + -webkit-border-bottom-right-radius: 4px; + border-bottom-right-radius: 4px; + -moz-border-radius-bottomright: 4px; +} + +.table-bordered tfoot + tbody:last-child tr:last-child td:first-child { + -webkit-border-bottom-left-radius: 0; + border-bottom-left-radius: 0; + -moz-border-radius-bottomleft: 0; +} + +.table-bordered tfoot + tbody:last-child tr:last-child td:last-child { + -webkit-border-bottom-right-radius: 0; + border-bottom-right-radius: 0; + -moz-border-radius-bottomright: 0; +} + +.table-bordered caption + thead tr:first-child th:first-child, +.table-bordered caption + tbody tr:first-child td:first-child, +.table-bordered colgroup + thead tr:first-child th:first-child, +.table-bordered colgroup + tbody tr:first-child td:first-child { + -webkit-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; +} + +.table-bordered caption + thead tr:first-child th:last-child, +.table-bordered caption + tbody tr:first-child td:last-child, +.table-bordered colgroup + thead tr:first-child th:last-child, +.table-bordered colgroup + tbody tr:first-child td:last-child { + -webkit-border-top-right-radius: 4px; + border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; +} + +.table-striped tbody > tr:nth-child(odd) > td, +.table-striped tbody > tr:nth-child(odd) > th { + background-color: #f9f9f9; +} + +.table-hover tbody tr:hover > td, +.table-hover tbody tr:hover > th { + background-color: #f5f5f5; +} + +table td[class*="span"], +table th[class*="span"], +.row-fluid table td[class*="span"], +.row-fluid table th[class*="span"] { + display: table-cell; + float: none; + margin-left: 0; +} + +.table td.span1, +.table th.span1 { + float: none; + width: 44px; + margin-left: 0; +} + +.table td.span2, +.table th.span2 { + float: none; + width: 124px; + margin-left: 0; +} + +.table td.span3, +.table th.span3 { + float: none; + width: 204px; + margin-left: 0; +} + +.table td.span4, +.table th.span4 { + float: none; + width: 284px; + margin-left: 0; +} + +.table td.span5, +.table th.span5 { + float: none; + width: 364px; + margin-left: 0; +} + +.table td.span6, +.table th.span6 { + float: none; + width: 444px; + margin-left: 0; +} + +.table td.span7, +.table th.span7 { + float: none; + width: 524px; + margin-left: 0; +} + +.table td.span8, +.table th.span8 { + float: none; + width: 604px; + margin-left: 0; +} + +.table td.span9, +.table th.span9 { + float: none; + width: 684px; + margin-left: 0; +} + +.table td.span10, +.table th.span10 { + float: none; + width: 764px; + margin-left: 0; +} + +.table td.span11, +.table th.span11 { + float: none; + width: 844px; + margin-left: 0; +} + +.table td.span12, +.table th.span12 { + float: none; + width: 924px; + margin-left: 0; +} + +.table tbody tr.success > td { + background-color: #dff0d8; +} + +.table tbody tr.error > td { + background-color: #f2dede; +} + +.table tbody tr.warning > td { + background-color: #fcf8e3; +} + +.table tbody tr.info > td { + background-color: #d9edf7; +} + +.table-hover tbody tr.success:hover > td { + background-color: #d0e9c6; +} + +.table-hover tbody tr.error:hover > td { + background-color: #ebcccc; +} + +.table-hover tbody tr.warning:hover > td { + background-color: #faf2cc; +} + +.table-hover tbody tr.info:hover > td { + background-color: #c4e3f3; +} + +[class^="icon-"], +[class*=" icon-"] { + display: inline-block; + width: 14px; + height: 14px; + margin-top: 1px; + *margin-right: .3em; + line-height: 14px; + vertical-align: text-top; + background-image: url("../img/glyphicons-halflings.png"); + background-position: 14px 14px; + background-repeat: no-repeat; +} + +/* White icons with optional class, or on hover/focus/active states of certain elements */ + +.icon-white, +.nav-pills > .active > a > [class^="icon-"], +.nav-pills > .active > a > [class*=" icon-"], +.nav-list > .active > a > [class^="icon-"], +.nav-list > .active > a > [class*=" icon-"], +.navbar-inverse .nav > .active > a > [class^="icon-"], +.navbar-inverse .nav > .active > a > [class*=" icon-"], +.dropdown-menu > li > a:hover > [class^="icon-"], +.dropdown-menu > li > a:focus > [class^="icon-"], +.dropdown-menu > li > a:hover > [class*=" icon-"], +.dropdown-menu > li > a:focus > [class*=" icon-"], +.dropdown-menu > .active > a > [class^="icon-"], +.dropdown-menu > .active > a > [class*=" icon-"], +.dropdown-submenu:hover > a > [class^="icon-"], +.dropdown-submenu:focus > a > [class^="icon-"], +.dropdown-submenu:hover > a > [class*=" icon-"], +.dropdown-submenu:focus > a > [class*=" icon-"] { + background-image: url("../img/glyphicons-halflings-white.png"); +} + +.icon-glass { + background-position: 0 0; +} + +.icon-music { + background-position: -24px 0; +} + +.icon-search { + background-position: -48px 0; +} + +.icon-envelope { + background-position: -72px 0; +} + +.icon-heart { + background-position: -96px 0; +} + +.icon-star { + background-position: -120px 0; +} + +.icon-star-empty { + background-position: -144px 0; +} + +.icon-user { + background-position: -168px 0; +} + +.icon-film { + background-position: -192px 0; +} + +.icon-th-large { + background-position: -216px 0; +} + +.icon-th { + background-position: -240px 0; +} + +.icon-th-list { + background-position: -264px 0; +} + +.icon-ok { + background-position: -288px 0; +} + +.icon-remove { + background-position: -312px 0; +} + +.icon-zoom-in { + background-position: -336px 0; +} + +.icon-zoom-out { + background-position: -360px 0; +} + +.icon-off { + background-position: -384px 0; +} + +.icon-signal { + background-position: -408px 0; +} + +.icon-cog { + background-position: -432px 0; +} + +.icon-trash { + background-position: -456px 0; +} + +.icon-home { + background-position: 0 -24px; +} + +.icon-file { + background-position: -24px -24px; +} + +.icon-time { + background-position: -48px -24px; +} + +.icon-road { + background-position: -72px -24px; +} + +.icon-download-alt { + background-position: -96px -24px; +} + +.icon-download { + background-position: -120px -24px; +} + +.icon-upload { + background-position: -144px -24px; +} + +.icon-inbox { + background-position: -168px -24px; +} + +.icon-play-circle { + background-position: -192px -24px; +} + +.icon-repeat { + background-position: -216px -24px; +} + +.icon-refresh { + background-position: -240px -24px; +} + +.icon-list-alt { + background-position: -264px -24px; +} + +.icon-lock { + background-position: -287px -24px; +} + +.icon-flag { + background-position: -312px -24px; +} + +.icon-headphones { + background-position: -336px -24px; +} + +.icon-volume-off { + background-position: -360px -24px; +} + +.icon-volume-down { + background-position: -384px -24px; +} + +.icon-volume-up { + background-position: -408px -24px; +} + +.icon-qrcode { + background-position: -432px -24px; +} + +.icon-barcode { + background-position: -456px -24px; +} + +.icon-tag { + background-position: 0 -48px; +} + +.icon-tags { + background-position: -25px -48px; +} + +.icon-book { + background-position: -48px -48px; +} + +.icon-bookmark { + background-position: -72px -48px; +} + +.icon-print { + background-position: -96px -48px; +} + +.icon-camera { + background-position: -120px -48px; +} + +.icon-font { + background-position: -144px -48px; +} + +.icon-bold { + background-position: -167px -48px; +} + +.icon-italic { + background-position: -192px -48px; +} + +.icon-text-height { + background-position: -216px -48px; +} + +.icon-text-width { + background-position: -240px -48px; +} + +.icon-align-left { + background-position: -264px -48px; +} + +.icon-align-center { + background-position: -288px -48px; +} + +.icon-align-right { + background-position: -312px -48px; +} + +.icon-align-justify { + background-position: -336px -48px; +} + +.icon-list { + background-position: -360px -48px; +} + +.icon-indent-left { + background-position: -384px -48px; +} + +.icon-indent-right { + background-position: -408px -48px; +} + +.icon-facetime-video { + background-position: -432px -48px; +} + +.icon-picture { + background-position: -456px -48px; +} + +.icon-pencil { + background-position: 0 -72px; +} + +.icon-map-marker { + background-position: -24px -72px; +} + +.icon-adjust { + background-position: -48px -72px; +} + +.icon-tint { + background-position: -72px -72px; +} + +.icon-edit { + background-position: -96px -72px; +} + +.icon-share { + background-position: -120px -72px; +} + +.icon-check { + background-position: -144px -72px; +} + +.icon-move { + background-position: -168px -72px; +} + +.icon-step-backward { + background-position: -192px -72px; +} + +.icon-fast-backward { + background-position: -216px -72px; +} + +.icon-backward { + background-position: -240px -72px; +} + +.icon-play { + background-position: -264px -72px; +} + +.icon-pause { + background-position: -288px -72px; +} + +.icon-stop { + background-position: -312px -72px; +} + +.icon-forward { + background-position: -336px -72px; +} + +.icon-fast-forward { + background-position: -360px -72px; +} + +.icon-step-forward { + background-position: -384px -72px; +} + +.icon-eject { + background-position: -408px -72px; +} + +.icon-chevron-left { + background-position: -432px -72px; +} + +.icon-chevron-right { + background-position: -456px -72px; +} + +.icon-plus-sign { + background-position: 0 -96px; +} + +.icon-minus-sign { + background-position: -24px -96px; +} + +.icon-remove-sign { + background-position: -48px -96px; +} + +.icon-ok-sign { + background-position: -72px -96px; +} + +.icon-question-sign { + background-position: -96px -96px; +} + +.icon-info-sign { + background-position: -120px -96px; +} + +.icon-screenshot { + background-position: -144px -96px; +} + +.icon-remove-circle { + background-position: -168px -96px; +} + +.icon-ok-circle { + background-position: -192px -96px; +} + +.icon-ban-circle { + background-position: -216px -96px; +} + +.icon-arrow-left { + background-position: -240px -96px; +} + +.icon-arrow-right { + background-position: -264px -96px; +} + +.icon-arrow-up { + background-position: -289px -96px; +} + +.icon-arrow-down { + background-position: -312px -96px; +} + +.icon-share-alt { + background-position: -336px -96px; +} + +.icon-resize-full { + background-position: -360px -96px; +} + +.icon-resize-small { + background-position: -384px -96px; +} + +.icon-plus { + background-position: -408px -96px; +} + +.icon-minus { + background-position: -433px -96px; +} + +.icon-asterisk { + background-position: -456px -96px; +} + +.icon-exclamation-sign { + background-position: 0 -120px; +} + +.icon-gift { + background-position: -24px -120px; +} + +.icon-leaf { + background-position: -48px -120px; +} + +.icon-fire { + background-position: -72px -120px; +} + +.icon-eye-open { + background-position: -96px -120px; +} + +.icon-eye-close { + background-position: -120px -120px; +} + +.icon-warning-sign { + background-position: -144px -120px; +} + +.icon-plane { + background-position: -168px -120px; +} + +.icon-calendar { + background-position: -192px -120px; +} + +.icon-random { + width: 16px; + background-position: -216px -120px; +} + +.icon-comment { + background-position: -240px -120px; +} + +.icon-magnet { + background-position: -264px -120px; +} + +.icon-chevron-up { + background-position: -288px -120px; +} + +.icon-chevron-down { + background-position: -313px -119px; +} + +.icon-retweet { + background-position: -336px -120px; +} + +.icon-shopping-cart { + background-position: -360px -120px; +} + +.icon-folder-close { + width: 16px; + background-position: -384px -120px; +} + +.icon-folder-open { + width: 16px; + background-position: -408px -120px; +} + +.icon-resize-vertical { + background-position: -432px -119px; +} + +.icon-resize-horizontal { + background-position: -456px -118px; +} + +.icon-hdd { + background-position: 0 -144px; +} + +.icon-bullhorn { + background-position: -24px -144px; +} + +.icon-bell { + background-position: -48px -144px; +} + +.icon-certificate { + background-position: -72px -144px; +} + +.icon-thumbs-up { + background-position: -96px -144px; +} + +.icon-thumbs-down { + background-position: -120px -144px; +} + +.icon-hand-right { + background-position: -144px -144px; +} + +.icon-hand-left { + background-position: -168px -144px; +} + +.icon-hand-up { + background-position: -192px -144px; +} + +.icon-hand-down { + background-position: -216px -144px; +} + +.icon-circle-arrow-right { + background-position: -240px -144px; +} + +.icon-circle-arrow-left { + background-position: -264px -144px; +} + +.icon-circle-arrow-up { + background-position: -288px -144px; +} + +.icon-circle-arrow-down { + background-position: -312px -144px; +} + +.icon-globe { + background-position: -336px -144px; +} + +.icon-wrench { + background-position: -360px -144px; +} + +.icon-tasks { + background-position: -384px -144px; +} + +.icon-filter { + background-position: -408px -144px; +} + +.icon-briefcase { + background-position: -432px -144px; +} + +.icon-fullscreen { + background-position: -456px -144px; +} + +.dropup, +.dropdown { + position: relative; +} + +.dropdown-toggle { + *margin-bottom: -3px; +} + +.dropdown-toggle:active, +.open .dropdown-toggle { + outline: 0; +} + +.caret { + display: inline-block; + width: 0; + height: 0; + vertical-align: top; + border-top: 4px solid #000000; + border-right: 4px solid transparent; + border-left: 4px solid transparent; + content: ""; +} + +.dropdown .caret { + margin-top: 8px; + margin-left: 2px; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + list-style: none; + background-color: #ffffff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + *border-right-width: 2px; + *border-bottom-width: 2px; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; +} + +.dropdown-menu.pull-right { + right: 0; + left: auto; +} + +.dropdown-menu .divider { + *width: 100%; + height: 1px; + margin: 9px 1px; + *margin: -5px 0 5px; + overflow: hidden; + background-color: #e5e5e5; + border-bottom: 1px solid #ffffff; +} + +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 20px; + color: #333333; + white-space: nowrap; +} + +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus, +.dropdown-submenu:hover > a, +.dropdown-submenu:focus > a { + color: #ffffff; + text-decoration: none; + background-color: #0081c2; + background-image: -moz-linear-gradient(top, #0088cc, #0077b3); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); + background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); + background-image: -o-linear-gradient(top, #0088cc, #0077b3); + background-image: linear-gradient(to bottom, #0088cc, #0077b3); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0); +} + +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #ffffff; + text-decoration: none; + background-color: #0081c2; + background-image: -moz-linear-gradient(top, #0088cc, #0077b3); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); + background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); + background-image: -o-linear-gradient(top, #0088cc, #0077b3); + background-image: linear-gradient(to bottom, #0088cc, #0077b3); + background-repeat: repeat-x; + outline: 0; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0); +} + +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #999999; +} + +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: default; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.open { + *z-index: 1000; +} + +.open > .dropdown-menu { + display: block; +} + +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} + +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + border-top: 0; + border-bottom: 4px solid #000000; + content: ""; +} + +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; +} + +.dropdown-submenu { + position: relative; +} + +.dropdown-submenu > .dropdown-menu { + top: 0; + left: 100%; + margin-top: -6px; + margin-left: -1px; + -webkit-border-radius: 0 6px 6px 6px; + -moz-border-radius: 0 6px 6px 6px; + border-radius: 0 6px 6px 6px; +} + +.dropdown-submenu:hover > .dropdown-menu { + display: block; +} + +.dropup .dropdown-submenu > .dropdown-menu { + top: auto; + bottom: 0; + margin-top: 0; + margin-bottom: -2px; + -webkit-border-radius: 5px 5px 5px 0; + -moz-border-radius: 5px 5px 5px 0; + border-radius: 5px 5px 5px 0; +} + +.dropdown-submenu > a:after { + display: block; + float: right; + width: 0; + height: 0; + margin-top: 5px; + margin-right: -10px; + border-color: transparent; + border-left-color: #cccccc; + border-style: solid; + border-width: 5px 0 5px 5px; + content: " "; +} + +.dropdown-submenu:hover > a:after { + border-left-color: #ffffff; +} + +.dropdown-submenu.pull-left { + float: none; +} + +.dropdown-submenu.pull-left > .dropdown-menu { + left: -100%; + margin-left: 10px; + -webkit-border-radius: 6px 0 6px 6px; + -moz-border-radius: 6px 0 6px 6px; + border-radius: 6px 0 6px 6px; +} + +.dropdown .dropdown-menu .nav-header { + padding-right: 20px; + padding-left: 20px; +} + +.typeahead { + z-index: 1051; + margin-top: 2px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); +} + +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); +} + +.well-large { + padding: 24px; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +.well-small { + padding: 9px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + -moz-transition: opacity 0.15s linear; + -o-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} + +.fade.in { + opacity: 1; +} + +.collapse { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition: height 0.35s ease; + -moz-transition: height 0.35s ease; + -o-transition: height 0.35s ease; + transition: height 0.35s ease; +} + +.collapse.in { + height: auto; +} + +.close { + float: right; + font-size: 20px; + font-weight: bold; + line-height: 20px; + color: #000000; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.2; + filter: alpha(opacity=20); +} + +.close:hover, +.close:focus { + color: #000000; + text-decoration: none; + cursor: pointer; + opacity: 0.4; + filter: alpha(opacity=40); +} + +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} + +.btn { + display: inline-block; + *display: inline; + padding: 4px 12px; + margin-bottom: 0; + *margin-left: .3em; + font-size: 14px; + line-height: 20px; + color: #333333; + text-align: center; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + vertical-align: middle; + cursor: pointer; + background-color: #f5f5f5; + *background-color: #e6e6e6; + background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); + background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); + background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); + background-repeat: repeat-x; + border: 1px solid #cccccc; + *border: 0; + border-color: #e6e6e6 #e6e6e6 #bfbfbf; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + border-bottom-color: #b3b3b3; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + *zoom: 1; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.btn:hover, +.btn:focus, +.btn:active, +.btn.active, +.btn.disabled, +.btn[disabled] { + color: #333333; + background-color: #e6e6e6; + *background-color: #d9d9d9; +} + +.btn:active, +.btn.active { + background-color: #cccccc \9; +} + +.btn:first-child { + *margin-left: 0; +} + +.btn:hover, +.btn:focus { + color: #333333; + text-decoration: none; + background-position: 0 -15px; + -webkit-transition: background-position 0.1s linear; + -moz-transition: background-position 0.1s linear; + -o-transition: background-position 0.1s linear; + transition: background-position 0.1s linear; +} + +.btn:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +.btn.active, +.btn:active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.btn.disabled, +.btn[disabled] { + cursor: default; + background-image: none; + opacity: 0.65; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} + +.btn-large { + padding: 11px 19px; + font-size: 17.5px; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +.btn-large [class^="icon-"], +.btn-large [class*=" icon-"] { + margin-top: 4px; +} + +.btn-small { + padding: 2px 10px; + font-size: 11.9px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +.btn-small [class^="icon-"], +.btn-small [class*=" icon-"] { + margin-top: 0; +} + +.btn-mini [class^="icon-"], +.btn-mini [class*=" icon-"] { + margin-top: -1px; +} + +.btn-mini { + padding: 0 6px; + font-size: 10.5px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +.btn-block { + display: block; + width: 100%; + padding-right: 0; + padding-left: 0; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.btn-block + .btn-block { + margin-top: 5px; +} + +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} + +.btn-primary.active, +.btn-warning.active, +.btn-danger.active, +.btn-success.active, +.btn-info.active, +.btn-inverse.active { + color: rgba(255, 255, 255, 0.75); +} + +.btn-primary { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #006dcc; + *background-color: #0044cc; + background-image: -moz-linear-gradient(top, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); + background-image: -o-linear-gradient(top, #0088cc, #0044cc); + background-image: linear-gradient(to bottom, #0088cc, #0044cc); + background-repeat: repeat-x; + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.btn-primary:hover, +.btn-primary:focus, +.btn-primary:active, +.btn-primary.active, +.btn-primary.disabled, +.btn-primary[disabled] { + color: #ffffff; + background-color: #0044cc; + *background-color: #003bb3; +} + +.btn-primary:active, +.btn-primary.active { + background-color: #003399 \9; +} + +.btn-warning { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #faa732; + *background-color: #f89406; + background-image: -moz-linear-gradient(top, #fbb450, #f89406); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); + background-image: -webkit-linear-gradient(top, #fbb450, #f89406); + background-image: -o-linear-gradient(top, #fbb450, #f89406); + background-image: linear-gradient(to bottom, #fbb450, #f89406); + background-repeat: repeat-x; + border-color: #f89406 #f89406 #ad6704; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.btn-warning:hover, +.btn-warning:focus, +.btn-warning:active, +.btn-warning.active, +.btn-warning.disabled, +.btn-warning[disabled] { + color: #ffffff; + background-color: #f89406; + *background-color: #df8505; +} + +.btn-warning:active, +.btn-warning.active { + background-color: #c67605 \9; +} + +.btn-danger { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #da4f49; + *background-color: #bd362f; + background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f)); + background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f); + background-image: -o-linear-gradient(top, #ee5f5b, #bd362f); + background-image: linear-gradient(to bottom, #ee5f5b, #bd362f); + background-repeat: repeat-x; + border-color: #bd362f #bd362f #802420; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.btn-danger:hover, +.btn-danger:focus, +.btn-danger:active, +.btn-danger.active, +.btn-danger.disabled, +.btn-danger[disabled] { + color: #ffffff; + background-color: #bd362f; + *background-color: #a9302a; +} + +.btn-danger:active, +.btn-danger.active { + background-color: #942a25 \9; +} + +.btn-success { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #5bb75b; + *background-color: #51a351; + background-image: -moz-linear-gradient(top, #62c462, #51a351); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351)); + background-image: -webkit-linear-gradient(top, #62c462, #51a351); + background-image: -o-linear-gradient(top, #62c462, #51a351); + background-image: linear-gradient(to bottom, #62c462, #51a351); + background-repeat: repeat-x; + border-color: #51a351 #51a351 #387038; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.btn-success:hover, +.btn-success:focus, +.btn-success:active, +.btn-success.active, +.btn-success.disabled, +.btn-success[disabled] { + color: #ffffff; + background-color: #51a351; + *background-color: #499249; +} + +.btn-success:active, +.btn-success.active { + background-color: #408140 \9; +} + +.btn-info { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #49afcd; + *background-color: #2f96b4; + background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4)); + background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4); + background-image: -o-linear-gradient(top, #5bc0de, #2f96b4); + background-image: linear-gradient(to bottom, #5bc0de, #2f96b4); + background-repeat: repeat-x; + border-color: #2f96b4 #2f96b4 #1f6377; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.btn-info:hover, +.btn-info:focus, +.btn-info:active, +.btn-info.active, +.btn-info.disabled, +.btn-info[disabled] { + color: #ffffff; + background-color: #2f96b4; + *background-color: #2a85a0; +} + +.btn-info:active, +.btn-info.active { + background-color: #24748c \9; +} + +.btn-inverse { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #363636; + *background-color: #222222; + background-image: -moz-linear-gradient(top, #444444, #222222); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222)); + background-image: -webkit-linear-gradient(top, #444444, #222222); + background-image: -o-linear-gradient(top, #444444, #222222); + background-image: linear-gradient(to bottom, #444444, #222222); + background-repeat: repeat-x; + border-color: #222222 #222222 #000000; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.btn-inverse:hover, +.btn-inverse:focus, +.btn-inverse:active, +.btn-inverse.active, +.btn-inverse.disabled, +.btn-inverse[disabled] { + color: #ffffff; + background-color: #222222; + *background-color: #151515; +} + +.btn-inverse:active, +.btn-inverse.active { + background-color: #080808 \9; +} + +button.btn, +input[type="submit"].btn { + *padding-top: 3px; + *padding-bottom: 3px; +} + +button.btn::-moz-focus-inner, +input[type="submit"].btn::-moz-focus-inner { + padding: 0; + border: 0; +} + +button.btn.btn-large, +input[type="submit"].btn.btn-large { + *padding-top: 7px; + *padding-bottom: 7px; +} + +button.btn.btn-small, +input[type="submit"].btn.btn-small { + *padding-top: 3px; + *padding-bottom: 3px; +} + +button.btn.btn-mini, +input[type="submit"].btn.btn-mini { + *padding-top: 1px; + *padding-bottom: 1px; +} + +.btn-link, +.btn-link:active, +.btn-link[disabled] { + background-color: transparent; + background-image: none; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} + +.btn-link { + color: #0088cc; + cursor: pointer; + border-color: transparent; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.btn-link:hover, +.btn-link:focus { + color: #005580; + text-decoration: underline; + background-color: transparent; +} + +.btn-link[disabled]:hover, +.btn-link[disabled]:focus { + color: #333333; + text-decoration: none; +} + +.btn-group { + position: relative; + display: inline-block; + *display: inline; + *margin-left: .3em; + font-size: 0; + white-space: nowrap; + vertical-align: middle; + *zoom: 1; +} + +.btn-group:first-child { + *margin-left: 0; +} + +.btn-group + .btn-group { + margin-left: 5px; +} + +.btn-toolbar { + margin-top: 10px; + margin-bottom: 10px; + font-size: 0; +} + +.btn-toolbar > .btn + .btn, +.btn-toolbar > .btn-group + .btn, +.btn-toolbar > .btn + .btn-group { + margin-left: 5px; +} + +.btn-group > .btn { + position: relative; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.btn-group > .btn + .btn { + margin-left: -1px; +} + +.btn-group > .btn, +.btn-group > .dropdown-menu, +.btn-group > .popover { + font-size: 14px; +} + +.btn-group > .btn-mini { + font-size: 10.5px; +} + +.btn-group > .btn-small { + font-size: 11.9px; +} + +.btn-group > .btn-large { + font-size: 17.5px; +} + +.btn-group > .btn:first-child { + margin-left: 0; + -webkit-border-bottom-left-radius: 4px; + border-bottom-left-radius: 4px; + -webkit-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-topleft: 4px; +} + +.btn-group > .btn:last-child, +.btn-group > .dropdown-toggle { + -webkit-border-top-right-radius: 4px; + border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + border-bottom-right-radius: 4px; + -moz-border-radius-topright: 4px; + -moz-border-radius-bottomright: 4px; +} + +.btn-group > .btn.large:first-child { + margin-left: 0; + -webkit-border-bottom-left-radius: 6px; + border-bottom-left-radius: 6px; + -webkit-border-top-left-radius: 6px; + border-top-left-radius: 6px; + -moz-border-radius-bottomleft: 6px; + -moz-border-radius-topleft: 6px; +} + +.btn-group > .btn.large:last-child, +.btn-group > .large.dropdown-toggle { + -webkit-border-top-right-radius: 6px; + border-top-right-radius: 6px; + -webkit-border-bottom-right-radius: 6px; + border-bottom-right-radius: 6px; + -moz-border-radius-topright: 6px; + -moz-border-radius-bottomright: 6px; +} + +.btn-group > .btn:hover, +.btn-group > .btn:focus, +.btn-group > .btn:active, +.btn-group > .btn.active { + z-index: 2; +} + +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + +.btn-group > .btn + .dropdown-toggle { + *padding-top: 5px; + padding-right: 8px; + *padding-bottom: 5px; + padding-left: 8px; + -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.btn-group > .btn-mini + .dropdown-toggle { + *padding-top: 2px; + padding-right: 5px; + *padding-bottom: 2px; + padding-left: 5px; +} + +.btn-group > .btn-small + .dropdown-toggle { + *padding-top: 5px; + *padding-bottom: 4px; +} + +.btn-group > .btn-large + .dropdown-toggle { + *padding-top: 7px; + padding-right: 12px; + *padding-bottom: 7px; + padding-left: 12px; +} + +.btn-group.open .dropdown-toggle { + background-image: none; + -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.btn-group.open .btn.dropdown-toggle { + background-color: #e6e6e6; +} + +.btn-group.open .btn-primary.dropdown-toggle { + background-color: #0044cc; +} + +.btn-group.open .btn-warning.dropdown-toggle { + background-color: #f89406; +} + +.btn-group.open .btn-danger.dropdown-toggle { + background-color: #bd362f; +} + +.btn-group.open .btn-success.dropdown-toggle { + background-color: #51a351; +} + +.btn-group.open .btn-info.dropdown-toggle { + background-color: #2f96b4; +} + +.btn-group.open .btn-inverse.dropdown-toggle { + background-color: #222222; +} + +.btn .caret { + margin-top: 8px; + margin-left: 0; +} + +.btn-large .caret { + margin-top: 6px; +} + +.btn-large .caret { + border-top-width: 5px; + border-right-width: 5px; + border-left-width: 5px; +} + +.btn-mini .caret, +.btn-small .caret { + margin-top: 8px; +} + +.dropup .btn-large .caret { + border-bottom-width: 5px; +} + +.btn-primary .caret, +.btn-warning .caret, +.btn-danger .caret, +.btn-info .caret, +.btn-success .caret, +.btn-inverse .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; +} + +.btn-group-vertical { + display: inline-block; + *display: inline; + /* IE7 inline-block hack */ + + *zoom: 1; +} + +.btn-group-vertical > .btn { + display: block; + float: none; + max-width: 100%; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.btn-group-vertical > .btn + .btn { + margin-top: -1px; + margin-left: 0; +} + +.btn-group-vertical > .btn:first-child { + -webkit-border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; +} + +.btn-group-vertical > .btn:last-child { + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; +} + +.btn-group-vertical > .btn-large:first-child { + -webkit-border-radius: 6px 6px 0 0; + -moz-border-radius: 6px 6px 0 0; + border-radius: 6px 6px 0 0; +} + +.btn-group-vertical > .btn-large:last-child { + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; +} + +.alert { + padding: 8px 35px 8px 14px; + margin-bottom: 20px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + background-color: #fcf8e3; + border: 1px solid #fbeed5; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.alert, +.alert h4 { + color: #c09853; +} + +.alert h4 { + margin: 0; +} + +.alert .close { + position: relative; + top: -2px; + right: -21px; + line-height: 20px; +} + +.alert-success { + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.alert-success h4 { + color: #468847; +} + +.alert-danger, +.alert-error { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7; +} + +.alert-danger h4, +.alert-error h4 { + color: #b94a48; +} + +.alert-info { + color: #3a87ad; + background-color: #d9edf7; + border-color: #bce8f1; +} + +.alert-info h4 { + color: #3a87ad; +} + +.alert-block { + padding-top: 14px; + padding-bottom: 14px; +} + +.alert-block > p, +.alert-block > ul { + margin-bottom: 0; +} + +.alert-block p + p { + margin-top: 5px; +} + +.nav { + margin-bottom: 20px; + margin-left: 0; + list-style: none; +} + +.nav > li > a { + display: block; +} + +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} + +.nav > li > a > img { + max-width: none; +} + +.nav > .pull-right { + float: right; +} + +.nav-header { + display: block; + padding: 3px 15px; + font-size: 11px; + font-weight: bold; + line-height: 20px; + color: #999999; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-transform: uppercase; +} + +.nav li + .nav-header { + margin-top: 9px; +} + +.nav-list { + padding-right: 15px; + padding-left: 15px; + margin-bottom: 0; +} + +.nav-list > li > a, +.nav-list .nav-header { + margin-right: -15px; + margin-left: -15px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); +} + +.nav-list > li > a { + padding: 3px 15px; +} + +.nav-list > .active > a, +.nav-list > .active > a:hover, +.nav-list > .active > a:focus { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); + background-color: #0088cc; +} + +.nav-list [class^="icon-"], +.nav-list [class*=" icon-"] { + margin-right: 2px; +} + +.nav-list .divider { + *width: 100%; + height: 1px; + margin: 9px 1px; + *margin: -5px 0 5px; + overflow: hidden; + background-color: #e5e5e5; + border-bottom: 1px solid #ffffff; +} + +.nav-tabs, +.nav-pills { + *zoom: 1; +} + +.nav-tabs:before, +.nav-pills:before, +.nav-tabs:after, +.nav-pills:after { + display: table; + line-height: 0; + content: ""; +} + +.nav-tabs:after, +.nav-pills:after { + clear: both; +} + +.nav-tabs > li, +.nav-pills > li { + float: left; +} + +.nav-tabs > li > a, +.nav-pills > li > a { + padding-right: 12px; + padding-left: 12px; + margin-right: 2px; + line-height: 14px; +} + +.nav-tabs { + border-bottom: 1px solid #ddd; +} + +.nav-tabs > li { + margin-bottom: -1px; +} + +.nav-tabs > li > a { + padding-top: 8px; + padding-bottom: 8px; + line-height: 20px; + border: 1px solid transparent; + -webkit-border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; +} + +.nav-tabs > li > a:hover, +.nav-tabs > li > a:focus { + border-color: #eeeeee #eeeeee #dddddd; +} + +.nav-tabs > .active > a, +.nav-tabs > .active > a:hover, +.nav-tabs > .active > a:focus { + color: #555555; + cursor: default; + background-color: #ffffff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} + +.nav-pills > li > a { + padding-top: 8px; + padding-bottom: 8px; + margin-top: 2px; + margin-bottom: 2px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} + +.nav-pills > .active > a, +.nav-pills > .active > a:hover, +.nav-pills > .active > a:focus { + color: #ffffff; + background-color: #0088cc; +} + +.nav-stacked > li { + float: none; +} + +.nav-stacked > li > a { + margin-right: 0; +} + +.nav-tabs.nav-stacked { + border-bottom: 0; +} + +.nav-tabs.nav-stacked > li > a { + border: 1px solid #ddd; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.nav-tabs.nav-stacked > li:first-child > a { + -webkit-border-top-right-radius: 4px; + border-top-right-radius: 4px; + -webkit-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -moz-border-radius-topright: 4px; + -moz-border-radius-topleft: 4px; +} + +.nav-tabs.nav-stacked > li:last-child > a { + -webkit-border-bottom-right-radius: 4px; + border-bottom-right-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + border-bottom-left-radius: 4px; + -moz-border-radius-bottomright: 4px; + -moz-border-radius-bottomleft: 4px; +} + +.nav-tabs.nav-stacked > li > a:hover, +.nav-tabs.nav-stacked > li > a:focus { + z-index: 2; + border-color: #ddd; +} + +.nav-pills.nav-stacked > li > a { + margin-bottom: 3px; +} + +.nav-pills.nav-stacked > li:last-child > a { + margin-bottom: 1px; +} + +.nav-tabs .dropdown-menu { + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; +} + +.nav-pills .dropdown-menu { + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +.nav .dropdown-toggle .caret { + margin-top: 6px; + border-top-color: #0088cc; + border-bottom-color: #0088cc; +} + +.nav .dropdown-toggle:hover .caret, +.nav .dropdown-toggle:focus .caret { + border-top-color: #005580; + border-bottom-color: #005580; +} + +/* move down carets for tabs */ + +.nav-tabs .dropdown-toggle .caret { + margin-top: 8px; +} + +.nav .active .dropdown-toggle .caret { + border-top-color: #fff; + border-bottom-color: #fff; +} + +.nav-tabs .active .dropdown-toggle .caret { + border-top-color: #555555; + border-bottom-color: #555555; +} + +.nav > .dropdown.active > a:hover, +.nav > .dropdown.active > a:focus { + cursor: pointer; +} + +.nav-tabs .open .dropdown-toggle, +.nav-pills .open .dropdown-toggle, +.nav > li.dropdown.open.active > a:hover, +.nav > li.dropdown.open.active > a:focus { + color: #ffffff; + background-color: #999999; + border-color: #999999; +} + +.nav li.dropdown.open .caret, +.nav li.dropdown.open.active .caret, +.nav li.dropdown.open a:hover .caret, +.nav li.dropdown.open a:focus .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; + opacity: 1; + filter: alpha(opacity=100); +} + +.tabs-stacked .open > a:hover, +.tabs-stacked .open > a:focus { + border-color: #999999; +} + +.tabbable { + *zoom: 1; +} + +.tabbable:before, +.tabbable:after { + display: table; + line-height: 0; + content: ""; +} + +.tabbable:after { + clear: both; +} + +.tab-content { + overflow: auto; +} + +.tabs-below > .nav-tabs, +.tabs-right > .nav-tabs, +.tabs-left > .nav-tabs { + border-bottom: 0; +} + +.tab-content > .tab-pane, +.pill-content > .pill-pane { + display: none; +} + +.tab-content > .active, +.pill-content > .active { + display: block; +} + +.tabs-below > .nav-tabs { + border-top: 1px solid #ddd; +} + +.tabs-below > .nav-tabs > li { + margin-top: -1px; + margin-bottom: 0; +} + +.tabs-below > .nav-tabs > li > a { + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; +} + +.tabs-below > .nav-tabs > li > a:hover, +.tabs-below > .nav-tabs > li > a:focus { + border-top-color: #ddd; + border-bottom-color: transparent; +} + +.tabs-below > .nav-tabs > .active > a, +.tabs-below > .nav-tabs > .active > a:hover, +.tabs-below > .nav-tabs > .active > a:focus { + border-color: transparent #ddd #ddd #ddd; +} + +.tabs-left > .nav-tabs > li, +.tabs-right > .nav-tabs > li { + float: none; +} + +.tabs-left > .nav-tabs > li > a, +.tabs-right > .nav-tabs > li > a { + min-width: 74px; + margin-right: 0; + margin-bottom: 3px; +} + +.tabs-left > .nav-tabs { + float: left; + margin-right: 19px; + border-right: 1px solid #ddd; +} + +.tabs-left > .nav-tabs > li > a { + margin-right: -1px; + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; +} + +.tabs-left > .nav-tabs > li > a:hover, +.tabs-left > .nav-tabs > li > a:focus { + border-color: #eeeeee #dddddd #eeeeee #eeeeee; +} + +.tabs-left > .nav-tabs .active > a, +.tabs-left > .nav-tabs .active > a:hover, +.tabs-left > .nav-tabs .active > a:focus { + border-color: #ddd transparent #ddd #ddd; + *border-right-color: #ffffff; +} + +.tabs-right > .nav-tabs { + float: right; + margin-left: 19px; + border-left: 1px solid #ddd; +} + +.tabs-right > .nav-tabs > li > a { + margin-left: -1px; + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.tabs-right > .nav-tabs > li > a:hover, +.tabs-right > .nav-tabs > li > a:focus { + border-color: #eeeeee #eeeeee #eeeeee #dddddd; +} + +.tabs-right > .nav-tabs .active > a, +.tabs-right > .nav-tabs .active > a:hover, +.tabs-right > .nav-tabs .active > a:focus { + border-color: #ddd #ddd #ddd transparent; + *border-left-color: #ffffff; +} + +.nav > .disabled > a { + color: #999999; +} + +.nav > .disabled > a:hover, +.nav > .disabled > a:focus { + text-decoration: none; + cursor: default; + background-color: transparent; +} + +.navbar { + *position: relative; + *z-index: 2; + margin-bottom: 20px; + overflow: visible; +} + +.navbar-inner { + min-height: 40px; + padding-right: 20px; + padding-left: 20px; + background-color: #fafafa; + background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2)); + background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2); + background-image: -o-linear-gradient(top, #ffffff, #f2f2f2); + background-image: linear-gradient(to bottom, #ffffff, #f2f2f2); + background-repeat: repeat-x; + border: 1px solid #d4d4d4; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0); + *zoom: 1; + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); +} + +.navbar-inner:before, +.navbar-inner:after { + display: table; + line-height: 0; + content: ""; +} + +.navbar-inner:after { + clear: both; +} + +.navbar .container { + width: auto; +} + +.nav-collapse.collapse { + height: auto; + overflow: visible; +} + +.navbar .brand { + display: block; + float: left; + padding: 10px 20px 10px; + margin-left: -20px; + font-size: 20px; + font-weight: 200; + color: #777777; + text-shadow: 0 1px 0 #ffffff; +} + +.navbar .brand:hover, +.navbar .brand:focus { + text-decoration: none; +} + +.navbar-text { + margin-bottom: 0; + line-height: 40px; + color: #777777; +} + +.navbar-link { + color: #777777; +} + +.navbar-link:hover, +.navbar-link:focus { + color: #333333; +} + +.navbar .divider-vertical { + height: 40px; + margin: 0 9px; + border-right: 1px solid #ffffff; + border-left: 1px solid #f2f2f2; +} + +.navbar .btn, +.navbar .btn-group { + margin-top: 5px; +} + +.navbar .btn-group .btn, +.navbar .input-prepend .btn, +.navbar .input-append .btn, +.navbar .input-prepend .btn-group, +.navbar .input-append .btn-group { + margin-top: 0; +} + +.navbar-form { + margin-bottom: 0; + *zoom: 1; +} + +.navbar-form:before, +.navbar-form:after { + display: table; + line-height: 0; + content: ""; +} + +.navbar-form:after { + clear: both; +} + +.navbar-form input, +.navbar-form select, +.navbar-form .radio, +.navbar-form .checkbox { + margin-top: 5px; +} + +.navbar-form input, +.navbar-form select, +.navbar-form .btn { + display: inline-block; + margin-bottom: 0; +} + +.navbar-form input[type="image"], +.navbar-form input[type="checkbox"], +.navbar-form input[type="radio"] { + margin-top: 3px; +} + +.navbar-form .input-append, +.navbar-form .input-prepend { + margin-top: 5px; + white-space: nowrap; +} + +.navbar-form .input-append input, +.navbar-form .input-prepend input { + margin-top: 0; +} + +.navbar-search { + position: relative; + float: left; + margin-top: 5px; + margin-bottom: 0; +} + +.navbar-search .search-query { + padding: 4px 14px; + margin-bottom: 0; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 13px; + font-weight: normal; + line-height: 1; + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + border-radius: 15px; +} + +.navbar-static-top { + position: static; + margin-bottom: 0; +} + +.navbar-static-top .navbar-inner { + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; + margin-bottom: 0; +} + +.navbar-fixed-top .navbar-inner, +.navbar-static-top .navbar-inner { + border-width: 0 0 1px; +} + +.navbar-fixed-bottom .navbar-inner { + border-width: 1px 0 0; +} + +.navbar-fixed-top .navbar-inner, +.navbar-fixed-bottom .navbar-inner { + padding-right: 0; + padding-left: 0; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.navbar-static-top .container, +.navbar-fixed-top .container, +.navbar-fixed-bottom .container { + width: 940px; +} + +.navbar-fixed-top { + top: 0; +} + +.navbar-fixed-top .navbar-inner, +.navbar-static-top .navbar-inner { + -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); +} + +.navbar-fixed-bottom { + bottom: 0; +} + +.navbar-fixed-bottom .navbar-inner { + -webkit-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); + box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); +} + +.navbar .nav { + position: relative; + left: 0; + display: block; + float: left; + margin: 0 10px 0 0; +} + +.navbar .nav.pull-right { + float: right; + margin-right: 0; +} + +.navbar .nav > li { + float: left; +} + +.navbar .nav > li > a { + float: none; + padding: 10px 15px 10px; + color: #777777; + text-decoration: none; + text-shadow: 0 1px 0 #ffffff; +} + +.navbar .nav .dropdown-toggle .caret { + margin-top: 8px; +} + +.navbar .nav > li > a:focus, +.navbar .nav > li > a:hover { + color: #333333; + text-decoration: none; + background-color: transparent; +} + +.navbar .nav > .active > a, +.navbar .nav > .active > a:hover, +.navbar .nav > .active > a:focus { + color: #555555; + text-decoration: none; + background-color: #e5e5e5; + -webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); + -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); +} + +.navbar .btn-navbar { + display: none; + float: right; + padding: 7px 10px; + margin-right: 5px; + margin-left: 5px; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #ededed; + *background-color: #e5e5e5; + background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5)); + background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5); + background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5); + background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5); + background-repeat: repeat-x; + border-color: #e5e5e5 #e5e5e5 #bfbfbf; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); +} + +.navbar .btn-navbar:hover, +.navbar .btn-navbar:focus, +.navbar .btn-navbar:active, +.navbar .btn-navbar.active, +.navbar .btn-navbar.disabled, +.navbar .btn-navbar[disabled] { + color: #ffffff; + background-color: #e5e5e5; + *background-color: #d9d9d9; +} + +.navbar .btn-navbar:active, +.navbar .btn-navbar.active { + background-color: #cccccc \9; +} + +.navbar .btn-navbar .icon-bar { + display: block; + width: 18px; + height: 2px; + background-color: #f5f5f5; + -webkit-border-radius: 1px; + -moz-border-radius: 1px; + border-radius: 1px; + -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); + -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); +} + +.btn-navbar .icon-bar + .icon-bar { + margin-top: 3px; +} + +.navbar .nav > li > .dropdown-menu:before { + position: absolute; + top: -7px; + left: 9px; + display: inline-block; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-left: 7px solid transparent; + border-bottom-color: rgba(0, 0, 0, 0.2); + content: ''; +} + +.navbar .nav > li > .dropdown-menu:after { + position: absolute; + top: -6px; + left: 10px; + display: inline-block; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + border-left: 6px solid transparent; + content: ''; +} + +.navbar-fixed-bottom .nav > li > .dropdown-menu:before { + top: auto; + bottom: -7px; + border-top: 7px solid #ccc; + border-bottom: 0; + border-top-color: rgba(0, 0, 0, 0.2); +} + +.navbar-fixed-bottom .nav > li > .dropdown-menu:after { + top: auto; + bottom: -6px; + border-top: 6px solid #ffffff; + border-bottom: 0; +} + +.navbar .nav li.dropdown > a:hover .caret, +.navbar .nav li.dropdown > a:focus .caret { + border-top-color: #333333; + border-bottom-color: #333333; +} + +.navbar .nav li.dropdown.open > .dropdown-toggle, +.navbar .nav li.dropdown.active > .dropdown-toggle, +.navbar .nav li.dropdown.open.active > .dropdown-toggle { + color: #555555; + background-color: #e5e5e5; +} + +.navbar .nav li.dropdown > .dropdown-toggle .caret { + border-top-color: #777777; + border-bottom-color: #777777; +} + +.navbar .nav li.dropdown.open > .dropdown-toggle .caret, +.navbar .nav li.dropdown.active > .dropdown-toggle .caret, +.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret { + border-top-color: #555555; + border-bottom-color: #555555; +} + +.navbar .pull-right > li > .dropdown-menu, +.navbar .nav > li > .dropdown-menu.pull-right { + right: 0; + left: auto; +} + +.navbar .pull-right > li > .dropdown-menu:before, +.navbar .nav > li > .dropdown-menu.pull-right:before { + right: 12px; + left: auto; +} + +.navbar .pull-right > li > .dropdown-menu:after, +.navbar .nav > li > .dropdown-menu.pull-right:after { + right: 13px; + left: auto; +} + +.navbar .pull-right > li > .dropdown-menu .dropdown-menu, +.navbar .nav > li > .dropdown-menu.pull-right .dropdown-menu { + right: 100%; + left: auto; + margin-right: -1px; + margin-left: 0; + -webkit-border-radius: 6px 0 6px 6px; + -moz-border-radius: 6px 0 6px 6px; + border-radius: 6px 0 6px 6px; +} + +.navbar-inverse .navbar-inner { + background-color: #1b1b1b; + background-image: -moz-linear-gradient(top, #222222, #111111); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111)); + background-image: -webkit-linear-gradient(top, #222222, #111111); + background-image: -o-linear-gradient(top, #222222, #111111); + background-image: linear-gradient(to bottom, #222222, #111111); + background-repeat: repeat-x; + border-color: #252525; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0); +} + +.navbar-inverse .brand, +.navbar-inverse .nav > li > a { + color: #999999; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} + +.navbar-inverse .brand:hover, +.navbar-inverse .nav > li > a:hover, +.navbar-inverse .brand:focus, +.navbar-inverse .nav > li > a:focus { + color: #ffffff; +} + +.navbar-inverse .brand { + color: #999999; +} + +.navbar-inverse .navbar-text { + color: #999999; +} + +.navbar-inverse .nav > li > a:focus, +.navbar-inverse .nav > li > a:hover { + color: #ffffff; + background-color: transparent; +} + +.navbar-inverse .nav .active > a, +.navbar-inverse .nav .active > a:hover, +.navbar-inverse .nav .active > a:focus { + color: #ffffff; + background-color: #111111; +} + +.navbar-inverse .navbar-link { + color: #999999; +} + +.navbar-inverse .navbar-link:hover, +.navbar-inverse .navbar-link:focus { + color: #ffffff; +} + +.navbar-inverse .divider-vertical { + border-right-color: #222222; + border-left-color: #111111; +} + +.navbar-inverse .nav li.dropdown.open > .dropdown-toggle, +.navbar-inverse .nav li.dropdown.active > .dropdown-toggle, +.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle { + color: #ffffff; + background-color: #111111; +} + +.navbar-inverse .nav li.dropdown > a:hover .caret, +.navbar-inverse .nav li.dropdown > a:focus .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; +} + +.navbar-inverse .nav li.dropdown > .dropdown-toggle .caret { + border-top-color: #999999; + border-bottom-color: #999999; +} + +.navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret, +.navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret, +.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; +} + +.navbar-inverse .navbar-search .search-query { + color: #ffffff; + background-color: #515151; + border-color: #111111; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); + -webkit-transition: none; + -moz-transition: none; + -o-transition: none; + transition: none; +} + +.navbar-inverse .navbar-search .search-query:-moz-placeholder { + color: #cccccc; +} + +.navbar-inverse .navbar-search .search-query:-ms-input-placeholder { + color: #cccccc; +} + +.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder { + color: #cccccc; +} + +.navbar-inverse .navbar-search .search-query:focus, +.navbar-inverse .navbar-search .search-query.focused { + padding: 5px 15px; + color: #333333; + text-shadow: 0 1px 0 #ffffff; + background-color: #ffffff; + border: 0; + outline: 0; + -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); + -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); + box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); +} + +.navbar-inverse .btn-navbar { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #0e0e0e; + *background-color: #040404; + background-image: -moz-linear-gradient(top, #151515, #040404); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404)); + background-image: -webkit-linear-gradient(top, #151515, #040404); + background-image: -o-linear-gradient(top, #151515, #040404); + background-image: linear-gradient(to bottom, #151515, #040404); + background-repeat: repeat-x; + border-color: #040404 #040404 #000000; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.navbar-inverse .btn-navbar:hover, +.navbar-inverse .btn-navbar:focus, +.navbar-inverse .btn-navbar:active, +.navbar-inverse .btn-navbar.active, +.navbar-inverse .btn-navbar.disabled, +.navbar-inverse .btn-navbar[disabled] { + color: #ffffff; + background-color: #040404; + *background-color: #000000; +} + +.navbar-inverse .btn-navbar:active, +.navbar-inverse .btn-navbar.active { + background-color: #000000 \9; +} + +.breadcrumb { + padding: 8px 15px; + margin: 0 0 20px; + list-style: none; + background-color: #f5f5f5; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.breadcrumb > li { + display: inline-block; + *display: inline; + text-shadow: 0 1px 0 #ffffff; + *zoom: 1; +} + +.breadcrumb > li > .divider { + padding: 0 5px; + color: #ccc; +} + +.breadcrumb > .active { + color: #999999; +} + +.pagination { + margin: 20px 0; +} + +.pagination ul { + display: inline-block; + *display: inline; + margin-bottom: 0; + margin-left: 0; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + *zoom: 1; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.pagination ul > li { + display: inline; +} + +.pagination ul > li > a, +.pagination ul > li > span { + float: left; + padding: 4px 12px; + line-height: 20px; + text-decoration: none; + background-color: #ffffff; + border: 1px solid #dddddd; + border-left-width: 0; +} + +.pagination ul > li > a:hover, +.pagination ul > li > a:focus, +.pagination ul > .active > a, +.pagination ul > .active > span { + background-color: #f5f5f5; +} + +.pagination ul > .active > a, +.pagination ul > .active > span { + color: #999999; + cursor: default; +} + +.pagination ul > .disabled > span, +.pagination ul > .disabled > a, +.pagination ul > .disabled > a:hover, +.pagination ul > .disabled > a:focus { + color: #999999; + cursor: default; + background-color: transparent; +} + +.pagination ul > li:first-child > a, +.pagination ul > li:first-child > span { + border-left-width: 1px; + -webkit-border-bottom-left-radius: 4px; + border-bottom-left-radius: 4px; + -webkit-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-topleft: 4px; +} + +.pagination ul > li:last-child > a, +.pagination ul > li:last-child > span { + -webkit-border-top-right-radius: 4px; + border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + border-bottom-right-radius: 4px; + -moz-border-radius-topright: 4px; + -moz-border-radius-bottomright: 4px; +} + +.pagination-centered { + text-align: center; +} + +.pagination-right { + text-align: right; +} + +.pagination-large ul > li > a, +.pagination-large ul > li > span { + padding: 11px 19px; + font-size: 17.5px; +} + +.pagination-large ul > li:first-child > a, +.pagination-large ul > li:first-child > span { + -webkit-border-bottom-left-radius: 6px; + border-bottom-left-radius: 6px; + -webkit-border-top-left-radius: 6px; + border-top-left-radius: 6px; + -moz-border-radius-bottomleft: 6px; + -moz-border-radius-topleft: 6px; +} + +.pagination-large ul > li:last-child > a, +.pagination-large ul > li:last-child > span { + -webkit-border-top-right-radius: 6px; + border-top-right-radius: 6px; + -webkit-border-bottom-right-radius: 6px; + border-bottom-right-radius: 6px; + -moz-border-radius-topright: 6px; + -moz-border-radius-bottomright: 6px; +} + +.pagination-mini ul > li:first-child > a, +.pagination-small ul > li:first-child > a, +.pagination-mini ul > li:first-child > span, +.pagination-small ul > li:first-child > span { + -webkit-border-bottom-left-radius: 3px; + border-bottom-left-radius: 3px; + -webkit-border-top-left-radius: 3px; + border-top-left-radius: 3px; + -moz-border-radius-bottomleft: 3px; + -moz-border-radius-topleft: 3px; +} + +.pagination-mini ul > li:last-child > a, +.pagination-small ul > li:last-child > a, +.pagination-mini ul > li:last-child > span, +.pagination-small ul > li:last-child > span { + -webkit-border-top-right-radius: 3px; + border-top-right-radius: 3px; + -webkit-border-bottom-right-radius: 3px; + border-bottom-right-radius: 3px; + -moz-border-radius-topright: 3px; + -moz-border-radius-bottomright: 3px; +} + +.pagination-small ul > li > a, +.pagination-small ul > li > span { + padding: 2px 10px; + font-size: 11.9px; +} + +.pagination-mini ul > li > a, +.pagination-mini ul > li > span { + padding: 0 6px; + font-size: 10.5px; +} + +.pager { + margin: 20px 0; + text-align: center; + list-style: none; + *zoom: 1; +} + +.pager:before, +.pager:after { + display: table; + line-height: 0; + content: ""; +} + +.pager:after { + clear: both; +} + +.pager li { + display: inline; +} + +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + border-radius: 15px; +} + +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #f5f5f5; +} + +.pager .next > a, +.pager .next > span { + float: right; +} + +.pager .previous > a, +.pager .previous > span { + float: left; +} + +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #999999; + cursor: default; + background-color: #fff; +} + +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000000; +} + +.modal-backdrop.fade { + opacity: 0; +} + +.modal-backdrop, +.modal-backdrop.fade.in { + opacity: 0.8; + filter: alpha(opacity=80); +} + +.modal { + position: fixed; + top: 10%; + left: 50%; + z-index: 1050; + width: 560px; + margin-left: -280px; + background-color: #ffffff; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, 0.3); + *border: 1px solid #999; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + outline: none; + -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + -webkit-background-clip: padding-box; + -moz-background-clip: padding-box; + background-clip: padding-box; +} + +.modal.fade { + top: -25%; + -webkit-transition: opacity 0.3s linear, top 0.3s ease-out; + -moz-transition: opacity 0.3s linear, top 0.3s ease-out; + -o-transition: opacity 0.3s linear, top 0.3s ease-out; + transition: opacity 0.3s linear, top 0.3s ease-out; +} + +.modal.fade.in { + top: 10%; +} + +.modal-header { + padding: 9px 15px; + border-bottom: 1px solid #eee; +} + +.modal-header .close { + margin-top: 2px; +} + +.modal-header h3 { + margin: 0; + line-height: 30px; +} + +.modal-body { + position: relative; + max-height: 400px; + padding: 15px; + overflow-y: auto; +} + +.modal-form { + margin-bottom: 0; +} + +.modal-footer { + padding: 14px 15px 15px; + margin-bottom: 0; + text-align: right; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; + *zoom: 1; + -webkit-box-shadow: inset 0 1px 0 #ffffff; + -moz-box-shadow: inset 0 1px 0 #ffffff; + box-shadow: inset 0 1px 0 #ffffff; +} + +.modal-footer:before, +.modal-footer:after { + display: table; + line-height: 0; + content: ""; +} + +.modal-footer:after { + clear: both; +} + +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} + +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} + +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} + +.tooltip { + position: absolute; + z-index: 1030; + display: block; + font-size: 11px; + line-height: 1.4; + opacity: 0; + filter: alpha(opacity=0); + visibility: visible; +} + +.tooltip.in { + opacity: 0.8; + filter: alpha(opacity=80); +} + +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} + +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} + +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} + +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} + +.tooltip-inner { + max-width: 200px; + padding: 8px; + color: #ffffff; + text-align: center; + text-decoration: none; + background-color: #000000; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-top-color: #000000; + border-width: 5px 5px 0; +} + +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-right-color: #000000; + border-width: 5px 5px 5px 0; +} + +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-left-color: #000000; + border-width: 5px 0 5px 5px; +} + +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-bottom-color: #000000; + border-width: 0 5px 5px; +} + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1010; + display: none; + max-width: 276px; + padding: 1px; + text-align: left; + white-space: normal; + background-color: #ffffff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; +} + +.popover.top { + margin-top: -10px; +} + +.popover.right { + margin-left: 10px; +} + +.popover.bottom { + margin-top: 10px; +} + +.popover.left { + margin-left: -10px; +} + +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + -webkit-border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + border-radius: 5px 5px 0 0; +} + +.popover-title:empty { + display: none; +} + +.popover-content { + padding: 9px 14px; +} + +.popover .arrow, +.popover .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.popover .arrow { + border-width: 11px; +} + +.popover .arrow:after { + border-width: 10px; + content: ""; +} + +.popover.top .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, 0.25); + border-bottom-width: 0; +} + +.popover.top .arrow:after { + bottom: 1px; + margin-left: -10px; + border-top-color: #ffffff; + border-bottom-width: 0; +} + +.popover.right .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999; + border-right-color: rgba(0, 0, 0, 0.25); + border-left-width: 0; +} + +.popover.right .arrow:after { + bottom: -10px; + left: 1px; + border-right-color: #ffffff; + border-left-width: 0; +} + +.popover.bottom .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, 0.25); + border-top-width: 0; +} + +.popover.bottom .arrow:after { + top: 1px; + margin-left: -10px; + border-bottom-color: #ffffff; + border-top-width: 0; +} + +.popover.left .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-left-color: #999; + border-left-color: rgba(0, 0, 0, 0.25); + border-right-width: 0; +} + +.popover.left .arrow:after { + right: 1px; + bottom: -10px; + border-left-color: #ffffff; + border-right-width: 0; +} + +.thumbnails { + margin-left: -20px; + list-style: none; + *zoom: 1; +} + +.thumbnails:before, +.thumbnails:after { + display: table; + line-height: 0; + content: ""; +} + +.thumbnails:after { + clear: both; +} + +.row-fluid .thumbnails { + margin-left: 0; +} + +.thumbnails > li { + float: left; + margin-bottom: 20px; + margin-left: 20px; +} + +.thumbnail { + display: block; + padding: 4px; + line-height: 20px; + border: 1px solid #ddd; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); + -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); + -webkit-transition: all 0.2s ease-in-out; + -moz-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} + +a.thumbnail:hover, +a.thumbnail:focus { + border-color: #0088cc; + -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); + -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); + box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); +} + +.thumbnail > img { + display: block; + max-width: 100%; + margin-right: auto; + margin-left: auto; +} + +.thumbnail .caption { + padding: 9px; + color: #555555; +} + +.media, +.media-body { + overflow: hidden; + *overflow: visible; + zoom: 1; +} + +.media, +.media .media { + margin-top: 15px; +} + +.media:first-child { + margin-top: 0; +} + +.media-object { + display: block; +} + +.media-heading { + margin: 0 0 5px; +} + +.media > .pull-left { + margin-right: 10px; +} + +.media > .pull-right { + margin-left: 10px; +} + +.media-list { + margin-left: 0; + list-style: none; +} + +.label, +.badge { + display: inline-block; + padding: 2px 4px; + font-size: 11.844px; + font-weight: bold; + line-height: 14px; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + white-space: nowrap; + vertical-align: baseline; + background-color: #999999; +} + +.label { + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +.badge { + padding-right: 9px; + padding-left: 9px; + -webkit-border-radius: 9px; + -moz-border-radius: 9px; + border-radius: 9px; +} + +.label:empty, +.badge:empty { + display: none; +} + +a.label:hover, +a.label:focus, +a.badge:hover, +a.badge:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} + +.label-important, +.badge-important { + background-color: #b94a48; +} + +.label-important[href], +.badge-important[href] { + background-color: #953b39; +} + +.label-warning, +.badge-warning { + background-color: #f89406; +} + +.label-warning[href], +.badge-warning[href] { + background-color: #c67605; +} + +.label-success, +.badge-success { + background-color: #468847; +} + +.label-success[href], +.badge-success[href] { + background-color: #356635; +} + +.label-info, +.badge-info { + background-color: #3a87ad; +} + +.label-info[href], +.badge-info[href] { + background-color: #2d6987; +} + +.label-inverse, +.badge-inverse { + background-color: #333333; +} + +.label-inverse[href], +.badge-inverse[href] { + background-color: #1a1a1a; +} + +.btn .label, +.btn .badge { + position: relative; + top: -1px; +} + +.btn-mini .label, +.btn-mini .badge { + top: 0; +} + +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +@-moz-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +@-ms-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +@-o-keyframes progress-bar-stripes { + from { + background-position: 0 0; + } + to { + background-position: 40px 0; + } +} + +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f7f7f7; + background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9)); + background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9); + background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9); + background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9); + background-repeat: repeat-x; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0); + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.progress .bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + color: #ffffff; + text-align: center; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #0e90d2; + background-image: -moz-linear-gradient(top, #149bdf, #0480be); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be)); + background-image: -webkit-linear-gradient(top, #149bdf, #0480be); + background-image: -o-linear-gradient(top, #149bdf, #0480be); + background-image: linear-gradient(to bottom, #149bdf, #0480be); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0); + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-transition: width 0.6s ease; + -moz-transition: width 0.6s ease; + -o-transition: width 0.6s ease; + transition: width 0.6s ease; +} + +.progress .bar + .bar { + -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); +} + +.progress-striped .bar { + background-color: #149bdf; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + -webkit-background-size: 40px 40px; + -moz-background-size: 40px 40px; + -o-background-size: 40px 40px; + background-size: 40px 40px; +} + +.progress.active .bar { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -moz-animation: progress-bar-stripes 2s linear infinite; + -ms-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} + +.progress-danger .bar, +.progress .bar-danger { + background-color: #dd514c; + background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35)); + background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35); + background-image: -o-linear-gradient(top, #ee5f5b, #c43c35); + background-image: linear-gradient(to bottom, #ee5f5b, #c43c35); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0); +} + +.progress-danger.progress-striped .bar, +.progress-striped .bar-danger { + background-color: #ee5f5b; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-success .bar, +.progress .bar-success { + background-color: #5eb95e; + background-image: -moz-linear-gradient(top, #62c462, #57a957); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957)); + background-image: -webkit-linear-gradient(top, #62c462, #57a957); + background-image: -o-linear-gradient(top, #62c462, #57a957); + background-image: linear-gradient(to bottom, #62c462, #57a957); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0); +} + +.progress-success.progress-striped .bar, +.progress-striped .bar-success { + background-color: #62c462; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-info .bar, +.progress .bar-info { + background-color: #4bb1cf; + background-image: -moz-linear-gradient(top, #5bc0de, #339bb9); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9)); + background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9); + background-image: -o-linear-gradient(top, #5bc0de, #339bb9); + background-image: linear-gradient(to bottom, #5bc0de, #339bb9); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0); +} + +.progress-info.progress-striped .bar, +.progress-striped .bar-info { + background-color: #5bc0de; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-warning .bar, +.progress .bar-warning { + background-color: #faa732; + background-image: -moz-linear-gradient(top, #fbb450, #f89406); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); + background-image: -webkit-linear-gradient(top, #fbb450, #f89406); + background-image: -o-linear-gradient(top, #fbb450, #f89406); + background-image: linear-gradient(to bottom, #fbb450, #f89406); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0); +} + +.progress-warning.progress-striped .bar, +.progress-striped .bar-warning { + background-color: #fbb450; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.accordion { + margin-bottom: 20px; +} + +.accordion-group { + margin-bottom: 2px; + border: 1px solid #e5e5e5; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.accordion-heading { + border-bottom: 0; +} + +.accordion-heading .accordion-toggle { + display: block; + padding: 8px 15px; +} + +.accordion-toggle { + cursor: pointer; +} + +.accordion-inner { + padding: 9px 15px; + border-top: 1px solid #e5e5e5; +} + +.carousel { + position: relative; + margin-bottom: 20px; + line-height: 1; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} + +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: 0.6s ease-in-out left; + -moz-transition: 0.6s ease-in-out left; + -o-transition: 0.6s ease-in-out left; + transition: 0.6s ease-in-out left; +} + +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + line-height: 1; +} + +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} + +.carousel-inner > .active { + left: 0; +} + +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} + +.carousel-inner > .next { + left: 100%; +} + +.carousel-inner > .prev { + left: -100%; +} + +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} + +.carousel-inner > .active.left { + left: -100%; +} + +.carousel-inner > .active.right { + left: 100%; +} + +.carousel-control { + position: absolute; + top: 40%; + left: 15px; + width: 40px; + height: 40px; + margin-top: -20px; + font-size: 60px; + font-weight: 100; + line-height: 30px; + color: #ffffff; + text-align: center; + background: #222222; + border: 3px solid #ffffff; + -webkit-border-radius: 23px; + -moz-border-radius: 23px; + border-radius: 23px; + opacity: 0.5; + filter: alpha(opacity=50); +} + +.carousel-control.right { + right: 15px; + left: auto; +} + +.carousel-control:hover, +.carousel-control:focus { + color: #ffffff; + text-decoration: none; + opacity: 0.9; + filter: alpha(opacity=90); +} + +.carousel-indicators { + position: absolute; + top: 15px; + right: 15px; + z-index: 5; + margin: 0; + list-style: none; +} + +.carousel-indicators li { + display: block; + float: left; + width: 10px; + height: 10px; + margin-left: 5px; + text-indent: -999px; + background-color: #ccc; + background-color: rgba(255, 255, 255, 0.25); + border-radius: 5px; +} + +.carousel-indicators .active { + background-color: #fff; +} + +.carousel-caption { + position: absolute; + right: 0; + bottom: 0; + left: 0; + padding: 15px; + background: #333333; + background: rgba(0, 0, 0, 0.75); +} + +.carousel-caption h4, +.carousel-caption p { + line-height: 20px; + color: #ffffff; +} + +.carousel-caption h4 { + margin: 0 0 5px; +} + +.carousel-caption p { + margin-bottom: 0; +} + +.hero-unit { + padding: 60px; + margin-bottom: 30px; + font-size: 18px; + font-weight: 200; + line-height: 30px; + color: inherit; + background-color: #eeeeee; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +.hero-unit h1 { + margin-bottom: 0; + font-size: 60px; + line-height: 1; + letter-spacing: -1px; + color: inherit; +} + +.hero-unit li { + line-height: 30px; +} + +.pull-right { + float: right; +} + +.pull-left { + float: left; +} + +.hide { + display: none; +} + +.show { + display: block; +} + +.invisible { + visibility: hidden; +} + +.affix { + position: fixed; +} + +/*! + * Bootstrap Responsive v2.3.2 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ + +.clearfix { + *zoom: 1; +} + +.clearfix:before, +.clearfix:after { + display: table; + line-height: 0; + content: ""; +} + +.clearfix:after { + clear: both; +} + +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.input-block-level { + display: block; + width: 100%; + min-height: 30px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +@-ms-viewport { + width: device-width; +} + +.hidden { + display: none; + visibility: hidden; +} + +.visible-phone { + display: none !important; +} + +.visible-tablet { + display: none !important; +} + +.hidden-desktop { + display: none !important; +} + +.visible-desktop { + display: inherit !important; +} + +@media (min-width: 768px) and (max-width: 979px) { + .hidden-desktop { + display: inherit !important; + } + .visible-desktop { + display: none !important ; + } + .visible-tablet { + display: inherit !important; + } + .hidden-tablet { + display: none !important; + } +} + +@media (max-width: 767px) { + .hidden-desktop { + display: inherit !important; + } + .visible-desktop { + display: none !important; + } + .visible-phone { + display: inherit !important; + } + .hidden-phone { + display: none !important; + } +} + +.visible-print { + display: none !important; +} + +@media print { + .visible-print { + display: inherit !important; + } + .hidden-print { + display: none !important; + } +} + +@media (min-width: 1200px) { + .row { + margin-left: -30px; + *zoom: 1; + } + .row:before, + .row:after { + display: table; + line-height: 0; + content: ""; + } + .row:after { + clear: both; + } + [class*="span"] { + float: left; + min-height: 1px; + margin-left: 30px; + } + .container, + .navbar-static-top .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { + width: 1170px; + } + .span12 { + width: 1170px; + } + .span11 { + width: 1070px; + } + .span10 { + width: 970px; + } + .span9 { + width: 870px; + } + .span8 { + width: 770px; + } + .span7 { + width: 670px; + } + .span6 { + width: 570px; + } + .span5 { + width: 470px; + } + .span4 { + width: 370px; + } + .span3 { + width: 270px; + } + .span2 { + width: 170px; + } + .span1 { + width: 70px; + } + .offset12 { + margin-left: 1230px; + } + .offset11 { + margin-left: 1130px; + } + .offset10 { + margin-left: 1030px; + } + .offset9 { + margin-left: 930px; + } + .offset8 { + margin-left: 830px; + } + .offset7 { + margin-left: 730px; + } + .offset6 { + margin-left: 630px; + } + .offset5 { + margin-left: 530px; + } + .offset4 { + margin-left: 430px; + } + .offset3 { + margin-left: 330px; + } + .offset2 { + margin-left: 230px; + } + .offset1 { + margin-left: 130px; + } + .row-fluid { + width: 100%; + *zoom: 1; + } + .row-fluid:before, + .row-fluid:after { + display: table; + line-height: 0; + content: ""; + } + .row-fluid:after { + clear: both; + } + .row-fluid [class*="span"] { + display: block; + float: left; + width: 100%; + min-height: 30px; + margin-left: 2.564102564102564%; + *margin-left: 2.5109110747408616%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .row-fluid [class*="span"]:first-child { + margin-left: 0; + } + .row-fluid .controls-row [class*="span"] + [class*="span"] { + margin-left: 2.564102564102564%; + } + .row-fluid .span12 { + width: 100%; + *width: 99.94680851063829%; + } + .row-fluid .span11 { + width: 91.45299145299145%; + *width: 91.39979996362975%; + } + .row-fluid .span10 { + width: 82.90598290598291%; + *width: 82.8527914166212%; + } + .row-fluid .span9 { + width: 74.35897435897436%; + *width: 74.30578286961266%; + } + .row-fluid .span8 { + width: 65.81196581196582%; + *width: 65.75877432260411%; + } + .row-fluid .span7 { + width: 57.26495726495726%; + *width: 57.21176577559556%; + } + .row-fluid .span6 { + width: 48.717948717948715%; + *width: 48.664757228587014%; + } + .row-fluid .span5 { + width: 40.17094017094017%; + *width: 40.11774868157847%; + } + .row-fluid .span4 { + width: 31.623931623931625%; + *width: 31.570740134569924%; + } + .row-fluid .span3 { + width: 23.076923076923077%; + *width: 23.023731587561375%; + } + .row-fluid .span2 { + width: 14.52991452991453%; + *width: 14.476723040552828%; + } + .row-fluid .span1 { + width: 5.982905982905983%; + *width: 5.929714493544281%; + } + .row-fluid .offset12 { + margin-left: 105.12820512820512%; + *margin-left: 105.02182214948171%; + } + .row-fluid .offset12:first-child { + margin-left: 102.56410256410257%; + *margin-left: 102.45771958537915%; + } + .row-fluid .offset11 { + margin-left: 96.58119658119658%; + *margin-left: 96.47481360247316%; + } + .row-fluid .offset11:first-child { + margin-left: 94.01709401709402%; + *margin-left: 93.91071103837061%; + } + .row-fluid .offset10 { + margin-left: 88.03418803418803%; + *margin-left: 87.92780505546462%; + } + .row-fluid .offset10:first-child { + margin-left: 85.47008547008548%; + *margin-left: 85.36370249136206%; + } + .row-fluid .offset9 { + margin-left: 79.48717948717949%; + *margin-left: 79.38079650845607%; + } + .row-fluid .offset9:first-child { + margin-left: 76.92307692307693%; + *margin-left: 76.81669394435352%; + } + .row-fluid .offset8 { + margin-left: 70.94017094017094%; + *margin-left: 70.83378796144753%; + } + .row-fluid .offset8:first-child { + margin-left: 68.37606837606839%; + *margin-left: 68.26968539734497%; + } + .row-fluid .offset7 { + margin-left: 62.393162393162385%; + *margin-left: 62.28677941443899%; + } + .row-fluid .offset7:first-child { + margin-left: 59.82905982905982%; + *margin-left: 59.72267685033642%; + } + .row-fluid .offset6 { + margin-left: 53.84615384615384%; + *margin-left: 53.739770867430444%; + } + .row-fluid .offset6:first-child { + margin-left: 51.28205128205128%; + *margin-left: 51.175668303327875%; + } + .row-fluid .offset5 { + margin-left: 45.299145299145295%; + *margin-left: 45.1927623204219%; + } + .row-fluid .offset5:first-child { + margin-left: 42.73504273504273%; + *margin-left: 42.62865975631933%; + } + .row-fluid .offset4 { + margin-left: 36.75213675213675%; + *margin-left: 36.645753773413354%; + } + .row-fluid .offset4:first-child { + margin-left: 34.18803418803419%; + *margin-left: 34.081651209310785%; + } + .row-fluid .offset3 { + margin-left: 28.205128205128204%; + *margin-left: 28.0987452264048%; + } + .row-fluid .offset3:first-child { + margin-left: 25.641025641025642%; + *margin-left: 25.53464266230224%; + } + .row-fluid .offset2 { + margin-left: 19.65811965811966%; + *margin-left: 19.551736679396257%; + } + .row-fluid .offset2:first-child { + margin-left: 17.094017094017094%; + *margin-left: 16.98763411529369%; + } + .row-fluid .offset1 { + margin-left: 11.11111111111111%; + *margin-left: 11.004728132387708%; + } + .row-fluid .offset1:first-child { + margin-left: 8.547008547008547%; + *margin-left: 8.440625568285142%; + } + input, + textarea, + .uneditable-input { + margin-left: 0; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 30px; + } + input.span12, + textarea.span12, + .uneditable-input.span12 { + width: 1156px; + } + input.span11, + textarea.span11, + .uneditable-input.span11 { + width: 1056px; + } + input.span10, + textarea.span10, + .uneditable-input.span10 { + width: 956px; + } + input.span9, + textarea.span9, + .uneditable-input.span9 { + width: 856px; + } + input.span8, + textarea.span8, + .uneditable-input.span8 { + width: 756px; + } + input.span7, + textarea.span7, + .uneditable-input.span7 { + width: 656px; + } + input.span6, + textarea.span6, + .uneditable-input.span6 { + width: 556px; + } + input.span5, + textarea.span5, + .uneditable-input.span5 { + width: 456px; + } + input.span4, + textarea.span4, + .uneditable-input.span4 { + width: 356px; + } + input.span3, + textarea.span3, + .uneditable-input.span3 { + width: 256px; + } + input.span2, + textarea.span2, + .uneditable-input.span2 { + width: 156px; + } + input.span1, + textarea.span1, + .uneditable-input.span1 { + width: 56px; + } + .thumbnails { + margin-left: -30px; + } + .thumbnails > li { + margin-left: 30px; + } + .row-fluid .thumbnails { + margin-left: 0; + } +} + +@media (min-width: 768px) and (max-width: 979px) { + .row { + margin-left: -20px; + *zoom: 1; + } + .row:before, + .row:after { + display: table; + line-height: 0; + content: ""; + } + .row:after { + clear: both; + } + [class*="span"] { + float: left; + min-height: 1px; + margin-left: 20px; + } + .container, + .navbar-static-top .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { + width: 724px; + } + .span12 { + width: 724px; + } + .span11 { + width: 662px; + } + .span10 { + width: 600px; + } + .span9 { + width: 538px; + } + .span8 { + width: 476px; + } + .span7 { + width: 414px; + } + .span6 { + width: 352px; + } + .span5 { + width: 290px; + } + .span4 { + width: 228px; + } + .span3 { + width: 166px; + } + .span2 { + width: 104px; + } + .span1 { + width: 42px; + } + .offset12 { + margin-left: 764px; + } + .offset11 { + margin-left: 702px; + } + .offset10 { + margin-left: 640px; + } + .offset9 { + margin-left: 578px; + } + .offset8 { + margin-left: 516px; + } + .offset7 { + margin-left: 454px; + } + .offset6 { + margin-left: 392px; + } + .offset5 { + margin-left: 330px; + } + .offset4 { + margin-left: 268px; + } + .offset3 { + margin-left: 206px; + } + .offset2 { + margin-left: 144px; + } + .offset1 { + margin-left: 82px; + } + .row-fluid { + width: 100%; + *zoom: 1; + } + .row-fluid:before, + .row-fluid:after { + display: table; + line-height: 0; + content: ""; + } + .row-fluid:after { + clear: both; + } + .row-fluid [class*="span"] { + display: block; + float: left; + width: 100%; + min-height: 30px; + margin-left: 2.7624309392265194%; + *margin-left: 2.709239449864817%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .row-fluid [class*="span"]:first-child { + margin-left: 0; + } + .row-fluid .controls-row [class*="span"] + [class*="span"] { + margin-left: 2.7624309392265194%; + } + .row-fluid .span12 { + width: 100%; + *width: 99.94680851063829%; + } + .row-fluid .span11 { + width: 91.43646408839778%; + *width: 91.38327259903608%; + } + .row-fluid .span10 { + width: 82.87292817679558%; + *width: 82.81973668743387%; + } + .row-fluid .span9 { + width: 74.30939226519337%; + *width: 74.25620077583166%; + } + .row-fluid .span8 { + width: 65.74585635359117%; + *width: 65.69266486422946%; + } + .row-fluid .span7 { + width: 57.18232044198895%; + *width: 57.12912895262725%; + } + .row-fluid .span6 { + width: 48.61878453038674%; + *width: 48.56559304102504%; + } + .row-fluid .span5 { + width: 40.05524861878453%; + *width: 40.00205712942283%; + } + .row-fluid .span4 { + width: 31.491712707182323%; + *width: 31.43852121782062%; + } + .row-fluid .span3 { + width: 22.92817679558011%; + *width: 22.87498530621841%; + } + .row-fluid .span2 { + width: 14.3646408839779%; + *width: 14.311449394616199%; + } + .row-fluid .span1 { + width: 5.801104972375691%; + *width: 5.747913483013988%; + } + .row-fluid .offset12 { + margin-left: 105.52486187845304%; + *margin-left: 105.41847889972962%; + } + .row-fluid .offset12:first-child { + margin-left: 102.76243093922652%; + *margin-left: 102.6560479605031%; + } + .row-fluid .offset11 { + margin-left: 96.96132596685082%; + *margin-left: 96.8549429881274%; + } + .row-fluid .offset11:first-child { + margin-left: 94.1988950276243%; + *margin-left: 94.09251204890089%; + } + .row-fluid .offset10 { + margin-left: 88.39779005524862%; + *margin-left: 88.2914070765252%; + } + .row-fluid .offset10:first-child { + margin-left: 85.6353591160221%; + *margin-left: 85.52897613729868%; + } + .row-fluid .offset9 { + margin-left: 79.8342541436464%; + *margin-left: 79.72787116492299%; + } + .row-fluid .offset9:first-child { + margin-left: 77.07182320441989%; + *margin-left: 76.96544022569647%; + } + .row-fluid .offset8 { + margin-left: 71.2707182320442%; + *margin-left: 71.16433525332079%; + } + .row-fluid .offset8:first-child { + margin-left: 68.50828729281768%; + *margin-left: 68.40190431409427%; + } + .row-fluid .offset7 { + margin-left: 62.70718232044199%; + *margin-left: 62.600799341718584%; + } + .row-fluid .offset7:first-child { + margin-left: 59.94475138121547%; + *margin-left: 59.838368402492065%; + } + .row-fluid .offset6 { + margin-left: 54.14364640883978%; + *margin-left: 54.037263430116376%; + } + .row-fluid .offset6:first-child { + margin-left: 51.38121546961326%; + *margin-left: 51.27483249088986%; + } + .row-fluid .offset5 { + margin-left: 45.58011049723757%; + *margin-left: 45.47372751851417%; + } + .row-fluid .offset5:first-child { + margin-left: 42.81767955801105%; + *margin-left: 42.71129657928765%; + } + .row-fluid .offset4 { + margin-left: 37.01657458563536%; + *margin-left: 36.91019160691196%; + } + .row-fluid .offset4:first-child { + margin-left: 34.25414364640884%; + *margin-left: 34.14776066768544%; + } + .row-fluid .offset3 { + margin-left: 28.45303867403315%; + *margin-left: 28.346655695309746%; + } + .row-fluid .offset3:first-child { + margin-left: 25.69060773480663%; + *margin-left: 25.584224756083227%; + } + .row-fluid .offset2 { + margin-left: 19.88950276243094%; + *margin-left: 19.783119783707537%; + } + .row-fluid .offset2:first-child { + margin-left: 17.12707182320442%; + *margin-left: 17.02068884448102%; + } + .row-fluid .offset1 { + margin-left: 11.32596685082873%; + *margin-left: 11.219583872105325%; + } + .row-fluid .offset1:first-child { + margin-left: 8.56353591160221%; + *margin-left: 8.457152932878806%; + } + input, + textarea, + .uneditable-input { + margin-left: 0; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 20px; + } + input.span12, + textarea.span12, + .uneditable-input.span12 { + width: 710px; + } + input.span11, + textarea.span11, + .uneditable-input.span11 { + width: 648px; + } + input.span10, + textarea.span10, + .uneditable-input.span10 { + width: 586px; + } + input.span9, + textarea.span9, + .uneditable-input.span9 { + width: 524px; + } + input.span8, + textarea.span8, + .uneditable-input.span8 { + width: 462px; + } + input.span7, + textarea.span7, + .uneditable-input.span7 { + width: 400px; + } + input.span6, + textarea.span6, + .uneditable-input.span6 { + width: 338px; + } + input.span5, + textarea.span5, + .uneditable-input.span5 { + width: 276px; + } + input.span4, + textarea.span4, + .uneditable-input.span4 { + width: 214px; + } + input.span3, + textarea.span3, + .uneditable-input.span3 { + width: 152px; + } + input.span2, + textarea.span2, + .uneditable-input.span2 { + width: 90px; + } + input.span1, + textarea.span1, + .uneditable-input.span1 { + width: 28px; + } +} + +@media (max-width: 767px) { + body { + padding-right: 20px; + padding-left: 20px; + } + .navbar-fixed-top, + .navbar-fixed-bottom, + .navbar-static-top { + margin-right: -20px; + margin-left: -20px; + } + .container-fluid { + padding: 0; + } + .dl-horizontal dt { + float: none; + width: auto; + clear: none; + text-align: left; + } + .dl-horizontal dd { + margin-left: 0; + } + .container { + width: auto; + } + .row-fluid { + width: 100%; + } + .row, + .thumbnails { + margin-left: 0; + } + .thumbnails > li { + float: none; + margin-left: 0; + } + [class*="span"], + .uneditable-input[class*="span"], + .row-fluid [class*="span"] { + display: block; + float: none; + width: 100%; + margin-left: 0; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .span12, + .row-fluid .span12 { + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .row-fluid [class*="offset"]:first-child { + margin-left: 0; + } + .input-large, + .input-xlarge, + .input-xxlarge, + input[class*="span"], + select[class*="span"], + textarea[class*="span"], + .uneditable-input { + display: block; + width: 100%; + min-height: 30px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .input-prepend input, + .input-append input, + .input-prepend input[class*="span"], + .input-append input[class*="span"] { + display: inline-block; + width: auto; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 0; + } + .modal { + position: fixed; + top: 20px; + right: 20px; + left: 20px; + width: auto; + margin: 0; + } + .modal.fade { + top: -100px; + } + .modal.fade.in { + top: 20px; + } +} + +@media (max-width: 480px) { + .nav-collapse { + -webkit-transform: translate3d(0, 0, 0); + } + .page-header h1 small { + display: block; + line-height: 20px; + } + input[type="checkbox"], + input[type="radio"] { + border: 1px solid #ccc; + } + .form-horizontal .control-label { + float: none; + width: auto; + padding-top: 0; + text-align: left; + } + .form-horizontal .controls { + margin-left: 0; + } + .form-horizontal .control-list { + padding-top: 0; + } + .form-horizontal .form-actions { + padding-right: 10px; + padding-left: 10px; + } + .media .pull-left, + .media .pull-right { + display: block; + float: none; + margin-bottom: 10px; + } + .media-object { + margin-right: 0; + margin-left: 0; + } + .modal { + top: 10px; + right: 10px; + left: 10px; + } + .modal-header .close { + padding: 10px; + margin: -10px; + } + .carousel-caption { + position: static; + } +} + +@media (max-width: 979px) { + body { + padding-top: 0; + } + .navbar-fixed-top, + .navbar-fixed-bottom { + position: static; + } + .navbar-fixed-top { + margin-bottom: 20px; + } + .navbar-fixed-bottom { + margin-top: 20px; + } + .navbar-fixed-top .navbar-inner, + .navbar-fixed-bottom .navbar-inner { + padding: 5px; + } + .navbar .container { + width: auto; + padding: 0; + } + .navbar .brand { + padding-right: 10px; + padding-left: 10px; + margin: 0 0 0 -5px; + } + .nav-collapse { + clear: both; + } + .nav-collapse .nav { + float: none; + margin: 0 0 10px; + } + .nav-collapse .nav > li { + float: none; + } + .nav-collapse .nav > li > a { + margin-bottom: 2px; + } + .nav-collapse .nav > .divider-vertical { + display: none; + } + .nav-collapse .nav .nav-header { + color: #777777; + text-shadow: none; + } + .nav-collapse .nav > li > a, + .nav-collapse .dropdown-menu a { + padding: 9px 15px; + font-weight: bold; + color: #777777; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + } + .nav-collapse .btn { + padding: 4px 10px 4px; + font-weight: normal; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + } + .nav-collapse .dropdown-menu li + li a { + margin-bottom: 2px; + } + .nav-collapse .nav > li > a:hover, + .nav-collapse .nav > li > a:focus, + .nav-collapse .dropdown-menu a:hover, + .nav-collapse .dropdown-menu a:focus { + background-color: #f2f2f2; + } + .navbar-inverse .nav-collapse .nav > li > a, + .navbar-inverse .nav-collapse .dropdown-menu a { + color: #999999; + } + .navbar-inverse .nav-collapse .nav > li > a:hover, + .navbar-inverse .nav-collapse .nav > li > a:focus, + .navbar-inverse .nav-collapse .dropdown-menu a:hover, + .navbar-inverse .nav-collapse .dropdown-menu a:focus { + background-color: #111111; + } + .nav-collapse.in .btn-group { + padding: 0; + margin-top: 5px; + } + .nav-collapse .dropdown-menu { + position: static; + top: auto; + left: auto; + display: none; + float: none; + max-width: none; + padding: 0; + margin: 0 15px; + background-color: transparent; + border: none; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + } + .nav-collapse .open > .dropdown-menu { + display: block; + } + .nav-collapse .dropdown-menu:before, + .nav-collapse .dropdown-menu:after { + display: none; + } + .nav-collapse .dropdown-menu .divider { + display: none; + } + .nav-collapse .nav > li > .dropdown-menu:before, + .nav-collapse .nav > li > .dropdown-menu:after { + display: none; + } + .nav-collapse .navbar-form, + .nav-collapse .navbar-search { + float: none; + padding: 10px 15px; + margin: 10px 0; + border-top: 1px solid #f2f2f2; + border-bottom: 1px solid #f2f2f2; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + } + .navbar-inverse .nav-collapse .navbar-form, + .navbar-inverse .nav-collapse .navbar-search { + border-top-color: #111111; + border-bottom-color: #111111; + } + .navbar .nav-collapse .nav.pull-right { + float: none; + margin-left: 0; + } + .nav-collapse, + .nav-collapse.collapse { + height: 0; + overflow: hidden; + } + .navbar .btn-navbar { + display: block; + } + .navbar-static .navbar-inner { + padding-right: 10px; + padding-left: 10px; + } +} + +@media (min-width: 980px) { + .nav-collapse.collapse { + height: auto !important; + overflow: visible !important; + } +} + +/*! + * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */ +/* FONT PATH + * -------------------------- */ +@font-face { + font-family: 'FontAwesome'; + src: url('../fonts/fontawesome-webfont.eot?v=4.2.0'); + src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg'); + font-weight: normal; + font-style: normal; +} +.fa { + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +/* makes the font 33% larger relative to the icon container */ +.fa-lg { + font-size: 1.33333333em; + line-height: 0.75em; + vertical-align: -15%; +} +.fa-2x { + font-size: 2em; +} +.fa-3x { + font-size: 3em; +} +.fa-4x { + font-size: 4em; +} +.fa-5x { + font-size: 5em; +} +.fa-fw { + width: 1.28571429em; + text-align: center; +} +.fa-ul { + padding-left: 0; + margin-left: 2.14285714em; + list-style-type: none; +} +.fa-ul > li { + position: relative; +} +.fa-li { + position: absolute; + left: -2.14285714em; + width: 2.14285714em; + top: 0.14285714em; + text-align: center; +} +.fa-li.fa-lg { + left: -1.85714286em; +} +.fa-border { + padding: .2em .25em .15em; + border: solid 0.08em #eeeeee; + border-radius: .1em; +} +.pull-right { + float: right; +} +.pull-left { + float: left; +} +.fa.pull-left { + margin-right: .3em; +} +.fa.pull-right { + margin-left: .3em; +} +.fa-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; +} +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +.fa-rotate-90 { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} +.fa-rotate-180 { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} +.fa-rotate-270 { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg); +} +.fa-flip-horizontal { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} +.fa-flip-vertical { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); + -webkit-transform: scale(1, -1); + -ms-transform: scale(1, -1); + transform: scale(1, -1); +} +:root .fa-rotate-90, +:root .fa-rotate-180, +:root .fa-rotate-270, +:root .fa-flip-horizontal, +:root .fa-flip-vertical { + filter: none; +} +.fa-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.fa-stack-1x, +.fa-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.fa-stack-1x { + line-height: inherit; +} +.fa-stack-2x { + font-size: 2em; +} +.fa-inverse { + color: #ffffff; +} +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ +.fa-glass:before { + content: "\f000"; +} +.fa-music:before { + content: "\f001"; +} +.fa-search:before { + content: "\f002"; +} +.fa-envelope-o:before { + content: "\f003"; +} +.fa-heart:before { + content: "\f004"; +} +.fa-star:before { + content: "\f005"; +} +.fa-star-o:before { + content: "\f006"; +} +.fa-user:before { + content: "\f007"; +} +.fa-film:before { + content: "\f008"; +} +.fa-th-large:before { + content: "\f009"; +} +.fa-th:before { + content: "\f00a"; +} +.fa-th-list:before { + content: "\f00b"; +} +.fa-check:before { + content: "\f00c"; +} +.fa-remove:before, +.fa-close:before, +.fa-times:before { + content: "\f00d"; +} +.fa-search-plus:before { + content: "\f00e"; +} +.fa-search-minus:before { + content: "\f010"; +} +.fa-power-off:before { + content: "\f011"; +} +.fa-signal:before { + content: "\f012"; +} +.fa-gear:before, +.fa-cog:before { + content: "\f013"; +} +.fa-trash-o:before { + content: "\f014"; +} +.fa-home:before { + content: "\f015"; +} +.fa-file-o:before { + content: "\f016"; +} +.fa-clock-o:before { + content: "\f017"; +} +.fa-road:before { + content: "\f018"; +} +.fa-download:before { + content: "\f019"; +} +.fa-arrow-circle-o-down:before { + content: "\f01a"; +} +.fa-arrow-circle-o-up:before { + content: "\f01b"; +} +.fa-inbox:before { + content: "\f01c"; +} +.fa-play-circle-o:before { + content: "\f01d"; +} +.fa-rotate-right:before, +.fa-repeat:before { + content: "\f01e"; +} +.fa-refresh:before { + content: "\f021"; +} +.fa-list-alt:before { + content: "\f022"; +} +.fa-lock:before { + content: "\f023"; +} +.fa-flag:before { + content: "\f024"; +} +.fa-headphones:before { + content: "\f025"; +} +.fa-volume-off:before { + content: "\f026"; +} +.fa-volume-down:before { + content: "\f027"; +} +.fa-volume-up:before { + content: "\f028"; +} +.fa-qrcode:before { + content: "\f029"; +} +.fa-barcode:before { + content: "\f02a"; +} +.fa-tag:before { + content: "\f02b"; +} +.fa-tags:before { + content: "\f02c"; +} +.fa-book:before { + content: "\f02d"; +} +.fa-bookmark:before { + content: "\f02e"; +} +.fa-print:before { + content: "\f02f"; +} +.fa-camera:before { + content: "\f030"; +} +.fa-font:before { + content: "\f031"; +} +.fa-bold:before { + content: "\f032"; +} +.fa-italic:before { + content: "\f033"; +} +.fa-text-height:before { + content: "\f034"; +} +.fa-text-width:before { + content: "\f035"; +} +.fa-align-left:before { + content: "\f036"; +} +.fa-align-center:before { + content: "\f037"; +} +.fa-align-right:before { + content: "\f038"; +} +.fa-align-justify:before { + content: "\f039"; +} +.fa-list:before { + content: "\f03a"; +} +.fa-dedent:before, +.fa-outdent:before { + content: "\f03b"; +} +.fa-indent:before { + content: "\f03c"; +} +.fa-video-camera:before { + content: "\f03d"; +} +.fa-photo:before, +.fa-image:before, +.fa-picture-o:before { + content: "\f03e"; +} +.fa-pencil:before { + content: "\f040"; +} +.fa-map-marker:before { + content: "\f041"; +} +.fa-adjust:before { + content: "\f042"; +} +.fa-tint:before { + content: "\f043"; +} +.fa-edit:before, +.fa-pencil-square-o:before { + content: "\f044"; +} +.fa-share-square-o:before { + content: "\f045"; +} +.fa-check-square-o:before { + content: "\f046"; +} +.fa-arrows:before { + content: "\f047"; +} +.fa-step-backward:before { + content: "\f048"; +} +.fa-fast-backward:before { + content: "\f049"; +} +.fa-backward:before { + content: "\f04a"; +} +.fa-play:before { + content: "\f04b"; +} +.fa-pause:before { + content: "\f04c"; +} +.fa-stop:before { + content: "\f04d"; +} +.fa-forward:before { + content: "\f04e"; +} +.fa-fast-forward:before { + content: "\f050"; +} +.fa-step-forward:before { + content: "\f051"; +} +.fa-eject:before { + content: "\f052"; +} +.fa-chevron-left:before { + content: "\f053"; +} +.fa-chevron-right:before { + content: "\f054"; +} +.fa-plus-circle:before { + content: "\f055"; +} +.fa-minus-circle:before { + content: "\f056"; +} +.fa-times-circle:before { + content: "\f057"; +} +.fa-check-circle:before { + content: "\f058"; +} +.fa-question-circle:before { + content: "\f059"; +} +.fa-info-circle:before { + content: "\f05a"; +} +.fa-crosshairs:before { + content: "\f05b"; +} +.fa-times-circle-o:before { + content: "\f05c"; +} +.fa-check-circle-o:before { + content: "\f05d"; +} +.fa-ban:before { + content: "\f05e"; +} +.fa-arrow-left:before { + content: "\f060"; +} +.fa-arrow-right:before { + content: "\f061"; +} +.fa-arrow-up:before { + content: "\f062"; +} +.fa-arrow-down:before { + content: "\f063"; +} +.fa-mail-forward:before, +.fa-share:before { + content: "\f064"; +} +.fa-expand:before { + content: "\f065"; +} +.fa-compress:before { + content: "\f066"; +} +.fa-plus:before { + content: "\f067"; +} +.fa-minus:before { + content: "\f068"; +} +.fa-asterisk:before { + content: "\f069"; +} +.fa-exclamation-circle:before { + content: "\f06a"; +} +.fa-gift:before { + content: "\f06b"; +} +.fa-leaf:before { + content: "\f06c"; +} +.fa-fire:before { + content: "\f06d"; +} +.fa-eye:before { + content: "\f06e"; +} +.fa-eye-slash:before { + content: "\f070"; +} +.fa-warning:before, +.fa-exclamation-triangle:before { + content: "\f071"; +} +.fa-plane:before { + content: "\f072"; +} +.fa-calendar:before { + content: "\f073"; +} +.fa-random:before { + content: "\f074"; +} +.fa-comment:before { + content: "\f075"; +} +.fa-magnet:before { + content: "\f076"; +} +.fa-chevron-up:before { + content: "\f077"; +} +.fa-chevron-down:before { + content: "\f078"; +} +.fa-retweet:before { + content: "\f079"; +} +.fa-shopping-cart:before { + content: "\f07a"; +} +.fa-folder:before { + content: "\f07b"; +} +.fa-folder-open:before { + content: "\f07c"; +} +.fa-arrows-v:before { + content: "\f07d"; +} +.fa-arrows-h:before { + content: "\f07e"; +} +.fa-bar-chart-o:before, +.fa-bar-chart:before { + content: "\f080"; +} +.fa-twitter-square:before { + content: "\f081"; +} +.fa-facebook-square:before { + content: "\f082"; +} +.fa-camera-retro:before { + content: "\f083"; +} +.fa-key:before { + content: "\f084"; +} +.fa-gears:before, +.fa-cogs:before { + content: "\f085"; +} +.fa-comments:before { + content: "\f086"; +} +.fa-thumbs-o-up:before { + content: "\f087"; +} +.fa-thumbs-o-down:before { + content: "\f088"; +} +.fa-star-half:before { + content: "\f089"; +} +.fa-heart-o:before { + content: "\f08a"; +} +.fa-sign-out:before { + content: "\f08b"; +} +.fa-linkedin-square:before { + content: "\f08c"; +} +.fa-thumb-tack:before { + content: "\f08d"; +} +.fa-external-link:before { + content: "\f08e"; +} +.fa-sign-in:before { + content: "\f090"; +} +.fa-trophy:before { + content: "\f091"; +} +.fa-github-square:before { + content: "\f092"; +} +.fa-upload:before { + content: "\f093"; +} +.fa-lemon-o:before { + content: "\f094"; +} +.fa-phone:before { + content: "\f095"; +} +.fa-square-o:before { + content: "\f096"; +} +.fa-bookmark-o:before { + content: "\f097"; +} +.fa-phone-square:before { + content: "\f098"; +} +.fa-twitter:before { + content: "\f099"; +} +.fa-facebook:before { + content: "\f09a"; +} +.fa-github:before { + content: "\f09b"; +} +.fa-unlock:before { + content: "\f09c"; +} +.fa-credit-card:before { + content: "\f09d"; +} +.fa-rss:before { + content: "\f09e"; +} +.fa-hdd-o:before { + content: "\f0a0"; +} +.fa-bullhorn:before { + content: "\f0a1"; +} +.fa-bell:before { + content: "\f0f3"; +} +.fa-certificate:before { + content: "\f0a3"; +} +.fa-hand-o-right:before { + content: "\f0a4"; +} +.fa-hand-o-left:before { + content: "\f0a5"; +} +.fa-hand-o-up:before { + content: "\f0a6"; +} +.fa-hand-o-down:before { + content: "\f0a7"; +} +.fa-arrow-circle-left:before { + content: "\f0a8"; +} +.fa-arrow-circle-right:before { + content: "\f0a9"; +} +.fa-arrow-circle-up:before { + content: "\f0aa"; +} +.fa-arrow-circle-down:before { + content: "\f0ab"; +} +.fa-globe:before { + content: "\f0ac"; +} +.fa-wrench:before { + content: "\f0ad"; +} +.fa-tasks:before { + content: "\f0ae"; +} +.fa-filter:before { + content: "\f0b0"; +} +.fa-briefcase:before { + content: "\f0b1"; +} +.fa-arrows-alt:before { + content: "\f0b2"; +} +.fa-group:before, +.fa-users:before { + content: "\f0c0"; +} +.fa-chain:before, +.fa-link:before { + content: "\f0c1"; +} +.fa-cloud:before { + content: "\f0c2"; +} +.fa-flask:before { + content: "\f0c3"; +} +.fa-cut:before, +.fa-scissors:before { + content: "\f0c4"; +} +.fa-copy:before, +.fa-files-o:before { + content: "\f0c5"; +} +.fa-paperclip:before { + content: "\f0c6"; +} +.fa-save:before, +.fa-floppy-o:before { + content: "\f0c7"; +} +.fa-square:before { + content: "\f0c8"; +} +.fa-navicon:before, +.fa-reorder:before, +.fa-bars:before { + content: "\f0c9"; +} +.fa-list-ul:before { + content: "\f0ca"; +} +.fa-list-ol:before { + content: "\f0cb"; +} +.fa-strikethrough:before { + content: "\f0cc"; +} +.fa-underline:before { + content: "\f0cd"; +} +.fa-table:before { + content: "\f0ce"; +} +.fa-magic:before { + content: "\f0d0"; +} +.fa-truck:before { + content: "\f0d1"; +} +.fa-pinterest:before { + content: "\f0d2"; +} +.fa-pinterest-square:before { + content: "\f0d3"; +} +.fa-google-plus-square:before { + content: "\f0d4"; +} +.fa-google-plus:before { + content: "\f0d5"; +} +.fa-money:before { + content: "\f0d6"; +} +.fa-caret-down:before { + content: "\f0d7"; +} +.fa-caret-up:before { + content: "\f0d8"; +} +.fa-caret-left:before { + content: "\f0d9"; +} +.fa-caret-right:before { + content: "\f0da"; +} +.fa-columns:before { + content: "\f0db"; +} +.fa-unsorted:before, +.fa-sort:before { + content: "\f0dc"; +} +.fa-sort-down:before, +.fa-sort-desc:before { + content: "\f0dd"; +} +.fa-sort-up:before, +.fa-sort-asc:before { + content: "\f0de"; +} +.fa-envelope:before { + content: "\f0e0"; +} +.fa-linkedin:before { + content: "\f0e1"; +} +.fa-rotate-left:before, +.fa-undo:before { + content: "\f0e2"; +} +.fa-legal:before, +.fa-gavel:before { + content: "\f0e3"; +} +.fa-dashboard:before, +.fa-tachometer:before { + content: "\f0e4"; +} +.fa-comment-o:before { + content: "\f0e5"; +} +.fa-comments-o:before { + content: "\f0e6"; +} +.fa-flash:before, +.fa-bolt:before { + content: "\f0e7"; +} +.fa-sitemap:before { + content: "\f0e8"; +} +.fa-umbrella:before { + content: "\f0e9"; +} +.fa-paste:before, +.fa-clipboard:before { + content: "\f0ea"; +} +.fa-lightbulb-o:before { + content: "\f0eb"; +} +.fa-exchange:before { + content: "\f0ec"; +} +.fa-cloud-download:before { + content: "\f0ed"; +} +.fa-cloud-upload:before { + content: "\f0ee"; +} +.fa-user-md:before { + content: "\f0f0"; +} +.fa-stethoscope:before { + content: "\f0f1"; +} +.fa-suitcase:before { + content: "\f0f2"; +} +.fa-bell-o:before { + content: "\f0a2"; +} +.fa-coffee:before { + content: "\f0f4"; +} +.fa-cutlery:before { + content: "\f0f5"; +} +.fa-file-text-o:before { + content: "\f0f6"; +} +.fa-building-o:before { + content: "\f0f7"; +} +.fa-hospital-o:before { + content: "\f0f8"; +} +.fa-ambulance:before { + content: "\f0f9"; +} +.fa-medkit:before { + content: "\f0fa"; +} +.fa-fighter-jet:before { + content: "\f0fb"; +} +.fa-beer:before { + content: "\f0fc"; +} +.fa-h-square:before { + content: "\f0fd"; +} +.fa-plus-square:before { + content: "\f0fe"; +} +.fa-angle-double-left:before { + content: "\f100"; +} +.fa-angle-double-right:before { + content: "\f101"; +} +.fa-angle-double-up:before { + content: "\f102"; +} +.fa-angle-double-down:before { + content: "\f103"; +} +.fa-angle-left:before { + content: "\f104"; +} +.fa-angle-right:before { + content: "\f105"; +} +.fa-angle-up:before { + content: "\f106"; +} +.fa-angle-down:before { + content: "\f107"; +} +.fa-desktop:before { + content: "\f108"; +} +.fa-laptop:before { + content: "\f109"; +} +.fa-tablet:before { + content: "\f10a"; +} +.fa-mobile-phone:before, +.fa-mobile:before { + content: "\f10b"; +} +.fa-circle-o:before { + content: "\f10c"; +} +.fa-quote-left:before { + content: "\f10d"; +} +.fa-quote-right:before { + content: "\f10e"; +} +.fa-spinner:before { + content: "\f110"; +} +.fa-circle:before { + content: "\f111"; +} +.fa-mail-reply:before, +.fa-reply:before { + content: "\f112"; +} +.fa-github-alt:before { + content: "\f113"; +} +.fa-folder-o:before { + content: "\f114"; +} +.fa-folder-open-o:before { + content: "\f115"; +} +.fa-smile-o:before { + content: "\f118"; +} +.fa-frown-o:before { + content: "\f119"; +} +.fa-meh-o:before { + content: "\f11a"; +} +.fa-gamepad:before { + content: "\f11b"; +} +.fa-keyboard-o:before { + content: "\f11c"; +} +.fa-flag-o:before { + content: "\f11d"; +} +.fa-flag-checkered:before { + content: "\f11e"; +} +.fa-terminal:before { + content: "\f120"; +} +.fa-code:before { + content: "\f121"; +} +.fa-mail-reply-all:before, +.fa-reply-all:before { + content: "\f122"; +} +.fa-star-half-empty:before, +.fa-star-half-full:before, +.fa-star-half-o:before { + content: "\f123"; +} +.fa-location-arrow:before { + content: "\f124"; +} +.fa-crop:before { + content: "\f125"; +} +.fa-code-fork:before { + content: "\f126"; +} +.fa-unlink:before, +.fa-chain-broken:before { + content: "\f127"; +} +.fa-question:before { + content: "\f128"; +} +.fa-info:before { + content: "\f129"; +} +.fa-exclamation:before { + content: "\f12a"; +} +.fa-superscript:before { + content: "\f12b"; +} +.fa-subscript:before { + content: "\f12c"; +} +.fa-eraser:before { + content: "\f12d"; +} +.fa-puzzle-piece:before { + content: "\f12e"; +} +.fa-microphone:before { + content: "\f130"; +} +.fa-microphone-slash:before { + content: "\f131"; +} +.fa-shield:before { + content: "\f132"; +} +.fa-calendar-o:before { + content: "\f133"; +} +.fa-fire-extinguisher:before { + content: "\f134"; +} +.fa-rocket:before { + content: "\f135"; +} +.fa-maxcdn:before { + content: "\f136"; +} +.fa-chevron-circle-left:before { + content: "\f137"; +} +.fa-chevron-circle-right:before { + content: "\f138"; +} +.fa-chevron-circle-up:before { + content: "\f139"; +} +.fa-chevron-circle-down:before { + content: "\f13a"; +} +.fa-html5:before { + content: "\f13b"; +} +.fa-css3:before { + content: "\f13c"; +} +.fa-anchor:before { + content: "\f13d"; +} +.fa-unlock-alt:before { + content: "\f13e"; +} +.fa-bullseye:before { + content: "\f140"; +} +.fa-ellipsis-h:before { + content: "\f141"; +} +.fa-ellipsis-v:before { + content: "\f142"; +} +.fa-rss-square:before { + content: "\f143"; +} +.fa-play-circle:before { + content: "\f144"; +} +.fa-ticket:before { + content: "\f145"; +} +.fa-minus-square:before { + content: "\f146"; +} +.fa-minus-square-o:before { + content: "\f147"; +} +.fa-level-up:before { + content: "\f148"; +} +.fa-level-down:before { + content: "\f149"; +} +.fa-check-square:before { + content: "\f14a"; +} +.fa-pencil-square:before { + content: "\f14b"; +} +.fa-external-link-square:before { + content: "\f14c"; +} +.fa-share-square:before { + content: "\f14d"; +} +.fa-compass:before { + content: "\f14e"; +} +.fa-toggle-down:before, +.fa-caret-square-o-down:before { + content: "\f150"; +} +.fa-toggle-up:before, +.fa-caret-square-o-up:before { + content: "\f151"; +} +.fa-toggle-right:before, +.fa-caret-square-o-right:before { + content: "\f152"; +} +.fa-euro:before, +.fa-eur:before { + content: "\f153"; +} +.fa-gbp:before { + content: "\f154"; +} +.fa-dollar:before, +.fa-usd:before { + content: "\f155"; +} +.fa-rupee:before, +.fa-inr:before { + content: "\f156"; +} +.fa-cny:before, +.fa-rmb:before, +.fa-yen:before, +.fa-jpy:before { + content: "\f157"; +} +.fa-ruble:before, +.fa-rouble:before, +.fa-rub:before { + content: "\f158"; +} +.fa-won:before, +.fa-krw:before { + content: "\f159"; +} +.fa-bitcoin:before, +.fa-btc:before { + content: "\f15a"; +} +.fa-file:before { + content: "\f15b"; +} +.fa-file-text:before { + content: "\f15c"; +} +.fa-sort-alpha-asc:before { + content: "\f15d"; +} +.fa-sort-alpha-desc:before { + content: "\f15e"; +} +.fa-sort-amount-asc:before { + content: "\f160"; +} +.fa-sort-amount-desc:before { + content: "\f161"; +} +.fa-sort-numeric-asc:before { + content: "\f162"; +} +.fa-sort-numeric-desc:before { + content: "\f163"; +} +.fa-thumbs-up:before { + content: "\f164"; +} +.fa-thumbs-down:before { + content: "\f165"; +} +.fa-youtube-square:before { + content: "\f166"; +} +.fa-youtube:before { + content: "\f167"; +} +.fa-xing:before { + content: "\f168"; +} +.fa-xing-square:before { + content: "\f169"; +} +.fa-youtube-play:before { + content: "\f16a"; +} +.fa-dropbox:before { + content: "\f16b"; +} +.fa-stack-overflow:before { + content: "\f16c"; +} +.fa-instagram:before { + content: "\f16d"; +} +.fa-flickr:before { + content: "\f16e"; +} +.fa-adn:before { + content: "\f170"; +} +.fa-bitbucket:before { + content: "\f171"; +} +.fa-bitbucket-square:before { + content: "\f172"; +} +.fa-tumblr:before { + content: "\f173"; +} +.fa-tumblr-square:before { + content: "\f174"; +} +.fa-long-arrow-down:before { + content: "\f175"; +} +.fa-long-arrow-up:before { + content: "\f176"; +} +.fa-long-arrow-left:before { + content: "\f177"; +} +.fa-long-arrow-right:before { + content: "\f178"; +} +.fa-apple:before { + content: "\f179"; +} +.fa-windows:before { + content: "\f17a"; +} +.fa-android:before { + content: "\f17b"; +} +.fa-linux:before { + content: "\f17c"; +} +.fa-dribbble:before { + content: "\f17d"; +} +.fa-skype:before { + content: "\f17e"; +} +.fa-foursquare:before { + content: "\f180"; +} +.fa-trello:before { + content: "\f181"; +} +.fa-female:before { + content: "\f182"; +} +.fa-male:before { + content: "\f183"; +} +.fa-gittip:before { + content: "\f184"; +} +.fa-sun-o:before { + content: "\f185"; +} +.fa-moon-o:before { + content: "\f186"; +} +.fa-archive:before { + content: "\f187"; +} +.fa-bug:before { + content: "\f188"; +} +.fa-vk:before { + content: "\f189"; +} +.fa-weibo:before { + content: "\f18a"; +} +.fa-renren:before { + content: "\f18b"; +} +.fa-pagelines:before { + content: "\f18c"; +} +.fa-stack-exchange:before { + content: "\f18d"; +} +.fa-arrow-circle-o-right:before { + content: "\f18e"; +} +.fa-arrow-circle-o-left:before { + content: "\f190"; +} +.fa-toggle-left:before, +.fa-caret-square-o-left:before { + content: "\f191"; +} +.fa-dot-circle-o:before { + content: "\f192"; +} +.fa-wheelchair:before { + content: "\f193"; +} +.fa-vimeo-square:before { + content: "\f194"; +} +.fa-turkish-lira:before, +.fa-try:before { + content: "\f195"; +} +.fa-plus-square-o:before { + content: "\f196"; +} +.fa-space-shuttle:before { + content: "\f197"; +} +.fa-slack:before { + content: "\f198"; +} +.fa-envelope-square:before { + content: "\f199"; +} +.fa-wordpress:before { + content: "\f19a"; +} +.fa-openid:before { + content: "\f19b"; +} +.fa-institution:before, +.fa-bank:before, +.fa-university:before { + content: "\f19c"; +} +.fa-mortar-board:before, +.fa-graduation-cap:before { + content: "\f19d"; +} +.fa-yahoo:before { + content: "\f19e"; +} +.fa-google:before { + content: "\f1a0"; +} +.fa-reddit:before { + content: "\f1a1"; +} +.fa-reddit-square:before { + content: "\f1a2"; +} +.fa-stumbleupon-circle:before { + content: "\f1a3"; +} +.fa-stumbleupon:before { + content: "\f1a4"; +} +.fa-delicious:before { + content: "\f1a5"; +} +.fa-digg:before { + content: "\f1a6"; +} +.fa-pied-piper:before { + content: "\f1a7"; +} +.fa-pied-piper-alt:before { + content: "\f1a8"; +} +.fa-drupal:before { + content: "\f1a9"; +} +.fa-joomla:before { + content: "\f1aa"; +} +.fa-language:before { + content: "\f1ab"; +} +.fa-fax:before { + content: "\f1ac"; +} +.fa-building:before { + content: "\f1ad"; +} +.fa-child:before { + content: "\f1ae"; +} +.fa-paw:before { + content: "\f1b0"; +} +.fa-spoon:before { + content: "\f1b1"; +} +.fa-cube:before { + content: "\f1b2"; +} +.fa-cubes:before { + content: "\f1b3"; +} +.fa-behance:before { + content: "\f1b4"; +} +.fa-behance-square:before { + content: "\f1b5"; +} +.fa-steam:before { + content: "\f1b6"; +} +.fa-steam-square:before { + content: "\f1b7"; +} +.fa-recycle:before { + content: "\f1b8"; +} +.fa-automobile:before, +.fa-car:before { + content: "\f1b9"; +} +.fa-cab:before, +.fa-taxi:before { + content: "\f1ba"; +} +.fa-tree:before { + content: "\f1bb"; +} +.fa-spotify:before { + content: "\f1bc"; +} +.fa-deviantart:before { + content: "\f1bd"; +} +.fa-soundcloud:before { + content: "\f1be"; +} +.fa-database:before { + content: "\f1c0"; +} +.fa-file-pdf-o:before { + content: "\f1c1"; +} +.fa-file-word-o:before { + content: "\f1c2"; +} +.fa-file-excel-o:before { + content: "\f1c3"; +} +.fa-file-powerpoint-o:before { + content: "\f1c4"; +} +.fa-file-photo-o:before, +.fa-file-picture-o:before, +.fa-file-image-o:before { + content: "\f1c5"; +} +.fa-file-zip-o:before, +.fa-file-archive-o:before { + content: "\f1c6"; +} +.fa-file-sound-o:before, +.fa-file-audio-o:before { + content: "\f1c7"; +} +.fa-file-movie-o:before, +.fa-file-video-o:before { + content: "\f1c8"; +} +.fa-file-code-o:before { + content: "\f1c9"; +} +.fa-vine:before { + content: "\f1ca"; +} +.fa-codepen:before { + content: "\f1cb"; +} +.fa-jsfiddle:before { + content: "\f1cc"; +} +.fa-life-bouy:before, +.fa-life-buoy:before, +.fa-life-saver:before, +.fa-support:before, +.fa-life-ring:before { + content: "\f1cd"; +} +.fa-circle-o-notch:before { + content: "\f1ce"; +} +.fa-ra:before, +.fa-rebel:before { + content: "\f1d0"; +} +.fa-ge:before, +.fa-empire:before { + content: "\f1d1"; +} +.fa-git-square:before { + content: "\f1d2"; +} +.fa-git:before { + content: "\f1d3"; +} +.fa-hacker-news:before { + content: "\f1d4"; +} +.fa-tencent-weibo:before { + content: "\f1d5"; +} +.fa-qq:before { + content: "\f1d6"; +} +.fa-wechat:before, +.fa-weixin:before { + content: "\f1d7"; +} +.fa-send:before, +.fa-paper-plane:before { + content: "\f1d8"; +} +.fa-send-o:before, +.fa-paper-plane-o:before { + content: "\f1d9"; +} +.fa-history:before { + content: "\f1da"; +} +.fa-circle-thin:before { + content: "\f1db"; +} +.fa-header:before { + content: "\f1dc"; +} +.fa-paragraph:before { + content: "\f1dd"; +} +.fa-sliders:before { + content: "\f1de"; +} +.fa-share-alt:before { + content: "\f1e0"; +} +.fa-share-alt-square:before { + content: "\f1e1"; +} +.fa-bomb:before { + content: "\f1e2"; +} +.fa-soccer-ball-o:before, +.fa-futbol-o:before { + content: "\f1e3"; +} +.fa-tty:before { + content: "\f1e4"; +} +.fa-binoculars:before { + content: "\f1e5"; +} +.fa-plug:before { + content: "\f1e6"; +} +.fa-slideshare:before { + content: "\f1e7"; +} +.fa-twitch:before { + content: "\f1e8"; +} +.fa-yelp:before { + content: "\f1e9"; +} +.fa-newspaper-o:before { + content: "\f1ea"; +} +.fa-wifi:before { + content: "\f1eb"; +} +.fa-calculator:before { + content: "\f1ec"; +} +.fa-paypal:before { + content: "\f1ed"; +} +.fa-google-wallet:before { + content: "\f1ee"; +} +.fa-cc-visa:before { + content: "\f1f0"; +} +.fa-cc-mastercard:before { + content: "\f1f1"; +} +.fa-cc-discover:before { + content: "\f1f2"; +} +.fa-cc-amex:before { + content: "\f1f3"; +} +.fa-cc-paypal:before { + content: "\f1f4"; +} +.fa-cc-stripe:before { + content: "\f1f5"; +} +.fa-bell-slash:before { + content: "\f1f6"; +} +.fa-bell-slash-o:before { + content: "\f1f7"; +} +.fa-trash:before { + content: "\f1f8"; +} +.fa-copyright:before { + content: "\f1f9"; +} +.fa-at:before { + content: "\f1fa"; +} +.fa-eyedropper:before { + content: "\f1fb"; +} +.fa-paint-brush:before { + content: "\f1fc"; +} +.fa-birthday-cake:before { + content: "\f1fd"; +} +.fa-area-chart:before { + content: "\f1fe"; +} +.fa-pie-chart:before { + content: "\f200"; +} +.fa-line-chart:before { + content: "\f201"; +} +.fa-lastfm:before { + content: "\f202"; +} +.fa-lastfm-square:before { + content: "\f203"; +} +.fa-toggle-off:before { + content: "\f204"; +} +.fa-toggle-on:before { + content: "\f205"; +} +.fa-bicycle:before { + content: "\f206"; +} +.fa-bus:before { + content: "\f207"; +} +.fa-ioxhost:before { + content: "\f208"; +} +.fa-angellist:before { + content: "\f209"; +} +.fa-cc:before { + content: "\f20a"; +} +.fa-shekel:before, +.fa-sheqel:before, +.fa-ils:before { + content: "\f20b"; +} +.fa-meanpath:before { + content: "\f20c"; +} + +/* + * Table + */ +table.dataTable { + margin: 0 auto; + clear: both; + width: 100%; +} + +table.dataTable thead th { + padding: 3px 18px 3px 10px; + border-bottom: 1px solid black; + font-weight: bold; + cursor: pointer; + *cursor: hand; +} + +table.dataTable tfoot th { + padding: 3px 18px 3px 10px; + border-top: 1px solid black; + font-weight: bold; +} + +table.dataTable td { + padding: 3px 10px; +} + +table.dataTable td.center, +table.dataTable td.dataTables_empty { + text-align: center; +} + +table.dataTable tr.odd { background-color: #E2E4FF; } +table.dataTable tr.even { background-color: white; } + +table.dataTable tr.odd td.sorting_1 { background-color: #D3D6FF; } +table.dataTable tr.odd td.sorting_2 { background-color: #DADCFF; } +table.dataTable tr.odd td.sorting_3 { background-color: #E0E2FF; } +table.dataTable tr.even td.sorting_1 { background-color: #EAEBFF; } +table.dataTable tr.even td.sorting_2 { background-color: #F2F3FF; } +table.dataTable tr.even td.sorting_3 { background-color: #F9F9FF; } + + +/* + * Table wrapper + */ +.dataTables_wrapper { + position: relative; + clear: both; + *zoom: 1; +} + + +/* + * Page length menu + */ +.dataTables_length { + float: left; +} + + +/* + * Filter + */ +.dataTables_filter { + float: right; + text-align: right; +} + + +/* + * Table information + */ +.dataTables_info { + clear: both; + float: left; +} + + +/* + * Pagination + */ +.dataTables_paginate { + float: right; + text-align: right; +} + +/* Two button pagination - previous / next */ +.paginate_disabled_previous, +.paginate_enabled_previous, +.paginate_disabled_next, +.paginate_enabled_next { + height: 19px; + float: left; + cursor: pointer; + *cursor: hand; + color: #111 !important; +} +.paginate_disabled_previous:hover, +.paginate_enabled_previous:hover, +.paginate_disabled_next:hover, +.paginate_enabled_next:hover { + text-decoration: none !important; +} +.paginate_disabled_previous:active, +.paginate_enabled_previous:active, +.paginate_disabled_next:active, +.paginate_enabled_next:active { + outline: none; +} + +.paginate_disabled_previous, +.paginate_disabled_next { + color: #666 !important; +} +.paginate_disabled_previous, +.paginate_enabled_previous { + padding-left: 23px; +} +.paginate_disabled_next, +.paginate_enabled_next { + padding-right: 23px; + margin-left: 10px; +} + +.paginate_enabled_previous { background: url('../images/back_enabled.png') no-repeat top left; } +.paginate_enabled_previous:hover { background: url('../images/back_enabled_hover.png') no-repeat top left; } +.paginate_disabled_previous { background: url('../images/back_disabled.png') no-repeat top left; } + +.paginate_enabled_next { background: url('../images/forward_enabled.png') no-repeat top right; } +.paginate_enabled_next:hover { background: url('../images/forward_enabled_hover.png') no-repeat top right; } +.paginate_disabled_next { background: url('../images/forward_disabled.png') no-repeat top right; } + +/* Full number pagination */ +.paging_full_numbers { + height: 22px; + line-height: 22px; +} +.paging_full_numbers a:active { + outline: none +} +.paging_full_numbers a:hover { + text-decoration: none; +} + +.paging_full_numbers a.paginate_button, +.paging_full_numbers a.paginate_active { + border: 1px solid #aaa; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + padding: 2px 5px; + margin: 0 3px; + cursor: pointer; + *cursor: hand; + color: #333 !important; +} + +.paging_full_numbers a.paginate_button { + background-color: #ddd; +} + +.paging_full_numbers a.paginate_button:hover { + background-color: #ccc; + text-decoration: none !important; +} + +.paging_full_numbers a.paginate_active { + background-color: #99B3FF; +} + + +/* + * Processing indicator + */ +.dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 250px; + height: 30px; + margin-left: -125px; + margin-top: -15px; + padding: 14px 0 2px 0; + border: 1px solid #ddd; + text-align: center; + color: #999; + font-size: 14px; + background-color: white; +} + + +/* + * Sorting + */ +.sorting { background: url('../images/sort_both.png') no-repeat center right; } +.sorting_asc { background: url('../images/sort_asc.png') no-repeat center right; } +.sorting_desc { background: url('../images/sort_desc.png') no-repeat center right; } + +.sorting_asc_disabled { background: url('../images/sort_asc_disabled.png') no-repeat center right; } +.sorting_desc_disabled { background: url('../images/sort_desc_disabled.png') no-repeat center right; } + +table.dataTable thead th:active, +table.dataTable thead td:active { + outline: none; +} + + +/* + * Scrolling + */ +.dataTables_scroll { + clear: both; +} + +.dataTables_scrollBody { + *margin-top: -1px; + -webkit-overflow-scrolling: touch; +} + + +html, +body { + height: 100%; + width: 100%; + background-color: #e2e7ef; + margin: 0; + padding: 0; + font-size: 13px; +} +a:hover { + cursor: pointer; +} +input:focus:invalid, +textarea:focus:invalid, +select:focus:invalid { + color: #b94a48; + background-color: #f2dede; +} +[class^="icon-"] { + background-image: none; +} +[class^="fa-"] { + background-image: none; +} +a.is-connected-user { + text-decoration: none; + cursor: default; +} +.fa-user.user-online, +.fa-globe.participant-online, +.fa-graduation-cap.master { + color: #4ca300; +} +.fa-user.user-offline { + color: #708090; +} +.fa-graduation-cap.no-master, +.fa-globe.participant-offline { + color: #A30400; +} +.hidden { + display: none; + visibility: hidden; +} +::-webkit-scrollbar { + width: 10px; + height: 10px; +} +::-webkit-scrollbar-button:start:decrement, +::-webkit-scrollbar-button:end:increment { + height: 5px; + width: 5px; + display: block; + background-color: #e2e7ef; +} +::-webkit-scrollbar-track-piece { + background-color: #e2e7ef; +} +::-webkit-scrollbar-thumb { + height: 50px; + border: none; + -webkit-border-radius: 2px; + background-color: rgba(100, 100, 100, 0.4); +} +.post:hover::-webkit-scrollbar-thumb, +.photoset-photos:hover::-webkit-scrollbar-thumb, +body::-webkit-scrollbar-thumb { + background-color: rgba(150, 150, 150, 0.6); +} +*::-webkit-scrollbar-thumb:hover { + background-color: rgba(100, 100, 100, 0.7) !important; +} +*::-webkit-scrollbar-thumb:active { + background-color: rgba(70, 70, 70, 0.8) !important; +} +.plm-icon-nav { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; +} +.plm-icon-menu { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 0 8px 0 0; +} +.plm-icon-action { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; +} +.plm-icon-nav-documents { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-documents.png"); +} +.plm-icon-nav-folder { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-folder.png"); +} +.plm-icon-nav-folder-opened { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-folder-opened.png"); +} +.plm-icon-nav-folder-home { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-user-home.png"); +} +.plm-icon-nav-products { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-product.png"); +} +.plm-icon-nav-configuration { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-configuration.png"); +} +.plm-icon-nav-baselines { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-baselines.png"); +} +.plm-icon-nav-product-instances { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-product-instances.png"); +} +.plm-icon-nav-parts { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-part.png"); +} +.plm-icon-nav-folder-status-closed { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + width: 12px; + background-image: url("../images/icon-nav-bullet-right.png"); + opacity: 0.6; +} +.plm-icon-nav-folder-status-opened { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + width: 12px; + background-image: url("../images/icon-nav-bullet-down.png"); + opacity: 0.6; +} +.plm-icon-nav-tags { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-tags.png"); +} +.plm-icon-nav-tag { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-tag.png"); +} +.plm-icon-nav-workflows { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-workflows.png"); +} +.plm-icon-nav-milestones { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-milestones.png"); +} +.plm-icon-nav-issues { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-issues.png"); +} +.plm-icon-nav-orders { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-orders.png"); +} +.plm-icon-nav-requests { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-requests.png"); +} +.plm-icon-nav-templates { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-templates.png"); +} +.plm-icon-nav-checkedouts { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-checkedouts.png"); +} +.plm-icon-nav-tasks { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-tasks.png"); +} +.plm-icon-nav-search { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 2px 6px 0 0; + background-image: url("../images/icon-nav-search.png"); +} +.plm-icon-action-delete { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-delete.png"); +} +.plm-icon-action-duplicate { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-duplicate.png"); +} +.plm-icon-action-new-baseline { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-new-baseline.png"); +} +.plm-icon-action-new-product-intances { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-new-product-intances.png"); +} +.plm-icon-action-document-new { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-document-new.png"); +} +.plm-icon-action-template-new { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-template-new.png"); +} +.plm-icon-action-product-new { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-product-new.png"); +} +.plm-icon-action-configuration-new { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-configuration-new.png"); +} +.plm-icon-action-udf { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-udf.png"); +} +.plm-icon-action-part-new { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-part-new.png"); +} +.plm-icon-action-checkout { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-document-checkout.png"); +} +.plm-icon-action-undocheckout { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-document-undocheckout.png"); +} +.plm-icon-action-checkin { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-document-checkin.png"); +} +.plm-icon-action-tags { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-nav-tag.png"); +} +.plm-icon-action-workflow-new { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-workflow-new.png"); +} +.plm-icon-action-workflow-roles { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-workflow-roles.png"); +} +.plm-icon-action-new-version { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-document-new-version.png"); +} +.plm-icon-action-new-release { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-document-new-release.png"); +} +.plm-icon-action-mark-as-obsolete { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-mark-as-obsolete.png"); +} +.plm-icon-action-acl { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-document-acl.png"); +} +.plm-icon-action-milestone-new { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-milestone-new.png"); +} +.plm-icon-action-issue-new { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-issue-new.png"); +} +.plm-icon-action-request-new { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-request-new.png"); +} +.plm-icon-action-order-new { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-order-new.png"); +} +.plm-icon-action-lov { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-lov.png"); +} +.plm-icon-menu-edit { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 0 8px 0 0; + background-image: url("../images/icon-menu-edit.png"); +} +.plm-icon-menu-delete { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 0 8px 0 0; + background-image: url("../images/icon-menu-edit-delete.png"); +} +.plm-icon-menu-folder-new { + background: no-repeat center; + display: block; + height: 22px; + width: 22px; + margin: 0 8px 0 0; + background-image: url("../images/icon-menu-folder-new.png"); +} +.modal-backdrop { + position: absolute; +} +.modal { + position: absolute; + max-height: none; + overflow: visible; +} +.modal .modal-body { + max-height: 421px; + overflow: auto; +} +.modal .modal-body .tab-content { + overflow: visible; +} +.modal.no-overflow .modal-body { + overflow: visible; +} +div.modal-header i { + float: left; + margin: 0 8px 0 0 !important; + line-height: 28px; + font-size: 20px; +} +div.modal-header span.icon { + float: left; + margin: 0 8px 0 0 !important; +} +.document-modal .modal-body { + height: 421px; +} +.document-modal .pager { + margin: 0; +} +.document-modal .fa-pencil { + cursor: pointer; +} +.Modal { + position: absolute; + max-height: none; +} +.Modal-body { + height: 421px; + max-height: 421px; + overflow: auto; +} +.no-overflow .Modal-body { + overflow: visible; +} +.ModalHeader-icon { + float: left; + margin: 0 8px 0 0 !important; +} +.ModalHeader i { + line-height: 28px; + font-size: 20px; +} +.ModalBody-tabs { + height: 100%; +} +.ModalTabs-form { + height: calc(100% - 64px); +} +.ModalTabsContent, +.ModalTabsPane { + height: 100%; + overflow: auto; +} +#header.Header { + position: absolute; + right: 0; + left: 0; + z-index: 1030; + min-height: 40px; + border-width: 0; +} +#header.Header.loaded { + box-shadow: 0px 1px 3px #9b9b9b; +} +#header .Header-collapseBtn { + position: absolute; + z-index: 101; + right: 5px; +} +#header .Header-collapseBtn.btn-navbar { + background: #2b426a; + background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #1a273f), color-stop(1, #375487)); + background: -ms-linear-gradient(bottom, #1a273f, #375487); + background: -moz-linear-gradient(center bottom, #1a273f 0%, #375487 100%); + background: -o-linear-gradient(#375487, #1a273f); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#375487', endColorstr='#1a273f', GradientType=0); + background-image: linear-gradient(top, #1a273f, #375487); + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); +} +#header .Header-content { + z-index: 100; + padding: 0 10px; +} +#header .Brand { + position: relative; + z-index: 10; + float: left; + display: block; + margin-left: -20px; + padding: 0px 20px 0px; + font-size: 20px; + font-weight: 200; + color: #ffffff; + text-shadow: 0 1px 0 #3f5f99; +} +#header .Brand-logo { + float: left; + display: inline-block; + height: 30px; + width: 30px; + margin: 5px; +} +#header .Brand-name { + display: inline-block; + padding-top: 11px; + padding-bottom: 11px; + line-height: 18px; +} +#header .HeaderContent { + background: #334d7c; + background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #213251), color-stop(1, #3f5f99)); + background: -ms-linear-gradient(bottom, #213251, #3f5f99); + background: -moz-linear-gradient(center bottom, #213251 0%, #3f5f99 100%); + background: -o-linear-gradient(#3f5f99, #213251); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#3f5f99', endColorstr='#213251', GradientType=0); + background-image: linear-gradient(top, #213251, #3f5f99); + border-bottom: 1px solid #0f1726; +} +#header .HeaderFluidContent { + padding: 0; +} +#header .nav > .dropdown > .dropdown-toggle { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +#header .HeaderMenu, +#header .BreadcrumbMenu { + position: relative; + left: 0; + display: block; +} +#header .HeaderMenu-item .dropdown-toggle:hover, +#header .BreadcrumbMenu-item .dropdown-toggle:hover { + background-color: #213251; +} +#header .HeaderMenu-item.open .dropdown-toggle, +#header .BreadcrumbMenu-item.open .dropdown-toggle { + background-color: transparent; +} +#header .HeaderMenu-item.open .dropdown-toggle:hover, +#header .BreadcrumbMenu-item.open .dropdown-toggle:hover { + background-color: #213251; +} +#header .HeaderMenu-item i, +#header .BreadcrumbMenu-item i { + margin-top: 1px; +} +#header .BreadcrumbMenu { + margin: 0 10px 0 0; +} +#header .BreadcrumbMenu-item:first-child > a { + border-left: 1px; + border-top-left-radius: 10px; + border-bottom-left-radius: 10px; +} +#header .BreadcrumbMenu-item:last-child > a { + border-right: 1px; + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; +} +#header .BreadcrumbMenu-item:first-child > a:before, +#header .BreadcrumbMenu-item:last-child > a:after { + display: none; +} +#header .BreadcrumbMenuItem { + padding: 8px 0px; + border-radius: 0; +} +#header .BreadcrumbMenuItem-link { + padding: 4px 16px; + background-color: #334d7c; + border: 1px solid #213251; + border-right: 0px; + border-left: 0px; + line-height: 14px; +} +#header .BreadcrumbMenuItem-link:hover:after { + border-left: 8px solid #213251; +} +#header .BreadcrumbMenuItem-link:before, +#header .BreadcrumbMenuItem-link:after { + content: ""; + position: absolute; + top: 12px; + border-top: 8px solid transparent; + border-bottom: 8px solid transparent; +} +#header .BreadcrumbMenuItem-link:before { + left: -2px; + border-left: 8px solid #d4f2ff; +} +#header .BreadcrumbMenuItem-link:after { + right: -3px; + z-index: 1; + border-left: 8px solid #334d7c; +} +#header .BreadcrumbMenuList { + padding: 6px 0; + line-height: 22px; +} +#header .BreadcrumbMenuList-sectionTitle { + padding-left: 8px; + font-weight: bold; +} +#header .BreadcrumbMenuList-hr { + margin: 5px 0; +} +#header .BreadcrumbMenuList i.fa-check { + margin-left: -14px; +} +#header .HeaderDropdownMenu > li.dropdown-section-title { + padding-left: 8px; +} +#header .HeaderDropdownMenu > li.dropdown-hr hr { + margin: 5px 0; +} +#header .HeaderDropdownMenu > li i.fa-check { + margin-left: -14px; +} +@media (max-width: 979px) { + #header .nav-collapse .dropdown-menu a { + color: white; + } + #header .nav-collapse + div { + clear: both; + } + #header .HeaderMenu-item .caret, + #header .BreadcrumbMenu-item .caret { + display: none; + } + #header .BreadcrumbMenu { + margin: 0px; + } + #header .BreadcrumbMenu-item > a { + padding: 9px 15px; + background-color: transparent; + line-height: 20px; + } + #header .BreadcrumbMenu-item:first-child > a, + #header .BreadcrumbMenu-item:last-child > a { + border: 1px solid transparent; + border-radius: 3px; + } + #header .BreadcrumbMenuItem-link:before, + #header .BreadcrumbMenuItem-link:after { + display: none; + } + #header .BreadcrumbMenuList { + color: white; + } + #header .BreadcrumbMenuList-sectionTitle { + display: none; + } + #header .BreadcrumbMenuList-hr { + display: none; + } + #header .BreadcrumbSubMenuItem { + font-weight: normal; + } + #header .BreadcrumbSubMenuItem-title { + display: inline-block; + float: left; + width: calc(100% - 155px); + height: 18px; + padding: 9px 15px; + color: white; + line-height: 18px; + } + #header .BreadcrumbSubMenuItem-title i { + color: rgba(255, 255, 255, 0.5); + } + #header .BreadcrumbSubMenuList { + display: inline-block; + } + #header .BreadcrumbSubMenuList li { + float: left; + list-style-type: none; + } + #header .BreadcrumbSubMenuList li a { + width: 13px; + height: 13px; + padding: 10px; + margin: 0; + } +} +@media (max-width: 767px) { + #header.Header { + margin: 0; + } +} +#content { + z-index: 0; + top: 41px; + margin: 0; + padding: 0; + bottom: 0; + position: absolute; + left: 0; + right: 0; +} +#content #content-loader-indicator { + position: absolute; + top: 40%; + left: 50%; + margin-left: -32px; + margin-top: -32px; + width: 64px; + height: 64px; + border-radius: 64px; + background: #ffffff; + transform-origin: 32px 32px; + animation: loaderAnimation 1s linear infinite; + -webkit-animation: loaderAnimation 1s linear infinite; + -moz-animation: loaderAnimation 1s linear infinite; + -o-animation: loaderAnimation 1s linear infinite; +} +#content .dataTables_wrapper .dataTables_filter { + float: left; +} +#content table { + border: none; +} +@keyframes loaderAnimation { + 0% { + transform: scale(0, 0); + } + 100% { + transform: scale(1, 1); + opacity: 0; + } +} +@-webkit-keyframes loaderAnimation { + 0% { + transform: scale(0, 0); + } + 100% { + transform: scale(1, 1); + opacity: 0; + } +} +@-moz-keyframes loaderAnimation { + 0% { + transform: scale(0, 0); + } + 100% { + transform: scale(1, 1); + opacity: 0; + } +} +@-o-keyframes loaderAnimation { + 0% { + transform: scale(0, 0); + } + 100% { + transform: scale(1, 1); + opacity: 0; + } +} +.has-switch { + display: inline-block; + cursor: pointer; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + border: 1px solid; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + position: relative; + text-align: left; + overflow: hidden; + line-height: 8px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + min-width: 100px; +} +.has-switch.switch-mini { + min-width: 72px; +} +.has-switch.switch-small { + min-width: 80px; +} +.has-switch.switch-large { + min-width: 120px; +} +.has-switch.deactivate { + opacity: 0.5; + filter: alpha(opacity=50); + cursor: default !important; +} +.has-switch.deactivate label, +.has-switch.deactivate span { + cursor: default !important; +} +.has-switch > div { + display: inline-block; + width: 150%; + position: relative; + top: 0; +} +.has-switch > div.switch-animate { + -webkit-transition: left 0.5s; + -moz-transition: left 0.5s; + -o-transition: left 0.5s; + transition: left 0.5s; +} +.has-switch > div.switch-off { + left: -50%; +} +.has-switch > div.switch-on { + left: 0%; +} +.has-switch input[type=checkbox] { + display: none; +} +.has-switch span, +.has-switch label { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + cursor: pointer; + position: relative; + display: inline-block; + height: 100%; + padding-bottom: 4px; + padding-top: 4px; + font-size: 14px; + line-height: 20px; +} +.has-switch span.switch-mini, +.has-switch label.switch-mini { + padding-bottom: 4px; + padding-top: 4px; + font-size: 10px; + line-height: 9px; +} +.has-switch span.switch-small, +.has-switch label.switch-small { + padding-bottom: 3px; + padding-top: 3px; + font-size: 12px; + line-height: 18px; +} +.has-switch span.switch-large, +.has-switch label.switch-large { + padding-bottom: 9px; + padding-top: 9px; + font-size: 16px; + line-height: normal; +} +.has-switch label { + text-align: center; + margin-top: -1px; + margin-bottom: -1px; + z-index: 100; + width: 34%; + border-left: 1px solid #bbbbbb; + border-right: 1px solid #bbbbbb; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #f5f5f5; + background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); + background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); + background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); + border-color: #e6e6e6 #e6e6e6 #bfbfbf; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #e6e6e6; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +.has-switch label:hover, +.has-switch label:active, +.has-switch label.active, +.has-switch label.disabled, +.has-switch label[disabled] { + color: #ffffff; + background-color: #e6e6e6; + *background-color: #d9d9d9; +} +.has-switch label:active, +.has-switch label.active { + background-color: #cccccc \9; +} +.has-switch label i { + color: #000; + text-shadow: 0 1px 0 #fff; + line-height: 18px; + pointer-events: none; +} +.has-switch span { + text-align: center; + z-index: 1; + width: 33%; +} +.has-switch span.switch-left { + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + border-top-left-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + border-bottom-left-radius: 4px; +} +.has-switch span.switch-right { + color: #333333; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + background-color: #f0f0f0; + background-image: -moz-linear-gradient(top, #e6e6e6, #ffffff); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#e6e6e6), to(#ffffff)); + background-image: -webkit-linear-gradient(top, #e6e6e6, #ffffff); + background-image: -o-linear-gradient(top, #e6e6e6, #ffffff); + background-image: linear-gradient(to bottom, #e6e6e6, #ffffff); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe6e6e6', endColorstr='#ffffffff', GradientType=0); + border-color: #ffffff #ffffff #d9d9d9; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #ffffff; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +.has-switch span.switch-right:hover, +.has-switch span.switch-right:active, +.has-switch span.switch-right.active, +.has-switch span.switch-right.disabled, +.has-switch span.switch-right[disabled] { + color: #333333; + background-color: #ffffff; + *background-color: #f2f2f2; +} +.has-switch span.switch-right:active, +.has-switch span.switch-right.active { + background-color: #e6e6e6 \9; +} +.has-switch span.switch-primary, +.has-switch span.switch-left { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #005fcc; + background-image: -moz-linear-gradient(top, #0044cc, #0088cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0044cc), to(#0088cc)); + background-image: -webkit-linear-gradient(top, #0044cc, #0088cc); + background-image: -o-linear-gradient(top, #0044cc, #0088cc); + background-image: linear-gradient(to bottom, #0044cc, #0088cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0044cc', endColorstr='#ff0088cc', GradientType=0); + border-color: #0088cc #0088cc #005580; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #0088cc; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +.has-switch span.switch-primary:hover, +.has-switch span.switch-left:hover, +.has-switch span.switch-primary:active, +.has-switch span.switch-left:active, +.has-switch span.switch-primary.active, +.has-switch span.switch-left.active, +.has-switch span.switch-primary.disabled, +.has-switch span.switch-left.disabled, +.has-switch span.switch-primary[disabled], +.has-switch span.switch-left[disabled] { + color: #ffffff; + background-color: #0088cc; + *background-color: #0077b3; +} +.has-switch span.switch-primary:active, +.has-switch span.switch-left:active, +.has-switch span.switch-primary.active, +.has-switch span.switch-left.active { + background-color: #006699 \9; +} +.has-switch span.switch-info { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #41a7c5; + background-image: -moz-linear-gradient(top, #2f96b4, #5bc0de); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#2f96b4), to(#5bc0de)); + background-image: -webkit-linear-gradient(top, #2f96b4, #5bc0de); + background-image: -o-linear-gradient(top, #2f96b4, #5bc0de); + background-image: linear-gradient(to bottom, #2f96b4, #5bc0de); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2f96b4', endColorstr='#ff5bc0de', GradientType=0); + border-color: #5bc0de #5bc0de #28a1c5; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #5bc0de; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +.has-switch span.switch-info:hover, +.has-switch span.switch-info:active, +.has-switch span.switch-info.active, +.has-switch span.switch-info.disabled, +.has-switch span.switch-info[disabled] { + color: #ffffff; + background-color: #5bc0de; + *background-color: #46b8da; +} +.has-switch span.switch-info:active, +.has-switch span.switch-info.active { + background-color: #31b0d5 \9; +} +.has-switch span.switch-success { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #58b058; + background-image: -moz-linear-gradient(top, #51a351, #62c462); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#51a351), to(#62c462)); + background-image: -webkit-linear-gradient(top, #51a351, #62c462); + background-image: -o-linear-gradient(top, #51a351, #62c462); + background-image: linear-gradient(to bottom, #51a351, #62c462); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff51a351', endColorstr='#ff62c462', GradientType=0); + border-color: #62c462 #62c462 #3b9e3b; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #62c462; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +.has-switch span.switch-success:hover, +.has-switch span.switch-success:active, +.has-switch span.switch-success.active, +.has-switch span.switch-success.disabled, +.has-switch span.switch-success[disabled] { + color: #ffffff; + background-color: #62c462; + *background-color: #4fbd4f; +} +.has-switch span.switch-success:active, +.has-switch span.switch-success.active { + background-color: #42b142 \9; +} +.has-switch span.switch-warning { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #f9a123; + background-image: -moz-linear-gradient(top, #f89406, #fbb450); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f89406), to(#fbb450)); + background-image: -webkit-linear-gradient(top, #f89406, #fbb450); + background-image: -o-linear-gradient(top, #f89406, #fbb450); + background-image: linear-gradient(to bottom, #f89406, #fbb450); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff89406', endColorstr='#fffbb450', GradientType=0); + border-color: #fbb450 #fbb450 #f89406; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #fbb450; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +.has-switch span.switch-warning:hover, +.has-switch span.switch-warning:active, +.has-switch span.switch-warning.active, +.has-switch span.switch-warning.disabled, +.has-switch span.switch-warning[disabled] { + color: #ffffff; + background-color: #fbb450; + *background-color: #faa937; +} +.has-switch span.switch-warning:active, +.has-switch span.switch-warning.active { + background-color: #fa9f1e \9; +} +.has-switch span.switch-danger { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #d14641; + background-image: -moz-linear-gradient(top, #bd362f, #ee5f5b); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#bd362f), to(#ee5f5b)); + background-image: -webkit-linear-gradient(top, #bd362f, #ee5f5b); + background-image: -o-linear-gradient(top, #bd362f, #ee5f5b); + background-image: linear-gradient(to bottom, #bd362f, #ee5f5b); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffbd362f', endColorstr='#ffee5f5b', GradientType=0); + border-color: #ee5f5b #ee5f5b #e51d18; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #ee5f5b; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +.has-switch span.switch-danger:hover, +.has-switch span.switch-danger:active, +.has-switch span.switch-danger.active, +.has-switch span.switch-danger.disabled, +.has-switch span.switch-danger[disabled] { + color: #ffffff; + background-color: #ee5f5b; + *background-color: #ec4844; +} +.has-switch span.switch-danger:active, +.has-switch span.switch-danger.active { + background-color: #e9322d \9; +} +.form-search .combobox-container, +.form-inline .combobox-container { + display: inline-block; + margin-bottom: 0; + vertical-align: top; +} +.form-search .combobox-container .input-group-addon, +.form-inline .combobox-container .input-group-addon { + width: auto; +} +.combobox-selected .caret { + display: none; +} +/* :not doesn't work in IE8 */ +.combobox-container:not(.combobox-selected) .glyphicon-remove { + display: none; +} +.typeahead-long { + max-height: 300px; + overflow-y: auto; +} +.control-group.error .combobox-container .add-on { + color: #B94A48; + border-color: #B94A48; +} +.control-group.error .combobox-container .caret { + border-top-color: #B94A48; +} +.control-group.warning .combobox-container .add-on { + color: #C09853; + border-color: #C09853; +} +.control-group.warning .combobox-container .caret { + border-top-color: #C09853; +} +.control-group.success .combobox-container .add-on { + color: #468847; + border-color: #468847; +} +.control-group.success .combobox-container .caret { + border-top-color: #468847; +} +.has-switch.custom-switch span.switch-custom-off { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #6785aa; + background-image: -moz-linear-gradient(top, #5b779b, #7a99c1); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5b779b), to(#7a99c1)); + background-image: -webkit-linear-gradient(top, #5b779b, #7a99c1); + background-image: -o-linear-gradient(top, #5b779b, #7a99c1); + background-image: linear-gradient(to bottom, #5b779b, #7a99c1); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5b779b', endColorstr='#ff7a99c1', GradientType=0); + border-color: #7a99c1 #7a99c1 #4c72a3; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #7a99c1; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +.has-switch.custom-switch span.switch-custom-off:hover, +.has-switch.custom-switch span.switch-custom-off:active, +.has-switch.custom-switch span.switch-custom-off.active, +.has-switch.custom-switch span.switch-custom-off.disabled, +.has-switch.custom-switch span.switch-custom-off[disabled] { + color: #ffffff; + background-color: #7a99c1; + *background-color: #698cb9; +} +.has-switch.custom-switch span.switch-custom-off:active, +.has-switch.custom-switch span.switch-custom-off.active { + background-color: #577eb1 \9; +} +.has-switch.custom-switch span.switch-custom-on { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #5b779b; + background-image: -moz-linear-gradient(top, #7a99c1, #2c4361); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#7a99c1), to(#2c4361)); + background-image: -webkit-linear-gradient(top, #7a99c1, #2c4361); + background-image: -o-linear-gradient(top, #7a99c1, #2c4361); + background-image: linear-gradient(to bottom, #7a99c1, #2c4361); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff7a99c1', endColorstr='#ff2c4361', GradientType=0); + border-color: #2c4361 #2c4361 #141f2c; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #2c4361; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +.has-switch.custom-switch span.switch-custom-on:hover, +.has-switch.custom-switch span.switch-custom-on:active, +.has-switch.custom-switch span.switch-custom-on.active, +.has-switch.custom-switch span.switch-custom-on.disabled, +.has-switch.custom-switch span.switch-custom-on[disabled] { + color: #ffffff; + background-color: #2c4361; + *background-color: #24374f; +} +.has-switch.custom-switch span.switch-custom-on:active, +.has-switch.custom-switch span.switch-custom-on.active { + background-color: #1c2b3e \9; +} +.btn { + padding: 4px 12px; + font-size: 13px; +} +.btn.btn-custom { + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #5b779b; + background-image: -moz-linear-gradient(top, #7a99c1, #2c4361); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#7a99c1), to(#2c4361)); + background-image: -webkit-linear-gradient(top, #7a99c1, #2c4361); + background-image: -o-linear-gradient(top, #7a99c1, #2c4361); + background-image: linear-gradient(to bottom, #7a99c1, #2c4361); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff7a99c1', endColorstr='#ff2c4361', GradientType=0); + border-color: #2c4361 #2c4361 #141f2c; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #ffffff; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.75); +} +.btn.btn-custom:hover, +.btn.btn-custom:active, +.btn.btn-custom.active, +.btn.btn-custom.disabled, +.btn.btn-custom[disabled] { + color: #fff; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.75); + background-color: #2c4361; + background-image: -moz-linear-gradient(top, #2c4361, #2c4361); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#2c4361), to(#2c4361)); + background-image: -webkit-linear-gradient(top, #2c4361, #2c4361); + background-image: -o-linear-gradient(top, #2c4361, #2c4361); + background-image: linear-gradient(to bottom, #2c4361, #2c4361); + background-repeat: repeat-x; +} +.btn.btn-custom:hover, +.btn.btn-custom:focus { + background-position: 0 0; +} +#footer { + border: none; + text-align: center; + clear: both; + margin-top: 20px; + margin-bottom: 20px; +} +#footer p { + font-size: 0.8em; +} +#footer a { + color: #0061c0; +} +.popover.left { + margin: -27px 0 0 -18px; +} +.popover.right { + margin: -27px 0 0 18px; +} +.popover.above-modal-popover { + z-index: 1060; +} +.popover.reach-user-popover { + background: #f5f5f5; +} +.popover.reach-user-popover h3.popover-title { + font-weight: bold; + background: #3F5F99; + background-image: -moz-linear-gradient(top, #3f5f99, #213251); + background-image: -webkit-linear-gradient(top, #3f5f99, #213251); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + height: 18px; + line-height: 18px; + text-shadow: none; + color: white; +} +.popover.reach-user-popover div.popover-content { + text-shadow: none; + color: #333; +} +.popover.reach-user-popover.small { + width: 20px; + height: 50px; +} +.popover.reach-user-popover.small h3.popover-title { + display: none; +} +#content table { + border-radius: 3px; + box-shadow: 0px 1px 5px #9b9b9b; + margin: 20px auto; + width: 96% !important; +} +#content table thead { + background-color: #5b779b; + background-image: -moz-linear-gradient(top, #7a99c1, #2c4361); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#7a99c1), to(#2c4361)); + background-image: -webkit-linear-gradient(top, #7a99c1, #2c4361); + background-image: -o-linear-gradient(top, #7a99c1, #2c4361); + background-image: linear-gradient(to bottom, #7a99c1, #2c4361); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff7a99c1', endColorstr='#ff2c4361', GradientType=0); + color: white; + text-shadow: #555555 0px -1px 0px; +} +#content table thead tr { + height: 40px; + font-size: 13px; +} +#content table thead tr th { + vertical-align: middle; + padding: 0 .5em; +} +#content table thead tr th:first-child { + border-radius: 3px 0 0 0; +} +#content table thead tr th:last-child { + border-radius: 0 3px 0 0; +} +#content table tbody { + color: #62697B; + font-size: 12px; +} +#content table tbody tr { + height: 35px; +} +#content table tbody tr td { + padding: 0 .5em; + vertical-align: middle; +} +#content table tbody tr td.reference, +#content table tbody tr td.part_number, +#content table tbody tr td.product_id, +#content table tbody tr td.configuration_id, +#content table tbody tr td.author_login { + cursor: pointer; + font-weight: bold; +} +#content table tbody tr td.reference i, +#content table tbody tr td.part_number i, +#content table tbody tr td.product_id i, +#content table tbody tr td.configuration_id i, +#content table tbody tr td.author_login i { + font-size: 14px; + margin-right: 4px; + text-align: center; + display: inline-block; + width: 14px; +} +#content table tbody tr td.reference:hover, +#content table tbody tr td.part_number:hover, +#content table tbody tr td.product_id:hover { + color: #333; +} +#content table tbody tr td:first-child { + horizontal-align: center; +} +#content table.table-striped tbody tr td { + background: transparent; +} +#content table.table-striped tbody tr:nth-child(odd) { + background: #f5f5f5; +} +#content table.table-striped tbody tr:nth-child(odd):hover { + background: #eee; +} +#content table.table-striped tbody tr:nth-child(even) { + background: white; +} +#content table.table-striped tbody tr:nth-child(even):hover { + background: #eee; +} +.dataTables_wrapper .dataTables_filter { + margin: 10px 0 0 2%; +} +.dataTables_wrapper .dataTables_filter label { + color: #62697B; +} +.dataTables_wrapper .dataTables_filter label input { + margin: 8px; + height: 14px!important; + background: #f5f5f5; +} +.dataTables_wrapper table.dataTable { + margin: 0 auto 20px auto!important; +} +.dataTables_wrapper table.dataTable thead th, +.dataTables_wrapper table.dataTable thead td { + padding-right: 14px!important; + cursor: pointer; +} +.dataTables_wrapper table.dataTable thead th:active, +.dataTables_wrapper table.dataTable thead td:active { + outline: none; +} +.dataTables_wrapper table.dataTable thead th.sorting, +.dataTables_wrapper table.dataTable thead td.sorting { + background: none; +} +.dataTables_wrapper table.dataTable thead th.sorting:hover, +.dataTables_wrapper table.dataTable thead td.sorting:hover { + background: url('../images/sort_both.png') no-repeat center right; + text-decoration: underline; +} +.dataTables_wrapper table.dataTable thead th.sorting_asc, +.dataTables_wrapper table.dataTable thead td.sorting_asc { + background: url('../images/sort_asc.png') no-repeat center right; + text-decoration: underline; +} +.dataTables_wrapper table.dataTable thead th.sorting_desc, +.dataTables_wrapper table.dataTable thead td.sorting_desc { + background: url('../images/sort_desc.png') no-repeat center right; + text-decoration: underline; +} +.actions { + min-height: 48px; + border-radius: 0; + -webkit-box-shadow: 0 0px 3px #9b9b9b; + -moz-box-shadow: 0 0px 3px #9b9b9b; + box-shadow: 0 0px 3px #9b9b9b; + margin: 0; + padding: 0 20px; + border: none; +} +.actions > .btn { + height: 32px; + margin: 8px 8px 8px 8px; +} +.actions .btn-group { + margin: 8px; +} +.actions .btn-group .btn { + float: none; +} +.actions .new-document .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-document-new.png"); +} +.actions .new-template .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-template-new.png"); +} +.actions .new-product .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-product-new.png"); +} +.actions .new-configuration .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-configuration-new.png"); +} +.actions .udf .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-udf.png"); +} +.actions .new-part .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-part-new.png"); +} +.actions .new-workflow .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-workflow-new.png"); +} +.actions .new-issue .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-issue-new.png"); +} +.actions .new-request .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-request-new.png"); +} +.actions .new-order .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-order-new.png"); +} +.actions .new-milestone .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-milestone-new.png"); +} +.actions .new-product-instance .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-new-product-intances.png"); +} +.actions .roles .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-workflow-roles.png"); +} +.actions .list-lov .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-lov.png"); +} +.actions .delete { + display: none; +} +.actions .delete .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-delete.png"); +} +.actions .duplicate { + display: none; +} +.actions .duplicate .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-duplicate.png"); +} +.actions .new-baseline { + display: none; +} +.actions .new-baseline .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-new-baseline.png"); +} +.actions .new-version { + display: none; +} +.actions .new-version .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-document-new-version.png"); +} +.actions .new-release { + display: none; +} +.actions .new-release .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-document-new-release.png"); +} +.actions .mark-as-obsolete { + display: none; +} +.actions .mark-as-obsolete .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-mark-as-obsolete.png"); +} +.actions .edit-acl { + display: none; +} +.actions .edit-acl .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-document-acl.png"); +} +.actions .checkout-group { + display: none; +} +.actions .checkout .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-document-checkout.png"); +} +.actions .undocheckout .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-document-undocheckout.png"); +} +.actions .checkin .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-action-document-checkin.png"); +} +.actions .tags { + display: none; +} +.actions .tags .icon { + background: no-repeat center; + display: block; + height: 22px; + margin: 0 8px; + width: 22px; + background-image: url("../images/icon-nav-tag.png"); +} +.form-horizontal .control-label { + padding-top: 0; +} +#product_creation_modal .modal-body { + overflow: inherit; +} +.attributes-edit .add { + margin-bottom: 12px; +} +.attributes-edit .sortable-handler { + opacity: 0.2; + cursor: move; +} +.attributes-edit .fa.fa-bars.sortable-handler.invisible { + visibility: hidden; +} +.attributes-edit .fa.fa-bars.sortable-handler.shift-margin { + margin-right: 15px; +} +.attributes-edit .fa-times { + float: right; + font-size: 15px; + line-height: 18px; + color: black; + text-shadow: 0 1px 0 white; + opacity: 0.2; +} +.attributes-edit .fa-times:hover { + text-decoration: none; + color: #F00; + opacity: 1; +} +.attributes-edit .fa.fa-times.invisible { + visibility: hidden; +} +.attributes-edit .list-item { + padding: 6px; + margin-bottom: 6px; +} +.attributes-edit .list-item > * { + display: inline-block; + vertical-align: middle; + margin: 0 2px; +} +.attributes-edit .list-item > * > .value { + max-width: 153px; +} +.attributes-edit .list-item > * > select { + max-width: 134px; +} +.attributes-edit .list-item > * > .name { + width: auto; + max-width: 109px; +} +.attributes-edit .list-item.highlight { + border: 1px dashed #0088cc; +} +.attributes-edit .controls.boolean > label { + height: 12px; +} +.attributes-edit .controls.boolean > label > input:checked ~ .false { + display: none; +} +.attributes-edit .controls.boolean > label > input:not(:checked) ~ .true { + display: none; +} +.attributes-edit .controls.boolean input[type="checkbox"].input-xlarge.value { + height: 18px; +} +.attributes-edit i.fa + div.controls > .input-xlarge.name { + max-width: 100px; +} +.attributes-edit .controls input[type="date"] { + display: -webkit-inline-box; + max-width: 153px; +} +.attributes-edit .controls.shift-margin { + margin-left: 3px; +} +.attributes-edit .controls input[disabled="disabled"].input-xlarge.name { + margin-right: 9px; + max-width: 120px; + padding-left: 5px; +} +.attributes-edit .controls select.lovItemValues { + max-width: 167px; + width: auto; +} +.attributes-edit .controls.listOfValues { + width: 124px; + margin-left: 12px; +} +.attributes-edit .attribute-link { + word-break: break-all; +} +.attributes-edit div.type { + width: 126px; + max-width: 139px; + text-transform: capitalize; + margin: 0 1px 0 0; +} +.attributes-edit input[disabled] { + padding: 4px 0; + border: none; + background: none; + box-shadow: none; +} +.attributes-edit input[disabled].name { + font-weight: bold; +} +div.attribute-edit { + display: inline-block; +} +div.attribute-edit select { + width: 80px; +} +div.attribute-edit input { + width: 170px; +} +div.attribute-edit input.boolean { + width: 80px; +} +div.attribute-edit div { + display: inline-block; +} +div.attribute-edit div.stroke { + color: red; +} +div.attribute-edit div.stroke div.type { + width: 80px; +} +div.attribute-edit div.stroke div { + text-decoration: line-through; + width: 170px; +} +div.attachedFiles div.progress .bar { + color: #333; +} +div.attachedFiles .notifications { + margin-top: 10px; +} +div.attachedFiles h4 { + font-size: 13px; +} +div.attachedFiles.idle div.progress { + width: 0%; + display: none; +} +div.attachedFiles.idle button.cancel-upload-btn { + display: none; +} +div.attachedFiles.idle p.upload-file-shortname { + display: none; +} +div.attachedFiles.uploading input.upload-btn { + display: none; +} +div.attachedFiles.uploading div.filedroparea { + display: none; +} +div.attachedFiles form.upload-form { + position: relative; + margin: 20px 0 0 0; +} +div.attachedFiles input.upload-btn { + cursor: pointer; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + line-height: normal; + opacity: 0; +} +div.attachedFiles span.droppable { + margin: 0 0 0 18px; +} +div.attachedFiles .filedroparea { + position: relative; + font-weight: bold; + padding: 20px; + margin: 10px 0 0 0; + color: #555; + border: 2px dashed #555; + border-radius: 7px; + cursor: pointer; +} +div.attachedFiles .filedroparea.hover { + color: #555; + border-color: #9acd32; + border-style: solid; + box-shadow: inset 0 1px 4px #888; +} +div.attachedFiles .filedroparea input { + cursor: pointer; +} +div.attachedFiles input[type="checkbox"].file-check { + margin: 0 0 3px 0; +} +div.attachedFiles li { + margin: 4px; +} +div.attachedFiles li ul.file-list { + margin: 0; +} +div.attachedFiles li .stroke { + text-decoration: line-through; + color: red; +} +div.attachedFiles li .stroke, +div.attachedFiles li a.stroke, +div.attachedFiles li a.stroke { + text-decoration: line-through; + color: red; +} +div.attachedFiles li .edit-name { + cursor: pointer; +} +div.attachedFiles li .edit-name:hover { + color: #0088cc; +} +div.attachedFiles li.edition .filename-edit-form { + font-size: 13px; + display: inline-block; +} +div.attachedFiles li.edition .filename-edit-form i { + cursor: pointer; +} +div.attachedFiles li.edition .filename-edit-form .validate-name { + color: #33ae24; +} +div.attachedFiles li.edition .filename-edit-form .cancel-name { + color: #ff5622; +} +div.attachedFiles li.edition .edit-name, +div.attachedFiles li.edition .fileName { + display: none; +} +div.attachedFiles li .filename-edit-form { + display: none; +} +div.attachedFiles li .edit-name, +div.attachedFiles li .fileName { + display: inline-block; +} +div.attachedFiles li input[name=filename] { + line-height: 18px; + height: 19px; + font-size: 13px; + padding: 0; + border: none; +} +div.attachedFiles input.file-delete { + margin: 5px; +} +.dropdown-menu { + min-width: 140px; +} +.dropdown-menu li > a { + line-height: 18px; + height: 18px; + padding: 3px 16px; +} +.dropdown-menu li > a:hover { + color: #fff; + background-color: #147be8; + background-image: -moz-linear-gradient(top, #147be8, #147be8); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#147be8), to(#147be8)); + background-image: -webkit-linear-gradient(top, #147be8, #147be8); + background-image: -o-linear-gradient(top, #147be8, #147be8); + background-image: linear-gradient(to bottom, #147be8, #147be8); +} +.dropdown-menu .divider { + margin: 5px 1px; +} +.dropdown-menu.large-entries li > a { + line-height: 30px; + height: 30px; +} +.dropdown-menu.large-entries li > a:hover { + color: #111; + background: #eee; +} +div.linked-items-view label.reference-typeahead-label { + font-weight: bold; +} +div.linked-items-view button#toggle-links-edit-mode { + float: right; +} +div.linked-items-view div.change-items-legend { + padding: 10px 0px; +} +div.linked-items-view div.change-items-legend span { + margin-right: 10px; +} +div.linked-items-view div.change-items-legend span:before { + content: ""; + display: inline-block; + width: 5px; + height: 5px; + margin: 0px 2px; + border: 2px solid #000; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + border-radius: 50%; +} +div.linked-items-view div.change-items-legend span.change-items-low:before { + background: #5cb85c; +} +div.linked-items-view div.change-items-legend span.change-items-medium:before { + background: #5bc0de; +} +div.linked-items-view div.change-items-legend span.change-items-high:before { + background: #f0ad4e; +} +div.linked-items-view div.change-items-legend span.change-items-emergency:before { + background: #d9534f; +} +div.linked-items-view ul.linked-items { + margin: 12px 0; + list-style: none; +} +div.linked-items-view ul.linked-items li.linked-item { + padding: 6px 4px 2px 4px; + margin: 0 12px 12px 0; + display: inline-block; +} +div.linked-items-view ul.linked-items li.linked-item.priorityColor-LOW { + background-color: #5cb85c; + color: white; +} +div.linked-items-view ul.linked-items li.linked-item.priorityColor-MEDIUM { + background-color: #5bc0de; + color: white; +} +div.linked-items-view ul.linked-items li.linked-item.priorityColor-HIGH { + background-color: #f0ad4e; + color: white; +} +div.linked-items-view ul.linked-items li.linked-item.priorityColor-EMERGENCY { + background-color: #d9534f; + color: white; +} +div.linked-items-view ul.linked-items li.linked-item .delete-linked-item { + font-size: 15px; + color: #333; + opacity: 0.2; +} +div.linked-items-view ul.linked-items li.linked-item .delete-linked-item:hover { + text-decoration: none; + opacity: 0.8; + color: #333; +} +div.linked-items-view ul.linked-items li.linked-item .edit-linked-item-comment { + color: #333; + opacity: 0.8; +} +div.linked-items-view ul.linked-items li.linked-item .reference { + padding: 0 6px; + font-weight: bold; + margin: 0; + display: inline-block; +} +div.linked-items-view ul.linked-items li.linked-item div[name="comment"] { + display: none; +} +div.linked-items-view ul.linked-items li.linked-item span.documentTitle { + font-weight: bold; +} +div.linked-items-view ul.linked-items li.linked-item span.comment { + display: block; + font-style: italic; + color: #333; + opacity: 0.8; +} +div.linked-items-view ul.linked-items li.linked-item i.delete-comment { + color: #ff5622; +} +div.linked-items-view ul.linked-items li.linked-item i.validate-comment { + color: #33ae24; +} +div.linked-items-view ul.linked-items li.linked-item.edition div[name="comment"] { + display: block; +} +div.linked-items-view ul.linked-items li.linked-item.edition span.comment { + display: none; +} +div.group-title { + font-weight: bold; +} +div.used-by-items-view ul.used-by-items { + margin: 12px 0; + list-style: none; +} +div.used-by-items-view ul.used-by-items li.used-by-item { + padding: 6px 4px 2px 4px; + margin: 0 12px 12px 0; + display: inline-block; +} +div.used-by-items-view ul.used-by-items li.used-by-item .reference { + padding: 0 6px; + font-weight: bold; + margin: 0; + display: inline-block; +} +div.used-by-items-view ul.used-by-items li.used-by-item .reference-path { + padding: 0 6px; + font-weight: normal; + margin: 0; +} +div#iteration-modification-notifications div.modification-notification-group { + margin-top: 10px; + padding: 10px; + padding-bottom: 0px; +} +div#iteration-modification-notifications div.group-title { + font-weight: bold; +} +div#iteration-modification-notifications div.modification-notification { + padding: 10px; + padding-right: 0px; +} +div#iteration-modification-notifications i.fa-circle { + float: left; + margin-top: 3px; + color: #62697B; +} +div#iteration-modification-notifications div.modification-notification-details { + margin-left: 20px; +} +div#iteration-modification-notifications div.group-comment input#group-acknowledgement-comment { + width: calc(100% - 179px); +} +div#iteration-modification-notifications div.comment input#acknowledgement-comment { + width: calc(100% - 152px); +} +div#iteration-modification-notifications div.comment-italic { + font-style: italic; +} +#part-modal .checkbox.lock { + display: none; +} +#part-modal #tab-part-links { + min-height: 280px; +} +#part-modal #tab-part-files .conversion-status { + margin: 18px 0 0 4px; +} +#part-modal #tab-part-files .conversion-status button.reload { + float: right; +} +#part-modal #tab-part-attributes p { + font-weight: bold; +} +#part-modal .pager { + margin: 0; +} +#part-modal .substitute-parts { + margin: 18px 20px 0px 21px; +} +#part-modal .display-substitute-part { + margin-left: 6px; + color: rgba(103, 103, 103, 0.5); +} +#part-modal .subParts-CADInstance, +#part-modal .data-sub-part { + display: none; +} +#iteration-components { + min-height: 250px; +} +#iteration-components input#create-part-revision-as-part-usage-link, +#iteration-components input#create-part-revision-as-part-substitute-link { + float: left; +} +#iteration-components input#part-usage-link-type-ahead, +#iteration-components input#part-substitute-link-type-ahead { + float: right; +} +#iteration-components .create-part-usage-link-menu { + display: block; +} +#iteration-components .create-part-substitute-link-menu { + display: none; +} +#iteration-components.component-selected .create-part-usage-link-menu { + display: none; +} +#iteration-components.component-selected .create-part-substitute-link-menu { + display: block; +} +#iteration-components .components { + margin-top: 20px; +} +#iteration-components .component { + border: 2px solid transparent; + border-radius: 2px; + position: relative; + float: left; + padding: 9px; + width: 95%; + margin-bottom: 4px; +} +#iteration-components .component.part-substitute-link { + width: 86%; + display: inline-block; + border: 1px dashed #a1a0a0; + background-color: rgba(237, 237, 237, 0.61); + margin: 0 22px; +} +#iteration-components .component:hover { + background-color: rgba(193, 193, 193, 0.61); + cursor: pointer; +} +#iteration-components .component.selected { + opacity: 1; + background-color: rgba(193, 193, 193, 0.61); + border: 2px dashed #ACACAC; + box-shadow: none; +} +#iteration-components .component a.part-substitute-links-count { + display: table; + margin: 8px 0 0 22px; +} +#iteration-components .component a.remove, +#iteration-components .component a.removeSub { + margin: 5px; + float: left; + font-size: 20px; + font-weight: bold; + line-height: 18px; + color: #000000; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.2; +} +#iteration-components .component a.remove:hover, +#iteration-components .component a.removeSub:hover { + text-decoration: none; + color: #F00; + opacity: 1; + cursor: hand; +} +#iteration-components .component input[name="number"] { + width: 160px; +} +#iteration-components .component input[name="name"] { + width: 160px; +} +#iteration-components .component input[name="amount"] { + margin-top: 8px; + width: 61px; +} +#iteration-components .component input[name="reference-description"] { + margin: 8px 0 0 22px; + width: 160px; +} +#iteration-components .component input[name="optional"] { + margin: -1px 10px 0 5px; +} +#iteration-components .component .selectize-control { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + width: 61px; + margin-top: 14px; +} +#iteration-components .component .selectize-control > div { + padding: 4px; +} +#iteration-components .component .selectize-control .selectize-input:after { + right: 6px; +} +#iteration-components .component label[name="optional"] { + display: inline; + vertical-align: middle; + padding-right: 15px; +} +#iteration-components .component > .toggle-cad-instances { + font-size: 18px; + cursor: pointer; + opacity: 0.5; + position: absolute; + bottom: 8px; + right: 12px; +} +#iteration-components .component > .toggle-cad-instances:hover { + opacity: 1; +} +#iteration-components .component .add-cad-instance, +#iteration-components .component .remove-cad-instance { + margin: 0 0 0 10px; + opacity: 0.4; +} +#iteration-components .component .add-cad-instance:hover, +#iteration-components .component .remove-cad-instance:hover { + cursor: pointer; + opacity: 1; +} +#iteration-components .component.cad-instances-opened > .cad-instances { + display: inline-table; +} +#iteration-components .component.cad-instances-opened > .toggle-cad-instances { + transform: rotate(180deg); +} +#iteration-components .component > .part-substitute-links { + display: none; +} +#iteration-components .component.substitutes-opened > .part-substitute-links { + display: block; +} +#iteration-components .component > .cad-instances { + margin: 10px 0 0 21px; + clear: both; + display: none; +} +#iteration-components .component > .cad-instances .cad-instance { + cursor: pointer; + float: left; + clear: both; +} +#iteration-components .component > .cad-instances .cad-instance h5 { + font-weight: normal; + cursor: not-allowed; +} +#iteration-components .component > .cad-instances .cad-instance .coord-group { + float: left; + width: 45%; +} +#iteration-components .component > .cad-instances .cad-instance .coord-group input.coord { + width: 22%; +} +#iteration-components .component > .cad-instances .cad-instance .coord-group select { + width: 90%; +} +#iteration-components .component > .cad-instances .cad-instance .coord-group .delete-cad-instance { + margin-top: 42px; + float: left; + cursor: pointer; + opacity: 0.5; +} +#iteration-components .component > .cad-instances .cad-instance .coord-group .delete-cad-instance:hover { + opacity: 1; +} +#iteration-components .component > .cad-instances .cad-instance .coord-group-remove { + width: 10%; + margin-top: 4px; +} +#iteration-components .component > .cad-instances .cad-instance:only-child .delete-cad-instance { + display: none; +} +#iteration-components .tooltip { + position: relative; + color: rgba(0, 0, 0, 0.4); + margin-right: 5px; + font-size: 14px; + text-decoration: none; + opacity: 1; +} +#iteration-components .tooltip:hover:after { + background: #333; + background: rgba(0, 0, 0, 0.8); + border-radius: 5px; + bottom: 26px; + color: #fff; + content: attr(data-tooltip); + left: 20%; + padding: 5px 15px; + position: absolute; + z-index: 98; + width: 220px; +} +#registration_link_container { + text-shadow: 0px -1px 0px black; + color: #CCC; + margin-top: 10px; +} +#registration_link_container a { + display: inline; +} +#form-roles .well.roles-item { + padding: 6px; + margin-bottom: 6px; +} +#form-roles .well.roles-item a { + margin: 0 3px; + font-size: 15px; + line-height: 18px; + color: black; + text-shadow: 0 1px 0 white; + opacity: 0.2; +} +#form-roles .well.roles-item a:hover { + text-decoration: none; + color: #9d261d; + opacity: 1; +} +#form-roles .well.roles-item input { + margin: 0 3px; +} +#form-roles .well.roles-item select { + margin: 0 3px; +} +.role-mapping .well.roles-item { + padding: 6px; + margin-bottom: 6px; +} +.role-mapping .well.roles-item a { + display: none; +} +.role-mapping .well.roles-item input { + margin: 0 3px; +} +.role-mapping .well.roles-item select { + margin: 0 3px; +} +.LifecycleModalTab-historyContent { + max-height: 68px; + overflow-y: scroll; + border: solid 1px lightblue; +} +.LifecycleModalTab-historyContent a:not(.active) { + color: #333; +} +.LifecycleModalLegend { + padding: 10px 0px; +} +.LifecycleModalLegend-line { + margin-right: 10px; +} +.LifecycleModalLegend-line:before { + content: ""; + display: inline-block; + width: 5px; + height: 5px; + margin: 0px 2px; + border: 2px solid #000; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + border-radius: 50%; +} +.incomplete.LifecycleModalLegend-line:before { + background: #aaaaaa; +} +.complete.LifecycleModalLegend-line:before { + background: #5bb75b; +} +.inProgress.LifecycleModalLegend-line:before { + background: royalblue; +} +.rejected.LifecycleModalLegend-line:before { + background: #ee5f5b; +} +.LifecycleActivitiesWrapper { + height: 100%; +} +#lifecycle-activities { + display: inline-flex; + white-space: nowrap; + padding: 0; + margin: 0; +} +#lifecycle-activities .activity { + display: inline-block; + float: left; + margin: 0 18px 0 0; + padding: 0 18px; + background: white; +} +#lifecycle-activities .activity.complete { + border: 3px solid #5bb75b; +} +#lifecycle-activities .activity.in_progress { + border: 3px dashed royalblue; +} +#lifecycle-activities .activity.rejected { + border: 3px solid #ee5f5b; +} +#lifecycle-activities .activity .tasks .task { + max-width: 200px; + margin-right: 5px; + padding: 9px 19px; +} +#lifecycle-activities .activity .tasks .task.not_started { + border: 1px dotted #aaaaaa; +} +#lifecycle-activities .activity .tasks .task.in_progress { + border: 2px dashed royalblue; +} +#lifecycle-activities .activity .tasks .task.approved { + border: 2px solid #5bb75b; +} +#lifecycle-activities .activity .tasks .task.rejected { + border: 2px solid #ee5f5b; +} +#lifecycle-activities .activity .tasks .task i { + cursor: pointer; + margin: 0 0 0 5px; + opacity: 0.5; +} +#lifecycle-activities .activity .tasks .task i:hover { + opacity: 1; + text-decoration: none; +} +#lifecycle-activities .activity .tasks .task i.fa-times:hover { + color: #F00; +} +#lifecycle-activities .activity .tasks .task i.fa-check:hover { + color: #33ae24; +} +#lifecycle-activities .activity .tasks .task h5 { + margin: 0; +} +#lifecycle-activities .activity .tasks .task h5.mark_task { + white-space: normal; +} +#lifecycle-activities .activity .tasks .task p { + word-wrap: break-word; + white-space: pre-line; +} +#lifecycle-activities .activity .tasks .task div.task-comment { + margin-top: 10px; + display: none; + border-top: 1px solid #aaaaaa; +} +#lifecycle-activities .activity .tasks .task div.task-comment.toggled { + display: block; +} +#lifecycle-activities .activity .tasks .task div.task-comment p { + margin: 10px 0; +} +#lifecycle-activities .activity .tasks .task .closure-comment { + margin-top: 10px; + display: none; + border-top: 1px solid #aaaaaa; +} +#lifecycle-activities .activity .tasks .task .closure-comment.toggled { + display: block; +} +#lifecycle-activities .activity .tasks .task .closure-comment h5 { + margin: 10px 0 ; +} +#lifecycle-activities .activity .tasks .task .closure-comment .closure-comment-form > div { + margin: 10px 0; +} +#lifecycle-activities .activity .tasks .task .closure-comment .closure-comment-form > div > input.input-medium { + width: 188px; +} +#lifecycle-activities .activity .tasks .task .closure-comment .closure-comment-form .task-buttons input { + float: right; +} +#lifecycle-activities .activity .tasks .task .closure-comment .closure-comment-form .task-signing i.clear-signing { + display: none; +} +#lifecycle-activities .activity .tasks .task .closure-comment .closure-comment-form .task-signing i.save-signing { + display: none; +} +#lifecycle-activities .activity .tasks .task .closure-comment .closure-comment-form .lifecycle-activities-canvas { + border: 1px solid #b5b5b5; + cursor: crosshair; +} +#lifecycle-activities .activity .tasks .task .closure-comment .closure-comment-form .lifecycle-activities-canvas:active { + cursor: crosshair; +} +#prompt_modal .control-label { + float: left; + width: 160px; + padding-top: 5px; + padding-right: 10px; + text-align: right; +} +#switch-iteration { + float: left; + margin-bottom: 0px; +} +.acl-info i.read-only, +.document-acl-info i.read-only, +.part-acl-info i.read-only, +.template-acl-info i.read-only, +.workflow-acl-info i.read-only { + color: #ffb600; +} +.acl-info i.full-access, +.document-acl-info i.full-access, +.part-acl-info i.full-access, +.template-acl-info i.full-access, +.workflow-acl-info i.full-access { + color: #388052; +} +.acl-item.well, +.membership-item.well { + padding: 6px; + margin-bottom: 6px; +} +.acl-item.well .acl-key, +.membership-item.well .acl-key { + box-shadow: none; + outline: none; + font-weight: bold; + width: 150px; + border: none; + background: transparent; +} +.acl-item.well .acl-radios, +.membership-item.well .acl-radios { + display: inline-block; +} +.acl-item.well .acl-radios input[type=radio], +.membership-item.well .acl-radios input[type=radio] { + display: none; +} +.acl-item.well .acl-radios input[type=radio] + label, +.membership-item.well .acl-radios input[type=radio] + label { + margin-left: 10px; + display: inline-block; + opacity: 0.35; +} +.acl-item.well .acl-radios input[type=radio] + label:hover, +.membership-item.well .acl-radios input[type=radio] + label:hover { + opacity: 0.8; +} +.acl-item.well .acl-radios input[type=radio] + label > i, +.membership-item.well .acl-radios input[type=radio] + label > i { + display: inline-block; + vertical-align: middle; + font-size: 13px; +} +.acl-item.well .acl-radios input[type=radio][disabled="disabled"] + label, +.membership-item.well .acl-radios input[type=radio][disabled="disabled"] + label { + opacity: 0; +} +.acl-item.well .acl-radios input[type=radio]:checked + label, +.membership-item.well .acl-radios input[type=radio]:checked + label { + opacity: 1; +} +.acl-item.well .acl-radios input[type=radio][value="FORBIDDEN"] + label > i, +.membership-item.well .acl-radios input[type=radio][value="FORBIDDEN"] + label > i { + color: #ff675b; +} +.acl-item.well .acl-radios input[type=radio][value="READ_ONLY"] + label > i, +.membership-item.well .acl-radios input[type=radio][value="READ_ONLY"] + label > i { + color: #ffb600; +} +.acl-item.well .acl-radios input[type=radio][value="FULL_ACCESS"] + label > i, +.membership-item.well .acl-radios input[type=radio][value="FULL_ACCESS"] + label > i { + color: #388052; +} +.Alert { + margin: 0; +} +.Alert-title { + display: inline-block; +} +.alert-info .Alert-title:before { + display: inline; + content: "Info ! "; + font-weight: bold; + padding-right: 48px; + font-size: 18px; +} +.alert-warning .Alert-title:before { + display: inline; + content: "Warning ! "; + font-weight: bold; + padding-right: 10px; + font-size: 18px; +} +.alert-error .Alert-title:before { + display: inline; + content: "Error ! "; + font-weight: bold; + padding-right: 38px; + font-size: 18px; +} +.Alert-message { + display: inline-block; + margin: 0; +} +#user_defined_function_modal textarea { + width: 280px; + height: 100px; +} +.conversion-status .conversion-status-actions { + float: right; + margin-right: 10px; +} +.conversion-status .fa { + margin-right: 4px; +} +.conversion-status .fa.success { + color: green; +} +.conversion-status .fa.fail { + color: #ee5f5b; +} +.lovItem .sortable-handler { + opacity: 0.2; + cursor: move; + display: inline-block; + vertical-align: top; + margin-top: 8px; + margin-right: 5px; +} +.lovItem .fa-times { + color: black; + opacity: 0.2; + display: inline-block; + vertical-align: text-top; + margin-right: 5px; +} +.lovItem .fa-times:hover { + text-decoration: none; + color: #F00; + opacity: 1; +} +.lovItem .expandIcon { + float: right; + margin-top: 5px; + cursor: pointer; +} +.lovItem .deleteLovItem { + display: inline-block; + vertical-align: bottom; +} +.lovItem .deleteLovItemPossibleValue { + display: inline-block; + vertical-align: top; + margin-top: 6px; +} +.lovItem .lovValueDiv { + display: inline-block; + vertical-align: top; + margin-top: 2px; +} +.lovItem .lovItemNameTitle { + vertical-align: middle; +} +.lovItem .lovValueTitle { + vertical-align: top; +} +.lovItem .lovValueDiv { + margin-left: 20px; +} +.lovItem div.lovValues { + display: none; +} +.lovItem input.lovItemNameInput { + display: none; +} +.lovItem button.addLOVValue { + display: none; +} +.lovItem span.lovNumberOfValue { + vertical-align: top; +} +.lovItem span.lovItemName { + vertical-align: middle; + margin-bottom: 2px; + font-weight: bold; + display: inline-block; + text-overflow: ellipsis; + overflow: hidden; + width: 50%; +} +.lovItem.edition div.lovValues { + display: inline-block; + vertical-align: top; + margin-left: 5px; +} +.lovItem.edition span.lovItemName { + display: none; +} +.lovItem.edition input.lovItemNameInput { + display: inline-block; + margin: 5px; +} +.lovItem.edition span.lovNumberOfValue { + display: none; +} +.lovItem.edition span.lovNumberOfValueTitle { + display: none; +} +.lovItem.edition i.expandIcon { + transform: rotate(180deg); +} +.lovItem.edition button.addLOVValue { + display: block; + margin-left: 24px; +} +.lovItem.edition .lovValueDiv { + margin: 5px; + margin-left: 30px; +} +.lovItem.edition .deleteLovItem { + display: inline-block; + vertical-align: text-top; +} +.lovItem.edition .expandIcon { + margin-top: 12px; +} +.lovItem.edition.isOldItem span.lovItemName { + vertical-align: middle; + margin-bottom: 2px; + font-weight: bold; + display: inline-block; + text-overflow: ellipsis; + overflow: hidden; + width: 24%; +} +.lovItem.edition.isOldItem .lovValueDiv { + margin: -3px; + margin-left: 30px; +} +.lovItem.edition.isOldItem .expandIcon { + margin-top: 5px; +} +#user_defined_function_modal .run-udf { + display: none; +} +#user_defined_function_modal .calculation { + margin-bottom: 10px; +} +#user_defined_function_modal .calculation select { + width: 111px; + display: inline; +} +#user_defined_function_modal .calculation .result { + margin-top: 10px; +} +.action-checkin-checkout { + text-align: center; + margin: 15px 0; +} +.action-checkin-checkout .action-checkin, +.action-checkin-checkout .action-checkout, +.action-checkin-checkout .action-undocheckout { + cursor: pointer; +} +.action-checkin-checkout .fa { + padding-right: 10px; +} +#chat_module { + position: absolute; + bottom: 1%; + right: 1%; + height: 0; + width: 0; +} +#chat_module.chat_module_hidden { + height: 100%; + background: red; + width: 2px; +} +#chat_module > .chat_session { + background: whiteSmoke; + box-shadow: 0 0 5px #9B9B9B; + position: absolute; + width: 310px; + z-index: 1200; + bottom: 0; + right: 0; + border-radius: 6px; +} +#chat_module > .chat_session.chat_session_on_top { + z-index: 1201; +} +#chat_module > .chat_session > .chat_session_header { + background: #3F5F99; + background-image: -moz-linear-gradient(top, #3f5f99, #213251); + background-image: -webkit-linear-gradient(top, #3f5f99, #213251); + padding: 5px; + color: white; + cursor: move; + border-radius: 6px 6px 0 0; +} +#chat_module > .chat_session > .chat_session_header > span.chat_session_title { + font-weight: bold; + width: 100%; + margin-bottom: 10px; +} +#chat_module > .chat_session > .chat_session_header > .fa-times { + position: absolute; + top: 6px; + right: 7px; + cursor: pointer; +} +#chat_module > .chat_session > .chat_session_header > .fa-video-camera { + position: absolute; + top: 6px; + right: 27px; + cursor: pointer; +} +#chat_module > .chat_session > ul.chat_session_messages { + width: 100%; + margin: 10px 0 0 0; + max-height: 300px; + overflow: hidden; + overflow-y: auto; + border-bottom: 1px solid #CCC; +} +#chat_module > .chat_session > ul.chat_session_messages > li { + text-overflow: ellipsis; + list-style: none; + word-wrap: break-word; + margin: 0 5px; +} +#chat_module > .chat_session > ul.chat_session_messages > li.chat_message_error { + color: #B94A48; +} +#chat_module > .chat_session > div.chat_webrtc_invite { + display: none; + padding: 5px; + border-bottom: 1px solid #CCC; +} +#chat_module > .chat_session > div.chat_webrtc_invite > span { + float: left; + width: 100%; + margin-bottom: 6px; +} +#chat_module > .chat_session > div.chat_webrtc_invite > .btn { + margin-right: 6px; +} +#chat_module > .chat_session > div.chat_session_reply { + padding: 5px; +} +#chat_module > .chat_session > div.chat_session_reply > .chat_session_reply_form { + margin: 0; +} +#chat_module > .chat_session > div.chat_session_reply > .chat_session_reply_form > input { + margin: 0; +} +#coworkers_access_module_entries { + min-width: 200px; +} +#coworkers_access_module_entries span { + float: left; +} +#coworkers_access_module_entries .fa-user { + margin: 4px 4px 0 -10px; + float: left; +} +#coworkers_access_module_entries .corworker-action { + opacity: 0.5; + float: right; + margin: 4px 0 0 0; + padding-right: 3px; +} +#coworkers_access_module_entries .corworker-action-disable { + opacity: 0.2; + float: right; + margin: 4px 0 0 0; + padding-right: 3px; +} +#coworkers_access_module_entries > li > a > i.corworker-action:hover { + opacity: 1; + color: white; + text-shadow: 0 -1px 0 #aaaaaa; +} +#webrtc_module { + background: rgba(0, 0, 0, 0.8); + box-shadow: 0 0 5px #9B9B9B; + position: absolute; + width: 0; + height: 0; + z-index: 1200; + top: 10%; + right: 0; + border-radius: 6px 0 0 6px; + -webkit-transition: width 0.5s ease; + -moz-transition: width 0.5s ease; + -o-transition: width 0.5s ease; + -ms-transition: width 0.5s ease; + transition: width 0.5s ease; + overflow: hidden; +} +#webrtc_module.webrtc_shown { + width: 70%; + height: 80%; +} +#webrtc_module.webrtc_minimized { + top: 6%; + width: 10%; + height: 19%; +} +#webrtc_module > #webrtc_module_header { + margin: 0; + padding: 5px; + border-radius: 6px 0 0 0; +} +#webrtc_module > #webrtc_module_header > h3#webrtc_module_title { + margin: 0; + color: white; + text-align: center; +} +#webrtc_module > #webrtc_module_header > h3#webrtc_module_controls { + font-size: 16px; + position: absolute; + color: white; + top: 0; + cursor: pointer; + left: 15px; +} +#webrtc_module > #webrtc_module_header > h3#webrtc_module_controls > i { + margin-right: 10px; + float: right; + opacity: 0.6; +} +#webrtc_module > #webrtc_module_header > h3#webrtc_module_controls > i:hover { + opacity: 0.8; +} +#webrtc_module > #webrtc_module_body { + text-align: center; + height: 100%; +} +#webrtc_module > #webrtc_module_body > #webrtc_call_state { + height: 6%; + color: white; +} +#webrtc_module.webrtc_minimized > #webrtc_module_body > #webrtc_call_state, +#webrtc_module.webrtc_minimized > #webrtc_module_body > #webrtc_video_container > #webrtc_local_video, +#webrtc_module.webrtc_minimized > #webrtc_module_header > h3#webrtc_module_title, +#webrtc_module.webrtc_minimized > #webrtc_module_header > h3#webrtc_module_controls > #webrtc_minimize_btn, +#webrtc_module.webrtc_shown > #webrtc_module_header > h3#webrtc_module_controls > #webrtc_restore_btn { + display: none; +} +#webrtc_module.webrtc_minimized > #webrtc_module_header { + height: 12%; +} +#webrtc_module.webrtc_minimized > #webrtc_module_body { + height: 93%; +} +#webrtc_module > #webrtc_module_body > #webrtc_video_container { + width: 85%; + height: 80%; + position: relative; + margin: 0 auto; + padding: 0; + background-color: rgba(0, 0, 0, 0.6); + border-radius: 6px; + box-shadow: 0 0 3px #666; + overflow: hidden; +} +#webrtc_module > #webrtc_module_body > #webrtc_video_container > video.webrtc_loading { + background-image: url(../images/loader.gif); + background-position: center center; + background-repeat: no-repeat; +} +#webrtc_module > #webrtc_module_body > #webrtc_video_container > #webrtc_remote_video { + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.4); + border-radius: 6px; + -webkit-transition: all 0.5s ease; + -moz-transition: all 0.5s ease; + -o-transition: all 0.5s ease; + -ms-transition: all 0.5s ease; + transition: all 0.5s ease; + opacity: 0; +} +#webrtc_module > #webrtc_module_body > #webrtc_video_container > #webrtc_local_video { + position: absolute; + z-index: 2; + bottom: 5px; + left: 5px; + width: 20%; + height: 25%; + background-color: rgba(0, 0, 0, 0.6); + box-shadow: 0 0 3px #666; + cursor: move; + border-radius: 6px; + opacity: 0; + -webkit-transition: opacity 0.5s ease; + -moz-transition: opacity 0.5s ease; + -o-transition: opacity 0.5s ease; + -ms-transition: opacity 0.5s ease; + transition: opacity 0.5s ease; + -webkit-transform: scale(-1, 1); + -moz-transform: scale(-1, 1); + -webkit-backface-visibility: hidden; + -moz-backface-visibility: hidden; +} +#organization-management-content { + margin: 30px; +} diff --git a/docdoku-web-front/app/parts/index.html b/docdoku-web-front/app/parts/index.html new file mode 100644 index 0000000000..fd655ba81e --- /dev/null +++ b/docdoku-web-front/app/parts/index.html @@ -0,0 +1,27 @@ + + + + + + DocDokuPLM - Part + + + + + + + + + + + + + +
                                +
                                +
                                + + + + + diff --git a/docdoku-web-front/app/parts/js/app.js b/docdoku-web-front/app/parts/js/app.js new file mode 100644 index 0000000000..a4d5191d1c --- /dev/null +++ b/docdoku-web-front/app/parts/js/app.js @@ -0,0 +1,89 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/part-permalink.html', + 'views/part-revision', + 'common-objects/views/not-found', + 'common-objects/views/prompt' +], function (Backbone, Mustache, template, PartRevisionView, NotFoundView, PromptView) { + 'use strict'; + + var AppView = Backbone.View.extend({ + + el: '#content', + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n + })).show(); + this.$notifications = this.$('.notifications'); + return this; + }, + + onPartFetched:function(part){ + this.$('.part-revision').html(new PartRevisionView().render(part).$el, null); + }, + + showPartRevision:function(workspace, partNumber, partVersion){ + $.getJSON(App.config.contextPath + '/api/shared/' + workspace + '/parts/'+partNumber+'-'+partVersion) + .then(this.onPartFetched.bind(this), this.onError.bind(this)); + }, + + showSharedEntity:function(uuid){ + this.uuid = uuid; + var password = this.password; + $.ajax({ + type:'GET', + url:App.config.contextPath + '/api/shared/' + uuid + '/parts', + beforeSend: function setPassword(xhr) { + if(password){ + xhr.setRequestHeader('password', password); + } + } + }).then(this.onSharedEntityFetched.bind(this), this.onSharedEntityError.bind(this)); + }, + + onSharedEntityFetched:function(part){ + this.$('.part-revision').html(new PartRevisionView().render(part, this.uuid).$el); + }, + + onSharedEntityError:function(err){ + if(err.status === 404){ + this.$el.html(new NotFoundView().render(err).$el); + } + else if(err.status === 403 && err.getResponseHeader('Reason-Phrase') === 'password-protected'){ + this.promptSharedEntityPassword(); + } + }, + + onError:function(err){ + if(err.status === 404){ + this.$el.html(new NotFoundView().render(err).$el); + } + else if(err.status === 403 || err.status === 401){ + window.location.href = App.config.contextPath + '/?denied=true&originURL=' + encodeURIComponent(window.location.pathname + window.location.hash); + } + }, + + promptSharedEntityPassword:function(){ + + var _this = this; + var promptView = new PromptView(); + promptView.setPromptOptions(App.config.i18n.PROTECTED_RESOURCE, + null,App.config.i18n.OK,App.config.i18n.CANCEL, null, App.config.i18n.PASSWORD, 'password'); + window.document.body.appendChild(promptView.render().el); + promptView.openModal(); + this.listenTo(promptView, 'prompt-ok', function (args) { + _this.password = args[0]; + _this.showSharedEntity(_this.uuid); + }); + this.listenTo(promptView, 'prompt-cancel', function () { + _this.password = null; + }); + } + + }); + + return AppView; +}); diff --git a/docdoku-web-front/app/parts/js/router.js b/docdoku-web-front/app/parts/js/router.js new file mode 100644 index 0000000000..c34a5ff967 --- /dev/null +++ b/docdoku-web-front/app/parts/js/router.js @@ -0,0 +1,24 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/common/singleton_decorator' +], +function (Backbone, singletonDecorator) { + 'use strict'; + var Router = Backbone.Router.extend({ + routes: { + ':workspaceId/:partNumber/:partVersion': 'showPartRevision', + ':uuid': 'showSharedEntity', + }, + + showPartRevision:function(workspace, partNumber, partVersion){ + App.appView.showPartRevision(workspace, partNumber, partVersion); + }, + + showSharedEntity:function(uuid){ + App.appView.showSharedEntity(uuid); + } + }); + + return singletonDecorator(Router); +}); diff --git a/docdoku-web-front/app/parts/js/templates/cad-file.html b/docdoku-web-front/app/parts/js/templates/cad-file.html new file mode 100644 index 0000000000..388fef9b20 --- /dev/null +++ b/docdoku-web-front/app/parts/js/templates/cad-file.html @@ -0,0 +1,5 @@ + + + {{i18n.CAD_FILE}} + +
                                diff --git a/docdoku-web-front/app/parts/js/templates/part-permalink.html b/docdoku-web-front/app/parts/js/templates/part-permalink.html new file mode 100644 index 0000000000..fcc08260e8 --- /dev/null +++ b/docdoku-web-front/app/parts/js/templates/part-permalink.html @@ -0,0 +1,2 @@ +
                                +
                                diff --git a/docdoku-web-front/app/parts/js/templates/part-revision.html b/docdoku-web-front/app/parts/js/templates/part-revision.html new file mode 100644 index 0000000000..e95a3e1de3 --- /dev/null +++ b/docdoku-web-front/app/parts/js/templates/part-revision.html @@ -0,0 +1,169 @@ +

                                {{part.partKey}}

                                +
                                + + + +
                                +
                                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{#part.releaseAuthor}} + + + + + + + + + {{/part.releaseAuthor}} + {{#part.obsoleteAuthor}} + + + + + + + + + {{/part.obsoleteAuthor}} + +
                                {{i18n.CREATION_DATE}}{{part.creationDate}}
                                {{i18n.TYPE}}{{part.type}}
                                {{i18n.NAME}}{{part.name}}
                                {{i18n.STANDARD_PART}} + {{#part.standardPart}}{{i18n.STANDARD_PART_TRUE}}{{/part.standardPart}} + {{^part.standardPart}}{{i18n.STANDARD_PART_FALSE}}{{/part.standardPart}} +
                                {{i18n.AUTHOR}}{{part.author.name}}
                                {{i18n.LIFECYCLE_STATE}}{{part.lifeCycleState}}
                                {{i18n.DESCRIPTION}}{{part.description}}
                                {{i18n.TAGS}}{{part.tags}}
                                {{i18n.RELEASED_BY}}{{part.releaseAuthor.name}}
                                {{i18n.RELEASE_DATE}}{{part.releaseDate}}
                                {{i18n.OBSOLETE_AUTHOR}}{{part.obsoleteAuthor.name}}
                                {{i18n.OBSOLETE_DATE}}{{part.obsoleteDate}}
                                + +
                                + +
                                + + + + + + + + + + + + + + + + + + + + + + + +
                                {{i18n.ITERATION}}{{lastIteration.iteration}}
                                {{i18n.REVISION_NOTE}}{{lastIteration.iterationNote}}
                                {{i18n.REVISION_DATE}} + {{#part.checkOutUser}} + {{lastIteration.creationDate}} + {{/part.checkOutUser}} + {{^part.checkOutUser}} + {{lastIteration.checkInDate}} + {{/part.checkOutUser}} +
                                {{i18n.MODIFICATION_DATE}}{{lastIteration.modificationDate}}
                                {{i18n.AUTHOR}}{{lastIteration.author.name}}
                                +
                                + +
                                + {{#lastIteration.instanceAttributes.length}} + + + + + + + + + {{#lastIteration.instanceAttributes}} + + + + + {{/lastIteration.instanceAttributes}} + +
                                {{i18n.NAME}}{{i18n.VALUE}}
                                {{name}}{{value}}
                                + {{/lastIteration.instanceAttributes.length}} +
                                + + {{#lastIteration.geometryFileURI}} +
                                + +
                                + {{/lastIteration.geometryFileURI}} + +
                                +
                                +
                                + + {{#lastIteration.components.length}} +
                                + +
                                + {{/lastIteration.components.length}} + + +
                                + +
                                + +{{i18n.SEE_IN_PRODUCT_MANAGEMENT}} diff --git a/docdoku-web-front/app/parts/js/views/cad-file-view.js b/docdoku-web-front/app/parts/js/views/cad-file-view.js new file mode 100644 index 0000000000..a5a17d9bcc --- /dev/null +++ b/docdoku-web-front/app/parts/js/views/cad-file-view.js @@ -0,0 +1,278 @@ +/*global define,App,THREE,requestAnimationFrame,_*/ +define([ + 'backbone', + 'mustache', + 'text!templates/cad-file.html' +], function (Backbone, Mustache, template) { + 'use strict'; + + var CADFileView = Backbone.View.extend({ + + id:'cad-file-view', + + resize:function(){ + setTimeout(this.handleResize.bind(this),50); + }, + + render:function(nativeCADFile, fileName, uuid){ + + var extension = fileName.substr(fileName.lastIndexOf('.') + 1).toLowerCase(); + var texturePath = fileName.substring(0, fileName.lastIndexOf('/')); + var width, height; + + if(uuid){ + fileName += '/uuid/'+uuid; + nativeCADFile += '/uuid/'+uuid; + } + + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + contextPath:App.config.contextPath, + nativeCADFile:nativeCADFile + })); + + var $container = this.$('#cad-file'); + + function calculateWith(){ + var max = $container.innerWidth() - 20; + return max > 10 ? max : 10; + } + + function calculateHeight(){ + var fit = width / 16 * 9; + var max = window.innerHeight - 340; + if(max <= 10){ + max = 10; + } + return fit < max ? fit : max; + } + + width = calculateWith(); + height = calculateHeight(); + + var scene = new THREE.Scene(); + var camera = new THREE.PerspectiveCamera(45, width / height, App.SceneOptions.cameraNear, App.SceneOptions.cameraFar); + var control; + var renderer = new THREE.WebGLRenderer({alpha: true}); + + function addLightsToCamera(camera) { + var dirLight1 = new THREE.DirectionalLight(App.SceneOptions.cameraLight1Color); + dirLight1.position.set(200, 200, 1000).normalize(); + dirLight1.name = 'CameraLight1'; + camera.add(dirLight1); + camera.add(dirLight1.target); + + var dirLight2 = new THREE.DirectionalLight( App.SceneOptions.cameraLight2Color, 1 ); + dirLight2.color.setHSL( 0.1, 1, 0.95 ); + dirLight2.position.set( -1, 1.75, 1 ); + dirLight2.position.multiplyScalar( 50 ); + dirLight2.name='CameraLight2'; + camera.add( dirLight2 ); + + dirLight2.castShadow = true; + + dirLight2.shadowMapWidth = 2048; + dirLight2.shadowMapHeight = 2048; + + var d = 50; + + dirLight2.shadowCameraLeft = -d; + dirLight2.shadowCameraRight = d; + dirLight2.shadowCameraTop = d; + dirLight2.shadowCameraBottom = -d; + + dirLight2.shadowCameraFar = 3500; + dirLight2.shadowBias = -0.0001; + dirLight2.shadowDarkness = 0.35; + + var hemiLight = new THREE.HemisphereLight( App.SceneOptions.ambientLightColor, App.SceneOptions.ambientLightColor, 0.6 ); + hemiLight.color.setHSL( 0.6, 1, 0.6 ); + hemiLight.groundColor.setHSL( 0.095, 1, 0.75 ); + hemiLight.position.set( 0, 0, 500 ); + hemiLight.name='AmbientLight'; + camera.add( hemiLight ); + + } + + camera.position.copy(App.SceneOptions.defaultCameraPosition); + addLightsToCamera(camera); + + renderer.setSize(width, height); + $container.append(renderer.domElement); + scene.add(camera); + scene.updateMatrixWorld(); + control = new THREE.TrackballControls(camera, $container[0]); + + function centerOn(mesh) { + mesh.geometry.computeBoundingBox(); + var boundingBox = mesh.geometry.boundingBox; + var cog = new THREE.Vector3().copy(boundingBox.center()); + var size = boundingBox.size(); + var radius = Math.max(size.x, size.y, size.z); + var distance = radius ? radius * 2 : 1000; + distance = (distance < App.SceneOptions.cameraNear) ? App.SceneOptions.cameraNear + 100 : distance; + camera.position.set(cog.x + distance, cog.y, cog.z + distance); + control.target.copy(cog); + } + + function render() { + scene.updateMatrixWorld(); + renderer.render(scene, camera); + } + + function animate() { + requestAnimationFrame(animate); + control.update(); + render(); + } + + function getMeshGeometries(collada, geometries) { + if (collada) { + _.each(collada.children, function (child) { + if (child instanceof THREE.Mesh && child.geometry) { + geometries.push(child.geometry); + } + getMeshGeometries(child, geometries); + }); + } + } + + var defaultMaterial = new THREE.MeshLambertMaterial({color:new THREE.Color(0x62697B)}); + + function setShadows(object){ + object.traverse( function ( o ) { + if ( o instanceof THREE.Mesh) { + o.castShadow = true; + o.receiveShadow = true; + } + }); + } + + function updateMaterial(object){ + object.traverse( function ( o ) { + if ( o instanceof THREE.Mesh && !o.material.name) { + o.material = defaultMaterial; + } + }); + } + + function onParseSuccess(object) { + scene.add(object); + centerOn(object.children[0]); + } + + function handleResize() { + width = calculateWith(); + height = calculateHeight(); + camera.aspect = width / height; + camera.updateProjectionMatrix(); + renderer.setSize(width, height); + control.handleResize(); + } + + window.addEventListener('resize', handleResize, false); + + switch (extension) { + + case 'dae': + + var colladaLoader = new THREE.ColladaLoader(); + + colladaLoader.load(fileName, function (collada) { + + var geometries = [], combined = new THREE.Geometry(); + getMeshGeometries(collada.scene, geometries); + + // Merge all sub meshes into one + _.each(geometries, function (geometry) { + THREE.GeometryUtils.merge(combined, geometry); + }); + + combined.dynamic = false; + combined.mergeVertices(); + + combined.computeBoundingSphere(); + var object = new THREE.Object3D(); + object.add(new THREE.Mesh(combined)); + setShadows(object); + updateMaterial(object); + onParseSuccess(object); + + }); + + break; + + case 'stl': + var stlLoader = new THREE.STLLoader(); + + stlLoader.load(fileName, function (geometry) { + var object = new THREE.Object3D(); + object.add(new THREE.Mesh(geometry)); + setShadows(object); + updateMaterial(object); + onParseSuccess(object); + }); + + break; + + // Used for json files only (no referenced buffers) + case 'json': + var jsonLoader = new THREE.JSONLoader(); + + jsonLoader.load(fileName, function (geometry, materials) { + geometry.dynamic = false; + var object = new THREE.Object3D(); + object.add(new THREE.Mesh(geometry,new THREE.MeshFaceMaterial(materials))); + setShadows(object); + onParseSuccess(object); + }, texturePath+'/attachedfiles/'); + + break; + + // Used for binary json files only (referenced buffers - bin file) + case 'js': + var binaryLoader = new THREE.BinaryLoader(); + + binaryLoader.load(fileName, function (geometry, materials) { + var _material = new THREE.MeshPhongMaterial({color: materials[0].color, overdraw: true}); + geometry.dynamic = false; + var object = new THREE.Object3D(); + object.add(new THREE.Mesh(geometry,_material)); + setShadows(object); + onParseSuccess(object); + }, texturePath); + + break; + + case 'obj' : + + var OBJLoader = new THREE.OBJLoader(); + + OBJLoader.load(fileName, texturePath + '/attachedfiles/', function (object) { + setShadows(object); + updateMaterial(object); + onParseSuccess(object); + }); + + break; + + default: + break; + + } + + control.addEventListener('change', function () { + render(); + }); + + this.handleResize = handleResize; + + animate(); + handleResize(); + + return this; + } + }); + + return CADFileView; +}); diff --git a/docdoku-web-front/app/parts/js/views/part-revision.js b/docdoku-web-front/app/parts/js/views/part-revision.js new file mode 100644 index 0000000000..77f8494a02 --- /dev/null +++ b/docdoku-web-front/app/parts/js/views/part-revision.js @@ -0,0 +1,60 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/part-revision.html', + 'views/cad-file-view' +], function (Backbone, Mustache, template, CADFileView) { + 'use strict'; + + var PartRevisionView = Backbone.View.extend({ + + events : { + 'click a[href="#tab-cad-file"]':'showCADFileView' + }, + + showCADFileView:function(){ + if(this.lastIteration.geometryFileURI){ + if(!this.cadFileView){ + var fileName = App.config.contextPath + this.lastIteration.geometryFileURI; + var nativeCADFile = App.config.contextPath + '/api/files/' + this.lastIteration.nativeCADFile; + this.cadFileView = new CADFileView().render(nativeCADFile, fileName, this.uuid); + this.$('#tab-cad-file').html(this.cadFileView.$el); + } + this.cadFileView.resize(); + } + }, + + render: function (part, uuid) { + + var _this = this; + this.uuid = uuid; + this.part = part; + var lastIteration = part.partIterations[part.partIterations.length-1]; + this.lastIteration = lastIteration; + + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + contextPath:App.config.contextPath, + part:part, + lastIteration:lastIteration + })).show(); + + this.$accordion = this.$('#tab-part-files > .accordion'); + + _.each(lastIteration.attachedFiles,function(file){ + var url = App.config.contextPath+'/api/viewer?'; + if(uuid){ + url+= 'uuid=' + encodeURIComponent(uuid) + '&'; + } + $.get(url+'fileName='+encodeURIComponent(file)).then(function(data){ + _this.$accordion.append(data); + }); + }); + + return this; + } + }); + + return PartRevisionView; +}); diff --git a/docdoku-web-front/app/parts/main.js b/docdoku-web-front/app/parts/main.js new file mode 100644 index 0000000000..3d14a51d23 --- /dev/null +++ b/docdoku-web-front/app/parts/main.js @@ -0,0 +1,119 @@ +/*global _,require,window*/ + +var App = { + debug:false, + config:{ + locale: window.localStorage.getItem('locale') || 'en' + }, + SceneOptions: { + zoomSpeed: 1.2, + rotateSpeed: 1.0, + panSpeed: 0.3, + cameraNear: 0.1, + cameraFar: 5E4, + defaultCameraPosition: {x: 0, y: 50, z: 200}, + ambientLightColor: 0xffffff, + cameraLight1Color: 0xbcbcbc, + cameraLight2Color: 0xffffff + } +}; + +App.log=function(message){ + 'use strict'; + if(App.debug){ + window.console.log(message); + } +}; + +require.config({ + + baseUrl: 'js', + + shim: { + jqueryUI: { deps: ['jquery'], exports: 'jQuery' }, + bootstrap: { deps: ['jquery', 'jqueryUI'], exports: 'jQuery' }, + backbone: { deps: ['underscore', 'jquery'], exports: 'Backbone'}, + pointerlockcontrols: {deps: ['threecore'], exports: 'THREE'}, + trackballcontrols: {deps: ['threecore'], exports: 'THREE'}, + orbitcontrols: {deps: ['threecore'], exports: 'THREE'}, + binaryloader: {deps: ['threecore'], exports: 'THREE'}, + colladaloader: {deps: ['threecore'], exports: 'THREE'}, + stlloader: {deps: ['threecore'], exports: 'THREE'}, + objloader: {deps: ['threecore'], exports: 'THREE'}, + mtlloader:{deps:['threecore'],exports:'THREE'}, + buffergeometryutils: {deps: ['threecore'], exports: 'THREE'} + }, + + paths: { + jquery: '../../bower_components/jquery/jquery', + jqueryUI: '../../bower_components/jqueryui/ui/jquery-ui', + backbone: '../../bower_components/backbone/backbone', + underscore: '../../bower_components/underscore/underscore', + mustache: '../../bower_components/mustache/mustache', + text: '../../bower_components/requirejs-text/text', + i18n: '../../bower_components/requirejs-i18n/i18n', + bootstrap: '../../bower_components/bootstrap/docs/assets/js/bootstrap', + 'common-objects': '../../js/common-objects', + localization: '../../js/localization', + pluginDetect:'../../js/lib/plugin-detect', + threecore: '../../bower_components/threejs/build/three', + pointerlockcontrols: '../../js/dmu/controls/PointerLockControls', + trackballcontrols: '../../js/dmu/controls/TrackballControls', + orbitcontrols: '../../js/dmu/controls/OrbitControls', + binaryloader: '../../js/dmu/loaders/BinaryLoader', + colladaloader: '../../js/dmu/loaders/ColladaLoader', + buffergeometryutils: '../../js/dmu/utils/BufferGeometryUtils', + stlloader: '../../js/dmu/loaders/STLLoader', + objloader: '../../js/dmu/loaders/OBJLoader', + mtlloader: '../../js/dmu/loaders/MTLLoader' + }, + + deps: [ + 'jquery', + 'underscore', + 'bootstrap', + 'jqueryUI', + 'pluginDetect', + 'threecore', + 'pointerlockcontrols', + 'trackballcontrols', + 'orbitcontrols', + 'binaryloader', + 'colladaloader', + 'stlloader', + 'objloader', + 'mtlloader', + 'buffergeometryutils' + ], + config: { + i18n: { + locale: (function(){ + 'use strict'; + try{ + return App.config.locale; + }catch(ex){ + return 'en'; + } + })() + } + } +}); + +require(['common-objects/contextResolver','i18n!localization/nls/common','i18n!localization/nls/index'], + function (ContextResolver, commonStrings, indexStrings) { + 'use strict'; + + App.config.i18n = _.extend(commonStrings, indexStrings); + ContextResolver.resolveServerProperties() + .then(function buildView(){ + require(['backbone','app','router','common-objects/views/header'],function(Backbone, AppView, Router, HeaderView){ + App.appView = new AppView(); + App.headerView = new HeaderView(); + App.appView.render(); + App.headerView.render(); + App.router = Router.getInstance(); + Backbone.history.start(); + }); + }); + }); + diff --git a/docdoku-web-front/app/product-management/index.html b/docdoku-web-front/app/product-management/index.html new file mode 100644 index 0000000000..1c14db25ec --- /dev/null +++ b/docdoku-web-front/app/product-management/index.html @@ -0,0 +1,29 @@ + + + + + + DocDokuPLM - Product management + + + + + + + + + + + + + +
                                +
                                +
                                +
                                +
                                + + + + + diff --git a/docdoku-web-front/app/product-management/js/app.js b/docdoku-web-front/app/product-management/js/app.js new file mode 100644 index 0000000000..1d97253950 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/app.js @@ -0,0 +1,38 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/models/workspace', + 'text!templates/content.html' +], function (Backbone, Mustache, Workspace, template) { + 'use strict'; + var AppView = Backbone.View.extend({ + el: '#content', + + events: {}, + + template: Mustache.parse(template), + + initialize: function () { + this.model = new Workspace({id: App.config.workspaceId}); + }, + + render: function () { + + this.$el.html(Mustache.render(template, {model: this.model, i18n: App.config.i18n})); + + App.$productManagementMenu = this.$('#product-management-menu'); + App.$productManagementContent = this.$('#product-management-content'); + + App.$productManagementMenu.customResizable({ + containment: this.$el + }); + + this.$el.show(); + return this; + } + + }); + + return AppView; +}); diff --git a/docdoku-web-front/app/product-management/js/collections/checkedouts_part_collection.js b/docdoku-web-front/app/product-management/js/collections/checkedouts_part_collection.js new file mode 100644 index 0000000000..116b5a2ef2 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/collections/checkedouts_part_collection.js @@ -0,0 +1,69 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/part' +], function (Backbone, Part) { + 'use strict'; + var PartList = Backbone.Collection.extend({ + model: Part, + + className: 'PartList', + + setMainPart: function (part) { + this.part = part; + }, + initialize: function () { + this.urlBase = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/checkedout'; + }, + + fetchPageCount: function () { + }, + + hasSeveralPages: function () { + return false; + }, + + setCurrentPage: function () { + return this; + }, + + getPageCount: function () { + return this.pageCount; + }, + + getCurrentPage: function () { + return 0; + }, + + isLastPage: function () { + return true; + }, + + isFirstPage: function () { + return true; + }, + + setFirstPage: function () { + return this; + }, + + setLastPage: function () { + return this; + }, + + setNextPage: function () { + return this; + }, + + setPreviousPage: function () { + return this; + }, + + url: function () { + return this.urlBase; + } + + }); + + return PartList; +}); diff --git a/docdoku-web-front/app/product-management/js/collections/configuration_items.js b/docdoku-web-front/app/product-management/js/collections/configuration_items.js new file mode 100644 index 0000000000..235d143e1a --- /dev/null +++ b/docdoku-web-front/app/product-management/js/collections/configuration_items.js @@ -0,0 +1,14 @@ +/*global define,App*/ +define(['backbone', 'models/configuration_item' ], +function (Backbone, ConfigurationItem) { + 'use strict'; + var ConfigurationItemCollection = Backbone.Collection.extend({ + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products'; + }, + model: ConfigurationItem + }); + + return ConfigurationItemCollection; + +}); diff --git a/docdoku-web-front/app/product-management/js/collections/configurations.js b/docdoku-web-front/app/product-management/js/collections/configurations.js new file mode 100644 index 0000000000..fc56dffecd --- /dev/null +++ b/docdoku-web-front/app/product-management/js/collections/configurations.js @@ -0,0 +1,14 @@ +/*global define,App*/ +define(['backbone', 'models/configuration' ], +function (Backbone, Configuration) { + 'use strict'; + var ConfigurationCollection = Backbone.Collection.extend({ + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/configurations'; + }, + model: Configuration + }); + + return ConfigurationCollection; + +}); diff --git a/docdoku-web-front/app/product-management/js/collections/part_templates.js b/docdoku-web-front/app/product-management/js/collections/part_templates.js new file mode 100644 index 0000000000..e313dff92a --- /dev/null +++ b/docdoku-web-front/app/product-management/js/collections/part_templates.js @@ -0,0 +1,17 @@ +/*global define,App*/ +define([ + 'backbone', + 'models/part_template' +], function (Backbone, Template) { + 'use strict'; + var TemplateList = Backbone.Collection.extend({ + model: Template, + + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/part-templates'; + } + + }); + + return TemplateList; +}); diff --git a/docdoku-web-front/app/product-management/js/collections/tag_part_collection.js b/docdoku-web-front/app/product-management/js/collections/tag_part_collection.js new file mode 100644 index 0000000000..7f44e36c33 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/collections/tag_part_collection.js @@ -0,0 +1,81 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/models/part' +], function (Backbone, Part) { + 'use strict'; + var PartList = Backbone.Collection.extend({ + + model: Part, + + className: 'PartList', + + tag:null, + + setTag:function(tag){ + this.tag = tag; + return this; + }, + + setMainPart: function (part) { + this.part = part; + }, + + urlBase:function(){ + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/tags/'+ encodeURIComponent(this.tag); + }, + + initialize: function () { + }, + + fetchPageCount: function () { + }, + + hasSeveralPages: function () { + return false; + }, + + setCurrentPage: function () { + return this; + }, + + getPageCount: function () { + return this.pageCount; + }, + + getCurrentPage: function () { + return 0; + }, + + isLastPage: function () { + return true; + }, + + isFirstPage: function () { + return true; + }, + + setFirstPage: function () { + return this; + }, + + setLastPage: function () { + return this; + }, + + setNextPage: function () { + return this; + }, + + setPreviousPage: function () { + return this; + }, + + url: function () { + return this.urlBase(); + } + + }); + + return PartList; +}); diff --git a/docdoku-web-front/app/product-management/js/models/baselined_part.js b/docdoku-web-front/app/product-management/js/models/baselined_part.js new file mode 100644 index 0000000000..dfc8034124 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/models/baselined_part.js @@ -0,0 +1,41 @@ +/*global _,define*/ +'use strict'; +define(['backbone'], function (Backbone) { + + var BaselinedPart = Backbone.Model.extend({ + initialize: function () { + _.bindAll(this); + }, + + getNumber: function () { + return this.get('number'); + }, + + getName: function () { + return this.get('name'); + }, + + getVersion: function () { + return this.get('version'); + }, + + setVersion: function (version) { + this.set('version', version); + }, + + getIteration: function () { + return this.get('iteration'); + }, + + setIteration: function (iteration) { + this.set('iteration', iteration); + }, + + getAvailableIterations: function () { + return this.get('availableIterations'); + } + + }); + + return BaselinedPart; +}); diff --git a/docdoku-web-front/app/product-management/js/models/configuration.js b/docdoku-web-front/app/product-management/js/models/configuration.js new file mode 100644 index 0000000000..36599a72bd --- /dev/null +++ b/docdoku-web-front/app/product-management/js/models/configuration.js @@ -0,0 +1,99 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'common-objects/utils/date', + 'common-objects/utils/acl-checker' +], function (Backbone,date, ACLChecker) { + 'use strict'; + var Configuration = Backbone.Model.extend({ + urlRoot: function () { + if (this.configurationItemId) { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.configurationItemId + '/configurations'; + } + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/configurations'; + }, + initialize: function () { + _.bindAll(this); + this.configurationItemId = this.get('configurationItemId'); + }, + getId:function(){ + return this.get('id'); + }, + getName:function(){ + return this.get('name'); + }, + getAuthor:function(){ + return this.get('author').name; + }, + setName:function(name){ + this.set('name',name); + }, + + getConfigurationItemId:function(){ + return this.get('configurationItemId'); + }, + + getDescription:function(){ + return this.get('description'); + }, + + setDescription:function(description){ + this.set('description',description); + }, + + getSubstitutesParts:function(){ + return this.get('substitutesParts'); + }, + getOptionalsParts:function(){ + return this.get('optionalsParts'); + }, + + getCreationDate:function(){ + return this.get('creationDate'); + }, + + getFormattedCreationDate:function(){ + return date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getCreationDate() + ); + }, + + getACL: function () { + return this.get('acl'); + }, + + updateACL: function (args) { + $.ajax({ + type: 'PUT', + url: this.url() + '/acl', + data: JSON.stringify(args.acl), + contentType: 'application/json; charset=utf-8', + success: args.success, + error: args.error + }); + }, + + hasACLForCurrentUser: function () { + return this.getACLPermissionForCurrentUser() !== false; + }, + + isForbidden: function () { + return this.getACLPermissionForCurrentUser() === 'FORBIDDEN'; + }, + + isReadOnly: function () { + return this.getACLPermissionForCurrentUser() === 'READ_ONLY'; + }, + + isFullAccess: function () { + return this.getACLPermissionForCurrentUser() === 'FULL_ACCESS'; + }, + + getACLPermissionForCurrentUser: function () { + return ACLChecker.getPermission(this.getACL()); + } + + }); + return Configuration; +}); diff --git a/docdoku-web-front/app/product-management/js/models/configuration_item.js b/docdoku-web-front/app/product-management/js/models/configuration_item.js new file mode 100644 index 0000000000..a4490e1d58 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/models/configuration_item.js @@ -0,0 +1,154 @@ +/*global _,$,define,App*/ +define(['backbone'], function (Backbone) { + 'use strict'; + var ConfigurationItem = Backbone.Model.extend({ + idAttribute: '_id', + + urlRoot: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products'; + }, + + initialize: function () { + _.bindAll(this); + }, + + parse: function (response) { + response._id = response.id; + return response; + }, + + getId: function () { + return this.get('id'); + }, + + hasUnreadModificationNotifications: function () { + return this.get('hasModificationNotification'); + }, + + getAuthorName: function () { + return this.get('author').name; + }, + + getAuthorLogin: function () { + return this.get('author').login; + }, + + getDesignItemNumber: function () { + return this.get('designItemNumber'); + }, + getDesignItemName: function () { + return this.get('designItemName'); + }, + + getDesignItemLatestVersion: function () { + return this.get('designItemLatestVersion'); + }, + + getDescription: function () { + return this.get('description'); + }, + + getIndexUrl: function () { + return App.config.contextPath + '/product-structure/#' + App.config.workspaceId + '/' + encodeURIComponent(this.getId()); + }, + + getBomUrl: function () { + return App.config.contextPath + '/product-structure/#' + App.config.workspaceId + '/' + encodeURIComponent(this.getId()) + '/config-spec/wip/bom'; + }, + + getSceneUrl: function () { + return App.config.contextPath + '/product-structure/#' + App.config.workspaceId + '/' + encodeURIComponent(this.getId()) + '/config-spec/wip/scene'; + }, + + getZipUrl: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + encodeURIComponent(this.getId()) + '/export-files?configSpecType=latest'; + }, + + hasPathToPathLink: function () { + return this.getPathToPathLinks().length; + }, + + getPathToPathLinks: function () { + return this.get('pathToPathLinks'); + }, + + createBaseline: function (baselineArgs, callbacks) { + return $.ajax({ + type: 'POST', + url: this.urlRoot() + '/' + this.getId() + '/baselines', + data: JSON.stringify(baselineArgs), + contentType: 'application/json; charset=utf-8', + success: callbacks.success, + error: callbacks.error + }); + }, + + createConfiguration: function (configurationArgs) { + return $.ajax({ + type: 'POST', + url: this.urlRoot() + '/' + this.getId() + '/configurations', + data: JSON.stringify(configurationArgs), + contentType: 'application/json; charset=utf-8' + }); + }, + + getConfigurations: function () { + return $.getJSON(this.urlRoot() + '/' + this.getId() + '/configurations'); + }, + + getReleasedChoices: function () { + return $.getJSON(this.urlRoot() + '/' + this.getId() + '/path-choices?type=RELEASED'); + }, + + getLatestChoices: function () { + return $.getJSON(this.urlRoot() + '/' + this.getId() + '/path-choices?type=LATEST'); + }, + + getReleasedParts: function () { + return $.getJSON(this.urlRoot() + '/' + this.getId() + '/versions-choices'); + }, + + deleteBaselines: function (baselines, callbacks) { + var _this = this; + var toDelete = _.size(baselines); + _.each(baselines, function (baseline) { + _this.deleteBaseline(baseline.getId(), { + success: function (data) { + toDelete--; + if (toDelete === 0 && callbacks && _.isFunction(callbacks.success)) { + callbacks.success(data); + } + }, + error: function (err) { + if (callbacks && _.isFunction(callbacks.error)) { + callbacks.error(baseline, err); + } + } + }); + }); + }, + + deleteBaseline: function (baselineId, callbacks) { + $.ajax({ + type: 'DELETE', + async: false, + url: this.urlRoot() + '/' + this.getId() + '/baselines/' + baselineId, + contentType: 'application/json; charset=utf-8', + success: function (data) { + if (callbacks && _.isFunction(callbacks.success)) { + callbacks.success(data); + } + }, + error: function (err) { + if (callbacks && _.isFunction(callbacks.error)) { + callbacks.error(err); + } + } + }); + } + + }); + + return ConfigurationItem; + +}); diff --git a/docdoku-web-front/app/product-management/js/models/part_template.js b/docdoku-web-front/app/product-management/js/models/part_template.js new file mode 100644 index 0000000000..dcf4608611 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/models/part_template.js @@ -0,0 +1,146 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'common-objects/collections/file/attached_file_collection', + 'common-objects/utils/date', + 'common-objects/utils/acl-checker' +], function (Backbone, AttachedFileCollection, Date,ACLChecker) { + 'use strict'; + var Template = Backbone.Model.extend({ + className: 'PartTemplate', + + initialize: function () { + this.resetAttachedFile(); + }, + + resetAttachedFile: function () { + var fullName = this.get('attachedFile'); + if (fullName) { + var attachedFile = { + fullName: fullName, + shortName: _.last(fullName.split('/')), + created: true + }; + this._attachedFile = new AttachedFileCollection(attachedFile); + } else { + this._attachedFile = new AttachedFileCollection(); + } + }, + + parse: function (response) { + return response; + }, + + toJSON: function () { + return this.clone().set({attributeTemplates: _.reject(this.get('attributeTemplates'), + function (attribute) { + return attribute.name === ''; + } + )}, {silent: true}).attributes; + }, + + getUploadBaseUrl: function () { + return App.config.contextPath + '/api/files/' + this.get('workspaceId') + '/part-templates/' + this.get('id') + '/'; + }, + + getId: function () { + return this.get('id'); + }, + + getMask: function () { + return this.get('mask'); + }, + + isIdGenerated: function () { + return this.get('idGenerated'); + }, + + getAuthorName: function () { + return this.get('author').name; + }, + + getAuthorLogin: function () { + return this.get('author').login; + }, + + getCreationDate: function () { + return this.get('creationDate'); + }, + + getModificationDate: function () { + return this.get('modificationDate'); + }, + + hasAttachedFiles: function(){ + return this._attachedFile.length; + }, + getFormattedCreationDate: function () { + return Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getCreationDate() + ); + }, + + getFormattedModificationDate: function () { + return Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getModificationDate() + ); + }, + + getPartType: function () { + return this.get('partType'); + }, + + url: function () { + if (this.get('id')) { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/part-templates/' + this.get('id'); + } else { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/part-templates'; + } + }, + + generateIdUrl: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/part-templates/' + this.get('id') + '/generate_id'; + }, + + getBaseName: function () { + return App.config.workspaceId + '/part-templates/' + this.get('id'); + }, + + isAttributesLocked: function () { + return this.get('attributesLocked'); + }, + hasACLForCurrentUser: function () { + return this.getACLPermissionForCurrentUser() !== false; + }, + + isForbidden: function () { + return this.getACLPermissionForCurrentUser() === 'FORBIDDEN'; + }, + + isReadOnly: function () { + return this.getACLPermissionForCurrentUser() === 'READ_ONLY'; + }, + + isFullAccess: function () { + return this.getACLPermissionForCurrentUser() === 'FULL_ACCESS'; + }, + + getACLPermissionForCurrentUser: function () { + return ACLChecker.getPermission(this.get('acl')); + }, + updateACL: function (args) { + $.ajax({ + type: 'PUT', + url: this.url()+'/acl', + data: JSON.stringify(args.acl), + contentType: 'application/json; charset=utf-8', + success: args.success, + error: args.error + }); + } + + }); + return Template; +}); diff --git a/docdoku-web-front/app/product-management/js/models/path_choice.js b/docdoku-web-front/app/product-management/js/models/path_choice.js new file mode 100644 index 0000000000..c12038533b --- /dev/null +++ b/docdoku-web-front/app/product-management/js/models/path_choice.js @@ -0,0 +1,38 @@ +/*global _,define*/ +'use strict'; +define(['backbone'], function (Backbone) { + + var PathChoice = Backbone.Model.extend({ + + initialize: function () { + _.bindAll(this); + }, + + getResolvedPath:function(){ + return this.get('resolvedPath'); + }, + + getPartUsageLink: function () { + return this.get('partUsageLink'); + }, + + getPartUsageLinkId: function () { + return this.get('partUsageLink').fullId; + }, + + getResolvedPathAsString:function(){ + return this.getResolvedPath().map(function(resolvedPath){ + return resolvedPath.partLink.fullId; + }).join('-'); + }, + + getKey:function(){ + return this.getResolvedPath().map(function(resolvedPath){ + return resolvedPath.partIteration.number + '-' + resolvedPath.partIteration.version; + }).join('-'); + } + + }); + + return PathChoice; +}); diff --git a/docdoku-web-front/app/product-management/js/router.js b/docdoku-web-front/app/product-management/js/router.js new file mode 100644 index 0000000000..76f5e89c9f --- /dev/null +++ b/docdoku-web-front/app/product-management/js/router.js @@ -0,0 +1,107 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/common/singleton_decorator', + 'views/nav/product_nav', + 'views/nav/configuration_nav', + 'views/nav/baselines_nav', + 'views/nav/product_instances_nav', + 'views/nav/part_nav', + 'views/nav/part_template_nav', + 'views/nav/checkedouts_nav', + 'views/nav/tag_nav' +], +function (Backbone, singletonDecorator, ProductNavView, ConfigurationNavView, BaselinesNavView, ProductInstancesNavView, PartNavView, PartTemplateNavView, CheckedOutNavView, TagNavView) { + 'use strict'; + var Router = Backbone.Router.extend({ + routes: { + ':workspaceId/products': 'products', + ':workspaceId/configurations': 'configurations', + ':workspaceId/baselines': 'baselines', + ':workspaceId/product-instances': 'productInstances', + ':workspaceId/parts': 'parts', + ':workspaceId/tags/:tag': 'tags', + ':workspaceId/checkedouts': 'checkedoutsParts', + ':workspaceId/part-templates': 'partsTemplate', + ':workspaceId/parts-search/:query': 'search', + ':workspaceId': 'products', + ':workspaceId/*path': 'products' + }, + + executeOrReload:function(workspaceId,fn){ + if(workspaceId !== App.config.workspaceId && decodeURIComponent(workspaceId).trim() !== App.config.workspaceId) { + location.reload(); + }else{ + fn.bind(this).call(); + } + }, + + initNavViews: function () { + ProductNavView.getInstance().cleanView(); + ConfigurationNavView.getInstance().cleanView(); + BaselinesNavView.getInstance().cleanView(); + ProductInstancesNavView.getInstance().cleanView(); + PartNavView.getInstance().cleanView(); + PartTemplateNavView.getInstance().cleanView(); + CheckedOutNavView.getInstance().cleanView(); + TagNavView.getInstance(); + }, + + products: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + ProductNavView.getInstance().showContent(); + }); + }, + configurations: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + ConfigurationNavView.getInstance().showContent(); + }); + }, + baselines: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + BaselinesNavView.getInstance().showContent(); + }); + }, + productInstances: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + ProductInstancesNavView.getInstance().showContent(); + }); + }, + parts: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + PartNavView.getInstance().showContent(); + }); + }, + checkedoutsParts:function(workspaceId){ + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + CheckedOutNavView.getInstance().showContent(); + }); + }, + tags:function(workspaceId,tag){ + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + TagNavView.getInstance().showContent(tag); + }); + }, + search: function (workspaceId, query) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + PartNavView.getInstance().showContent(query); + }); + }, + partsTemplate: function (workspaceId) { + this.executeOrReload(workspaceId,function(){ + this.initNavViews(); + PartTemplateNavView.getInstance().showContent(); + }); + } + }); + Router = singletonDecorator(Router); + return Router; +}); diff --git a/docdoku-web-front/app/product-management/js/templates/baselines/baseline_choice_list.html b/docdoku-web-front/app/product-management/js/templates/baselines/baseline_choice_list.html new file mode 100644 index 0000000000..2d1139f1da --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/baselines/baseline_choice_list.html @@ -0,0 +1 @@ +
                                diff --git a/docdoku-web-front/app/product-management/js/templates/baselines/baseline_choice_list_item.html b/docdoku-web-front/app/product-management/js/templates/baselines/baseline_choice_list_item.html new file mode 100644 index 0000000000..b5704d1a03 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/baselines/baseline_choice_list_item.html @@ -0,0 +1,41 @@ + +
                                +
                                  + {{#model.getPartUsageLink}} + + + {{#substitutes}} + + {{/substitutes}} + {{#optional}} +
                                • + +
                                • + {{/optional}} + {{/model.getPartUsageLink}} + +
                                + +
                                diff --git a/docdoku-web-front/app/product-management/js/templates/baselines/baseline_configuration_list.html b/docdoku-web-front/app/product-management/js/templates/baselines/baseline_configuration_list.html new file mode 100644 index 0000000000..a03eb90026 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/baselines/baseline_configuration_list.html @@ -0,0 +1,7 @@ +
                                + +
                                + +
                                +
                                diff --git a/docdoku-web-front/app/product-management/js/templates/baselines/baseline_creation_view.html b/docdoku-web-front/app/product-management/js/templates/baselines/baseline_creation_view.html new file mode 100644 index 0000000000..4f02cc412b --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/baselines/baseline_creation_view.html @@ -0,0 +1,87 @@ + diff --git a/docdoku-web-front/app/product-management/js/templates/baselines/baseline_detail.html b/docdoku-web-front/app/product-management/js/templates/baselines/baseline_detail.html new file mode 100644 index 0000000000..a2c46f317a --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/baselines/baseline_detail.html @@ -0,0 +1,58 @@ + diff --git a/docdoku-web-front/app/product-management/js/templates/baselines/baseline_list_item.html b/docdoku-web-front/app/product-management/js/templates/baselines/baseline_list_item.html new file mode 100644 index 0000000000..311cdef976 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/baselines/baseline_list_item.html @@ -0,0 +1 @@ +{{model.attributes.name}} \ No newline at end of file diff --git a/docdoku-web-front/app/product-management/js/templates/baselines/baselined_part_list.html b/docdoku-web-front/app/product-management/js/templates/baselines/baselined_part_list.html new file mode 100644 index 0000000000..4bc012cd8f --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/baselines/baselined_part_list.html @@ -0,0 +1 @@ +
                                  diff --git a/docdoku-web-front/app/product-management/js/templates/baselines/baselined_part_list_item.html b/docdoku-web-front/app/product-management/js/templates/baselines/baselined_part_list_item.html new file mode 100644 index 0000000000..912b3febb8 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/baselines/baselined_part_list_item.html @@ -0,0 +1,23 @@ +
                                  + {{#editMode}} + + {{#model.getAvailableIterations}} +
                                  + + +
                                  + {{/model.getAvailableIterations}} + + {{/editMode}} + {{^editMode}} + {{model.getName}} < {{model.getNumber}}-{{model.getVersion}}-{{model.getIteration}} > + + {{/editMode}} +
                                  diff --git a/docdoku-web-front/app/product-management/js/templates/baselines/baselines_content.html b/docdoku-web-front/app/product-management/js/templates/baselines/baselines_content.html new file mode 100644 index 0000000000..8ff189635e --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/baselines/baselines_content.html @@ -0,0 +1,14 @@ +
                                  +
                                  +
                                  + + +
                                  +
                                  + {{> snapButton}} + {{> newProductInstanceButton}} + {{> deleteButton}} + {{> udfButton}} +
                                  + +
                                  diff --git a/docdoku-web-front/app/product-management/js/templates/baselines/baselines_list.html b/docdoku-web-front/app/product-management/js/templates/baselines/baselines_list.html new file mode 100644 index 0000000000..4fdfabad83 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/baselines/baselines_list.html @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + +
                                  {{i18n.NAME}}{{i18n.PRODUCT}}{{i18n.TYPE}}{{i18n.AUTHOR}}
                                  diff --git a/docdoku-web-front/app/product-management/js/templates/baselines/baselines_list_item.html b/docdoku-web-front/app/product-management/js/templates/baselines/baselines_list_item.html new file mode 100644 index 0000000000..dafa4f2b6d --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/baselines/baselines_list_item.html @@ -0,0 +1,20 @@ + +{{#model.hasObsoletePartRevisions}}{{/model.hasObsoletePartRevisions}} +{{model.getName}} +{{model.getConfigurationItemId}} + + {{#model.isReleased}} + {{i18n.HEAD_RELEASED}} + {{/model.isReleased}} + {{^model.isReleased}} + {{i18n.HEAD_CHECKIN}} + {{/model.isReleased}} + +{{model.getAuthor}} + + {{#model.hasPathToPathLink}}{{/model.hasPathToPathLink}} + + + + + diff --git a/docdoku-web-front/app/product-management/js/templates/configuration/configuration_content.html b/docdoku-web-front/app/product-management/js/templates/configuration/configuration_content.html new file mode 100644 index 0000000000..7548bd151b --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/configuration/configuration_content.html @@ -0,0 +1,7 @@ +
                                  + {{> newConfigurationButton}} + {{> deleteButton}} + {{> aclButton}} +
                                  +
                                  +
                                  diff --git a/docdoku-web-front/app/product-management/js/templates/configuration/configuration_creation_view.html b/docdoku-web-front/app/product-management/js/templates/configuration/configuration_creation_view.html new file mode 100644 index 0000000000..e2e87f5b00 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/configuration/configuration_creation_view.html @@ -0,0 +1,78 @@ + diff --git a/docdoku-web-front/app/product-management/js/templates/configuration/configuration_details.html b/docdoku-web-front/app/product-management/js/templates/configuration/configuration_details.html new file mode 100644 index 0000000000..0280d1b737 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/configuration/configuration_details.html @@ -0,0 +1,72 @@ + + diff --git a/docdoku-web-front/app/product-management/js/templates/configuration/configuration_list.html b/docdoku-web-front/app/product-management/js/templates/configuration/configuration_list.html new file mode 100644 index 0000000000..c7bc22b890 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/configuration/configuration_list.html @@ -0,0 +1,11 @@ + + + + {{i18n.NAME}} + {{i18n.PRODUCT}} + {{i18n.CREATION_DATE}} + {{i18n.AUTHOR}} + {{i18n.ACL}} + + + diff --git a/docdoku-web-front/app/product-management/js/templates/configuration/configuration_list_item.html b/docdoku-web-front/app/product-management/js/templates/configuration/configuration_list_item.html new file mode 100644 index 0000000000..2977caabfc --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/configuration/configuration_list_item.html @@ -0,0 +1,13 @@ + +{{model.getName}} +{{model.getConfigurationItemId}} +{{model.getFormattedCreationDate}} +{{model.getAuthor}} + + {{#model.isReadOnly}} + + {{/model.isReadOnly}} + {{#model.isFullAccess}} + + {{/model.isFullAccess}} + diff --git a/docdoku-web-front/app/product-management/js/templates/content.html b/docdoku-web-front/app/product-management/js/templates/content.html new file mode 100644 index 0000000000..719987100e --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/content.html @@ -0,0 +1,15 @@ +
                                  + +
                                  +
                                  diff --git a/docdoku-web-front/app/product-management/js/templates/importer.html b/docdoku-web-front/app/product-management/js/templates/importer.html new file mode 100644 index 0000000000..8efee79dab --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/importer.html @@ -0,0 +1,52 @@ + diff --git a/docdoku-web-front/app/product-management/js/templates/nav/baselines_nav.html b/docdoku-web-front/app/product-management/js/templates/nav/baselines_nav.html new file mode 100644 index 0000000000..7a05cd6acb --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/nav/baselines_nav.html @@ -0,0 +1,6 @@ +
                                  + + + {{i18n.BASELINES}} + +
                                  diff --git a/docdoku-web-front/app/product-management/js/templates/nav/checkedouts_nav.html b/docdoku-web-front/app/product-management/js/templates/nav/checkedouts_nav.html new file mode 100644 index 0000000000..48299e6709 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/nav/checkedouts_nav.html @@ -0,0 +1,7 @@ +
                                  + + + {{i18n.CHECKOUTS}} + + +
                                  diff --git a/docdoku-web-front/app/product-management/js/templates/nav/configuration_nav.html b/docdoku-web-front/app/product-management/js/templates/nav/configuration_nav.html new file mode 100644 index 0000000000..c7ac64996f --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/nav/configuration_nav.html @@ -0,0 +1,6 @@ +
                                  + + + {{i18n.CONFIGURATIONS}} + +
                                  diff --git a/docdoku-web-front/app/product-management/js/templates/nav/part_nav.html b/docdoku-web-front/app/product-management/js/templates/nav/part_nav.html new file mode 100644 index 0000000000..96fb9b6760 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/nav/part_nav.html @@ -0,0 +1,6 @@ +
                                  + + + {{i18n.PARTS}} + +
                                  diff --git a/docdoku-web-front/app/product-management/js/templates/nav/part_template_nav.html b/docdoku-web-front/app/product-management/js/templates/nav/part_template_nav.html new file mode 100644 index 0000000000..801daa9e7e --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/nav/part_template_nav.html @@ -0,0 +1,6 @@ +
                                  + + + {{i18n.PART_TEMPLATES}} + +
                                  diff --git a/docdoku-web-front/app/product-management/js/templates/nav/product_instances_nav.html b/docdoku-web-front/app/product-management/js/templates/nav/product_instances_nav.html new file mode 100644 index 0000000000..130952bf59 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/nav/product_instances_nav.html @@ -0,0 +1,6 @@ +
                                  + + + {{i18n.PRODUCT_INSTANCES}} + +
                                  diff --git a/docdoku-web-front/app/product-management/js/templates/nav/product_nav.html b/docdoku-web-front/app/product-management/js/templates/nav/product_nav.html new file mode 100644 index 0000000000..7329dd87f8 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/nav/product_nav.html @@ -0,0 +1,6 @@ +
                                  + + + {{i18n.PRODUCTS}} + +
                                  diff --git a/docdoku-web-front/app/product-management/js/templates/nav/tag_nav.html b/docdoku-web-front/app/product-management/js/templates/nav/tag_nav.html new file mode 100644 index 0000000000..7d6e8fec41 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/nav/tag_nav.html @@ -0,0 +1,7 @@ +
                                  + + + {{i18n.TAGS}} + +
                                  +
                                    diff --git a/docdoku-web-front/app/product-management/js/templates/nav/tag_nav_item.html b/docdoku-web-front/app/product-management/js/templates/nav/tag_nav_item.html new file mode 100644 index 0000000000..b5b7b067d1 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/nav/tag_nav_item.html @@ -0,0 +1,20 @@ +
                                    + + + {{model.id}} + + + +
                                    diff --git a/docdoku-web-front/app/product-management/js/templates/part-template/part_template_content.html b/docdoku-web-front/app/product-management/js/templates/part-template/part_template_content.html new file mode 100644 index 0000000000..bb6499495e --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/part-template/part_template_content.html @@ -0,0 +1,14 @@ +
                                    + + + {{> deleteButton}} + {{> aclButton}} + + +
                                    +
                                    +
                                    diff --git a/docdoku-web-front/app/product-management/js/templates/part-template/part_template_creation_view.html b/docdoku-web-front/app/product-management/js/templates/part-template/part_template_creation_view.html new file mode 100644 index 0000000000..006c65d730 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/part-template/part_template_creation_view.html @@ -0,0 +1,94 @@ + diff --git a/docdoku-web-front/app/product-management/js/templates/part-template/part_template_list.html b/docdoku-web-front/app/product-management/js/templates/part-template/part_template_list.html new file mode 100644 index 0000000000..ccd40a7156 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/part-template/part_template_list.html @@ -0,0 +1,15 @@ + + + + {{i18n.REFERENCE}} + {{i18n.TYPE}} + {{i18n.MASK}} + {{i18n.AUTHOR}} + {{i18n.CREATION_DATE}} + {{i18n.MODIFICATION_DATE}} + {{i18n.ACL}} + + + + + diff --git a/docdoku-web-front/app/product-management/js/templates/part-template/part_template_list_item.html b/docdoku-web-front/app/product-management/js/templates/part-template/part_template_list_item.html new file mode 100644 index 0000000000..c0d0cced34 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/part-template/part_template_list_item.html @@ -0,0 +1,20 @@ + +{{getId}} +{{getPartType}} +{{getMask}} +{{getAuthorName}} +{{getFormattedCreationDate}} +{{getFormattedModificationDate}} + + {{#isReadOnly}} + + {{/isReadOnly}} + {{#isFullAccess}} + + {{/isFullAccess}} + + + {{#hasAttachedFiles}} + + {{/hasAttachedFiles}} + diff --git a/docdoku-web-front/app/product-management/js/templates/part/part_content.html b/docdoku-web-front/app/product-management/js/templates/part/part_content.html new file mode 100644 index 0000000000..d5bb6c884e --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/part/part_content.html @@ -0,0 +1,30 @@ +
                                    + {{> searchForm}} + + + {{> tagsButton}} + {{> deleteButton}} + {{> checkoutButtonGroup}} + {{> aclButton}} + {{> newVersionButton}} + {{> releaseButton}} + {{> obsoleteButton}} + {{> newProductButton}} + {{> importButton}} + +
                                    +
                                    +
                                    +
                                    +
                                    + + + + + + +
                                    +
                                    +
                                    diff --git a/docdoku-web-front/app/product-management/js/templates/part/part_creation_view.html b/docdoku-web-front/app/product-management/js/templates/part/part_creation_view.html new file mode 100644 index 0000000000..bd621c7147 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/part/part_creation_view.html @@ -0,0 +1,80 @@ + diff --git a/docdoku-web-front/app/product-management/js/templates/part/part_grouped_by_list.html b/docdoku-web-front/app/product-management/js/templates/part/part_grouped_by_list.html new file mode 100644 index 0000000000..2d19028784 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/part/part_grouped_by_list.html @@ -0,0 +1,15 @@ +{{#groups}} +{{#name}} +

                                    {{name}}

                                    +{{/name}} + + + + {{#columns}} + + {{/columns}} + + + +
                                    {{name}}
                                    +{{/groups}} diff --git a/docdoku-web-front/app/product-management/js/templates/part/part_grouped_by_list_item.html b/docdoku-web-front/app/product-management/js/templates/part/part_grouped_by_list_item.html new file mode 100644 index 0000000000..81d1307bee --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/part/part_grouped_by_list_item.html @@ -0,0 +1,32 @@ +{{#columns}} + + {{#isDate}} + {{#value}} + {{.}}
                                    + {{/value}} + {{/isDate}} + + {{#isStringArray}} + {{#value}} + {{.}} + {{/value}} + {{/isStringArray}} + + {{#isStringValue}} + {{#value}} + {{.}}
                                    + {{/value}} + {{/isStringValue}} + + {{#isLinkedDocuments}} + {{#value}} + {{name}} + {{/value}} + {{/isLinkedDocuments}} + + {{#isPartNumber}} + {{value}} + {{/isPartNumber}} + + +{{/columns}} diff --git a/docdoku-web-front/app/product-management/js/templates/part/part_import_form.html b/docdoku-web-front/app/product-management/js/templates/part/part_import_form.html new file mode 100644 index 0000000000..10502e46a1 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/part/part_import_form.html @@ -0,0 +1,107 @@ + + diff --git a/docdoku-web-front/app/product-management/js/templates/part/part_import_modal.html b/docdoku-web-front/app/product-management/js/templates/part/part_import_modal.html new file mode 100644 index 0000000000..68a5c2466d --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/part/part_import_modal.html @@ -0,0 +1,9 @@ + diff --git a/docdoku-web-front/app/product-management/js/templates/part/part_list.html b/docdoku-web-front/app/product-management/js/templates/part/part_list.html new file mode 100644 index 0000000000..55ec033846 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/part/part_list.html @@ -0,0 +1,21 @@ + + + + + + {{i18n.PART_NUMBER}} + {{i18n.VERSION}} + {{i18n.ITERATION}} + {{i18n.TYPE}} + {{i18n.PART_NAME}} + {{i18n.AUTHOR}} + {{i18n.MODIFICATION_DATE}} + {{i18n.LIFECYCLE_STATE}} + {{i18n.CHECKOUT_BY}} + {{i18n.ACL}} + + + + + + diff --git a/docdoku-web-front/app/product-management/js/templates/part/part_list_item.html b/docdoku-web-front/app/product-management/js/templates/part/part_list_item.html new file mode 100644 index 0000000000..c921567667 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/part/part_list_item.html @@ -0,0 +1,63 @@ + {{^model.isLocked}} + + {{/model.isLocked}} + + + + {{#model.hasUnreadModificationNotifications}} + + {{/model.hasUnreadModificationNotifications}} + + + {{#model.isCheckout}} + {{#model.isCheckoutByConnectedUser}}{{/model.isCheckoutByConnectedUser}} + {{^model.isCheckoutByConnectedUser}}{{/model.isCheckoutByConnectedUser}} + {{/model.isCheckout}} + {{^model.isCheckout}} + {{#model.isReleased}}{{/model.isReleased}} + {{^model.isReleased}} + {{#model.isObsolete}}{{/model.isObsolete}} + {{^model.isObsolete}}{{/model.isObsolete}} + {{/model.isReleased}} + {{/model.isCheckout}} + + {{#model.isLastIterationAssembly}} + + {{/model.isLastIterationAssembly}} + {{^model.isLastIterationAssembly}} + + {{/model.isLastIterationAssembly}} + + {{model.getNumber}} + +{{model.getVersion}} + + {{#model.getLastIteration}}{{id}}{{/model.getLastIteration}}{{^model.getLastIteration}}-{{/model.getLastIteration}} + +{{model.getType}} +{{model.getName}} +{{model.getAuthorName}} +{{model.getFormattedModificationDate}} +{{model.getLifeCycleState}} +{{model.getCheckOutUserName}} + + {{#model.isReadOnly}} + + {{/model.isReadOnly}} + {{#model.isFullAccess}} + + {{/model.isFullAccess}} + + + + + + {{#model.hasLastIterationAttachedFiles}} + + {{/model.hasLastIterationAttachedFiles}} + + + {{#model.isLastIterationAssembly}} + + {{/model.isLastIterationAssembly}} + diff --git a/docdoku-web-front/app/product-management/js/templates/part/part_new_version.html b/docdoku-web-front/app/product-management/js/templates/part/part_new_version.html new file mode 100644 index 0000000000..1488d44a30 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/part/part_new_version.html @@ -0,0 +1,47 @@ + diff --git a/docdoku-web-front/app/product-management/js/templates/part/search_part_form.html b/docdoku-web-front/app/product-management/js/templates/part/search_part_form.html new file mode 100644 index 0000000000..4b4e501fc2 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/part/search_part_form.html @@ -0,0 +1,6 @@ +
                                    + + + + +
                                    diff --git a/docdoku-web-front/app/product-management/js/templates/product-instances/product_instance_modal.html b/docdoku-web-front/app/product-management/js/templates/product-instances/product_instance_modal.html new file mode 100644 index 0000000000..dfce17e4c6 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/product-instances/product_instance_modal.html @@ -0,0 +1,149 @@ + diff --git a/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_content.html b/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_content.html new file mode 100644 index 0000000000..806dfc8540 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_content.html @@ -0,0 +1,15 @@ +
                                    +
                                    +
                                    + + +
                                    +
                                    + + {{> newProductInstanceButton}} + {{> deleteButton}} + {{> aclButton}} + {{> importButton}} + +
                                    +
                                    diff --git a/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_creation.html b/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_creation.html new file mode 100644 index 0000000000..3363d112a8 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_creation.html @@ -0,0 +1,57 @@ + diff --git a/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_import_form.html b/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_import_form.html new file mode 100644 index 0000000000..6b881c8e15 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_import_form.html @@ -0,0 +1,89 @@ + + + diff --git a/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_import_modal.html b/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_import_modal.html new file mode 100644 index 0000000000..8cb0d5e4c0 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_import_modal.html @@ -0,0 +1,9 @@ + diff --git a/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_list.html b/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_list.html new file mode 100644 index 0000000000..20165d2984 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_list.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + +
                                    {{i18n.SERIAL_NUMBER}}{{i18n.ITERATION}}{{i18n.PRODUCT}}{{i18n.REBASE_AUTHOR}}{{i18n.MODIFICATION_DATE}}{{i18n.ACL}}
                                    diff --git a/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_list_item.html b/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_list_item.html new file mode 100644 index 0000000000..11450eacaa --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/product-instances/product_instances_list_item.html @@ -0,0 +1,29 @@ + +{{model.getSerialNumber}} +{{model.getNbIterations}} +{{model.getConfigurationItemId}} +{{model.getUpdateAuthorName}} +{{model.getModificationDate}} + + {{#isReadOnly}} + + {{/isReadOnly}} + {{#isFullAccess}} + + {{/isFullAccess}} + + + {{#model.hasPathDataInLastIteration}}{{/model.hasPathDataInLastIteration}} + + + {{#model.hasPathToPathLink}}{{/model.hasPathToPathLink}} + + + {{#model.hasAttachedFilesInLastIteration}} + + {{/model.hasAttachedFilesInLastIteration}} + + + + + diff --git a/docdoku-web-front/app/product-management/js/templates/product/product_content.html b/docdoku-web-front/app/product-management/js/templates/product/product_content.html new file mode 100644 index 0000000000..1921084e54 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/product/product_content.html @@ -0,0 +1,11 @@ +
                                    + + {{> newConfigurationButton}} + {{> snapButton}} + {{> deleteButton}} + {{> udfButton}} +
                                    +
                                    +
                                    diff --git a/docdoku-web-front/app/product-management/js/templates/product/product_creation_view.html b/docdoku-web-front/app/product-management/js/templates/product/product_creation_view.html new file mode 100644 index 0000000000..a5910cc329 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/product/product_creation_view.html @@ -0,0 +1,47 @@ + diff --git a/docdoku-web-front/app/product-management/js/templates/product/product_details.html b/docdoku-web-front/app/product-management/js/templates/product/product_details.html new file mode 100644 index 0000000000..c274a3a986 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/product/product_details.html @@ -0,0 +1,65 @@ + diff --git a/docdoku-web-front/app/product-management/js/templates/product/product_list.html b/docdoku-web-front/app/product-management/js/templates/product/product_list.html new file mode 100644 index 0000000000..5e5d328fff --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/product/product_list.html @@ -0,0 +1,13 @@ + + + + {{i18n.PRODUCT_ID}} + + {{i18n.ROOT_PART}} + {{i18n.AUTHOR}} + + + + + + diff --git a/docdoku-web-front/app/product-management/js/templates/product/product_list_item.html b/docdoku-web-front/app/product-management/js/templates/product/product_list_item.html new file mode 100644 index 0000000000..0779adb65a --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/product/product_list_item.html @@ -0,0 +1,12 @@ + +{{model.getId}} + + {{#model.hasUnreadModificationNotifications}} + + {{/model.hasUnreadModificationNotifications}} + +{{model.getDesignItemName}} < {{model.getDesignItemNumber}} > +{{model.getAuthorName}} + + + diff --git a/docdoku-web-front/app/product-management/js/templates/query_builder.html b/docdoku-web-front/app/product-management/js/templates/query_builder.html new file mode 100644 index 0000000000..772f84e56d --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/query_builder.html @@ -0,0 +1,51 @@ +
                                    + +

                                    {{i18n.QUERY_SEARCH}}

                                    + +
                                    +
                                    + +
                                    + + + + +
                                    + +
                                    + + +
                                    + +
                                    + + +
                                    + +
                                    + +
                                    +
                                    + +
                                    + + +
                                    +
                                    + + +
                                    + +
                                    + +
                                    + + + + + +
                                    + +
                                    diff --git a/docdoku-web-front/app/product-management/js/templates/search_part_advanced_form.html b/docdoku-web-front/app/product-management/js/templates/search_part_advanced_form.html new file mode 100644 index 0000000000..53317a6704 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/templates/search_part_advanced_form.html @@ -0,0 +1,168 @@ + diff --git a/docdoku-web-front/app/product-management/js/views/advanced_search.js b/docdoku-web-front/app/product-management/js/views/advanced_search.js new file mode 100644 index 0000000000..aa749e7ce0 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/advanced_search.js @@ -0,0 +1,221 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/search_part_advanced_form.html', + 'common-objects/collections/users', + 'common-objects/views/attributes/attribute_list', + 'collections/part_templates', + 'common-objects/utils/date', + 'common-objects/collections/lovs' +], function (Backbone, Mustache, template, Users, PartAttributeListView, Templates, date, LOVCollection) { + 'use strict'; + var AdvancedSearchView = Backbone.View.extend({ + + events: { + 'hidden #advanced_search_modal': 'onHidden', + 'submit #advanced_search_form': 'onSubmitForm', + 'click #search-add-attributes': 'addAttribute', + 'change #template-attributes-helper': 'changeAttributes' + }, + + lovs : new LOVCollection(), + + initialize: function () { + _.bindAll(this); + + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n,timeZone:App.config.timeZone})); + this.bindDomElements(); + this.fillInputs(); + this.initAttributesView(); + return this; + }, + + initAttributesView: function () { + + this.attributes = new Backbone.Collection(); + + var that = this; + this.lovs.fetch().success(function(){ + that.attributesView = new PartAttributeListView({ + collection: that.attributes, + lovs : that.lovs, + displayOnly: true + }); + + that.$('#attributes-list').html(that.attributesView.$el); + }); + + + + }, + + fillInputs: function () { + + var that = this; + + this.users = new Users(); + this.users.fetch({reset: true, success: function () { + that.users.each(function (user) { + that.$author.append(''); + }); + }}); + + this.templates = new Templates(); + this.types = []; + this.templatesId = []; + this.templates.fetch({reset: true, success: function () { + that.templates.each(function (template) { + var type = template.get('partType'); + if (!_.contains(that.types, type) && type) { + that.types.push(type); + that.$type.append(''); + } + var templateId = template.get('id'); + if (!_.contains(that.templatesId, templateId) && templateId) { + that.templatesId.push(type); + that.$templatesId.append(''); + } + }); + }}); + + }, + + addAttribute: function () { + this.attributes.add({ + name: '', + type: 'TEXT', + value: '' + }); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + onSubmitForm: function () { + var queryString = this.constructQueryString(); + if (queryString) { + App.router.navigate(App.config.workspaceId + '/parts-search/' + encodeURIComponent(queryString), {trigger: true}); + this.closeModal(); + } + return false; + }, + + bindDomElements: function () { + this.$modal = this.$('#advanced_search_modal'); + this.$number = this.$('#search-number'); + this.$name = this.$('#search-name'); + this.$type = this.$('#search-type'); + this.$version = this.$('#search-version'); + this.$author = this.$('#search-author'); + this.$tags = this.$('#search-tags'); + this.$createdFrom = this.$('#search-creation-from'); + this.$createdTo = this.$('#search-creation-to'); + this.$modifiedFrom = this.$('#search-modification-from'); + this.$modifiedTo = this.$('#search-modification-to'); + this.$templatesId = this.$('#template-attributes-helper'); + this.$standardPart = this.$('input[name=search-standardPart]'); + this.$content = this.$('#search-content'); + }, + + changeAttributes: function (e) { + if (e.target.value) { + var search = _.where(this.templates.models, {id: e.target.value}); + if (search[0]) { + this.attributes.reset(search[0].get('attributeTemplates')); + } + } else { + this.attributes.reset(); + } + }, + + constructQueryString: function () { + + var number = this.$number.val(); + var name = this.$name.val(); + var type = this.$type.val(); + var version = this.$version.val(); + var author = this.$author.val(); + var tags = this.$tags.val().replace(/ /g, ''); + var createdFrom = this.$createdFrom.val(); + var createdTo = this.$createdTo.val(); + var modifiedFrom = this.$modifiedFrom.val(); + var modifiedTo = this.$modifiedTo.val(); + var standardPart = this.$standardPart.filter(':checked').val() === 'all' ? null : this.$standardPart.filter(':checked').val(); + var content = this.$content.val(); + + var queryString = ''; + + if (number) { + queryString += '&number=' + number; + } + if (name) { + queryString += '&name=' + name; + } + if (type) { + queryString += '&type=' + type; + } + if (version) { + queryString += '&version=' + version; + } + if (author) { + queryString += '&author=' + author; + } + if (tags) { + queryString += '&tags=' + tags; + } + if (createdFrom) { + queryString += '&createdFrom=' + date.toUTCWithTimeZoneOffset(createdFrom); + } + if (createdTo) { + queryString += '&createdTo=' + date.toUTCWithTimeZoneOffset(createdTo); + } + if (modifiedFrom) { + queryString += '&modifiedFrom=' + date.toUTCWithTimeZoneOffset(modifiedFrom); + } + if (modifiedTo) { + queryString += '&modifiedTo=' + date.toUTCWithTimeZoneOffset(modifiedTo); + } + if (standardPart) { + queryString += '&standardPart=' + standardPart; + } + if (content) { + queryString += '&content=' + content; + } + + if (this.attributes.length) { + queryString += '&attributes='; + this.attributes.each(function (attribute) { + var type = attribute.get('type'); + var name = attribute.get('name'); + var value = attribute.get('value'); + value = type === 'BOOLEAN' ? (value ? 'true' : 'false') : value; + value = type === 'LOV' ? attribute.get('items')[value].name : value; + queryString += type + ':' + name + ':' + value + ';'; + }); + // remove last '+' + queryString = queryString.substr(0, queryString.length - 1); + } + //replace first occurence of & to ? + queryString = queryString.replace('&','?'); + return queryString; + + } + + }); + + return AdvancedSearchView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/baselines/baseline_choice_list.js b/docdoku-web-front/app/product-management/js/views/baselines/baseline_choice_list.js new file mode 100644 index 0000000000..4a2a99cf61 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/baselines/baseline_choice_list.js @@ -0,0 +1,84 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/baselines/baseline_choice_list.html', + 'views/baselines/baseline_choice_list_item', + 'models/path_choice' +], function (Backbone, Mustache, template, BaselineChoiceItemView, PathChoice) { + 'use strict'; + var BaselineChoicesView = Backbone.View.extend({ + + tagName: 'div', + + className: 'choices-list', + + initialize: function () { + _.bindAll(this); + this.choices = []; + this.choicesViews = []; + }, + + getChoices:function(){ + var choices = []; + _.each(this.choicesViews,function(view){ + if(view.model.retained){ + choices.push(view.getChoice()); + } + },this); + return choices; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.$list = this.$('.choices'); + return this; + }, + + renderList:function(choices){ + this.clear(); + + this.choices = choices.map(function(choice){ + return new PathChoice(choice); + }); + + this.choices.sort(function(a,b){ + return a.getKey() < b.getKey() ? -1:1; + }); + + _.each(this.choices,this.addChoiceItemView,this); + }, + + addChoiceItemView:function(choice){ + var view = new BaselineChoiceItemView({model:choice, removable:this.options.removableItems}).render(); + this.choicesViews.push(view); + this.$list.append(view.$el); + }, + + clear:function(){ + this.choices = []; + this.choicesViews = []; + this.removeSubviews(); + this.$list.empty(); + }, + + removeSubviews: function () { + _(this.choicesViews).invoke('remove'); + }, + + updateFromConfiguration:function(configuration){ + _.invoke(this.choicesViews,'resetNominal'); + if(configuration){ + _.each(configuration.substituteLinks,this.checkLink); + _.each(configuration.optionalUsageLinks,this.checkLink); + } + }, + + checkLink:function(link){ + this.$('[data-path="'+link+'"]').click(); + } + + }); + + return BaselineChoicesView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/baselines/baseline_choice_list_item.js b/docdoku-web-front/app/product-management/js/views/baselines/baseline_choice_list_item.js new file mode 100644 index 0000000000..dae7056946 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/baselines/baseline_choice_list_item.js @@ -0,0 +1,111 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/baselines/baseline_choice_list_item.html', + 'common-objects/views/part/part_modal_view', + 'common-objects/models/part' +], function (Backbone, Mustache, template, PartModalView, Part) { + 'use strict'; + var BaselineChoiceListItemView = Backbone.View.extend({ + + tagName: 'div', + + className: 'control-group', + + events: { + 'change input[type=radio]': 'changeChoice', + 'change .baseline-choice-optional': 'changeOptional', + 'change .choice-retain': 'toggleRetain', + 'click [data-part-key]':'openPartView' + }, + + template: Mustache.parse(template), + + initialize: function () { + _.bindAll(this); + this.model.retained = !this.options.removable; + }, + + render: function () { + this.$el.html(Mustache.render(template, { + model: this.model, + i18n: App.config.i18n, + removable:this.options.removable + })); + + this.$nominalLink = this.$('.nominal-link'); + this.$nominalLink.prop('checked',true); + + this.optional = false; + this.choice = this.$nominalLink.val(); + this.defaultChoice = this.choice; + + this.$('.fa-long-arrow-right').last().remove(); + + if(this.options.removable){ + this.$el.toggleClass('not-retained', !this.model.retained); + } + + return this; + }, + getChoice:function(){ + if(this.choice === this.defaultChoice){ + return null; + } + if(this.optional){ + return {optional:true,path:this.model.getResolvedPathAsString()+'-'+this.defaultChoice}; + } + return {path:this.model.getResolvedPathAsString()+'-'+this.choice}; + }, + changeChoice:function(e){ + this.choice = e.target.value; + + if(this.options.removable){ + this.model.retained = this.choice!==this.defaultChoice; + this.$el.toggleClass('not-retained', !this.model.retained); + } + }, + changeOptional:function(e){ + this.optional = e.target.checked; + this.$el.toggleClass('optional',e.target.checked); + var radios = this.$('input[type=radio]'); + if(e.target.checked){ + radios.prop('disabled',true); + radios.prop('checked',false); + this.choice = null; + }else{ + radios.prop('disabled',false); + radios.first().prop('checked',true); + this.choice = radios.first().val(); + } + + if(this.options.removable){ + this.model.retained = this.choice!==this.defaultChoice; + this.$el.toggleClass('not-retained', !this.model.retained); + } + }, + resetNominal:function(){ + var optionalCheckbox = this.$('.baseline-choice-optional'); + if(optionalCheckbox.is(':checked')){ + optionalCheckbox.click(); + } + this.$nominalLink.click(); + }, + + openPartView:function(e){ + this.$el.trigger('close-modal-request'); + setTimeout(function(){ + var part = new Part({partKey: e.target.dataset.partKey}); + part.fetch().success(function () { + var partModalView = new PartModalView({ + model: part + }); + partModalView.show(); + }); + },500); + } + }); + + return BaselineChoiceListItemView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/baselines/baseline_configuration_list.js b/docdoku-web-front/app/product-management/js/views/baselines/baseline_configuration_list.js new file mode 100644 index 0000000000..66d7ba44de --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/baselines/baseline_configuration_list.js @@ -0,0 +1,45 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/baselines/baseline_configuration_list.html', + 'models/configuration' +], function (Backbone, Mustache, template,Configuration) { + 'use strict'; + var BaselineConfigurationsView = Backbone.View.extend({ + + tagName: 'div', + + events:{ + 'change select':'onConfigurationChanged' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.$select = this.$('select'); + return this; + }, + + renderList:function(configurations){ + this.configurations = configurations; + this.$select.html(''); + _.each(configurations,this.addOption,this); + }, + + addOption:function(data){ + var configuration = new Configuration(data); + this.$select.append(''); + }, + + onConfigurationChanged:function(){ + this.trigger('configuration:changed', _.findWhere(this.configurations,{id:parseInt(this.$select.val(),10)})); + } + + }); + + return BaselineConfigurationsView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/baselines/baseline_creation_view.js b/docdoku-web-front/app/product-management/js/views/baselines/baseline_creation_view.js new file mode 100644 index 0000000000..713d9fe01d --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/baselines/baseline_creation_view.js @@ -0,0 +1,254 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/collections/baselines', + 'models/configuration_item', + 'common-objects/models/product_baseline', + 'collections/configuration_items', + 'collections/configurations', + 'text!templates/baselines/baseline_creation_view.html', + 'common-objects/views/alert', + 'views/baselines/baseline_choice_list', + 'views/baselines/baselined_part_list', + 'views/baselines/baseline_configuration_list' +], function (Backbone, Mustache, Baselines, ConfigurationItem, ProductBaseline, ConfigurationItemCollection, ConfigurationCollection, template, AlertView, BaselineChoiceListView, BaselinedPartsView, BaselineConfigurationsView) { + + 'use strict'; + + var BaselineCreationView = Backbone.View.extend({ + + events: { + 'change #inputConfigurationItem': 'onProductChange', + 'submit #baseline_creation_form': 'onSubmitForm', + 'click button[form=baseline_creation_form]': 'interceptSubmit', + 'hidden #baseline_creation_modal': 'onHidden', + 'change select#inputBaselineType':'changeBaselineType', + 'close-modal-request':'closeModal' + }, + + initialize: function () { + _.bindAll(this); + + this.choiceView = new BaselineChoiceListView({removableItems:false}).render(); + this.baselinePartListView = new BaselinedPartsView({ + editMode:true + }).render(); + this.productBaseline = new ProductBaseline(); + this.baselinePartListView.model = this.productBaseline; + + this.baselineConfigurationsView = new BaselineConfigurationsView().render(); + this.baselineConfigurationsView.on('configuration:changed',this.updateChoicesView); + }, + + render: function () { + + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + model:this.model + })); + + this.bindDomElements(); + this.hideLoader(); + this.fillProductSelect(); + + this.$baselineChoicesListArea.html(this.choiceView.$el); + this.$baselinedPartListArea.html(this.baselinePartListView.$el); + this.$baselinedConfigurationListArea.html(this.baselineConfigurationsView.$el); + + this.$inputBaselineName.customValidity(App.config.i18n.REQUIRED_FIELD); + + this.changeBaselineType(); + return this; + }, + + onProductChange:function(){ + this.model.set('id',this.$inputConfigurationItem.val()); + this.$inputBaselineType.val('LATEST').trigger('change'); + this.$inputBaselineType.prop('disabled',!this.model.getId()); + + }, + + bindDomElements: function () { + this.$modal = this.$('#baseline_creation_modal'); + this.$notifications = this.$el.find('.notifications').first(); + this.$inputBaselineName = this.$('#inputBaselineName'); + this.$inputBaselineDescription = this.$('#inputBaselineDescription'); + this.$submitButton = this.$('button.btn-primary').first(); + this.$inputBaselineType = this.$('#inputBaselineType'); + this.$inputConfigurationItem = this.$('#inputConfigurationItem'); + this.$baselinedPartListArea = this.$('.baselinedPartListArea'); + this.$baselineChoicesListArea = this.$('.baselineChoicesListArea'); + this.$baselinedConfigurationListArea = this.$('.baselinedConfigurationListArea'); + this.$loader = this.$('.loader'); + }, + + fillProductSelect:function(){ + if(this.$inputConfigurationItem){ + var _this = this; + var products = new ConfigurationItemCollection(); + products.fetch().success(function(){ + products.each(function(product){ + _this.$inputConfigurationItem.append(''); + }); + _this.$inputConfigurationItem.trigger('change'); + }); + } + }, + + changeBaselineType:function(){ + var type = this.$inputBaselineType.val(); + this.resetViews(); + this.fetchChoices(type); + }, + + resetViews:function(){ + this.baselinePartListView.clear(); + this.choiceView.clear(); + }, + + fetchChoices:function(type){ + if(this.model.getId()){ + if(type === 'RELEASED'){ + this.showLoader(); + this.model.getReleasedChoices().success(this.fillChoices).error(this.onRequestsError); + this.model.getReleasedParts().success(this.fillPartsResolutionView).error(this.onRequestsError); + } else if (type === 'LATEST'){ + this.showLoader(); + this.model.getLatestChoices().success(this.fillChoices).error(this.onRequestsError); + } + this.model.getConfigurations().success(this.fillConfigurations); + } + }, + + fillPartsResolutionView:function(partIterations){ + this.hideLoader(); + this.productBaseline.setBaselinedParts(partIterations); + this.baselinePartListView.renderList(); + }, + + fillChoices:function(pathChoices){ + this.hideLoader(); + this.choiceView.renderList(pathChoices); + }, + + fillConfigurations:function(configurations){ + this.hideLoader(); + this.baselineConfigurationsView.renderList(configurations); + }, + + onRequestsError:function(xhr,type,message){ + this.$loader.hide(); + this.$notifications.append(new AlertView({ + type:'error', + message:message + }).render().$el); + }, + + updateChoicesView:function(configuration){ + this.choiceView.updateFromConfiguration(configuration); + }, + + showLoader:function(){ + this.$loader.show(); + }, + hideLoader:function(){ + this.$loader.hide(); + }, + + interceptSubmit:function(){ + this.isValid = !this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + + if(this.isValid){ + + var optionalUsageLinks = []; + var substituteLinks = []; + + _.each(this.choiceView.getChoices(),function(choice){ + if(choice && choice.optional){ + optionalUsageLinks.push(choice.path); + } else if(choice && choice.path){ + substituteLinks.push(choice.path); + } + }); + + if(!this.model){ + this.model = new ConfigurationItem({id:this.$inputConfigurationItem.val()}); + } + + this.$submitButton.attr('disabled', 'disabled'); + var baselinedParts = this.baselinePartListView.getBaselinedParts(); + var data = { + name: this.$inputBaselineName.val(), + description: this.$inputBaselineDescription.val(), + baselinedParts:baselinedParts, + substituteLinks:substituteLinks, + optionalUsageLinks:optionalUsageLinks + }; + + if(data.name.trim()){ + var _this = this; + var callbacks = { + success: this.onBaselineCreated, + error: function(error){ + _this.onError(data,error); + _this.$submitButton.removeAttr('disabled'); + } + }; + + data.type = this.$inputBaselineType.val(); + + this.model.createBaseline(data, callbacks); + + }else{ + this.$submitButton.removeAttr('disabled'); + } + } + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onBaselineCreated: function (model) { + + if (model.message) { + this.trigger('warning', model.message); + } + + this.trigger('info',App.config.i18n.BASELINE_CREATED); + + if(this.collection){ + model.configurationItemId = this.model.getId(); + this.collection.add(model); + } + this.closeModal(); + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + } + }); + + return BaselineCreationView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/baselines/baseline_detail_view.js b/docdoku-web-front/app/product-management/js/views/baselines/baseline_detail_view.js new file mode 100644 index 0000000000..4601762540 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/baselines/baseline_detail_view.js @@ -0,0 +1,117 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/baselines/baseline_detail.html', + 'text!common-objects/templates/path/path.html', + 'common-objects/views/pathToPathLink/path_to_path_link_item' +], function (Backbone, Mustache, template, pathTemplate, PathToPathLinkItemView) { + 'use strict'; + var BaselineDetailView = Backbone.View.extend({ + + events: { + 'hidden #baseline_detail_modal': 'onHidden', + 'close-modal-request': 'closeModal' + }, + + initialize: function () { + this.productId = this.options.productId; + }, + + render: function () { + var that = this; + that.$el.html(Mustache.render(template, {i18n: App.config.i18n, model: that.model})); + that.bindDomElements(); + that.initExistingPathToPathView(); + that.renderChoices(); + that.openModal(); + + window.document.body.appendChild(this.el); + return this; + }, + + renderChoices: function () { + var substitutes = this.model.getSubstitutesParts(); + var optionals = this.model.getOptionalsParts(); + this.$substitutesCount.text(substitutes.length); + this.$optionalsCount.text(optionals.length); + + _.each(substitutes, this.drawSubstitutesChoice.bind(this)); + _.each(optionals, this.drawOptionalsChoice.bind(this)); + }, + + bindDomElements: function () { + this.$notifications = this.$('.notifications'); + this.$modal = this.$('#baseline_detail_modal'); + this.$tabs = this.$('.nav-tabs li'); + this.$substitutes = this.$('.substitutes-list'); + this.$substitutesCount = this.$('.substitutes-count'); + this.$optionals = this.$('.optionals-list'); + this.$optionalsCount = this.$('.optionals-count'); + }, + + drawSubstitutesChoice: function (data) { + this.$substitutes.append(Mustache.render(pathTemplate, { + i18n: App.config.i18n, + partLinks:data.partLinks + })); + this.$substitutes.find('.well i.fa-long-arrow-right').last().remove(); + }, + + drawOptionalsChoice: function (data) { + this.$optionals.append(Mustache.render(pathTemplate, { + i18n: App.config.i18n, + partLinks:data.partLinks + })); + this.$optionals.find('.well i.fa-long-arrow-right').last().remove(); + }, + + initExistingPathToPathView: function () { + + this.existingPathToPathLinkCollection = []; + var self = this; + + _.each(self.model.getPathToPathLinks(), function (pathToPathLink) { + self.existingPathToPathLinkCollection.push({ + pathToPath: pathToPathLink, + productId: self.productId, + serialNumber: self.model.getConfigurationItemId() + }); + }); + + _.each(self.existingPathToPathLinkCollection, function (pathToPathLink) { + var pathToPathLinkItem = new PathToPathLinkItemView({model: pathToPathLink}).render(); + self.$('#path-to-path-links').append(pathToPathLinkItem.el); + + pathToPathLinkItem.on('remove', function () { + self.existingPathToPathLinkCollection.splice(self.existingPathToPathLinkCollection.indexOf(pathToPathLink), 1); + }); + }); + + + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + activateTab: function (index) { + this.$tabs.eq(index).children().tab('show'); + }, + + activePathToPathLinkTab: function () { + this.activateTab(2); + } + + }); + + return BaselineDetailView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/baselines/baseline_list.js b/docdoku-web-front/app/product-management/js/views/baselines/baseline_list.js new file mode 100644 index 0000000000..9c12e41e59 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/baselines/baseline_list.js @@ -0,0 +1,52 @@ +/*global _,define*/ +define([ + 'backbone', + 'common-objects/collections/baselines', + 'views/baselines/baseline_list_item' +], function (Backbone, Baselines, BaselineListItemView) { + 'use strict'; + var BaselineListView = Backbone.View.extend({ + + tagName: 'ul', + + className: 'baselines-list', + + initialize: function (attributes, options) { + _.bindAll(this); + this.productId = options.productId; + }, + + render: function () { + this.collection = new Baselines({}, {productId: this.productId}); + this.listenToOnce(this.collection, 'reset', this.onCollectionReset); + this.collection.fetch({reset: true}); + return this; + }, + + onCollectionReset: function () { + var that = this; + this.$el.empty(); + this.subViews = []; + + this.collection.each(function (baseline) { + var view = new BaselineListItemView({model: baseline}).render(); + that.subViews.push(view); + that.$el.append(view.$el); + }); + }, + + getCheckedBaselines: function () { + var checkedBaselines = []; + _.each(this.subViews, function (subView) { + if (subView.isChecked()) { + checkedBaselines.push(subView.model); + } + }); + return checkedBaselines; + } + + + }); + + return BaselineListView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/baselines/baseline_list_item.js b/docdoku-web-front/app/product-management/js/views/baselines/baseline_list_item.js new file mode 100644 index 0000000000..0f4c946b57 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/baselines/baseline_list_item.js @@ -0,0 +1,53 @@ +/*global define*/ +define([ + 'backbone', + 'mustache', + 'text!templates/baselines/baseline_list_item.html', + 'views/baselines/baseline_detail_view' +], function (Backbone, Mustache, template,BaselineDetailView) { + 'use strict'; + var BaselineItemView = Backbone.View.extend({ + tagName: 'li', + + className: 'baseline-item', + + events: { + 'change input[type=checkbox]': 'toggleStroke', + 'click a': 'toBaselineDetailView' + }, + + render: function () { + this.$el.html(Mustache.render(template, {model: this.model})); + this.bindDomElements(); + return this; + }, + + bindDomElements: function () { + this.$a = this.$('a'); + this.$checkbox = this.$('input[type=checkbox]'); + }, + + toggleStroke: function () { + this.$a.toggleClass('stroke'); + }, + + isChecked: function () { + return this.$checkbox.is(':checked'); + }, + + toBaselineDetailView: function () { + setTimeout(this.openBaselineDetailView.bind(this),500); + this.$el.trigger('close-modal-request'); + }, + + openBaselineDetailView :function(){ + var model = this.model; + model.fetch().success(function(){ + new BaselineDetailView({model: model}, {productId: model.getConfigurationItemId()}).render(); + }); + } + + }); + + return BaselineItemView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/baselines/baselined_part_list.js b/docdoku-web-front/app/product-management/js/views/baselines/baselined_part_list.js new file mode 100644 index 0000000000..1203094498 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/baselines/baselined_part_list.js @@ -0,0 +1,86 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/baselines/baselined_part_list.html', + 'views/baselines/baselined_part_list_item', + 'models/baselined_part' +], function (Backbone, Mustache, template, BaselinedPartListItemView, BaselinedPart) { + 'use strict'; + var BaselinedPartsView = Backbone.View.extend({ + + tagName: 'div', + + className: 'baselined-parts-list', + + initialize: function () { + _.bindAll(this); + this.editMode = (this.options.editMode) ? this.options.editMode : false; + this.$el.on('remove', this.removeSubviews()); + this.baselinedParts = []; + this.baselinedPartsViews = []; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + return this; + }, + + bindDomElements: function () { + this.partsUL = this.$('.baselined-parts'); + }, + + renderList: function () { + + this.clear(); + + var collection = this.model.getBaselinedParts(); + + this.baselinedParts = []; + this.baselinedPartsViews = []; + + _.each(collection, function (bpData) { + + var baselinedPart = new BaselinedPart(bpData); + + var baselinedPartItemView = new BaselinedPartListItemView({ + model:baselinedPart, + editMode:this.editMode + }).render(); + + this.baselinedParts.push(baselinedPart); + this.baselinedPartsViews.push(baselinedPartItemView); + + this.partsUL.append(baselinedPartItemView.$el); + + },this); + }, + + getBaselinedParts: function () { + var baselinedParts = []; + _.each(this.baselinedParts, function (baselinedPart) { + baselinedParts.push({ + number: baselinedPart.getNumber(), + version: baselinedPart.getVersion(), + iteration: parseInt(baselinedPart.getIteration(), 10) + }); + }); + return baselinedParts; + }, + + clear:function(){ + this.baselinedParts = []; + this.baselinedPartsViews = []; + this.removeSubviews(); + this.partsUL.empty(); + }, + + removeSubviews: function () { + _(this.baselinedPartsViews).invoke('remove'); + } + + }); + + return BaselinedPartsView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/baselines/baselined_part_list_item.js b/docdoku-web-front/app/product-management/js/views/baselines/baselined_part_list_item.js new file mode 100644 index 0000000000..588abef9a8 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/baselines/baselined_part_list_item.js @@ -0,0 +1,91 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/baselines/baselined_part_list_item.html', + 'common-objects/models/part', + 'common-objects/views/part/part_modal_view' +], function (Backbone, Mustache, template, Part, PartModalView) { + 'use strict'; + var BaselinedPartListItemView = Backbone.View.extend({ + tagName: 'li', + + className: 'baselined-part-item', + + events: { + 'change input[type=radio]': 'changeChoice', + 'click .release': 'releasePart', + 'click [data-part-key]':'toPartModal' + }, + + template: Mustache.parse(template), + + initialize: function () { + _.bindAll(this); + this.editMode = (this.options.editMode) ? this.options.editMode : false; + this.availableIterations = _(this.model.getAvailableIterations()); + }, + + render: function () { + this.$el.html(Mustache.render(template, { + model: this.model, + i18n: App.config.i18n, + editMode:this.editMode + })); + this.$loader = this.$('.loader'); + this.$loader.hide(); + this.$release = this.$('.release'); + this.$errors = this.$('.errors'); + this.checkFirstInput(); + return this; + }, + + checkFirstInput:function(){ + this.$('input[type=radio]').first().prop('checked',true); + }, + + changeChoice:function(e){ + if (e.target.value) { + var versionIteration = e.target.value.split('-'); + var version = versionIteration[0]; + var iteration = versionIteration[1]; + this.model.setVersion(version); + this.model.setIteration(iteration); + } + }, + + releasePart:function(){ + this.$errors.text(''); + this.$loader.show(); + this.$release.hide(); + var part = new Part({partKey: this.model.getNumber()+'-'+this.model.getVersion()}); + part.release().success(this.onReleased).error(this.onReleaseError); + }, + onReleased:function(){ + this.$loader.hide(); + _.findWhere(this.model.getAvailableIterations(),{version:this.model.getVersion()}).released = true; + this.render(); + }, + onReleaseError:function(xhr){ + this.$errors.text(xhr.responseText); + this.$loader.hide(); + this.$release.show(); + }, + toPartModal:function(){ + setTimeout(this.openPartModal.bind(this),500); + this.$el.trigger('close-modal-request'); + }, + openPartModal:function(){ + var model = new Part({partKey:this.model.getNumber()+'-'+this.model.getVersion()}); + model.fetch().success(function () { + var partModalView = new PartModalView({ + model: model + }); + partModalView.show(); + }); + } + + }); + + return BaselinedPartListItemView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/baselines/baselines_content.js b/docdoku-web-front/app/product-management/js/views/baselines/baselines_content.js new file mode 100644 index 0000000000..8279831e26 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/baselines/baselines_content.js @@ -0,0 +1,175 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/collections/baselines', + 'collections/configuration_items', + 'models/configuration_item', + 'text!templates/baselines/baselines_content.html', + 'views/baselines/baselines_list', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/snap_button.html', + 'text!common-objects/templates/buttons/new_product_instance_button.html', + 'text!common-objects/templates/buttons/udf_button.html', + 'common-objects/views/alert', + 'views/baselines/baseline_creation_view', + 'views/product-instances/product_instances_creation', + 'common-objects/views/udf/user_defined_function' +], function (Backbone, Mustache, BaselinesCollection, ConfigurationItemCollection,ConfigurationItem, template, BaselinesListView, deleteButton, snapButton, newProductInstanceButton, udfButton, AlertView, BaselineCreationView, ProductInstanceCreationView, UserDefinedFunctionView) { + 'use strict'; + + var BaselinesContentView = Backbone.View.extend({ + partials: { + deleteButton: deleteButton, + snapButton:snapButton, + newProductInstanceButton:newProductInstanceButton, + udfButton: udfButton + }, + + events: { + 'click button.delete': 'deleteBaseline', + 'click button.new-baseline': 'createBaseline', + 'click button.new-product-instance': 'newProductInstance', + 'click button.udf': 'openUdfView' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + + var self = this; + + this.$el.html(Mustache.render(template, {i18n: App.config.i18n}, this.partials)); + this.bindDomElements(); + this.createBaselineButton.show(); + this.newProductInstanceButton.hide(); + new ConfigurationItemCollection().fetch().success(function(collection){ + if(!collection.length){ + self.$notifications.append(new AlertView({ + type: 'info', + message: App.config.i18n.CREATE_PRODUCT_BEFORE_BASELINE + }).render().$el); + } + }); + + this.$inputProductId.typeahead({ + source: function (query, process) { + $.getJSON(App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products', function (data) { + var ids = []; + _(data).each(function (d) { + ids.push(d.id); + }); + process(ids); + }); + } + }); + + this.bindEvent(); + this.createBaselineView(); + return this; + }, + + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.deleteButton = this.$('.delete'); + this.createBaselineButton = this.$('.new-baseline'); + this.newProductInstanceButton = this.$('.new-product-instance'); + this.$inputProductId = this.$('#inputProductId'); + }, + + bindEvent: function(){ + var _this = this; + this.$inputProductId.change(function () { + _this.createBaselineView(); + }); + this.delegateEvents(); + }, + + createBaseline: function () { + var baselineCreationView = new BaselineCreationView({collection:this.listView.collection,model:new ConfigurationItem()}); + window.document.body.appendChild(baselineCreationView.render().el); + baselineCreationView.on('warning', this.onWarning); + baselineCreationView.openModal(); + }, + + newProductInstance: function () { + var productInstanceCreationView = new ProductInstanceCreationView({ + baseline:this.listView.getSelectedBaseline() + }); + productInstanceCreationView.on('info',this.onInfo); + window.document.body.appendChild(productInstanceCreationView.render().el); + productInstanceCreationView.openModal(); + }, + + createBaselineView: function () { + if (this.listView) { + this.listView.remove(); + this.changeDeleteButtonDisplay(false); + } + if (this.$inputProductId.val()) { + this.listView = new BaselinesListView({ + collection: new BaselinesCollection({}, {type:'product',productId: this.$inputProductId.val()}) + }).render(); + } else { + this.listView = new BaselinesListView({ + collection: new BaselinesCollection({},{type:'product'}) + }).render(); + } + this.$el.append(this.listView.el); + this.listView.on('error', this.onError); + this.listView.on('warning', this.onWarning); + this.listView.on('delete-button:display', this.changeDeleteButtonDisplay); + this.listView.on('new-product-instance-button:display', this.changeNewProductInstanceButtonDisplay); + }, + + deleteBaseline: function () { + this.listView.deleteSelectedBaselines(); + }, + + changeDeleteButtonDisplay: function (state) { + this.deleteButton.toggle(state); + }, + + changeNewProductInstanceButtonDisplay:function(state){ + this.newProductInstanceButton.toggle(state); + }, + + onError:function(model, error){ + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + onWarning:function(model, error){ + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'warning', + message: errorMessage + }).render().$el); + }, + + onInfo:function(message){ + this.$notifications.append(new AlertView({ + type: 'info', + message: message + }).render().$el); + }, + + openUdfView:function(){ + var view = new UserDefinedFunctionView(); + view.render(); + view.setBaselineMode(); + document.body.appendChild(view.el); + view.openModal(); + } + + }); + + return BaselinesContentView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/baselines/baselines_list.js b/docdoku-web-front/app/product-management/js/views/baselines/baselines_list.js new file mode 100644 index 0000000000..51d0c4bdc9 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/baselines/baselines_list.js @@ -0,0 +1,197 @@ +/*global _,define,bootbox,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/baselines/baselines_list.html', + 'views/baselines/baselines_list_item' +], function (Backbone, Mustache, template, BaselinesListItemView) { + 'use strict'; + + var BaselinesListView = Backbone.View.extend({ + events: { + 'click .toggle-checkboxes': 'toggleSelection' + }, + + removeSubviews: function () { + _(this.listItemViews).invoke('remove'); // Invoke remove for each views in listItemViews + this.listItemViews = []; + }, + + initialize: function () { + _.bindAll(this); + this.listenTo(this.collection, 'reset', this.resetList); + this.listenTo(this.collection, 'add', this.addNewBaseline); + this.listItemViews = []; + this.$el.on('remove', this.removeSubviews); + }, + + render: function () { + this.collection.fetch({reset: true}); + return this; + }, + + bindDomElements: function () { + this.$table = this.$('#baseline_table'); + this.$items = this.$('.items'); + this.$checkbox = this.$('.toggle-checkboxes'); + }, + + resetList: function () { + if (this.oTable) { + this.oTable.fnDestroy(); + } + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + this.listItemViews = []; + var that = this; + this.collection.each(function (model) { + that.addBaseline(model); + }); + this.dataTable(); + }, + + addNewBaseline: function (model) { + this.addBaseline(model, true); + this.redraw(); + }, + + addBaseline: function (model, effect) { + var view = this.addBaselineView(model); + if (effect) { + view.$el.highlightEffect(); + } + }, + + removeBaseline: function (model) { + this.removeBaselineView(model); + this.redraw(); + }, + + removeBaselineView: function (model) { + + var viewToRemove = _(this.listItemViews).select(function (view) { + return view.model === model; + })[0]; + + if (viewToRemove) { + this.listItemViews = _(this.listItemViews).without(viewToRemove); + var row = viewToRemove.$el.get(0); + this.oTable.fnDeleteRow(this.oTable.fnGetPosition(row)); + viewToRemove.remove(); + } + + }, + + addBaselineView: function (model) { + var view = new BaselinesListItemView({model: model}).render(); + this.listItemViews.push(view); + this.$items.append(view.$el); + view.on('selectionChanged', this.onSelectionChanged); + view.on('rendered', this.redraw); + return view; + }, + + toggleSelection: function () { + if (this.$checkbox.is(':checked')) { + _(this.listItemViews).each(function (view) { + view.check(); + }); + } else { + _(this.listItemViews).each(function (view) { + view.unCheck(); + }); + } + this.onSelectionChanged(); + }, + + onSelectionChanged: function () { + + var checkedViews = _(this.listItemViews).select(function (view) { + return view.isChecked(); + }); + + if (checkedViews.length <= 0) { + this.onNoBaselineSelected(); + } else if (checkedViews.length === 1) { + this.onOneBaselineSelected(); + } else { + this.onSeveralBaselinesSelected(); + } + + }, + + onNoBaselineSelected: function () { + this.trigger('delete-button:display', false); + this.trigger('new-product-instance-button:display', false); + }, + + onOneBaselineSelected: function () { + this.trigger('delete-button:display', true); + this.trigger('new-product-instance-button:display', true); + }, + + onSeveralBaselinesSelected: function () { + this.trigger('delete-button:display', true); + this.trigger('new-product-instance-button:display', false); + }, + + getSelectedBaseline:function(){ + var selectedView = _.select(this.listItemViews,function(view){ + return view.isChecked(); + })[0]; + return selectedView ? selectedView.model : null; + }, + deleteSelectedBaselines: function () { + var _this = this; + bootbox.confirm(App.config.i18n.CONFIRM_DELETE_BASELINE, function(result){ + if(result){ + _(_this.listItemViews).each(function (view) { + if (view.isChecked()) { + view.model.destroy({ + dataType: 'text', // server doesn't send a json hash in the response body + success: function () { + _this.removeBaseline(view.model); + _this.onSelectionChanged(); + }, + error: function (model, err) { + _this.trigger('error',model,err); + _this.onSelectionChanged(); + } + }); + } + }); + } + }); + }, + redraw: function () { + this.dataTable(); + }, + dataTable: function () { + var oldSort = [ + [1, 'asc'] + ]; + if (this.oTable) { + oldSort = this.oTable.fnSettings().aaSorting; + this.oTable.fnDestroy(); + } + this.oTable = this.$table.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + { 'bSortable': false, 'aTargets': [ 0, 1, 6, 7, 8, 9 ] } + ] + }); + this.$el.find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + } + + }); + + return BaselinesListView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/baselines/baselines_list_item.js b/docdoku-web-front/app/product-management/js/views/baselines/baselines_list_item.js new file mode 100644 index 0000000000..27d354d63e --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/baselines/baselines_list_item.js @@ -0,0 +1,110 @@ +/*global define,App,$*/ +define([ + 'backbone', + 'mustache', + 'text!templates/baselines/baselines_list_item.html', + 'views/baselines/baseline_detail_view', + 'views/product/product_details_view', + 'models/configuration_item' +], function (Backbone, Mustache, template, BaselineDetailView, ProductDetailsView, ConfigurationItem) { + 'use strict'; + var BaselineListItemView = Backbone.View.extend({ + events: { + 'click input[type=checkbox]': 'selectionChanged', + 'click td.reference': 'openDetailView', + 'click a.product_id':'openProductDetailView', + 'click td.has-path-to-path-link':'openDetailViewOnPathToPathLinkTab' + }, + + tagName: 'tr', + + initialize: function () { + this._isChecked = false; + }, + + render: function () { + + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + model: this.model, + bomUrl: this.model.getBomUrl(), + sceneUrl:this.model.getSceneUrl(), + zipUrl: this.model.getZipUrl() + })); + this.$checkbox = this.$('input[type=checkbox]'); + this.bindUserPopover(); + this.trigger('rendered', this); + //LINKS + + var zipUrl = this.model.getZipUrl(); + this.$('.download-zip').popover({ + title: ''+App.config.i18n.DOWNLOAD_ZIP+'
                                    ', + animation: true, + html: true, + trigger: 'manual', + content: ''+App.config.i18n.CAD_FILE+' | ' + + ''+App.config.i18n.LINKS+' | ' + + ''+App.config.i18n.EVERYTHING+'', + placement: 'top' + }).click(function (e) { + $(this).popover('show'); + e.stopPropagation(); + e.preventDefault(); + return false; + }); + + return this; + }, + + selectionChanged: function () { + this._isChecked = this.$checkbox.prop('checked'); + this.trigger('selectionChanged', this); + }, + + isChecked: function () { + return this._isChecked; + }, + + check: function () { + this.$checkbox.prop('checked', true); + this._isChecked = true; + }, + + unCheck: function () { + this.$checkbox.prop('checked', false); + this._isChecked = false; + }, + + openDetailView: function () { + var model = this.model; + model.fetch().success(function () { + new BaselineDetailView({model: model, isForBaseline: true}).render(); + }); + }, + + openDetailViewOnPathToPathLinkTab: function () { + var model = this.model; + model.fetch().success(function () { + var baselineView = new BaselineDetailView({model: model}).render(); + baselineView.activePathToPathLinkTab(); + }.bind(this)); + + }, + + openProductDetailView:function(){ + var model = new ConfigurationItem(); + model.set('_id',this.model.getConfigurationItemId()); + model.fetch().success(function(){ + var view = new ProductDetailsView({model:model}); + window.document.body.appendChild(view.render().el); + view.openModal(); + }); + }, + + bindUserPopover: function () { + this.$('.author-popover').userPopover(this.model.getAuthorLogin(), App.config.i18n.BASELINE, 'left'); + } + }); + + return BaselineListItemView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/configuration/configuration_content.js b/docdoku-web-front/app/product-management/js/views/configuration/configuration_content.js new file mode 100644 index 0000000000..c69b99775e --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/configuration/configuration_content.js @@ -0,0 +1,149 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/configuration/configuration_content.html', + 'collections/configurations', + 'models/configuration_item', + 'views/configuration/configuration_list', + 'views/configuration/configuration_creation_view', + 'common-objects/views/security/acl_edit', + 'common-objects/views/alert', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/ACL_button.html', + 'text!common-objects/templates/buttons/new_configuration_button.html' +], function (Backbone, Mustache, template, ConfigurationCollection,ConfigurationItem, ConfigurationListView,ConfigurationCreationView, ACLEditView, AlertView, deleteButton, aclButton, newConfigurationButton) { + 'use strict'; + var ConfigurationContentView = Backbone.View.extend({ + + partials: { + deleteButton: deleteButton, + newConfigurationButton: newConfigurationButton, + aclButton: aclButton + }, + + events: { + 'click button.new-configuration': 'newConfiguration', + 'click button.delete': 'deleteConfiguration', + 'click button.edit-acl': 'actionEditAcl' + }, + + initialize: function () { + _.bindAll(this); + }, + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n}, this.partials)); + this.bindDomElements(); + + if(!this.configurationCollection){ + this.configurationCollection = new ConfigurationCollection(); + } + + if(this.listView){ + this.listView.remove(); + } + + this.listView = new ConfigurationListView({ + el: this.$('#configuration_table'), + collection: this.configurationCollection + }).render(); + + this.bindEvent(); + + return this; + }, + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.deleteButton = this.$('.delete'); + this.aclButton = this.$('.edit-acl'); + }, + bindEvent: function(){ + this.delegateEvents(); + this.listView.on('error', this.onError); + this.listView.on('warning', this.onWarning); + this.listView.on('info', this.onInfo); + this.listView.on('delete-button:display', this.changeDeleteButtonDisplay); + this.listView.on('acl-button:display', this.changeAclButtonDisplay); + }, + + deleteConfiguration: function () { + this.listView.deleteSelectedConfigurations(); + }, + + changeDeleteButtonDisplay: function (state) { + this.deleteButton.toggle(state); + }, + + changeAclButtonDisplay: function (state) { + this.aclButton.toggle(state); + }, + + newConfiguration:function(){ + var configurationCreationView = new ConfigurationCreationView({collection:this.configurationCollection,model:new ConfigurationItem()}); + window.document.body.appendChild(configurationCreationView.render().el); + configurationCreationView.on('configuration:created',this.configurationCollection.push,this.configurationCollection); + configurationCreationView.openModal(); + }, + + actionEditAcl: function () { + + var modelChecked = this.listView.getSelectedConfiguration(); + + if (modelChecked) { + + var self = this; + + var aclEditView = new ACLEditView({ + editMode: true, + acl: modelChecked.getACL() + }); + + aclEditView.setTitle(modelChecked.getName()); + window.document.body.appendChild(aclEditView.render().el); + + aclEditView.openModal(); + aclEditView.on('acl:update', function () { + var acl = aclEditView.toList(); + modelChecked.updateACL({ + acl: acl || {userEntries: {}, groupEntries: {}}, + success: function () { + modelChecked.set('acl', acl); + aclEditView.closeModal(); + self.listView.redraw(); + }, + error: function(model, error){ + aclEditView.onError(model, error); + } + }); + + }); + + } + }, + + onError:function(model, error){ + var errorMessage = error ? error.responseText : model; + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + onWarning:function(model, error){ + var errorMessage = error ? error.responseText : model; + this.$notifications.append(new AlertView({ + type: 'warning', + message: errorMessage + }).render().$el); + }, + + onInfo:function(message){ + this.$notifications.append(new AlertView({ + type: 'info', + message: message + }).render().$el); + } + + }); + return ConfigurationContentView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/configuration/configuration_creation_view.js b/docdoku-web-front/app/product-management/js/views/configuration/configuration_creation_view.js new file mode 100644 index 0000000000..f61aaa130b --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/configuration/configuration_creation_view.js @@ -0,0 +1,211 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/configuration/configuration_creation_view.html', + 'models/configuration', + 'models/configuration_item', + 'collections/configuration_items', + 'views/baselines/baseline_choice_list', + 'common-objects/views/security/acl', + 'common-objects/views/alert' +], function (Backbone, Mustache, template, Configuration, ConfigurationItem, ConfigurationItemCollection,BaselineChoiceListView, ACLView, AlertView) { + 'use strict'; + var ConfigurationCreationView = Backbone.View.extend({ + + events: { + 'change select#inputConfigurationItem': 'onProductChange', + 'change select#inputPSFilter':'changePSFilter', + 'click button[form=configuration_creation_form]': 'interceptSubmit', + 'submit #configuration_creation_form': 'onSubmitForm', + 'hidden #configuration_creation_modal': 'onHidden', + 'close-modal-request':'closeModal' + }, + + initialize: function () { + _.bindAll(this); + this.choiceView = new BaselineChoiceListView({removableItems:true}).render(); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + + this.$choicesListArea.html(this.choiceView.$el); + + if(this.model.getId()){ + this.onProductChange(); + }else{ + this.fillProductSelect(); + } + + this.workspaceMembershipsView = new ACLView({ + el: this.$('#acl-mapping'), + editMode: true + }).render(); + + this.$('input[required]').customValidity(App.config.i18n.REQUIRED_FIELD); + return this; + }, + + onProductChange:function(){ + this.model.set('id',this.$inputConfigurationItem.val()); + this.$inputPSFilter.val('LATEST').trigger('change'); + this.$inputPSFilter.prop('disabled',!this.model.getId()); + }, + + changePSFilter:function(){ + var type = this.$inputPSFilter.val(); + this.resetViews(); + this.fetchChoices(type); + }, + + resetViews:function(){ + this.choiceView.clear(); + }, + + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.$modal = this.$('#configuration_creation_modal'); + this.$choicesListArea = this.$('.choicesListArea'); + this.$inputConfigurationItem = this.$('#inputConfigurationItem'); + this.$inputPSFilter = this.$('#inputPSFilter'); + this.$submitButton = this.$('button[form=configuration_creation_form]'); + this.$loader = this.$('.loader'); + this.$inputName = this.$('#inputName'); + this.$inputDescription = this.$('#inputDescription'); + }, + + fillProductSelect:function(){ + if(this.$inputConfigurationItem){ + var _this = this; + var products = new ConfigurationItemCollection(); + products.fetch().success(function(){ + _this.hideLoader(); + products.each(function(product){ + _this.$inputConfigurationItem.append(''); + }); + _this.onProductChange(); + }).error(this.onError.bind(this)); + } + }, + + fetchChoices:function(type){ + if(this.model.getId()){ + if(type === 'RELEASED'){ + this.showLoader(); + this.model.getReleasedChoices().success(this.fillChoices).error(this.onRequestsError); + } else if (type === 'LATEST'){ + this.showLoader(); + this.model.getLatestChoices().success(this.fillChoices).error(this.onRequestsError); + } + } + }, + + fillChoices:function(pathChoices){ + this.hideLoader(); + this.choiceView.renderList(pathChoices); + }, + + onRequestsError:function(xhr,type,message){ + this.hideLoader(); + this.$notifications.append(new AlertView({ + type:'error', + message:message + }).render().$el); + }, + + interceptSubmit:function(){ + this.isValid = !this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + + var optionalUsageLinks = []; + var substituteLinks = []; + + _.each(this.choiceView.getChoices(),function(choice){ + if(choice && choice.optional){ + optionalUsageLinks.push(choice.path); + } else if(choice && choice.path){ + substituteLinks.push(choice.path); + } + }); + + if(!optionalUsageLinks.length && !substituteLinks.length){ + this.$notifications.append(new AlertView({ + type: 'error', + message: App.config.i18n.EMPTY_CHOICES + }).render().$el); + }else{ + + if(!this.model){ + this.model = new ConfigurationItem({id:this.$inputConfigurationItem.val()}); + } + + this.$submitButton.attr('disabled', 'disabled'); + + var data = { + name: this.$inputName.val(), + description: this.$inputDescription.val(), + substituteLinks:substituteLinks, + optionalUsageLinks:optionalUsageLinks, + acl: this.workspaceMembershipsView.toList() + }; + + this.model.createConfiguration(data).success(this.onConfigurationCreated.bind(this)).error(this.onError.bind(this)); + + } + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onConfigurationCreated: function (model) { + + if (model.message) { + this.trigger('warning', model.message); + } + + this.trigger('info', App.config.i18n.CONFIGURATION_CREATED); + + if (this.collection) { + model.configurationItemId = this.model.getId(); + this.collection.add(model); + } + this.closeModal(); + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + showLoader:function(){ + this.$loader.show(); + }, + hideLoader:function(){ + this.$loader.hide(); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + } + + }); + + return ConfigurationCreationView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/configuration/configuration_details_view.js b/docdoku-web-front/app/product-management/js/views/configuration/configuration_details_view.js new file mode 100644 index 0000000000..099aef4b35 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/configuration/configuration_details_view.js @@ -0,0 +1,80 @@ +/*global _,define,App */ +define([ + 'backbone', + 'mustache', + 'text!templates/configuration/configuration_details.html', + 'text!common-objects/templates/path/path.html', + 'common-objects/views/alert' +], function (Backbone, Mustache, template, pathTemplate, AlertView) { + 'use strict'; + var ConfigurationDetailsView = Backbone.View.extend({ + + events: { + 'hidden #configuration_details_modal': 'onHidden' + }, + + + initialize: function () { + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, model: this.model})); + this.bindDomElements(); + this.renderChoices(); + return this; + }, + + renderChoices:function(){ + var substitutes = this.model.getSubstitutesParts(); + var optionals = this.model.getOptionalsParts(); + this.$substitutesCount.text(substitutes.length); + this.$optionalsCount.text(optionals.length); + + _.each(substitutes,this.drawSubstitutesChoice.bind(this)); + _.each(optionals,this.drawOptionalsChoice.bind(this)); + }, + + drawSubstitutesChoice:function(data){ + this.$substitutes.append(Mustache.render(pathTemplate, {i18n: App.config.i18n, partLinks: data.partLinks})); + this.$substitutes.find('.well i.fa-long-arrow-right').last().remove(); + }, + + drawOptionalsChoice:function(data){ + this.$optionals.append(Mustache.render(pathTemplate, {i18n: App.config.i18n, partLinks: data.partLinks})); + this.$optionals.find('.well i.fa-long-arrow-right').last().remove(); + }, + + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.$modal = this.$('#configuration_details_modal'); + this.$substitutes = this.$('.substitutes-list'); + this.$substitutesCount = this.$('.substitutes-count'); + this.$optionals = this.$('.optionals-list'); + this.$optionalsCount = this.$('.optionals-count'); + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + } + + }); + + return ConfigurationDetailsView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/configuration/configuration_list.js b/docdoku-web-front/app/product-management/js/views/configuration/configuration_list.js new file mode 100644 index 0000000000..3bead970dd --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/configuration/configuration_list.js @@ -0,0 +1,206 @@ +/*global _,define,bootbox,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/configuration/configuration_list.html', + 'views/configuration/configuration_list_item' +], function (Backbone, Mustache, template, ConfigurationListItemView) { + 'use strict'; + var ConfigurationListView = Backbone.View.extend({ + + events: { + 'click .toggle-checkboxes': 'toggleSelection' + }, + + removeSubviews: function () { + _(this.listItemViews).invoke('remove'); // Invoke remove for each views in listItemViews + this.listItemViews = []; + }, + + initialize: function () { + _.bindAll(this); + this.listenTo(this.collection, 'reset', this.resetList); + this.listenTo(this.collection, 'add', this.addNewConfiguration); + this.listItemViews = []; + this.$el.on('remove', this.removeSubviews); + }, + + render: function () { + var _this = this; + this.oTable=null; + this.collection.fetch({ + reset: true, + error:function(err){ + _this.trigger('error',err); + } + }); + return this; + }, + + bindDomElements: function () { + this.$items = this.$('.items'); + this.$checkbox = this.$('.toggle-checkboxes'); + }, + + resetList: function () { + var _this = this; + this.removeSubviews(); + + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + + this.collection.each(function (model) { + _this.addConfiguration(model); + }); + this.dataTable(); + }, + + addNewConfiguration: function (model) { + this.addConfiguration(model, true); + this.redraw(); + }, + + addConfiguration: function (model, effect) { + var view = this.addConfigurationView(model); + if (effect) { + view.$el.highlightEffect(); + } + }, + + removeConfiguration: function (model) { + this.removeConfigurationView(model); + this.redraw(); + }, + + removeConfigurationView: function (model) { + var viewToRemove = _(this.listItemViews).select(function (view) { + return view.model === model; + })[0]; + + if (viewToRemove) { + this.listItemViews = _(this.listItemViews).without(viewToRemove); + var row = viewToRemove.$el.get(0); + this.oTable.fnDeleteRow(this.oTable.fnGetPosition(row)); + viewToRemove.remove(); + } + + }, + + addConfigurationView: function (model) { + var view = new ConfigurationListItemView({model: model}).render(); + this.listItemViews.push(view); + this.$items.append(view.$el); + view.on('selectionChanged', this.onSelectionChanged); + view.on('rendered', this.redraw); + return view; + }, + + toggleSelection: function () { + if (this.$checkbox.is(':checked')) { + _(this.listItemViews).each(function (view) { + view.check(); + }); + } else { + _(this.listItemViews).each(function (view) { + view.unCheck(); + }); + } + this.onSelectionChanged(); + }, + + onSelectionChanged: function () { + + var checkedViews = _(this.listItemViews).select(function (view) { + return view.isChecked(); + }); + + if (checkedViews.length <= 0) { + this.onNoConfigurationSelected(); + } else if (checkedViews.length === 1) { + this.onOneConfigurationSelected(); + } else { + this.onSeveralConfigurationsSelected(); + } + + }, + + onNoConfigurationSelected: function () { + this.trigger('delete-button:display', false); + this.trigger('acl-button:display', false); + }, + + onOneConfigurationSelected: function () { + this.trigger('delete-button:display', true); + this.trigger('acl-button:display', true); + }, + + onSeveralConfigurationsSelected: function () { + this.trigger('delete-button:display', true); + this.trigger('acl-button:display', false); + }, + + getSelectedConfiguration: function () { + var model = null; + _(this.listItemViews).each(function (view) { + if (view.isChecked()) { + model = view.model; + } + }); + return model; + }, + + deleteSelectedConfigurations: function () { + var _this = this; + bootbox.confirm(App.config.i18n.CONFIRM_DELETE_CONFIGURATION, function(result){ + if(result){ + _(_this.listItemViews).each(function (view) { + if (view.isChecked()) { + view.model.destroy({ + dataType: 'text', // server doesn't send a json hash in the response body + success: function () { + _this.removeConfiguration(view.model); + _this.onSelectionChanged(); + }, + error: function (model, err) { + _this.trigger('error',model,err); + _this.onSelectionChanged(); + } + }); + } + }); + } + }); + }, + redraw: function () { + this.dataTable(); + }, + dataTable: function () { + var oldSort = [ + [0, 'asc'] + ]; + if (this.oTable) { + oldSort = this.oTable.fnSettings().aaSorting; + this.oTable.fnDestroy(); + } + this.oTable = this.$el.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + { 'bSortable': false, 'aTargets': [ 0 , 5 ] }, + { 'sType': App.config.i18n.DATE_SORT, 'aTargets': [3] } + ] + }); + this.$el.parent().find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + } + + }); + + return ConfigurationListView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/configuration/configuration_list_item.js b/docdoku-web-front/app/product-management/js/views/configuration/configuration_list_item.js new file mode 100644 index 0000000000..99d6ff9946 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/configuration/configuration_list_item.js @@ -0,0 +1,80 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/configuration/configuration_list_item.html', + 'views/configuration/configuration_details_view', + 'views/product/product_details_view', + 'models/configuration_item', + 'common-objects/utils/date' +], function (Backbone, Mustache, template, ConfigurationDetailsView, ProductDetailsView, ConfigurationItem, date) { + 'use strict'; + var ConfigurationListItemView = Backbone.View.extend({ + + events: { + 'click input[type=checkbox]': 'selectionChanged', + 'click .configuration_id':'openConfigurationDetailView', + 'click .product_id':'openProductDetailView' + }, + + tagName: 'tr', + + initialize: function () { + this._isChecked = false; + this.listenTo(this.model, 'change', this.render); + }, + + render: function () { + this.$el.html(Mustache.render(template, {model: this.model, i18n: App.config.i18n})); + this.$checkbox = this.$('input[type=checkbox]'); + if (this.isChecked()) { + this.check(); + this.trigger('selectionChanged', this); + } + this.trigger('rendered', this); + date.dateHelper(this.$('.date-popover')); + return this; + }, + + openConfigurationDetailView:function(){ + var model = this.model; + model.fetch().success(function(){ + var view = new ConfigurationDetailsView({model: model}); + window.document.body.appendChild(view.render().el); + view.openModal(); + }); + }, + + openProductDetailView:function(){ + var model = new ConfigurationItem(); + model.set('_id',this.model.getConfigurationItemId()); + model.fetch().success(function(){ + var view = new ProductDetailsView({model:model}); + window.document.body.appendChild(view.render().el); + view.openModal(); + }); + }, + + selectionChanged: function () { + this._isChecked = this.$checkbox.prop('checked'); + this.trigger('selectionChanged', this); + }, + + isChecked: function () { + return this._isChecked; + }, + + check: function () { + this.$checkbox.prop('checked', true); + this._isChecked = true; + }, + + unCheck: function () { + this.$checkbox.prop('checked', false); + this._isChecked = false; + } + + }); + + return ConfigurationListItemView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/nav/baselines_nav.js b/docdoku-web-front/app/product-management/js/views/nav/baselines_nav.js new file mode 100644 index 0000000000..c86c9c3043 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/nav/baselines_nav.js @@ -0,0 +1,50 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'text!templates/nav/baselines_nav.html', + 'views/baselines/baselines_content' +], function (Backbone, Mustache, singletonDecorator, template, BaselinesContentView) { + 'use strict'; + var BaselinesNavView = Backbone.View.extend({ + el: '#baselines-nav', + + initialize: function () { + this.render(); + this.contentView = undefined; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId})); + }, + + setActive: function () { + if (App.$productManagementMenu) { + App.$productManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + + showContent: function () { + this.setActive(); + this.cleanView(); + if(!this.contentView){ + this.contentView = new BaselinesContentView(); + } + this.contentView.render(); + App.$productManagementContent.html(this.contentView.el); + }, + + cleanView: function () { + if (this.contentView) { + this.contentView.undelegateEvents(); + App.$productManagementContent.html(''); + } + } + + }); + + BaselinesNavView = singletonDecorator(BaselinesNavView); + return BaselinesNavView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/nav/checkedouts_nav.js b/docdoku-web-front/app/product-management/js/views/nav/checkedouts_nav.js new file mode 100644 index 0000000000..052ac33427 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/nav/checkedouts_nav.js @@ -0,0 +1,72 @@ +/*global define,App,$*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'text!templates/nav/checkedouts_nav.html', + 'views/part/part_content', + 'collections/checkedouts_part_collection' +], function (Backbone, Mustache, singletonDecorator, template, PartContentView,CheckedOutPartsCollection) { + 'use strict'; + var CheckedOutNavView = Backbone.View.extend({ + el: '#checkedout-nav', + + initialize: function () { + this.render(); + Backbone.Events.on('part:iterationChange', this.refreshCount, this); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId})); + this.refreshCount(); + }, + + refreshCount:function(){ + var that = this; + $.ajax({ + context :this, + type:'GET', + url:App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/countCheckedOut' + }).success(function(data){ + var numberOfItem = data.count; + var badge = that.$('.badge.nav-checkedOut-number-item'); + badge.addClass('badge-info'); + badge.html(numberOfItem); + if(numberOfItem > 0){ + badge.addClass('badge-success'); + badge.removeClass('badge-info'); + } else{ + badge.addClass('badge-info'); + badge.removeClass('badge-success'); + } + }); + }, + + setActive: function () { + if (App.$productManagementMenu) { + App.$productManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + + showContent: function () { + this.setActive(); + this.cleanView(); + if(!this.partContentView){ + this.partContentView = new PartContentView(); + } + this.partContentView.setCollection(new CheckedOutPartsCollection()).render(); + App.$productManagementContent.html(this.partContentView.el); + }, + + cleanView: function () { + if (this.partContentView) { + this.partContentView.undelegateEvents(); + App.$productManagementContent.html(''); + } + } + }); + + CheckedOutNavView = singletonDecorator(CheckedOutNavView); + return CheckedOutNavView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/nav/configuration_nav.js b/docdoku-web-front/app/product-management/js/views/nav/configuration_nav.js new file mode 100644 index 0000000000..0c6aa6e792 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/nav/configuration_nav.js @@ -0,0 +1,51 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'text!templates/nav/configuration_nav.html', + 'views/configuration/configuration_content' +], function (Backbone, Mustache, singletonDecorator, template, ConfigurationContentView) { + 'use strict'; + var ConfigurationNavView = Backbone.View.extend({ + el: '#configuration-nav', + + initialize: function () { + this.render(); + this.configurationContentView = undefined; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId})); + }, + + setActive: function () { + if (App.$productManagementMenu) { + App.$productManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + + showContent: function () { + this.setActive(); + this.cleanView(); + if(!this.configurationContentView){ + this.configurationContentView = new ConfigurationContentView(); + } + this.configurationContentView.render(); + App.$productManagementContent.html(this.configurationContentView.el); + }, + + cleanView: function () { + if (this.configurationContentView) { + this.configurationContentView.undelegateEvents(); + App.$productManagementContent.html(''); + } + } + + }); + + ConfigurationNavView = singletonDecorator(ConfigurationNavView); + return ConfigurationNavView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/nav/part_nav.js b/docdoku-web-front/app/product-management/js/views/nav/part_nav.js new file mode 100644 index 0000000000..cfff7f2ec5 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/nav/part_nav.js @@ -0,0 +1,48 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'text!templates/nav/part_nav.html', + 'views/part/part_content' +], function (Backbone, Mustache, singletonDecorator, template, PartContentView) { + 'use strict'; + var PartNavView = Backbone.View.extend({ + el: '#part-nav', + + initialize: function () { + this.render(); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId})); + }, + + setActive: function () { + if (App.$productManagementMenu) { + App.$productManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + + showContent: function (query) { + this.setActive(); + this.cleanView(); + if(!this.partContentView){ + this.partContentView = new PartContentView(); + } + this.partContentView.setQuery(query).render(); + App.$productManagementContent.html(this.partContentView.el); + }, + + cleanView: function () { + if (this.partContentView) { + this.partContentView.undelegateEvents(); + App.$productManagementContent.html(''); + } + } + }); + + PartNavView = singletonDecorator(PartNavView); + return PartNavView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/nav/part_template_nav.js b/docdoku-web-front/app/product-management/js/views/nav/part_template_nav.js new file mode 100644 index 0000000000..ebee823b37 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/nav/part_template_nav.js @@ -0,0 +1,50 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'text!templates/nav/part_template_nav.html', + 'views/part-template/part_template_content' +], function (Backbone, Mustache, singletonDecorator, template, PartTemplateContentView) { + 'use strict'; + var PartTemplateNavView = Backbone.View.extend({ + el: '#part-template-nav', + + initialize: function () { + this.render(); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId})); + }, + + setActive: function () { + if (App.$productManagementMenu) { + App.$productManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + + showContent: function () { + this.setActive(); + this.cleanView(); + if(!this.partTemplateContentView){ + this.partTemplateContentView = new PartTemplateContentView(); + } + this.partTemplateContentView.render(); + App.$productManagementContent.html(this.partTemplateContentView.el); + }, + + cleanView: function () { + if (this.partTemplateContentView) { + this.partTemplateContentView.undelegateEvents(); + App.$productManagementContent.html(''); + } + } + + }); + + PartTemplateNavView = singletonDecorator(PartTemplateNavView); + return PartTemplateNavView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/nav/product_instances_nav.js b/docdoku-web-front/app/product-management/js/views/nav/product_instances_nav.js new file mode 100644 index 0000000000..550cc0639a --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/nav/product_instances_nav.js @@ -0,0 +1,49 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'text!templates/nav/product_instances_nav.html', + 'views/product-instances/product_instances_content' +], function (Backbone, Mustache, singletonDecorator, template, ProductInstancesContentView) { + 'use strict'; + var ProductInstancesNavView = Backbone.View.extend({ + el: '#product-instances-nav', + + initialize: function () { + this.render(); + this.contentView = undefined; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId})); + }, + + setActive: function () { + if (App.$productManagementMenu) { + App.$productManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + + showContent: function () { + this.setActive(); + this.cleanView(); + if(!this.contentView){ + this.contentView = new ProductInstancesContentView(); + } + this.contentView.render(); + App.$productManagementContent.html(this.contentView.el); + }, + + cleanView: function () { + if (this.contentView) { + this.contentView.undelegateEvents(); + App.$productManagementContent.html(''); + } + } + }); + + ProductInstancesNavView = singletonDecorator(ProductInstancesNavView); + return ProductInstancesNavView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/nav/product_nav.js b/docdoku-web-front/app/product-management/js/views/nav/product_nav.js new file mode 100644 index 0000000000..5fa130e9b4 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/nav/product_nav.js @@ -0,0 +1,51 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'text!templates/nav/product_nav.html', + 'views/product/product_content' +], function (Backbone, Mustache, singletonDecorator, template, ProductContentView) { + 'use strict'; + var ProductNavView = Backbone.View.extend({ + el: '#product-nav', + + initialize: function () { + this.render(); + this.productContentView = undefined; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId})); + }, + + setActive: function () { + if (App.$productManagementMenu) { + App.$productManagementMenu.find('.active').removeClass('active'); + } + this.$el.find('.nav-list-entry').first().addClass('active'); + }, + + showContent: function () { + this.setActive(); + this.cleanView(); + if(!this.productContentView){ + this.productContentView = new ProductContentView(); + } + this.productContentView.render(); + App.$productManagementContent.html(this.productContentView.el); + }, + + cleanView: function () { + if (this.productContentView) { + this.productContentView.undelegateEvents(); + App.$productManagementContent.html(''); + } + } + + }); + + ProductNavView = singletonDecorator(ProductNavView); + return ProductNavView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/nav/tag_nav.js b/docdoku-web-front/app/product-management/js/views/nav/tag_nav.js new file mode 100644 index 0000000000..f64e22a936 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/nav/tag_nav.js @@ -0,0 +1,76 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/common/singleton_decorator', + 'text!templates/nav/tag_nav.html', + 'views/nav/tag_nav_item', + 'views/part/part_content', + 'common-objects/collections/tag', + 'collections/tag_part_collection' +], function (Backbone, Mustache, singletonDecorator, template, TagNavItemView, PartContentView, TagCollection, TagPartsCollection) { + 'use strict'; + var TagNavView = Backbone.View.extend({ + el: '#tag-nav', + + events:{ + 'click .tag-toggle':'toggleCollapse' + }, + + collection : new TagCollection(), + + tag:null, + + tagPartCollection:new TagPartsCollection(), + + initialize: function () { + var that = this; + Backbone.Events.on('refreshTagNavViewCollection', function () { + that.collection.fetch({reset: true}); + }); + this.collection.on('reset',this.onTagListReset.bind(this)); + this.render(); + this.$('.items').hide(); + }, + + onTagListReset:function(){ + this.$('.items').empty(); + this.collection.each(this.addTagView.bind(this)); + if(this.tag){ + this.$('.items').find('.header[data-tag="'+this.tag+'"]').addClass('active'); + } + }, + + addTagView:function(tag){ + this.$('.items').append(new TagNavItemView({model:tag}).render().$el); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, workspaceId: App.config.workspaceId})); + this.collection.fetch({reset: true}); + }, + + showContent: function (tag) { + this.tag = tag; + this.$('.items').show(); + App.$productManagementMenu.find('.active').removeClass('active'); + this.$('.items').find('.header[data-tag="'+this.tag+'"]').addClass('active'); + + if(this.partContentView){ + this.partContentView.destroy(); + } + + this.partContentView = new PartContentView(); + this.tagPartCollection.setTag(this.tag); + this.partContentView.setCollection(this.tagPartCollection).render(); + App.$productManagementContent.html(this.partContentView.el); + }, + + toggleCollapse:function(){ + this.$('.items').toggle(); + } + }); + + TagNavView = singletonDecorator(TagNavView); + return TagNavView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/nav/tag_nav_item.js b/docdoku-web-front/app/product-management/js/views/nav/tag_nav_item.js new file mode 100644 index 0000000000..98d11f1c10 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/nav/tag_nav_item.js @@ -0,0 +1,80 @@ +/*global define,App,bootbox*/ +define([ + 'backbone', + 'mustache', + 'text!templates/nav/tag_nav_item.html', + 'common-objects/models/part' +], function (Backbone, Mustache, template, Part) { + 'use strict'; + var TagNavItemView = Backbone.View.extend({ + events:{ + 'click .delete': 'actionDelete', + 'mouseleave .header': 'hideActions', + 'dragenter >.nav-list-entry': 'onDragEnter', + 'dragover >.nav-list-entry': 'checkDrag', + 'dragleave >.nav-list-entry': 'onDragLeave', + 'drop >.nav-list-entry': 'onDrop' + }, + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + workspaceId: App.config.workspaceId, + model:this.model + })); + this.tagDiv = this.$('>.nav-list-entry'); + return this; + }, + hideActions:function() { + this.$('.header .btn-group').first().removeClass('open'); + }, + actionDelete:function() { + this.hideActions(); + var that = this ; + bootbox.confirm(App.config.i18n.DELETE_TAG_QUESTION, function(result){ + if(result){ + that.model.destroy({ + dataType: 'text' // server doesn't send a json hash in the response body + }).success(that.remove.bind(that)); + } + }); + return false; + }, + + onDragEnter: function () { + this.tagDiv.hasClass('move-part-into'); + }, + + checkDrag: function (e) { + e.dataTransfer.dropEffect = 'copy'; + this.tagDiv.addClass('move-part-into'); + return false; + }, + + onDragLeave: function (e) { + e.dataTransfer.dropEffect = 'none'; + this.tagDiv.removeClass('move-part-into'); + }, + + onDrop: function (e) { + if (e.dataTransfer.getData('part:text/plain')) { + this.tagPart(e); + } + }, + + tagPart: function(e) { + var that = this; + var part = new Part(JSON.parse(e.dataTransfer.getData('part:text/plain'))); + + part.addTags([this.model]).success(function () { + that.tagDiv.removeClass('move-part-into'); + that.tagDiv.highlightEffect(); + }).error(function () { + that.tagDiv.removeClass('move-part-into'); + }); + + } + + }); + + return TagNavItemView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/part-template/part_template_content.js b/docdoku-web-front/app/product-management/js/views/part-template/part_template_content.js new file mode 100644 index 0000000000..8a8a206b23 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/part-template/part_template_content.js @@ -0,0 +1,105 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'collections/part_templates', + 'text!templates/part-template/part_template_content.html', + 'views/part-template/part_template_list', + 'views/part-template/part_template_creation_view', + 'common-objects/views/security/acl_edit', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/ACL_button.html', + 'common-objects/views/lov/lov_modal' +], function (Backbone, Mustache, PartTemplateCollection, template, PartTemplateListView, PartTemplateCreationView,ACLEditView, deleteButton,aclButton, LOVModalView) { + 'use strict'; + var PartTemplateContentView = Backbone.View.extend({ + partials: { + deleteButton: deleteButton, + aclButton: aclButton + }, + + events: { + 'click button.new-template': 'newPartTemplate', + 'click button.delete': 'deletePartTemplate', + 'click .actions .edit-acl': 'onEditAcl', + 'click .actions .list-lov' : 'showLovs' + }, + + initialize: function () { + _.bindAll(this); + }, + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n}, this.partials)); + this.bindDomElements(); + + if(!this.partTemplateCollection){ + this.partTemplateCollection = new PartTemplateCollection(); + } + + if(this.partTemplateListView){ + this.partTemplateListView.remove(); + } + + this.partTemplateListView = new PartTemplateListView({ + el: this.$('#part_template_table'), + collection: this.partTemplateCollection + }).render(); + + this.bindEvent(); + return this; + }, + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.deleteButton = this.$('.delete'); + this.aclButton = this.$('.actions .edit-acl'); + + }, + bindEvent: function(){ + this.partTemplateListView.on('error', this.onError); + this.partTemplateListView.on('warning', this.onWarning); + this.partTemplateListView.on('delete-button:display', this.changeDeleteButtonDisplay); + this.partTemplateListView.on('acl-button:display', this.changeACLButtonDisplay); + this.delegateEvents(); + + }, + + + newPartTemplate: function () { + var partTemplateCreationView = new PartTemplateCreationView(); + partTemplateCreationView.on('part-template:created', this.partTemplateCollection.push, this.partTemplateCollection); + partTemplateCreationView.show(); + }, + deletePartTemplate: function () { + this.partTemplateListView.deleteSelectedPartTemplates(); + }, + + + changeDeleteButtonDisplay: function (state) { + if (state) { + this.deleteButton.show(); + } else { + this.deleteButton.hide(); + } + }, + changeACLButtonDisplay: function(state){ + if (state) { + this.aclButton.show(); + + } else { + this.aclButton.hide(); + } + }, + onEditAcl: function () { + this.partTemplateListView.editSelectedPartTemplateACL(); + }, + showLovs: function(){ + var lovmodal = new LOVModalView({ + }); + window.document.body.appendChild(lovmodal.render().el); + lovmodal.openModal(); + } + + }); + + return PartTemplateContentView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/part-template/part_template_creation_view.js b/docdoku-web-front/app/product-management/js/views/part-template/part_template_creation_view.js new file mode 100644 index 0000000000..6586d971c6 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/part-template/part_template_creation_view.js @@ -0,0 +1,123 @@ +/*global define,App*/ +define([ + 'common-objects/views/components/modal', + 'text!templates/part-template/part_template_creation_view.html', + 'models/part_template', + 'common-objects/views/alert', + 'common-objects/views/attributes/template_new_attributes', + 'common-objects/views/workflow/workflow_list' +], function (ModalView, template, PartTemplate, AlertView, TemplateNewAttributesView, WorkflowListView) { + 'use strict'; + var PartTemplateCreationView = ModalView.extend({ + template: template, + + initialize: function () { + ModalView.prototype.initialize.apply(this, arguments); + this.events['click .modal-footer .btn-primary'] = 'interceptSubmit'; + this.events['submit #part_template_creation_form'] = 'onSubmitForm'; + }, + + rendered: function () { + + this.bindDomElements(); + + this.workflowsView = new WorkflowListView({ + el: this.$('#workflows-list') + }); + + this.productInstanceAttributesView = this.addSubView( + new TemplateNewAttributesView({ + el: '#attribute-product-instance-list', + editMode: true, + unfreezable: true + }) + ).render(); + + this.attributesView = this.addSubView( + new TemplateNewAttributesView({ + el: '#attributes-list', + editMode: true, + attributesLocked: this.attributesLocked + }) + ).render(); + + + var $popoverLink = this.$('#mask-help'); + + $popoverLink.popover({ + title: App.config.i18n.MASK, + placement: 'left', + html: true, + trigger: 'manual', + content: App.config.i18n.MASK_HELP.nl2br(), + container:'#part_template_creation_modal' + }).click(function(e){ + $popoverLink.popover('show'); + e.stopPropagation(); + e.preventDefault(); + return false; + }); + + this.$partTemplateReference.customValidity(App.config.i18n.REQUIRED_FIELD); + + }, + + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.$partTemplateReference = this.$('#part-template-reference'); + this.$partTemplateType = this.$('#part-template-type'); + this.$partTemplateMask = this.$('#part-template-mask'); + this.$partTemplateIdGenerated = this.$('#part-template-id-generated'); + }, + + interceptSubmit:function(){ + this.isValid = !this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + + if(this.isValid){ + var workflow = this.workflowsView.selected(); + + this.model = new PartTemplate({ + reference: this.$partTemplateReference.val(), + partType: this.$partTemplateType.val(), + mask: this.$partTemplateMask.val(), + idGenerated: this.$partTemplateIdGenerated.is(':checked'), + attributeTemplates: this.attributesView.collection.toJSON(), + attributeInstanceTemplates: this.productInstanceAttributesView.collection.toJSON(), + attributesLocked: this.attributesView.isAttributesLocked(), + workflowModelId: workflow ? workflow.get('id') : null + }); + + this.model.save({}, { + wait: true, + success: this.onPartTemplateCreated, + error: this.onError + }); + } + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onPartTemplateCreated: function () { + this.trigger('part-template:created', this.model); + this.hide(); + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + } + + }); + + return PartTemplateCreationView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/part-template/part_template_edit_view.js b/docdoku-web-front/app/product-management/js/views/part-template/part_template_edit_view.js new file mode 100644 index 0000000000..c5e82d954d --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/part-template/part_template_edit_view.js @@ -0,0 +1,165 @@ +/*global define,App*/ +define([ + 'common-objects/views/components/modal', + 'text!templates/part-template/part_template_creation_view.html', + 'models/part_template', + 'common-objects/views/file/file_list', + 'common-objects/views/attributes/template_new_attributes', + 'common-objects/views/workflow/workflow_list', + 'common-objects/views/alert', + 'common-objects/utils/date' +], function (ModalView, template, PartTemplate, FileListView, TemplateNewAttributesView, WorkflowListView, AlertView, date) { + 'use strict'; + var PartTemplateEditView = ModalView.extend({ + + template: template, + + initialize: function () { + this.templateExtraData = {modificationDate : this.model.getFormattedModificationDate()}; + ModalView.prototype.initialize.apply(this, arguments); + this.events['click .modal-footer .btn-primary'] = 'interceptSubmit'; + this.events['submit #part_template_creation_form'] = 'onSubmitForm'; + }, + + rendered: function () { + + this.bindDomElements(); + + this.workflowsView = new WorkflowListView({ + el: this.$('#workflows-list') + }); + + var _this = this; + this.workflowsView.collection.on('reset', function() { + _this.workflowsView.setValue(_this.model.get('workflowModelId')); + }); + + this.attributesView = this.addSubView( + new TemplateNewAttributesView({ + el: '#attributes-list', + attributesLocked: this.model.isAttributesLocked(), + editMode:true + }) + ).render(); + + this.productInstanceAttributesView = this.addSubView( + new TemplateNewAttributesView({ + el: '#attribute-product-instance-list', + editMode: true, + unfreezable: true + }) + ).render(); + + + this.fileListView = new FileListView({ + baseName: this.model.getBaseName(), + deleteBaseUrl: this.model.url(), + uploadBaseUrl: this.model.getUploadBaseUrl(), + collection: this.model._attachedFile, + editMode: true, + singleFile: true + }).render(); + + this.$('#tab-files').append(this.fileListView.el); + + this.attributesView.collection.reset(this.model.get('attributeTemplates')); + this.productInstanceAttributesView.collection.reset(this.model.get('attributeInstanceTemplates')); + + var $popoverLink = this.$('a#mask-help'); + + $popoverLink.popover({ + title: App.config.i18n.MASK, + placement: 'left', + html: true, + trigger: 'manual', + content: App.config.i18n.MASK_HELP.nl2br(), + container:'#part_template_creation_modal' + }).click(function(e){ + $popoverLink.popover('show'); + e.stopPropagation(); + e.preventDefault(); + return false; + }); + date.dateHelper(this.$('.date-popover')); + }, + + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.$partTemplateReference = this.$('#part-template-reference'); + this.$partTemplateType = this.$('#part-template-type'); + this.$partTemplateMask = this.$('#part-template-mask'); + this.$partTemplateIdGenerated = this.$('#part-template-id-generated'); + this.tabs =this.$('.nav-tabs li'); + }, + + interceptSubmit:function(){ + this.isValid = !this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + + if(this.isValid){ + var workflow = this.workflowsView.selected(); + + // cannot pass a collection of cad file to server. + var attachedFile = this.fileListView.collection.first(); + this.fileListView.collection.reset(this.fileListView.collection.last()); + if (attachedFile) { + this.model.set('attachedFile', attachedFile.get('fullName')); + } else { + this.model.set('attachedFile', ''); + } + + this.model.save({ + id: this.$partTemplateReference.val(), + partType: this.$partTemplateType.val(), + mask: this.$partTemplateMask.val(), + idGenerated: this.$partTemplateIdGenerated.is(':checked'), + attributeTemplates: this.attributesView.collection.toJSON(), + attributeInstanceTemplates: this.productInstanceAttributesView.collection.toJSON(), + attributesLocked: this.attributesView.isAttributesLocked(), + workflowModelId: workflow ? workflow.get('id') : null + }, { + wait: true, + success: this.onPartTemplateSaved, + error: this.onError + }); + + this.fileListView.deleteFilesToDelete(); + } + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + activateTab: function (index) { + + this.tabs.eq(index).children().tab('show'); + }, + activateFileTab: function(){ + this.activateTab(3); + }, + cancelAction: function () { + this.fileListView.deleteNewFiles(); + }, + + onPartTemplateSaved: function () { + this.model.fetch(); + this.hide(); + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + } + + }); + + return PartTemplateEditView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/part-template/part_template_list.js b/docdoku-web-front/app/product-management/js/views/part-template/part_template_list.js new file mode 100644 index 0000000000..7b9eda749c --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/part-template/part_template_list.js @@ -0,0 +1,233 @@ +/*global _,define,App,bootbox*/ +define([ + 'backbone', + 'mustache', + 'text!templates/part-template/part_template_list.html', + 'views/part-template/part_template_list_item', + 'common-objects/views/security/acl_edit' +], function (Backbone, Mustache, template, PartTemplateListItemView, ACLEditView) { + 'use strict'; + var PartTemplateListView = Backbone.View.extend({ + + events: { + 'click .toggle-checkboxes': 'toggleSelection' + }, + + removeSubviews: function () { + _(this.listItemViews).invoke('remove'); // Invoke remove for each views in listItemViews + this.listItemViews = []; + }, + + initialize: function () { + _.bindAll(this); + this.listenTo(this.collection, 'reset', this.resetList); + this.listenTo(this.collection, 'add', this.addNewPartTemplate); + this.listItemViews = []; + this.$el.on('remove', this.removeSubviews); + }, + + render: function () { + var _this = this; + this.collection.fetch({ + reset: true, + error: function (err) { + _this.trigger('error', err); + } + }); + return this; + }, + bindDomElements: function () { + this.$items = this.$('.items'); + this.$checkbox = this.$('.toggle-checkboxes'); + }, + + resetList: function () { + var _this = this; + this.removeSubviews(); + + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + + this.collection.each(function (model) { + _this.addPartTemplate(model); + }); + this.dataTable(); + }, + + addNewPartTemplate: function (model) { + this.addPartTemplate(model, true); + this.redraw(); + }, + + addPartTemplate: function (model, effect) { + var view = this.addPartTemplateView(model); + if (effect) { + view.$el.highlightEffect(); + } + }, + + removePartTemplate: function (model) { + this.removePartTemplateView(model); + this.redraw(); + }, + + removePartTemplateView: function (model) { + + var viewToRemove = _(this.listItemViews).select(function (view) { + return view.model === model; + })[0]; + + if (viewToRemove) { + this.listItemViews = _(this.listItemViews).without(viewToRemove); + var row = viewToRemove.$el.get(0); + this.oTable.fnDeleteRow(this.oTable.fnGetPosition(row)); + viewToRemove.remove(); + } + + }, + + addPartTemplateView: function (model) { + var view = new PartTemplateListItemView({model: model}).render(); + this.listItemViews.push(view); + this.$items.append(view.$el); + view.on('selectionChanged', this.onSelectionChanged); + view.on('rendered', this.redraw); + return view; + }, + + toggleSelection: function () { + if (this.$checkbox.is(':checked')) { + _(this.listItemViews).each(function (view) { + view.check(); + }); + } else { + _(this.listItemViews).each(function (view) { + view.unCheck(); + }); + } + this.onSelectionChanged(); + }, + + onSelectionChanged: function () { + + var checkedViews = _(this.listItemViews).select(function (itemView) { + return itemView.isChecked(); + }); + + if (checkedViews.length <= 0) { + this.onNoPartTemplateSelected(); + } else if (checkedViews.length === 1) { + this.onOnePartTemplateSelected(); + } else { + this.onSeveralPartTemplatesSelected(); + } + + }, + + onNoPartTemplateSelected: function () { + this.trigger('delete-button:display', false); + this.trigger('acl-button:display', false); + }, + onOnePartTemplateSelected: function () { + this.trigger('delete-button:display', true); + this.trigger('acl-button:display', true); + }, + onSeveralPartTemplatesSelected: function () { + this.trigger('delete-button:display', true); + this.trigger('acl-button:display', false); + }, + + deleteSelectedPartTemplates: function () { + var _this = this; + bootbox.confirm(App.config.i18n.CONFIRM_DELETE_PART_TEMPLATE, function (result) { + if (result) { + _(_this.listItemViews).each(function (view) { + if (view.isChecked()) { + view.model.destroy({ + dataType: 'text', // server doesn't send a json hash in the response body + success: function () { + _this.removePartTemplate(view.model); + _this.onSelectionChanged(); + }, + error: function (model, err) { + _this.trigger('error', model, err); + _this.onSelectionChanged(); + } + }); + } + }); + } + }); + }, + + editSelectedPartTemplateACL: function () { + var templateSelected; + var _this = this; + _(_this.listItemViews).each(function (view) { + if (view.isChecked()) { + templateSelected = view.model; + } + }); + + var aclEditView = new ACLEditView({ + editMode: true, + acl: templateSelected.get('acl') + }); + + aclEditView.setTitle(templateSelected.getId()); + window.document.body.appendChild(aclEditView.render().el); + + aclEditView.openModal(); + aclEditView.on('acl:update', function () { + + var acl = aclEditView.toList(); + + templateSelected.updateACL({ + acl: acl || {userEntries: {}, groupEntries: {}}, + success: function () { + templateSelected.set('acl', acl); + aclEditView.closeModal(); + }, + error: function(error){ + aclEditView.onError(error); + } + }); + }); + + return false; + }, + + redraw: function () { + this.dataTable(); + }, + dataTable: function () { + var oldSort = [ + [0, 'asc'] + ]; + if (this.oTable) { + oldSort = this.oTable.fnSettings().aaSorting; + this.oTable.fnDestroy(); + } + this.oTable = this.$el.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + {'bSortable': false, 'aTargets': [0, 6, 7]}, + {'sType': App.config.i18n.DATE_SORT, 'aTargets': [5]} + ] + }); + this.$el.parent().find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + } + + }); + + return PartTemplateListView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/part-template/part_template_list_item.js b/docdoku-web-front/app/product-management/js/views/part-template/part_template_list_item.js new file mode 100644 index 0000000000..45031ffd21 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/part-template/part_template_list_item.js @@ -0,0 +1,76 @@ +/*global _,define*/ +define([ + 'backbone', + 'mustache', + 'models/part_template', + 'text!templates/part-template/part_template_list_item.html', + 'views/part-template/part_template_edit_view', + 'common-objects/utils/date' +], function (Backbone, Mustache, PartTemplate, template, PartTemplateEditView, date) { + 'use strict'; + var PartTemplateListItemView = Backbone.View.extend({ + + events: { + 'click input[type=checkbox]': 'selectionChanged', + 'click td.reference': 'toPartTemplateEditModal', + 'click .part-attached-files i' : 'openPartTemplateModal' + }, + + tagName: 'tr', + + initialize: function () { + _.bindAll(this); + this._isChecked = false; + this.listenTo(this.model, 'change', this.render); + }, + + render: function () { + this.$el.html(Mustache.render(template, this.model)); + this.$checkbox = this.$('input[type=checkbox]'); + if (this.isChecked()) { + this.check(); + this.trigger('selectionChanged', this); + } + this.bindUserPopover(); + date.dateHelper(this.$('.date-popover')); + this.trigger('rendered', this); + return this; + }, + + selectionChanged: function () { + this._isChecked = this.$checkbox.prop('checked'); + this.trigger('selectionChanged', this); + }, + + isChecked: function () { + return this._isChecked; + }, + + check: function () { + this.$checkbox.prop('checked', true); + this._isChecked = true; + }, + + unCheck: function () { + this.$checkbox.prop('checked', false); + this._isChecked = false; + }, + + bindUserPopover: function () { + this.$('.author-popover').userPopover(this.model.getAuthorLogin(), this.model.getId(), 'left'); + }, + + toPartTemplateEditModal: function () { + new PartTemplateEditView({model: this.model}); + }, + + openPartTemplateModal: function () { + var partTemplateEditView = new PartTemplateEditView({model: this.model}); + partTemplateEditView.activateFileTab(); + } + + }); + + return PartTemplateListItemView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/part/part_content.js b/docdoku-web-front/app/product-management/js/views/part/part_content.js new file mode 100644 index 0000000000..6da500791e --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/part/part_content.js @@ -0,0 +1,563 @@ +/*global _,$,define,App,bootbox,window*/ +define([ + 'backbone', + 'mustache', + 'async', + 'common-objects/collections/part_collection', + 'common-objects/collections/part_search_collection', + 'text!templates/part/part_content.html', + 'views/part/part_list', + 'views/part/part_creation_view', + 'views/part/part_new_version', + 'common-objects/views/prompt', + 'common-objects/views/security/acl_edit', + '../query_builder', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/checkout_button_group.html', + 'text!common-objects/templates/buttons/new_version_button.html', + 'text!common-objects/templates/buttons/release_button.html', + 'text!common-objects/templates/buttons/ACL_button.html', + 'text!common-objects/templates/buttons/new_product_button.html', + 'text!common-objects/templates/buttons/tags_button.html', + 'text!common-objects/templates/buttons/obsolete_button.html', + 'text!templates/part/search_part_form.html', + 'common-objects/views/alert', + 'common-objects/views/tags/tags_management', + 'views/part/part_importer', + 'views/product/product_creation_view', + 'views/advanced_search', + 'views/part/part_grouped_by_list', + 'text!common-objects/templates/buttons/import_button.html', +], function (Backbone, Mustache, Async, PartCollection, PartSearchCollection, template, PartListView, PartCreationView, PartNewVersionView, PromptView, ACLEditView, QueryBuilder, deleteButton, checkoutButtonGroup, newVersionButton, releaseButton, aclButton, newProductButton, tagsButton, obsoleteButton, searchForm, AlertView,TagsManagementView,PartImporterView,ProductCreationView,AdvancedSearchView, PartGroupedByView, importButton) { + 'use strict'; + var PartContentView = Backbone.View.extend({ + events: { + 'click button.new-part': 'newPart', + 'click button.delete': 'deletePart', + 'click button.checkout': 'checkout', + 'click button.undocheckout': 'undocheckout', + 'click button.checkin': 'checkin', + 'click button.edit-acl': 'updateACL', + 'click button.new-version': 'newVersion', + 'click button.new-release': 'releasePart', + 'click button.mark-as-obsolete': 'markAsObsolete', + 'click button.next-page': 'toNextPage', + 'click button.previous-page': 'toPreviousPage', + 'click .actions .tags': 'actionTags', + 'click button.first-page': 'toFirstPage', + 'click button.last-page': 'toLastPage', + 'click button.current-page': 'goToPage', + 'click button.show-all': 'showAll', + 'click button.new-product': 'newProduct', + 'submit #part-search-form': 'onQuickSearch', + 'click .advanced-search-button': 'onAdvancedSearch', + 'click .display-query-builder-button': 'toggleQueryBuilder', + 'click .import': 'showImporter' + }, + + partials: { + deleteButton: deleteButton, + aclButton: aclButton, + checkoutButtonGroup: checkoutButtonGroup, + newVersionButton: newVersionButton, + releaseButton: releaseButton, + searchForm: searchForm, + newProductButton:newProductButton, + tagsButton: tagsButton, + obsoleteButton:obsoleteButton, + importButton:importButton + }, + + initialize: function () { + _.bindAll(this); + this.query = null; + }, + + setCollection:function(collection){ + this.partsCollection = collection; + return this; + }, + + setQuery: function (query) { + this.query = query; + this.partsCollection = null; + return this; + }, + + render: function () { + this.isQueryBuilderDisplayed = false; + this.$el.html(Mustache.render(template, {i18n: App.config.i18n}, this.partials)); + this.bindDomElements(); + + //always show tag button + this.tagsButton.show(); + + if(!this.partsCollection){ + if (this.query) { + this.partsCollection = new PartSearchCollection(); + this.partsCollection.setQuery(this.query); + } else { + this.partsCollection = new PartCollection(); + } + } + + if(this.partListView){ + this.partListView.remove(); + } + + this.partListView = new PartListView({ + el: this.$('#part_table'), + collection: this.partsCollection + }).render(); + + + this.queryBuilder = new QueryBuilder({ + el: this.$('.query-builder') + }); + + this.bindEvent(); + return this; + }, + + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.deleteButton = this.$('.delete'); + this.checkoutGroup = this.$('.checkout-group'); + this.checkoutButton = this.$('.checkout'); + this.undoCheckoutButton = this.$('.undocheckout'); + this.aclButton = this.$('.edit-acl'); + this.checkinButton = this.$('.checkin'); + this.newVersionButton = this.$('.new-version'); + this.newProductButton = this.$('.new-product'); + this.releaseButton = this.$('.new-release'); + this.obsoleteButton = this.$('.mark-as-obsolete'); + this.tagsButton = this.$('.actions .tags'); + this.currentPageIndicator = this.$('.current-page'); + this.pageControls = this.$('.page-controls'); + this.pagingButtons = this.$('.paging-buttons'); + this.showAllButton = this.$('.show-all'); + }, + + bindEvent: function(){ + this.partListView.collection.on('page-count:fetch', this.onPageCountFetched); + this.partListView.collection.fetchPageCount(); + + this.partListView.on('error', this.onError); + this.partListView.on('warning', this.onWarning); + this.partListView.on('delete-button:display', this.changeDeleteButtonDisplay); + this.partListView.on('checkout-group:display', this.changeCheckoutGroupDisplay); + this.partListView.on('checkout-group:update', this.updateCheckoutButtons); + this.partListView.on('acl-edit-button:display', this.changeACLButtonDisplay); + this.partListView.on('new-version-button:display', this.changeVersionButtonDisplay); + this.partListView.on('release-button:display', this.changeReleaseButtonDisplay); + this.partListView.on('obsolete-button:display', this.changeObsoleteButtonDisplay); + this.partListView.on('new-product-button:display', this.changeNewProductButtonDisplay); + + this.delegateEvents(); + + var self = this; + this.queryBuilder.on('query:search', function(data){ + if(self.partListView){ + self.partListView.remove(); + self.pageControls.remove(); + self.$('#part_table_filter').remove(); + } + self.queryTable = new PartGroupedByView({ + data : data, + el: self.$('#query-table') + }).render(); + }); + + }, + + newPart: function () { + var partCreationView = new PartCreationView({ + autoAddTag: this.partListView.collection.tag + }); + this.listenTo(partCreationView, 'part:created', this.fetchPartAndAdd); + window.document.body.appendChild(partCreationView.el); + partCreationView.openModal(); + }, + + fetchPartAndAdd: function (part) { + var self = this; + part.set('partKey', part.getNumber() + '-A'); + part.fetch().success(function () { + self.addPartInList(part); + }); + this.partListView.collection.fetchPageCount(); + }, + + deletePart: function () { + this.partListView.deleteSelectedParts(); + }, + + addPartInList: function (part) { + this.partsCollection.push(part); + }, + + changeDeleteButtonDisplay: function (state) { + this.deleteButton.toggle(state); + }, + + changeACLButtonDisplay: function (state) { + this.aclButton.toggle(state); + }, + + changeCheckoutGroupDisplay: function (state) { + this.checkoutGroup.toggle(state); + }, + + changeVersionButtonDisplay: function (state) { + this.newVersionButton.toggle(state); + }, + + changeNewProductButtonDisplay: function (state) { + this.newProductButton.toggle(state); + }, + + changeReleaseButtonDisplay: function (state) { + this.releaseButton.toggle(state); + }, + + changeObsoleteButtonDisplay: function(state) { + this.obsoleteButton.toggle(state); + }, + + updateCheckoutButtons: function (values) { + this.checkoutButton.prop('disabled', !values.canCheckout); + this.undoCheckoutButton.prop('disabled', !values.canUndo); + this.checkinButton.prop('disabled', !values.canCheckin); + }, + + checkin: function () { + this.partListView.getSelectedPartIndexes(); + var selectedParts = this.partListView.getSelectedParts(); + var selectedPartsWithoutNote = 0; + + _.each(selectedParts, function (selectedPart) { + if (!selectedPart.getLastIteration().get('iterationNote')) { + selectedPartsWithoutNote++; + } + }); + + var _this = this; + + if (selectedPartsWithoutNote > 0) { + var promptView = new PromptView(); + + if (selectedParts.length > 1) { + promptView.setPromptOptions(App.config.i18n.REVISION_NOTE, App.config.i18n.PART_REVISION_NOTE_PROMPT_LABEL, App.config.i18n.REVISION_NOTE_PROMPT_OK, App.config.i18n.REVISION_NOTE_PROMPT_CANCEL); + } else { + promptView.setPromptOptions(App.config.i18n.REVISION_NOTE, App.config.i18n.REVISION_NOTE_PROMPT_LABEL, App.config.i18n.REVISION_NOTE_PROMPT_OK, App.config.i18n.REVISION_NOTE_PROMPT_CANCEL); + } + + promptView.specifyInput('textarea'); + window.document.body.appendChild(promptView.render().el); + promptView.openModal(); + + this.listenTo(promptView, 'prompt-ok', function (args) { + var iterationNote = args[0]; + if (_.isEqual(iterationNote, '')) { + iterationNote = null; + } + Async.each(selectedParts, function(part, callback) { + var revisionNote; + if (iterationNote) { + revisionNote = part.getLastIteration().get('iterationNote'); + if (!revisionNote) { + revisionNote = iterationNote; + } + } + + part.getLastIteration().save({ + iterationNote: revisionNote + }).then(function () { + return part.checkin(); + }).then(function(){ + callback(); + }); + + }, function(err) { + if (err) { + _this.onError(undefined, err); + } else { + _this.allCheckinDone(); + } + }); + + }); + + this.listenTo(promptView, 'prompt-cancel', function () { + var ajaxes = []; + _(selectedParts).each(function (part) { + ajaxes.push(part.checkin()); + }); + $.when.apply($, ajaxes).then(this.allCheckinDone); + }); + + } else { + Async.each(selectedParts, function(part, callback) { + + part.getLastIteration().save().success(function () { + part.checkin().success(callback); + }); + + }, function(err) { + if (err) { + _this.onError(undefined, err); + } else { + _this.allCheckinDone(); + } + }); + } + }, + + allCheckinDone: function () { + this.resetCollection(); + Backbone.Events.trigger('part:iterationChange'); + }, + + checkout: function () { + _(this.partListView.getSelectedParts()).each(function (view) { + view.checkout(); + }); + }, + undocheckout: function () { + this.partListView.getSelectedPartIndexes(); + var _this= this; + var toBeDone = this.partListView.selectedPartIndexes.length; + var done = 0; + var onSuccess = function() { + if(++done === toBeDone) { + _this.allCheckinDone(); + } + }; + bootbox.confirm(App.config.i18n.UNDO_CHECKOUT_QUESTION, function(result){ + if(result){ + _(_this.partListView.getSelectedParts()).each(function (view) { + view.undocheckout().success(onSuccess); + }); + } + + }); + }, + + updateACL: function () { + var _this = this; + + var selectedPart = _this.partListView.getSelectedPart(); + + var aclEditView = new ACLEditView({ + editMode: true, + acl: selectedPart.get('acl') + }); + + aclEditView.setTitle(selectedPart.getPartKey()); + + window.document.body.appendChild(aclEditView.render().el); + + aclEditView.openModal(); + + aclEditView.on('acl:update', function () { + + var acl = aclEditView.toList(); + + selectedPart.updateACL({ + acl: acl || {userEntries: {}, groupEntries: {}}, + success: function () { + selectedPart.set('acl', acl); + aclEditView.closeModal(); + }, + error: _this.onError + }); + + }); + + }, + + newVersion: function () { + var partNewVersionView = new PartNewVersionView({ + model: this.partListView.getSelectedPart() + }); + window.document.body.appendChild(partNewVersionView.render().el); + partNewVersionView.openModal(); + }, + + releasePart: function () { + this.partListView.releaseSelectedParts(); + }, + + resetCollection: function () { + this.partListView.collection.fetch({reset: true}).success(function () { + this.partListView.checkCheckboxes(); + this.partListView.canCheckinCheckoutOrUndoCheckout(); + }.bind(this)); + }, + + onPageCountFetched: function () { + this.updatePageIndicator(); + if (this.partListView.collection.hasSeveralPages()) { + this.pageControls.show(); + } else { + this.pageControls.hide(); + } + }, + + goToPage: function () { + var requestedPage = window.prompt(App.config.i18n.GO_TO_PAGE, '1'); + if (requestedPage - 1 >= 0 && requestedPage <= this.partListView.collection.getPageCount()) { + this.partListView.collection.setCurrentPage(requestedPage - 1).fetch({reset: true}); + this.updatePageIndicator(); + this.partListView.onNoPartSelected(); + } + }, + + toFirstPage: function () { + this.partListView.collection.setFirstPage().fetch({reset: true}); + this.updatePageIndicator(); + this.partListView.onNoPartSelected(); + }, + + toLastPage: function () { + this.partListView.collection.setLastPage().fetch({reset: true}); + this.updatePageIndicator(); + this.partListView.onNoPartSelected(); + }, + + toNextPage: function () { + this.partListView.collection.setNextPage().fetch({reset: true}); + this.updatePageIndicator(); + this.partListView.onNoPartSelected(); + }, + + toPreviousPage: function () { + this.partListView.collection.setPreviousPage().fetch({reset: true}); + this.updatePageIndicator(); + this.partListView.onNoPartSelected(); + }, + + updatePageIndicator: function () { + this.currentPageIndicator.text(this.partListView.collection.getCurrentPage() + ' / ' + this.partListView.collection.getPageCount()); + }, + + showAll: function () { + if (this.partListView.collection.resultsPerPage) { + this.partListView.collection.setResultsPerPage(0); + this.pagingButtons.hide(); + this.showAllButton.html(App.config.i18n.SHOW_BY_PAGE); + } else { + this.partListView.collection.setResultsPerPage(20); + this.pagingButtons.show(); + this.showAllButton.html(App.config.i18n.SHOW_ALL); + } + this.partListView.collection.fetch({reset: true}); + this.partListView.onNoPartSelected(); + }, + + onQuickSearch: function (e) { + if (e.target.children[1].value) { + App.router.navigate(App.config.workspaceId + '/parts-search/?q=' + e.target.children[1].value, {trigger: true}); + } + e.preventDefault(); + return false; + }, + onAdvancedSearch: function () { + var advancedSearchView = new AdvancedSearchView(); + window.document.body.appendChild(advancedSearchView.render().el); + advancedSearchView.openModal(); + }, + + + onError:function(model, error){ + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + onWarning:function(model, error){ + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'warning', + message: errorMessage + }).render().$el); + }, + + newProduct:function(){ + var productCreationView = new ProductCreationView(); + window.document.body.appendChild(productCreationView.render().el); + var that = this ; + productCreationView.on('product:created',function(){ + that.$notifications.append(new AlertView({ + type: 'info', + message: App.config.i18n.PRODUCT_CREATED + }).render().$el); + }); + productCreationView.setRootPart(this.partListView.getSelectedPart()) + .openModal(); + }, + actionTags: function () { + + var partsChecked = new Backbone.Collection(); + + this.partListView.eachChecked(function (view) { + partsChecked.push(view.model); + }); + + var tagsManagementView = new TagsManagementView({ + collection: partsChecked + }); + window.document.body.appendChild(tagsManagementView.el); + tagsManagementView.show(); + + return false; + + }, + markAsObsolete:function(){ + var _this = this; + bootbox.confirm(App.config.i18n.MARK_PART_AS_OBSOLETE_QUESTION, function(result){ + if(result){ + _(_this.partListView.getSelectedParts()).each(function (part) { + part.markAsObsolete(); + }); + } + }); + }, + + toggleQueryBuilder:function() { + this.$el.toggleClass('displayQueryBuilder', !this.isQueryBuilderDisplayed); + this.$('.display-query-builder-button').toggleClass('fa-angle-double-down', this.isQueryBuilderDisplayed); + this.$('.display-query-builder-button').toggleClass('fa-angle-double-up', !this.isQueryBuilderDisplayed); + + if (this.isQueryBuilderDisplayed) { + this.queryBuilder.destroy(); + } else { + this.queryBuilder.render(); + } + + this.isQueryBuilderDisplayed = !this.isQueryBuilderDisplayed; + }, + + showImporter:function(){ + var partImporterView = new PartImporterView(); + partImporterView.render(); + document.body.appendChild(partImporterView.el); + partImporterView.openModal(); + + return false; + }, + + destroy:function(){ + if(this.partListView){ + this.partListView.remove(); + } + this.remove(); + } + + }); + + return PartContentView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/part/part_creation_view.js b/docdoku-web-front/app/product-management/js/views/part/part_creation_view.js new file mode 100644 index 0000000000..202e9e7036 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/part/part_creation_view.js @@ -0,0 +1,279 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/part/part_creation_view.html', + 'common-objects/models/part', + 'common-objects/models/tag', + 'collections/part_templates', + 'common-objects/views/attributes/attributes', + 'common-objects/views/attributes/template_new_attributes', + 'common-objects/views/workflow/workflow_list', + 'common-objects/views/workflow/workflow_mapping', + 'common-objects/views/security/acl', + 'common-objects/views/alert' +], function (Backbone, Mustache, template, Part, Tag, PartTemplateCollection, AttributesView, TemplateNewAttributesView, WorkflowListView, DocumentWorkflowMappingView, ACLView, AlertView) { + 'use strict'; + var PartCreationView = Backbone.View.extend({ + + events: { + 'click .modal-footer .btn-primary': 'interceptSubmit', + 'submit #part_creation_form': 'onSubmitForm', + 'change select#inputPartTemplate': 'onChangeTemplate' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + this.bindPartTemplateSelector(); + this.bindAttributesView(); + + this.workflowsView = new WorkflowListView({ + el: this.$('#workflows-list') + }); + + this.workflowsMappingView = new DocumentWorkflowMappingView({ + el: this.$('#workflows-mapping') + }); + + this.workflowsView.on('workflow:change', this.workflowsMappingView.updateMapping); + + this.workspaceMembershipsView = new ACLView({ + el: this.$('#acl-mapping'), + editMode: true + }).render(); + + this.$inputPartNumber.customValidity(App.config.i18n.REQUIRED_FIELD); + return this; + }, + + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.$inputPartTemplate = this.$('#inputPartTemplate'); + this.$inputPartNumber = this.$('#inputPartNumber'); + this.$inputPartName = this.$('#inputPartName'); + this.$inputPartDescription = this.$('#inputPartDescription'); + this.$inputPartStandard = this.$('#inputPartStandard'); + }, + + bindPartTemplateSelector: function () { + this.templateCollection = new PartTemplateCollection(); + this.listenTo(this.templateCollection, 'reset', this.onTemplateCollectionReset); + this.templateCollection.fetch({reset: true}); + }, + + bindAttributesView: function (isAttributesLocked) { + //bindAttributesView can be called without arguments + // if it is, no templates has been choosen + // then attributesLocked is false + if(typeof(isAttributesLocked) ==='undefined') { + isAttributesLocked = false; + } + this.attributesView = new AttributesView({ + el: this.$('#attributes-list') + }); + this.attributesView.setAttributesLocked(isAttributesLocked); + + this.attributeTemplatesView = new TemplateNewAttributesView({ + el: this.$('#attribute-templates-list'), + attributesLocked: false, + editMode : true, + unfreezable: true + }); + + this.attributeTemplatesView.render(); + this.attributesView.render(); + }, + + setWorkflowModel: function(template) { + var workflowModelId = template ? template.get('workflowModelId') : null; + this.workflowsView.setValue(workflowModelId); + }, + + addAttributes: function (template) { + var that = this; + + _.each(template.get('attributeTemplates'), function (object) { + that.attributesView.collection.add({ + name: object.name, + type: object.attributeType, + mandatory: object.mandatory, + value: '', + lovName:object.lovName, + locked:object.locked + }); + }); + _.each(template.get('attributeInstanceTemplates'), function (object) { + that.attributeTemplatesView.collection.add({ + name: object.name, + attributeType: object.attributeType, + mandatory: object.mandatory, + value: '', + lovName:object.lovName, + locked:object.locked + }); + }); + + }, + + interceptSubmit:function(){ + this.isValid = !this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + + this.$notifications.empty(); + + if(this.isValid){ + this.model = new Part({ + number: this.$inputPartNumber.val(), + workspaceId: App.config.workspaceId, + description: this.$inputPartDescription.val(), + name: this.$inputPartName.val(), + standardPart: this.$inputPartStandard.is(':checked') ? 1 : 0 + }); + + var templateId = this.$inputPartTemplate.val(); + var workflow = this.workflowsView.selected(); + var saveOptions = { + templateId: templateId ? templateId : null, + workflowModelId: workflow ? workflow.get('id') : null, + roleMapping: workflow ? this.workflowsMappingView.toList() : null, + acl: this.workspaceMembershipsView.toList() + }; + + this.model.save(saveOptions, { + wait: true, + success: this.onPartCreated, + error: this.onError + }); + } + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onPartCreated: function () { + var that = this; + + this.model.getLastIteration().save({ + instanceAttributes: this.attributesView.collection.toJSON(), + instanceAttributeTemplates: that.attributeTemplatesView.collection.toJSON() + }, { + success: function () { + if (that.options.autoAddTag) { + var tag = new Tag({ + label: that.options.autoAddTag, + id: that.options.autoAddTag, + workspaceId: App.config.workspaceId + }); + that.model.addTags([tag]).success(function() { + that.closeModal(); + that.model.fetch({ + success: function (model) { + that.trigger('part:created', model); + Backbone.Events.trigger('part:iterationChange'); + } + }); + }); + } else { + that.closeModal(); + that.model.fetch({ + success: function (model) { + that.trigger('part:created', model); + Backbone.Events.trigger('part:iterationChange'); + } + }); + } + } + }); + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + openModal: function () { + this.$el.one('shown', this.render.bind(this)); + this.$el.one('hidden', this.onHidden.bind(this)); + this.$el.modal('show'); + }, + + closeModal: function () { + this.$el.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + onChangeTemplate: function () { + this.resetMask(); + + var templateId = this.$inputPartTemplate.val(); + + if (templateId) { + var template = this.templateCollection.get(templateId); + this.bindAttributesView(template.get('attributesLocked')); + + if (template.get('mask')) { + this.setMask(template); + } + + if (template.get('idGenerated')) { + this.generateId(template); + } + + this.setWorkflowModel(template); + + if (template.get('attributeTemplates') || template.get('attributeInstanceTemplates')) { + this.addAttributes(template); + } + + } else { + this.bindAttributesView(); + this.setWorkflowModel(); + } + }, + + resetMask: function () { + this.$inputPartNumber.unmask(this.mask).val(''); + }, + + setMask: function (template) { + this.mask = template.get('mask'); + this.$inputPartNumber.mask(this.mask); + }, + + onTemplateCollectionReset: function () { + var that = this; + this.templateCollection.each(function (model) { + that.$inputPartTemplate.append(''); + }); + }, + + generateId: function (template) { + var that = this; + // Set field mask + $.getJSON(template.generateIdUrl(), function (data) { + if (data) { + that.$inputPartNumber.val(data.id); + } + }, 'html'); + } + + }); + + return PartCreationView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/part/part_grouped_by_list.js b/docdoku-web-front/app/product-management/js/views/part/part_grouped_by_list.js new file mode 100644 index 0000000000..50c69459c8 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/part/part_grouped_by_list.js @@ -0,0 +1,107 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/part/part_grouped_by_list.html', + 'views/part/part_grouped_by_list_item' +], function (Backbone, Mustache, template, PartGroupedByListItemView){ + 'use strict'; + var PartGroupedByView = Backbone.View.extend({ + + events: { + + }, + + initialize: function () { + this.items = this.options.data.queryResponse; + this.selects = this.options.data.queryData.selects; + this.orderByList = this.options.data.queryData.orderByList; + this.groupedByList = this.options.data.queryData.groupedByList; + this.columnNameMapping = this.options.data.queryColumnNameMapping; + this.queryFilters = this.options.data.queryFilters; + }, + + render: function () { + var self = this; + + var itemsGroupBy = this.groupBy(); + + var groups = {}; + var i = 0; + + _.each(_.keys(itemsGroupBy).sort(), function(key){ + groups[''+i] = { + key:''+i, + name: key === 'undefined' ? null : key, + items:itemsGroupBy[key] + }; + i++; + }); + + var columns = []; + _.each(this.selects, function(column){ + var data = _.findWhere(self.columnNameMapping, {value:column}); + columns.push(data); + }); + + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + columns:columns, + groups: _.values(groups) + })); + + _.each(_.keys(groups),function(key){ + var values = self.orderBy(groups[key].items); + _.each(values, function(item){ + var itemView = new PartGroupedByListItemView({ + queryFilters: self.queryFilters, + item : item, + columns:columns + }).render(); + self.$('.items-'+key).append(itemView.el); + }); + }); + + return this; + }, + + groupBy:function(){ + var self = this; + + return !this.groupedByList.length ? {'undefined':this.items} : _.groupBy(this.items,function(item){ + + var groupByStringToUse = ''; + _.each(self.groupedByList, function(groupByColumn){ + groupByStringToUse = groupByStringToUse+' > '+item[groupByColumn]; + }); + + var keyTrimmed = groupByStringToUse.substring(3).trimRight(); + if(keyTrimmed[keyTrimmed.length-1]==='>'){ + keyTrimmed = keyTrimmed.substring(0, keyTrimmed.length-1); + } + + return keyTrimmed; + }); + + }, + + orderBy:function(items){ + var self = this; + return !self.orderByList.length ? items : items.sort(function(item1, item2){ + + var item1String = ''; + var item2String = ''; + _.each(self.orderByList, function(orderByColumn){ + item1String = item1String+'_'+item1[orderByColumn]; + item2String = item2String+'_'+item2[orderByColumn]; + }); + item1String = item1String.substring(1); + item2String = item2String.substring(1); + + return item1String > item2String ? 1 :item1String > item2String ? 0 : -1 ; + }); + } + }); + + return PartGroupedByView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/part/part_grouped_by_list_item.js b/docdoku-web-front/app/product-management/js/views/part/part_grouped_by_list_item.js new file mode 100644 index 0000000000..ff902a3ba7 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/part/part_grouped_by_list_item.js @@ -0,0 +1,133 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/part/part_grouped_by_list_item.html', + 'common-objects/utils/date', + 'common-objects/models/part', + 'common-objects/views/part/part_modal_view' +], function (Backbone, Mustache, template, Date, Part, PartModalView){ + 'use strict'; + var PartGroupedByListItemView = Backbone.View.extend({ + + events: { + }, + + tagName:'tr', + + initialize: function () { + this.item = this.options.item; + this.headerColumns = this.options.columns; + this.queryFilters = this.options.queryFilters; + }, + + render: function () { + var self = this; + var itemOrdered = []; + + _.each(this.headerColumns, function(column){ + + var value = self.item[column.value]; + + var filter = _.findWhere(self.queryFilters, {id : column.value}); + var type = filter ? filter.realType : 'string'; + + var isDate = type === 'date'; + var isPartNumber = type ==='partNumber'; + var isLinkedDocuments = type ==='linkedDocuments'; + + var isStringValue = !isDate && !isPartNumber && !isLinkedDocuments; + var isStringArray; + + if(isDate) { + if(_.isArray(value)) { + value = _.map(value,function(dateValue) { + + return Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + dateValue + ); + }); + } + else { + value = Date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + value + ); + } + } + + if(isLinkedDocuments){ + if (!value) { + value = []; + } + else{ + var documents = value.split(','); + documents.pop(); + var result = []; + _.each(documents,function(document){ + var lastDash = document.lastIndexOf('-'); + var idAndVersion = document.substr(0, lastDash); + + lastDash = idAndVersion.lastIndexOf('-'); + var id = idAndVersion.substr(0, lastDash); + var version = idAndVersion.substr(lastDash+1); + + result.push({ + link:'../documents/#'+App.config.workspaceId+'/'+id+'/'+version, + name:document + }); + + }); + value = result; + } + } + + if (isStringValue && self.item[column.value] instanceof Array) { + isStringArray = true; + isStringValue = false; + value = self.item[column.value]; + } + + var itemColumn = { + isDate : isDate, + isPartNumber : isPartNumber, + isStringValue : isStringValue, + isStringArray : isStringArray, + isLinkedDocuments : isLinkedDocuments, + value : isStringValue ? value.toString().split('\n') : value + }; + + itemOrdered.push(itemColumn); + }); + + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + columns: itemOrdered + })); + + this.$el.on('click', this.openModal.bind(this)); + + Date.dateHelper(this.$('.date-popover')); + return this; + }, + + openModal:function(e){ + + if(e.target.className === 'linkedDocument'){ + return; + } + + var model = new Part({partKey:this.item['pr.partKey']}); + model.fetch().success(function () { + var partModalView = new PartModalView({ + model: model + }); + partModalView.show(); + }); + } + + }); + + return PartGroupedByListItemView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/part/part_importer.js b/docdoku-web-front/app/product-management/js/views/part/part_importer.js new file mode 100644 index 0000000000..d30dea546c --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/part/part_importer.js @@ -0,0 +1,337 @@ +/*global _,define,App,window*/ +define([ + 'backbone', + 'mustache', + 'unorm', + 'common-objects/views/components/modal', + 'common-objects/models/file/attached_file', + 'common-objects/views/file/file', + 'common-objects/views/alert', + 'text!templates/part/part_import_modal.html', + 'text!templates/part/part_import_form.html', + 'common-objects/views/part/import_status_view' +], function (Backbone, Mustache, unorm, ModalView, AttachedFile, FileView, AlertView, modalTemplate, template, ImportStatusView) { + 'use strict'; + var PartImportView = Backbone.View.extend({ + + template: template, + modalTemplate: modalTemplate, + + tagName: 'div', + className: 'attachedFiles idle', + + events: { + 'click form button.cancel-upload-btn': 'cancelButtonClicked', + 'change form input.upload-btn': 'fileSelectHandler', + 'dragover .droppable': 'fileDragHover', + 'dragleave .droppable': 'fileDragHover', + 'drop .droppable': 'fileDropHandler', + 'click .import-button': 'formSubmit', + 'click #auto_checkout_part': 'changeAutoCheckout', + 'hidden .importer-view': 'onHidden', + 'click .import-preview-button': 'showPreview', + 'click .back-button': 'backToForm', + 'hidden.bs.modal .modal.importer-view':'deleteImportStatus' + }, + + initialize: function () { + this.importStatusViews = []; + + // Prevent browser behavior on file drop + window.addEventListener('drop', function (e) { + e.preventDefault(); + return false; + }, false); + + window.addEventListener('ondragenter', function (e) { + e.preventDefault(); + return false; + }, false); + + this.$el.on('remove', this.removeSubviews); + + this.importForm = true; + this.importPreview = false; + }, + + // cancel event and hover styling + fileDragHover: function (e) { + e.stopPropagation(); + e.preventDefault(); + if (e.type === 'dragover') { + this.filedroparea.addClass('hover'); + } + else { + this.filedroparea.removeClass('hover'); + } + }, + + fileDropHandler: function (e) { + this.fileDragHover(e); + if (this.options.singleFile && e.dataTransfer.files.length > 1) { + this.printNotifications('error', App.config.i18n.SINGLE_FILE_RESTRICTION); + return; + } + + _.each(e.dataTransfer.files, this.loadNewFile.bind(this)); + }, + + fileSelectHandler: function (e) { + _.each(e.target.files, this.loadNewFile.bind(this)); + }, + + cancelButtonClicked: function () { + //empty the file + this.file = null; + this.finished(); + }, + + render: function () { + + this.$el.html(Mustache.render(modalTemplate, {i18n: App.config.i18n})); + this.$el.find('#import-contain').append(Mustache.render(template, { + importForm: this.importForm, + importPreview: this.importPreview, + checkout: this.autocheckout, + checkin: this.autocheckin, + permissive: this.permissive, + revisionNote: this.revisionNote, + i18n: App.config.i18n + })); + this.bindDomElements(); + this.checkboxAutoCheckin.disabled = true; + this.fetchImports(); + + return this; + }, + + loadNewFile: function (file) { + + var fileName = unorm.nfc(file.name); + + var newFile = new AttachedFile({ + shortName: fileName + }); + + this.file = file; + this.addOneFile(newFile); + + }, + + rerender: function () { + this.$el.find('#import-contain').html(Mustache.render(template, { + importForm: this.importForm, + importPreview: this.importPreview, + checkout: this.autocheckout, + checkin: this.autocheckin, + permissive: this.permissive, + revisionNote: this.revisionNote, + partList:this.partCheckoutList, + searchingForPartList:this.searchingForPartList, + i18n: App.config.i18n, + options:this.options + })); + + //this.delegateEvents(); + this.bindDomElements(); + + if (!this.searchingForPartList) { + this.$('.import-button').removeAttr('disabled'); + } + + if(this.autocheckout) { + this.checkboxAutoCheckout.prop('checked', true); + } + + this.$('#revision_text_part').val(this.revisionNote); + + if(this.autocheckout){ + this.checkboxAutoCheckout.prop('checked', true); + this.checkboxAutoCheckin.prop('disabled', false); + } else { + this.checkboxAutoCheckin.prop('disabled', true); + } + + if(this.autocheckin){ + this.checkboxAutoCheckin.prop('checked', true); + } + + if(this.permissive){ + this.$('#permissive_update_part').prop('checked',true); + } + }, + + addOneFile: function (attachedFile) { + this.filedisplay.html('
                                  • ' + attachedFile.getShortName() + '
                                  • '); + this.$('.import-preview-button').removeAttr('disabled'); + }, + + bindDomElements: function () { + this.$modal = this.$('.modal.importer-view'); + this.filedroparea = this.$('.filedroparea'); + this.filedisplay = this.$('#file-selected ul'); + this.uploadInput = this.$('input.upload-btn'); + this.progressBars = this.$('div.progress-bars'); + this.notifications = this.$('div.notifications'); + this.checkboxAutoCheckin = this.$('#auto_checkin_part'); + this.checkboxAutoCheckout = this.$('#auto_checkout_part'); + }, + + /** + * to enable or disable checkbox for auto checkin + */ + changeAutoCheckout: function () { + if (this.checkboxAutoCheckout.is(':checked') === true) { + this.checkboxAutoCheckin.prop('disabled', false); + } else { + this.checkboxAutoCheckin.prop('disabled', true); + this.checkboxAutoCheckin.prop('checked', false); + } + }, + + showPreview: function () { + + this.clearNotifications(); + + this.autocheckin = this.checkboxAutoCheckin.is(':checked'); + this.autocheckout = this.checkboxAutoCheckout.is(':checked'); + this.permissive = this.$('#permissive_update_part').is(':checked'); + this.revisionNote = this.$('#revision_text_part').val().trim(); + + this.options = this.autocheckin || this.autocheckout || this.permissive || this.revisionNote!== ''; + + if (this.file) { + + if (this.autocheckout) { + + var params = { + autoCheckout: this.autocheckout, + autoCheckin: this.autocheckin, + permissiveUpdate: this.permissive + }; + + this.searchingForPartList = true; + var previewBaseUrl = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/importPreview'; + var previewUrl = previewBaseUrl + '?' + $.param(params); + var xhr = new XMLHttpRequest(); + xhr.open('POST', previewUrl, true); + var formData = new window.FormData(); + var _this = this; + xhr.onreadystatechange = function() { + if(xhr.readyState === 4 && xhr.status === 200){ + _this.partCheckoutList = jQuery.parseJSON(xhr.response); + _this.searchingForPartList = false; + _this.importForm = false; + _this.importPreview = true; + _this.rerender(); + } + }; + formData.append('upload', this.file); + xhr.send(formData); + } else { + this.partCheckoutList = null; + this.partCheckoutList = false; + } + + this.importForm = false; + this.importPreview = true; + this.rerender(); + + } + }, + formSubmit: function () { + + var baseUrl = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/import'; + + if (this.file) { + + var params = { + autoCheckout: this.autocheckout, + autoCheckin: this.autocheckin, + permissiveUpdate: this.permissive, + revisionNote: this.revisionNote + }; + + this.deleteImportStatus(); + + var importUrl = baseUrl + '?' + $.param(params); + + var xhr = new XMLHttpRequest(); + xhr.onload = this.fetchImports.bind(this); + xhr.open('POST', importUrl, true); + var formData = new window.FormData(); + formData.append('upload', this.file); + xhr.send(formData); + + } else if (!this.file) { + this.printNotifications('error', App.config.i18n.NO_FILE_TO_IMPORT); + } + + this.backToForm(); + return false; + }, + + fetchImports: function () { + var _this = this; + this.removeSubviews(); + _this.$('.import-status-views').empty(); + + if (this.file) { + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/imports/' + unorm.nfc(this.file.name); + $.get(url).then(function (imports) { + _.each(imports, function (pImport) { + var view = new ImportStatusView({model: pImport}).render(); + _this.importStatusViews.push(view); + _this.$('.import-status-views').append(view.$el); + }); + }); + } + }, + + printNotifications: function (type, message) { + this.notifications.append(new AlertView({ + type: type, + message: message + }).render().$el); + }, + + clearNotifications: function () { + this.notifications.text(''); + }, + + removeSubviews: function () { + _(this.importStatusViews).invoke('remove'); + this.importStatusViews = []; + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + backToForm: function () { + this.importPreview = false; + this.importForm = true; + this.partCheckoutList = null; + this.searchingForPartList = false; + this.rerender(); + this.loadNewFile(this.file); + }, + + deleteImportStatus: function (){ + _.each(this.importStatusViews, function(importSV){ + importSV.deleteImport(); + }); + } + + }); + return PartImportView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/part/part_list.js b/docdoku-web-front/app/product-management/js/views/part/part_list.js new file mode 100644 index 0000000000..b454eead15 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/part/part_list.js @@ -0,0 +1,397 @@ +/*global _,define,App,bootbox*/ +define([ + 'backbone', + 'mustache', + 'text!templates/part/part_list.html', + 'views/part/part_list_item' +], function (Backbone, Mustache, template, PartListItemView) { + 'use strict'; + var PartListView = Backbone.View.extend({ + + events: { + 'click .toggle-checkboxes': 'toggleSelection' + }, + + removeSubviews: function () { + _(this.listItemViews).invoke('remove'); // Invoke remove for each views in listItemViews + this.listItemViews = []; + }, + + initialize: function () { + _.bindAll(this); + this.listenTo(this.collection, 'reset', this.resetList); + this.listenTo(this.collection, 'add', this.addNewPart); + this.listItemViews = []; + this.selectedPartIndexes = []; + this.$el.on('remove', this.removeSubviews); + }, + + render: function () { + var that = this; + this.collection.fetch({reset: true}).error(function (err) { + that.trigger('error', null, err); + }); + return this; + }, + + bindDomElements: function () { + }, + + resetList: function () { + var _this = this; + this.removeSubviews(); + + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + + this.collection.each(function (model) { + _this.addPart(model); + }); + this.dataTable(); + this.onSelectionChanged(); + }, + + addNewPart: function (model) { + this.addPart(model, true); + this.redraw(); + }, + + addPart: function (model, effect) { + var view = this.addPartView(model); + if (effect) { + view.$el.highlightEffect(); + } + }, + + removePart: function (model) { + this.removePartView(model); + this.redraw(); + }, + + removePartView: function (model) { + + var viewToRemove = _(this.listItemViews).select(function (view) { + return view.model === model; + })[0]; + + if (viewToRemove) { + this.listItemViews = _(this.listItemViews).without(viewToRemove); + var row = viewToRemove.$el.get(0); + this.oTable.fnDeleteRow(this.oTable.fnGetPosition(row)); + viewToRemove.remove(); + } + + }, + + addPartView: function (model) { + var view = new PartListItemView({model: model}).render(); + this.listItemViews.push(view); + this.$('.items').append(view.$el); + view.on('selectionChanged', this.onSelectionChanged); + view.on('rendered', this.redraw); + return view; + }, + + toggleSelection: function () { + if (this.$('.toggle-checkboxes').is(':checked')) { + _(this.listItemViews).each(function (view) { + view.check(); + }); + } else { + _(this.listItemViews).each(function (view) { + view.unCheck(); + }); + } + this.onSelectionChanged(); + }, + + checkCheckboxes: function () { + var that = this; + _.each(that.selectedPartIndexes, function(selectedView) { + _.each(that.listItemViews, function(view) { + if(selectedView.model.getPartKey() === view.model.getPartKey()) { + view.check(); + view.selectionChanged(); + } + }); + }); + + this.selectedPartIndexes = []; + + }, + + onSelectionChanged: function () { + + var checkedViews = _(this.listItemViews).select(function (itemView) { + return itemView.isChecked(); + }); + + if (checkedViews.length <= 0) { + this.onNoPartSelected(); + } else if (checkedViews.length === 1) { + this.onOnePartSelected(); + + if (checkedViews[0].model.isCheckout()) { + if (checkedViews[0].model.isCheckoutByConnectedUser()) { + var canUndo = checkedViews[0].model.getLastIteration().get('iteration') > 1; + this.trigger('checkout-group:update', {canCheckout: false, canUndo: canUndo, canCheckin: true}); + } else { + this.trigger('checkout-group:update', {canCheckout: false, canUndo: false, canCheckin: false}); + } + } else { + this.trigger('checkout-group:update', {canCheckout: true, canUndo: false, canCheckin: false}); + } + + } else { + this.onSeveralPartsSelected(); + this.canCheckinCheckoutOrUndoCheckout(); + + + } + + }, + + onNoPartSelected: function () { + this.trigger('delete-button:display', false); + this.trigger('checkout-group:display', false); + this.trigger('acl-edit-button:display', false); + this.trigger('new-version-button:display', false); + this.trigger('release-button:display', false); + this.trigger('new-product-button:display', false); + this.trigger('obsolete-button:display', false); + }, + + onOnePartSelected: function () { + var partSelected = this.getSelectedPart(); + this.trigger('delete-button:display', true); + this.trigger('checkout-group:display', !partSelected.isReleased() && !partSelected.isObsolete()); + this.trigger('acl-edit-button:display', partSelected ? (App.config.workspaceAdmin || partSelected.getAuthorLogin() === App.config.login) : false); + this.trigger('new-version-button:display', !partSelected.isCheckout()); + this.trigger('release-button:display', (!partSelected.isCheckout() && !partSelected.isReleased() && !partSelected.isObsolete())); + this.trigger('new-product-button:display', true); + this.trigger('obsolete-button:display', partSelected.isReleased()); + }, + + onSeveralPartsSelected: function () { + this.trigger('delete-button:display', true); + this.trigger('acl-edit-button:display', false); + this.trigger('new-version-button:display', false); + this.trigger('release-button:display', this.isSelectedPartsReleasable()); + this.trigger('new-product-button:display', false); + this.trigger('obsolete-button:display', false); + }, + + deleteSelectedParts: function () { + var _this = this; + + bootbox.confirm(App.config.i18n.CONFIRM_DELETE_PART, function (result) { + if (result) { + var checkedViews = _(_this.listItemViews).select(function(view) { + return view.isChecked(); + }); + var requestsToBeDone = checkedViews.length; + var requestsDone = 0; + + var onRequestOver = function () { + if (++requestsDone === requestsToBeDone) { + _this.onSelectionChanged(); + Backbone.Events.trigger('part:iterationChange'); + } + }; + _(_this.listItemViews).each(function (view) { + if (view.isChecked()) { + view.model.destroy({ + wait: true, + dataType: 'text', // server doesn't send a json hash in the response body + success: function () { + _this.removePart(view.model); + onRequestOver(); + + }, + error: function (model, err) { + _this.trigger('error', model, err); + //must be called, if not the ones which succeed + //won't trigger the event while the part did change. + Backbone.Events.trigger('part:iterationChange'); + _this.onSelectionChanged(); + } + }); + } + }); + + } + }); + }, + + releaseSelectedParts: function () { + var that = this; + bootbox.confirm(App.config.i18n.RELEASE_SELECTION_QUESTION, function (result) { + if (result) { + _(that.listItemViews).each(function (view) { + if (view.isChecked()) { + view.model.release(); + } + }); + } + }); + }, + + getSelectedPart: function () { + var checkedView = _(this.listItemViews).select(function (itemView) { + return itemView.isChecked(); + })[0]; + + if (checkedView) { + return checkedView.model; + } + return null; + }, + + getSelectedParts: function () { + var checkedViews = []; + _(this.listItemViews).select(function (itemView) { + if (itemView.isChecked()) { + checkedViews.push(itemView.model); + } + + }); + return checkedViews; + }, + + getSelectedPartIndexes: function () { + for (var i=0; i0; + _(this.getSelectedParts()).each(function (view) { + if (view.isReleased() || view.isCheckout() || view.isObsolete()) { + isPartCheckout = false; + } + }); + return isPartCheckout; + }, + + areSelectedPartsCheckedOut: function () { + var isPartCheckedOut = this.getSelectedParts().length >0; + _(this.getSelectedParts()).each(function (view) { + if (!view.isCheckout()) { + isPartCheckedOut = false; + } + }); + return isPartCheckedOut; + }, + + areSelectedPartsAllNotCheckedOut: function () { + var isPartNotCheckedOut = this.getSelectedParts().length >0; + _(this.getSelectedParts()).each(function (view) { + if (view.isCheckout() || view.isReleased() || view.isObsolete()) { + isPartNotCheckedOut = false; + } + }); + return isPartNotCheckedOut; + }, + + areSelectedPartsCheckedOutByConnectedUser: function () { + var isPartCheckedOutByConnectedUser = this.getSelectedParts().length >0; + _(this.getSelectedParts()).each(function (view) { + if (!view.isCheckoutByConnectedUser()) { + isPartCheckedOutByConnectedUser = false; + } + }); + return isPartCheckedOutByConnectedUser; + }, + + haveMoreThanOneIteration: function () { + var hasMoreThanOneIteration = this.getSelectedParts().length >0; + _(this.getSelectedParts()).each(function (view) { + if (view.getLastIteration().get('iteration') <= 1) { + hasMoreThanOneIteration = false; + } + }); + return hasMoreThanOneIteration; + }, + + canCheckinCheckoutOrUndoCheckout: function () { + + if (this.areSelectedPartsCheckedOut()) { + if (this.areSelectedPartsCheckedOutByConnectedUser()) { + this.trigger('checkout-group:display', true); + this.trigger('checkout-group:update', {canCheckout: false, canUndo: this.haveMoreThanOneIteration(), canCheckin: true}); + } else { + this.trigger('checkout-group:display', true); + this.trigger('checkout-group:update', {canCheckout: false, canUndo: false, canCheckin: false}); + } + } else if(this.areSelectedPartsAllNotCheckedOut()) { + this.trigger('checkout-group:display', true); + this.trigger('checkout-group:update', {canCheckout: true, canUndo: false, canCheckin: false}); + } + + else if ( this.areSelectedPartsCheckoutable()){ + this.trigger('checkout-group:display', true); + this.trigger('checkout-group:update', {canCheckout: true, canUndo: false, canCheckin: false}); + } + else{ + this.trigger('checkout-group:display', false); + } + + }, + + redraw: function () { + this.dataTable(); + }, + + dataTable: function () { + var oldSort = [ + [0, 'asc'] + ]; + if (this.oTable) { + if(this.oTable.fnSettings()){ + oldSort = this.oTable.fnSettings().aaSorting; + } + this.oTable.fnDestroy(); + } + this.oTable = this.$el.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + { 'bSortable': false, 'aTargets': [ 0, 1, 2, 12, 13, 14, 15 ] }, + { 'sType': App.config.i18n.DATE_SORT, 'aTargets': [9] }, + { 'sType': 'strip_html', 'aTargets': [3] } + ] + }); + this.$el.parent().find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + } + + }); + return PartListView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/part/part_list_item.js b/docdoku-web-front/app/product-management/js/views/part/part_list_item.js new file mode 100644 index 0000000000..5502ae4b5e --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/part/part_list_item.js @@ -0,0 +1,165 @@ +/*global _,define,App,$*/ +define([ + 'backbone', + 'mustache', + 'common-objects/models/part', + 'text!templates/part/part_list_item.html', + 'common-objects/views/share/share_entity', + 'common-objects/views/part/part_modal_view', + 'common-objects/utils/date' +], function (Backbone, Mustache, Part, template, ShareView, PartModalView, date) { + 'use strict'; + var PartListItemView = Backbone.View.extend({ + + events: { + 'click input[type=checkbox]': 'selectionChanged', + 'click td.modification_notification i': 'toPartModalOnNotificationsTab', + 'click td.part_number': 'toPartModal', + 'click td.part-revision-share i': 'sharePart', + 'click td.part-attached-files i': 'toPartModalOnFilesTab', + 'dragstart a.parthandle': 'dragStart', + 'dragend a.parthandle': 'dragEnd' + }, + + tagName: 'tr', + + initialize: function () { + _.bindAll(this); + this._isChecked = false; + + this.listenTo(this.model, 'change', this.render); + this.listenTo(this.model, 'sync', this.render); + + // jQuery creates it's own event object, and it doesn't have a + // dataTransfer property yet. This adds dataTransfer to the event object. + $.event.props.push('dataTransfer'); + }, + + render: function () { + this.$el.html(Mustache.render(template, {model: this.model, i18n: App.config.i18n})); + this.$checkbox = this.$('input[type=checkbox]'); + if (this.isChecked()) { + this.check(); + this.trigger('selectionChanged', this); + } + this.bindUserPopover(); + this.bindDescriptionPopover(); + date.dateHelper(this.$('.date-popover')); + this.trigger('rendered', this); + return this; + }, + + selectionChanged: function () { + this._isChecked = this.$checkbox.prop('checked'); + this.trigger('selectionChanged', this); + }, + + isChecked: function () { + return this._isChecked; + }, + + check: function () { + this.$checkbox.prop('checked', true); + this._isChecked = true; + }, + + unCheck: function () { + this.$checkbox.prop('checked', false); + this._isChecked = false; + }, + + toPartModal: function () { + var self = this; + self.model.fetch().success(function () { + var partModalView = new PartModalView({ + model: self.model + }); + partModalView.show(); + }); + }, + + toPartModalOnFilesTab: function () { + var model = this.model; + model.fetch().success(function () { + var partModalView = new PartModalView({ + model: model + }); + partModalView.show(); + partModalView.activateFileTab(); + + }); + }, + + toPartModalOnNotificationsTab: function () { + var model = this.model; + model.fetch().success(function () { + var partModalView = new PartModalView({ + model: model + }); + partModalView.show(); + partModalView.activateNotificationsTab(); + }); + }, + + bindDescriptionPopover: function() { + if(this.model.getDescription() !== undefined && this.model.getDescription() !== null && this.model.getDescription() !== '') { + var self = this; + this.$('.part_number') + .popover({ + title: App.config.i18n.DESCRIPTION, + html: true, + content: self.model.getDescription(), + trigger: 'hover', + placement: 'top', + container: 'body' + }); + } + }, + + bindUserPopover: function () { + this.$('.author-popover').userPopover(this.model.getAuthorLogin(), this.model.getNumber(), 'left'); + if (this.model.isCheckout()) { + this.$('.checkout-user-popover').userPopover(this.model.getCheckOutUserLogin(), this.model.getNumber(), 'left'); + } + }, + + sharePart: function () { + var shareView = new ShareView({model: this.model, entityType: 'parts'}); + window.document.body.appendChild(shareView.render().el); + shareView.openModal(); + }, + + dragStart: function (e) { + var that = this; + this.$el.addClass('moving'); + + Backbone.Events.on('part-moved', function () { + Backbone.Events.off('part-moved'); + Backbone.Events.off('part-error-moved'); + that.model.collection.remove(that.model); + }); + Backbone.Events.on('part-error-moved', function () { + Backbone.Events.off('part-moved'); + Backbone.Events.off('part-error-moved'); + that.$el.removeClass('moving'); + }); + var data = JSON.stringify(this.model.toJSON()); + e.dataTransfer.setData('part:text/plain', data); + e.dataTransfer.dropEffect = 'none'; + e.dataTransfer.effectAllowed = 'copyMove'; + return e; + }, + + dragEnd: function (e) { + if (e.dataTransfer.dropEffect === 'none') { + Backbone.Events.off('part-moved'); + Backbone.Events.off('part-error-moved'); + } + this.$el.removeClass('moving'); + } + + }); + + return PartListItemView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/part/part_new_version.js b/docdoku-web-front/app/product-management/js/views/part/part_new_version.js new file mode 100644 index 0000000000..e970e02149 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/part/part_new_version.js @@ -0,0 +1,88 @@ +/*global define,App,_*/ +define([ + 'backbone', + 'mustache', + 'text!templates/part/part_new_version.html', + 'common-objects/views/workflow/workflow_mapping', + 'common-objects/views/workflow/workflow_list', + 'common-objects/views/security/acl_clone_edit', + 'common-objects/views/alert' +], function (Backbone, Mustache, template, WorkflowMappingView, WorkflowListView, ACLView, AlertView) { + 'use strict'; + var PartNewVersionView = Backbone.View.extend({ + + events: { + 'click #create-new-version-btn': 'createNewVersionAction', + 'click #cancel-new-version-btn': 'closeModalAction', + 'click a.close': 'closeModalAction', + 'hidden #new-version-modal':'onHidden' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + + this.template = Mustache.render(template, {i18n: App.config.i18n, model: this.model}); + + this.$el.html(this.template); + + this.bindDomElements(); + + this.workflowsView = new WorkflowListView(); + this.newVersionWorkflowDiv.html(this.workflowsView.el); + + this.workflowsMappingView = new WorkflowMappingView({ + el: this.$('#workflows-mapping') + }); + + this.workflowsView.on('workflow:change', this.workflowsMappingView.updateMapping); + + this.aclView = new ACLView({ + el: this.$('#acl-mapping'), + editMode: true, + acl: this.model.get('acl') + }).render(); + + this.$('.tabs').tabs(); + + return this; + }, + + bindDomElements: function () { + this.newVersionWorkflowDiv = this.$('#new-version-workflow'); + this.textAreaNewVersionDescription = this.$('#new-version-description'); + this.$modal = this.$('#new-version-modal'); + this.$notifications = this.$('.notifications'); + }, + + createNewVersionAction: function () { + this.model.createNewVersion(this.textAreaNewVersionDescription.val(), this.workflowsView.selected(), + this.workflowsMappingView.toList(), this.aclView.toList(),this.closeModalAction, this.onError); + }, + + openModal: function(){ + this.$modal.modal('show'); + }, + + closeModalAction: function () { + this.$modal.modal('hide'); + }, + + onHidden: function(){ + this.remove(); + }, + + onError: function(model) { + this.$notifications.append(new AlertView({ + type: 'error', + message: model.responseText + }).render().$el); + } + + }); + + return PartNewVersionView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/product-instances/product_instance_modal.js b/docdoku-web-front/app/product-management/js/views/product-instances/product_instance_modal.js new file mode 100644 index 0000000000..8e2af4b8c1 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/product-instances/product_instance_modal.js @@ -0,0 +1,402 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/product-instances/product_instance_modal.html', + 'text!common-objects/templates/path/path.html', + 'common-objects/utils/date', + 'common-objects/collections/attribute_collection', + 'common-objects/views/attributes/attributes', + 'common-objects/views/file/file_list', + 'common-objects/collections/linked/linked_document_collection', + 'common-objects/views/linked/linked_documents', + 'common-objects/collections/file/attached_file_collection', + 'common-objects/views/alert', + 'common-objects/collections/baselines', + 'common-objects/views/pathToPathLink/path_to_path_link_item' +], function (Backbone, Mustache, template, pathTemplate, date, AttributeCollection, ProductInstanceAttributeListView, FileListView, LinkedDocumentCollection, LinkedDocumentsView, AttachedFileCollection, AlertView, Baselines, PathToPathLinkItemView) { + 'use strict'; + var ProductInstancesModalView = Backbone.View.extend({ + events: { + 'click .btn-primary': 'interceptSubmit', + 'submit #product_instance_edit_form': 'onSubmitForm', + 'hidden #product_instance_modal': 'onHidden', + 'shown #product_instance_modal': 'onShown', + 'click a#previous-iteration': 'onPreviousIteration', + 'click a#next-iteration': 'onNextIteration', + 'close-modal-request': 'closeModal', + 'click .btn-rebase': 'onRebase' + }, + + template: Mustache.parse(template), + + initialize: function () { + this.productId = this.model.getConfigurationItemId(); + this.iteration = this.model.getLastIteration(); + this.iterations = this.model.getIterations(); + _.bindAll(this); + }, + + render: function () { + + this.editMode = this.iterations.isLast(this.iteration); + var data = { + i18n: App.config.i18n, + model: this.iteration, + editMode: this.editMode, + creationDate: this.model.getFormattedCreationDate() + }; + + if (this.model.hasIterations()) { + var hasNextIteration = this.iterations.hasNextIteration(this.iteration); + var hasPreviousIteration = this.iterations.hasPreviousIteration(this.iteration); + data.iteration = this.iteration.toJSON(); + data.iteration.formattedCreationDate = date.formatTimestamp(App.config.i18n._DATE_FORMAT, data.iteration.creationDate); + data.iteration.formattedModificationDate = date.formatTimestamp(App.config.i18n._DATE_FORMAT, data.iteration.modificationDate); + data.iteration.hasNextIteration = hasNextIteration; + data.iteration.hasPreviousIteration = hasPreviousIteration; + data.iteration.updateDate = date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + data.iteration.updateDate + ); + } + + this.$el.html(Mustache.render(template, data)); + this.bindDomElements(); + this.bindUserPopover(); + this.initAttributesView(); + this.initAttachedFileView(); + this.initLinkedDocumentsView(); + + this.initPathDataView(); + this.openModal(); + this.renderChoices(); + + var self = this; + this.collection = new Baselines({}, {productId: this.productId}); + this.collection.fetch({reset: true}).success(function () { + self.$('.rebase-baseline-select').html(''); + _.each(self.collection.models, function (baseline) { + if (self.iteration.getBasedOnId() === baseline.getId()) { + self.$('.rebase-baseline-select').append(''); + } else { + self.$('.rebase-baseline-select').append(''); + } + + }); + // to get the Existing PathToPath, we need to have all the baseline. + // should be changed, hack to make it work. + self.getExistingPathToPath(); + }); + + date.dateHelper(this.$('.date-popover')); + + return this; + }, + + onPreviousIteration: function () { + if (this.iterations.hasPreviousIteration(this.iteration)) { + this.switchIteration(this.iterations.previous(this.iteration)); + } + return false; + }, + + onNextIteration: function () { + if (this.iterations.hasNextIteration(this.iteration)) { + this.switchIteration(this.iterations.next(this.iteration)); + } + return false; + }, + + switchIteration: function (iteration) { + var index = this.getActiveTabIndex(); + this.iteration = iteration; + this.undelegateEvents(); + this.closeModal(); + this.delegateEvents(); + this.render(); + this.activateTab(index); + }, + getActiveTabIndex: function () { + return this.$tabs.filter('.active').index(); + }, + + bindDomElements: function () { + this.$notifications = this.$('.notifications'); + this.$modal = this.$('#product_instance_modal'); + this.$tabs = this.$('.nav-tabs li'); + this.$inputIterationNote = this.$('#inputIterationNote'); + this.$authorLink = this.$('.author-popover'); + this.$substitutes = this.$('.substitutes-list'); + this.$substitutesCount = this.$('.substitutes-count'); + this.$optionals = this.$('.optionals-list'); + this.$optionalsCount = this.$('.optionals-count'); + }, + + renderChoices: function () { + var substitutes = this.iteration.getSubstitutesParts(); + var optionals = this.iteration.getOptionalsParts(); + this.$substitutesCount.text(substitutes.length); + this.$optionalsCount.text(optionals.length); + + _.each(substitutes, this.drawSubstitutesChoice); + _.each(optionals, this.drawOptionalsChoice); + }, + + drawSubstitutesChoice: function (data) { + this.$substitutes.append(Mustache.render(pathTemplate, { + i18n: App.config.i18n, + partLinks:data.partLinks + })); + this.$substitutes.find('.well i.fa-long-arrow-right').last().remove(); + }, + + drawOptionalsChoice: function (data) { + this.$optionals.append(Mustache.render(pathTemplate, { + i18n: App.config.i18n, + partLinks:data.partLinks + })); + this.$optionals.find('.well i.fa-long-arrow-right').last().remove(); + }, + + initLinkedDocumentsView: function () { + this.linkedDocumentsView = new LinkedDocumentsView({ + editMode: this.editMode, + commentEditable: true, + documentIteration: this.iteration, + collection: new LinkedDocumentCollection(this.iteration.getlinkedDocuments()) + }).render(); + + /* Add the documentLinksView to the tab */ + this.$('#iteration-links').html(this.linkedDocumentsView.el); + }, + + initAttributesView: function () { + + var attributes = new AttributeCollection(this.iteration.getInstanceAttributes()); + + this.attributesView = new ProductInstanceAttributeListView({ + collection: attributes + }); + + this.$('#attributes-list').html(this.attributesView.$el); + + this.attributesView.setEditMode(this.editMode); + + this.attributesView.render(); + }, + + getExistingPathToPath: function () { + var self = this; + _.each(this.iteration.getPathToPathLinks(), function (pathToPathLink) { + var pathToPathLinkItem = new PathToPathLinkItemView({model: { + pathToPath: pathToPathLink, + serialNumber: self.model.getSerialNumber(), + productId: self.productId + }}).render(); + self.$('#path-to-path-links').append(pathToPathLinkItem.el); + }); + + }, + + initAttachedFileView: function () { + + var filesMapping = _.map(this.iteration.getAttachedFiles(), function (fullName) { + + return { + 'fullName': fullName, + shortName: _.last(fullName.split('/')), + created: true + }; + + + }); + var attachedFiles = new AttachedFileCollection(filesMapping); + + var _this = this; + this.fileListView = new FileListView({ + deleteBaseUrl: this.iteration.url(), + uploadBaseUrl: _this.iteration.getUploadBaseUrl(), + collection: attachedFiles, + editMode: true + }).render(); + + + // Add the fileListView to the tab + this.$('#tab-products-instances-files').append(this.fileListView.el); + + + }, + + initPathDataView: function () { + var pathDataList = this.$('#path-data-list'); + var paths = this.iteration.getPathDataPaths(); + var self = this; + var pathsHtml = []; + _.each(paths, function (path) { + + var html = $(Mustache.render(pathTemplate, { + i18n: App.config.i18n, + partLinks:path.partLinks, + editMode: self.editMode + })); + + html.find('button.close').click({fullPath: self.getFullPath(path)},self.removePathData); + html.find('i.fa-long-arrow-right').last().remove(); + pathsHtml.push(html); + }); + pathDataList.html(pathsHtml); + }, + + getFullPath: function(path) { + var fullPath = ''; + _.each(path.partLinks, function(partLink) { + fullPath += partLink.fullId + '-'; + }); + return fullPath.substr(0,fullPath.length-1); + }, + + removePathData: function(event) { + var self = this; + var fullPath = event.data.fullPath; + var pathData = _.findWhere(this.iteration.getPathData(), {path: fullPath}); + $.ajax({ + url: App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/'+self.model.getConfigurationItemId()+'/product-instances/' + self.model.getSerialNumber()+'/pathdata/'+pathData.id, + type: 'DELETE', + error: function(error, type) { + self.onError(type,error); + }, + success : function() { + self.model.fetch().success(function() { + self.iteration = self.model.getIterations().get(self.iteration.getIteration()); + self.initPathDataView(); + }); + } + }); + }, + + bindUserPopover: function () { + this.$authorLink.userPopover(this.model.getUpdateAuthor(), this.model.getSerialNumber(), 'right'); + }, + + interceptSubmit: function () { + this.isValid = !this.$('.tabs').invalidFormTabSwitcher(); + }, + + updateDataForm: function () { + this.iteration.setIterationNote(this.$inputIterationNote.val()); + this.iteration.setInstanceAttributes(this.attributesView.collection.toJSON()); + this.iteration.setLinkedDocuments(this.linkedDocumentsView.collection.toJSON()); + var files = this.iteration.get('attachedFiles'); + + /*tracking back files*/ + this.iteration.set({ + attachedFiles: files + }); + + }, + onSubmitForm: function (e) { + var _this = this; + this.updateDataForm(); + this.iteration.save(JSON.stringify(this.iteration), '', { + success: function () { + _this.model.fetch(); + _this.closeModal(); + }, + error: _this.onError + }); + this.fileListView.deleteFilesToDelete(); + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onRebase: function () { + var self = this; + //save the previous iteration before create a new one + this.updateDataForm(); + this.iteration.save(JSON.stringify(this.iteration), '', { + success: function () { + self.model.fetch(); + }, + error: self.onError + }); + this.fileListView.deleteFilesToDelete(); + + //Do the rebase + var selectedBaselineId = this.$('.rebase-baseline-select').val(); + + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.productId + '/product-instances/' + this.model.getSerialNumber() + '/rebase'; + $.ajax({ + type: 'PUT', + data: JSON.stringify({id: selectedBaselineId}), + contentType: 'application/json', + url: url, + success: function () { + self.model.fetch().success(function () { + self.initialize(); + self.undelegateEvents(); + self.closeModal(); + self.delegateEvents(); + self.render(); + self.activateTab(1); + self.onRebaseSuccess(); + }); + }, + error: function (errorMessage,type) { + self.onError(type,errorMessage); + } + }); + + }, + + onRebaseSuccess:function(){ + this.$notifications.append(new AlertView({ + type: 'success', + message: App.config.i18n.REBASE_SUCCESS + }).render().$el); + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + onShown: function () { + this.$modal.addClass('ready'); + }, + + activateTab: function (index) { + this.$tabs.eq(index).children().tab('show'); + }, + + activePathToPathLinkTab: function () { + this.activateTab(6); + }, + + activePathDataTab: function () { + this.activateTab(7); + }, + + activeFilesTab: function () { + this.activateTab(4); + } + + }); + return ProductInstancesModalView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_content.js b/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_content.js new file mode 100644 index 0000000000..dc84165ce8 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_content.js @@ -0,0 +1,160 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'mustache', + 'common-objects/collections/product_instances', + 'collections/configuration_items', + 'common-objects/collections/baselines', + 'text!templates/product-instances/product_instances_content.html', + 'views/product-instances/product_instances_list', + 'views/product-instances/product_instances_creation', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/ACL_button.html', + 'text!common-objects/templates/buttons/new_product_instance_button.html', + 'text!common-objects/templates/buttons/import_button.html', + 'common-objects/views/alert', + 'views/product-instances/product_instances_importer' +], function (Backbone, Mustache, ProductInstancesCollection, ConfigurationItemCollection, BaselinesCollection, template, ProductInstancesListView, ProductInstanceCreationView, deleteButton, aclButton, newProductInstanceButton,importButton, AlertView,ProductInstanceImporterView) { + 'use strict'; + var ProductInstancesContentView = Backbone.View.extend({ + + partials: { + deleteButton: deleteButton, + aclButton: aclButton, + newProductInstanceButton: newProductInstanceButton, + importButton:importButton + }, + + events: { + 'click button.new-product-instance': 'newProductInstance', + 'click button.delete': 'deleteProductInstances', + 'click button.edit-acl': 'editACLProductInstances', + 'click .import': 'showImporter', + 'hidden .importer-view':'onHidden' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n}, this.partials)); + this.bindDomElements(); + + this.$inputProductId.typeahead({ + source: function (query, process) { + $.getJSON(App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products', function (data) { + var ids = []; + _(data).each(function (d) { + ids.push(d.id); + }); + process(ids); + }); + } + }); + + var self = this; + new BaselinesCollection({}, {productId: ''}).fetch({ + success: function (list) { + if (!list.length) { + self.$notifications.append(new AlertView({ + type: 'info', + message: App.config.i18n.CREATE_BASELINE_BEFORE_PRODUCT_INSTANCE + }).render().$el); + + } + } + }); + + this.bindEvent(); + this.createProductInstancesView(); + return this; + }, + + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.deleteButton = this.$('.delete'); + this.aclButton = this.$('.edit-acl'); + this.$inputProductId = this.$('#inputProductId'); + }, + + bindEvent: function () { + var _this = this; + this.$inputProductId.change(function () { + _this.createProductInstancesView(); + }); + this.delegateEvents(); + }, + + newProductInstance: function () { + var productInstanceCreationView = new ProductInstanceCreationView({ + collection: this.collection + }); + window.document.body.appendChild(productInstanceCreationView.render().el); + productInstanceCreationView.openModal(); + }, + + createProductInstancesView: function () { + if (this.listView) { + this.listView.remove(); + this.changeDeleteButtonDisplay(false); + } + if (this.$inputProductId.val()) { + this.collection = new ProductInstancesCollection({}, {productId: this.$inputProductId.val()}); + } else { + this.collection = new ProductInstancesCollection({}); + } + this.listView = new ProductInstancesListView({ + collection: this.collection + }).render(); + this.$el.append(this.listView.el); + this.listView.on('error', this.onError); + this.listView.on('warning', this.onWarning); + this.listView.on('delete-button:display', this.changeDeleteButtonDisplay); + this.listView.on('acl-button:display', this.changeACLButtonDisplay); + }, + + deleteProductInstances: function () { + this.listView.deleteSelectedProductInstances(); + }, + editACLProductInstances: function () { + this.listView.editSelectedProductInstanceACL(); + }, + + changeDeleteButtonDisplay: function (state) { + this.deleteButton.toggle(state); + }, + + changeACLButtonDisplay: function (state) { + this.aclButton.toggle(state); + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + onWarning: function (model, error) { + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'warning', + message: errorMessage + }).render().$el); + }, + + showImporter:function(){ + var partImporterView = new ProductInstanceImporterView(); + partImporterView.render(); + document.body.appendChild(partImporterView.el); + partImporterView.openModal(); + return false; + } + }); + + return ProductInstancesContentView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_creation.js b/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_creation.js new file mode 100644 index 0000000000..0c2116b189 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_creation.js @@ -0,0 +1,152 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/product-instances/product_instances_creation.html', + 'common-objects/models/product_instance', + 'collections/configuration_items', + 'common-objects/collections/baselines', + 'common-objects/views/attributes/attributes', + 'common-objects/views/security/acl', + 'common-objects/views/alert' +], function (Backbone, Mustache, template, ProductInstanceModel, ConfigurationItemCollection, BaselinesCollection,AttributesView,ACLView, AlertView) { + 'use strict'; + + var ProductInstanceCreationView = Backbone.View.extend({ + + model: new ProductInstanceModel(), + + events: { + 'click .modal-footer .btn-primary': 'interceptSubmit', + 'submit #product_instance_creation_form': 'onSubmitForm', + 'hidden #product_instance_creation_modal': 'onHidden' + }, + + initialize: function () { + this._subViews = []; + _.bindAll(this); + this.$el.on('remove', this.removeSubviews); + }, + + removeSubviews: function () { + _(this._subViews).invoke('remove'); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + this.bindAttributesView(); + this.workspaceMembershipsView = new ACLView({ + el: this.$('#acl-mapping'), + editMode: true + }).render(); + this.$inputSerialNumber.customValidity(App.config.i18n.REQUIRED_FIELD); + + if(this.options.baseline){ + this.$inputBaseline.append(''); + this.$inputConfigurationItem.append(''); + }else{ + new ConfigurationItemCollection().fetch({success: this.fillConfigurationItemList}); + } + return this; + }, + + fillConfigurationItemList: function (list) { + var self = this; + list.each(function (product) { + self.$inputConfigurationItem.append(''); + }); + self.fillBaselineList(); + this.$inputConfigurationItem.change(function () { + self.fillBaselineList(); + }); + }, + fillBaselineList: function () { + var self = this; + this.$inputBaseline.empty(); + this.$inputBaseline.attr('disabled', 'disabled'); + new BaselinesCollection({}, {productId: self.$inputConfigurationItem.val()}).fetch({ + success: function (list) { + list.each(function (baseline) { + self.$inputBaseline.append(''); + }); + self.$inputBaseline.removeAttr('disabled'); + } + }); + }, + + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.$modal = this.$('#product_instance_creation_modal'); + this.$inputSerialNumber = this.$('#inputSerialNumber'); + this.$inputConfigurationItem = this.$('#inputConfigurationItem'); + this.$inputBaseline = this.$('#inputBaseline'); + }, + bindAttributesView: function () { + this.attributesView = new AttributesView({ + el: this.$('#tab-products-instances-attributes') + }).render(); + }, + + interceptSubmit: function () { + this.isValid = !this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + var data = { + serialNumber: this.$inputSerialNumber.val(), + configurationItemId: this.$inputConfigurationItem.val(), + baselineId: this.$inputBaseline.val(), + instanceAttributes: this.attributesView.collection.toJSON(), + acl: this.workspaceMembershipsView.toList() + + }; + + if (data.serialNumber && data.configurationItemId && data.baselineId) { + this.model.unset('identifier'); + this.model.unset('serialNumber'); + this.model.save(data, { + success: this.onProductInstanceCreated, + error: this.onError, + wait: true + }); + } + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onProductInstanceCreated: function () { + this.trigger('info',App.config.i18n.PRODUCT_INSTANCE_CREATED); + + if(this.collection){ + this.collection.fetch(); + } + this.closeModal(); + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + } + }); + + return ProductInstanceCreationView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_importer.js b/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_importer.js new file mode 100644 index 0000000000..e6a987565d --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_importer.js @@ -0,0 +1,259 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'unorm', + 'common-objects/views/components/modal', + 'common-objects/models/file/attached_file', + 'common-objects/views/file/file', + 'common-objects/views/alert', + 'text!templates/product-instances/product_instances_import_modal.html', + 'text!templates/product-instances/product_instances_import_form.html', + 'common-objects/views/part/import_status_view' +], function (Backbone, Mustache, unorm, ModalView, AttachedFile, FileView, AlertView,modalTemplate, template, ImportStatusView) { + 'use strict'; + var ProductInstanceImportView = Backbone.View.extend({ + + template: template, + modalTemplate: modalTemplate, + + tagName: 'div', + className: 'attachedFiles idle', + + events:{ + 'click form button.cancel-upload-btn':'cancelButtonClicked', + 'change form input.upload-btn':'fileSelectHandler', + 'dragover .droppable':'fileDragHover', + 'dragleave .droppable':'fileDragHover', + 'drop .droppable':'fileDropHandler', + 'click .import-preview-button': 'showPreview', + 'click .back-button': 'backToForm', + 'click .import-button':'formSubmit', + 'hidden.bs.modal .modal.importer-view':'deleteImportStatus' + }, + + initialize: function () { + + $.event.props.push('dataTransfer'); + + // Prevent browser behavior on file drop + window.addEventListener('drop', function (e) { + e.preventDefault(); + return false; + }, false); + + window.addEventListener('ondragenter', function (e) { + e.preventDefault(); + return false; + }, false); + + this.$el.on('remove', this.removeSubviews); + + this.importForm = true; + this.importPreview = false; + }, + + // cancel event and hover styling + fileDragHover: function (e) { + e.stopPropagation(); + e.preventDefault(); + if (e.type === 'dragover') { + this.filedroparea.addClass('hover'); + } + else { + this.filedroparea.removeClass('hover'); + } + }, + + fileDropHandler: function (e) { + this.fileDragHover(e); + if (this.options.singleFile && e.dataTransfer.files.length > 1) { + this.printNotifications('error', App.config.i18n.SINGLE_FILE_RESTRICTION); + return; + } + + _.each(e.dataTransfer.files, this.loadNewFile.bind(this)); + }, + + fileSelectHandler: function (e) { + _.each(e.target.files, this.loadNewFile.bind(this)); + }, + + cancelButtonClicked: function () { + //empty the file + this.file = null; + this.finished(); + }, + + render: function () { + + this.$el.html(Mustache.render(modalTemplate, {i18n: App.config.i18n})); + this.$el.find('#import-contain').append(Mustache.render(template, { + importForm: this.importForm, + importPreview: this.importPreview, + freeze:this.freeze, + permissive: this.permissive, + revisionNote: this.revisionNote, + i18n: App.config.i18n + })); + this.bindDomElements(); + this.fetchImports(); + + return this; + }, + + rerender: function () { + + this.$el.find('#import-contain').html(Mustache.render(template, { + importForm: this.importForm, + importPreview: this.importPreview, + freeze:this.freeze, + permissive: this.permissive, + revisionNote: this.revisionNote, + i18n: App.config.i18n, + options: this.options + })); + this.bindDomElements(); + + this.$('#revision_text_part').val(this.revisionNote); + if(this.freeze){ this.$('#freeze-checkbox').prop('checked', true); } + if(this.permissive){ this.$('#permissive_update_product_instance-checkbox').prop('checked', true); } + + return this; + }, + + loadNewFile: function (file) { + + var fileName = unorm.nfc(file.name); + + var newFile = new AttachedFile({ + shortName: fileName + }); + + this.file = file; + this.addOneFile(newFile); + + }, + + addOneFile: function (attachedFile) { + this.filedisplay.html('
                                  • ' + attachedFile.getShortName() + '
                                  • '); + this.$('.import-preview-button').removeAttr('disabled'); + }, + + bindDomElements: function () { + this.$modal = this.$('.modal.importer-view'); + this.filedroparea = this.$('.filedroparea'); + this.filedisplay = this.$('#file-selected ul'); + this.uploadInput = this.$('input.upload-btn'); + this.progressBars = this.$('div.progress-bars'); + this.notifications = this.$('div.notifications'); + }, + + showPreview: function(){ + this.freeze = this.$('#freeze-checkbox').is(':checked'); + this.permissive = this.$('#permissive_update_product_instance-checkbox').is(':checked'); + this.revisionNote = this.$('#revision_text_product').val().trim(); + + this.options = this.freeze || this.permissive || this.revisionNote!== ''; + + this.importForm = false; + this.importPreview = true; + this.rerender(); + + }, + + formSubmit: function () { + + this.clearNotifications(); + + if (this.file) { + + var baseUrl = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/product-instances/import'; + + var params = { + autoFreezeAfterUpdate: this.freeze, + permissiveUpdate: this.permissive, + revisionNote: this.revisionNote + }; + + this.deleteImportStatus(); + + var importUrl = baseUrl + '?' + $.param(params); + + var xhr = new XMLHttpRequest(); + xhr.onload = this.fetchImports.bind(this); + xhr.open('POST', importUrl); + var formdata = new window.FormData(); + formdata.append('upload', this.file); + xhr.send(formdata); + + } else if(!this.file){ + this.printNotifications('error', App.config.i18n.NO_FILE_TO_IMPORT); + } + + this.backToForm(); + return false; + }, + + fetchImports:function(){ + var _this = this; + this.removeSubviews(); + _this.$('.import-status-views').empty(); + + if (this.file) { + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/imports/' + unorm.nfc(this.file.name); + $.get(url).then(function (imports) { + _.each(imports, function (pImport) { + var view = new ImportStatusView({model: pImport}).render(); + _this.importStatusViews.push(view); + _this.$('.import-status-views').append(view.$el); + }); + }); + } + }, + + printNotifications: function (type, message) { + this.notifications.append(new AlertView({ + type: type, + message: message + }).render().$el); + }, + + clearNotifications: function () { + this.notifications.text(''); + }, + + removeSubviews: function(){ + _(this.importStatusViews).invoke('remove'); + this.importStatusViews = []; + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + backToForm: function () { + this.importPreview = false; + this.importForm = true; + this.rerender(); + this.loadNewFile(this.file); + }, + + deleteImportStatus: function (){ + _.each(this.importStatusViews, function(importSV){ + importSV.deleteImport(); + }); + } + + }); + return ProductInstanceImportView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_list.js b/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_list.js new file mode 100644 index 0000000000..27c433a9a9 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_list.js @@ -0,0 +1,244 @@ +/*global _,define,App,bootbox*/ +define([ + 'backbone', + 'mustache', + 'text!templates/product-instances/product_instances_list.html', + 'views/product-instances/product_instances_list_item', + 'common-objects/views/security/acl_edit' +], function (Backbone, Mustache, template, ProductInstancesListItemView, ACLEditView) { + 'use strict'; + var ProductInstancesListView = Backbone.View.extend({ + + events: { + 'click .toggle-checkboxes': 'toggleSelection' + }, + + removeSubviews: function () { + _(this.listItemViews).invoke('remove'); // Invoke remove for each views in listItemViews + this.listItemViews = []; + }, + + initialize: function () { + _.bindAll(this); + this.listenTo(this.collection, 'reset', this.resetList); + this.listenTo(this.collection, 'add', this.addNewProductInstances); + this.listItemViews = []; + this.$el.on('remove', this.removeSubviews); + }, + + render: function () { + this.collection.fetch({reset: true}); + return this; + }, + + bindDomElements: function () { + this.$table = this.$('#product_instances_table'); + this.$items = this.$('.items'); + this.$checkbox = this.$('.toggle-checkboxes'); + }, + + resetList: function () { + if (this.oTable) { + this.oTable.fnDestroy(); + } + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + this.listItemViews = []; + var that = this; + this.collection.each(function (model) { + that.addProductInstances(model); + }); + this.dataTable(); + }, + + addNewProductInstances: function (model) { + this.addProductInstances(model, true); + this.redraw(); + }, + + addProductInstances: function (model, effect) { + var view = this.addProductInstancesView(model); + if (effect) { + view.$el.highlightEffect(); + } + }, + + removeProductInstance: function (model) { + this.removeProductInstancesView(model); + this.redraw(); + }, + + removeProductInstancesView: function (model) { + + var viewToRemove = _(this.listItemViews).select(function (view) { + return view.model === model; + })[0]; + + if (viewToRemove) { + this.listItemViews = _(this.listItemViews).without(viewToRemove); + var row = viewToRemove.$el.get(0); + this.oTable.fnDeleteRow(this.oTable.fnGetPosition(row)); + viewToRemove.remove(); + } + + }, + + addProductInstancesView: function (model) { + var view = new ProductInstancesListItemView({model: model}).render(); + this.listItemViews.push(view); + this.$items.append(view.$el); + view.on('selectionChanged', this.onSelectionChanged); + view.on('rendered', this.redraw); + return view; + }, + + toggleSelection: function () { + if (this.$checkbox.is(':checked')) { + _(this.listItemViews).each(function (view) { + view.check(); + }); + } else { + _(this.listItemViews).each(function (view) { + view.unCheck(); + }); + } + this.onSelectionChanged(); + }, + + onSelectionChanged: function () { + + var checkedViews = _(this.listItemViews).select(function (view) { + return view.isChecked(); + }); + + if (checkedViews.length <= 0) { + this.onNoProductInstanceSelected(); + } else if (checkedViews.length === 1) { + this.onOneProductInstanceSelected(); + } else { + this.onSeveralProductInstancesSelected(); + } + + }, + + onNoProductInstanceSelected: function () { + this.trigger('delete-button:display', false); + this.trigger('acl-button:display', false); + + }, + + onOneProductInstanceSelected: function () { + this.trigger('delete-button:display', true); + this.trigger('acl-button:display', true); + + }, + + onSeveralProductInstancesSelected: function () { + this.trigger('delete-button:display', true); + this.trigger('acl-button:display', false); + }, + + getSelectedProductInstance: function () { + var model = null; + _(this.listItemViews).each(function (view) { + if (view.isChecked()) { + model = view.model; + } + }); + return model; + }, + + deleteSelectedProductInstances: function () { + var _this = this; + bootbox.confirm(App.config.i18n.CONFIRM_DELETE_PRODUCT_INSTANCE, function (result) { + if (result) { + _(_this.listItemViews).each(function (view) { + if (view.isChecked()) { + view.model.id = view.model.getSerialNumber(); + view.model.destroy({ + dataType: 'text', // server doesn't send a json hash in the response body + success: function () { + _this.removeProductInstance(view.model); + _this.onSelectionChanged(); + }, + error: function (model, err) { + _this.trigger('error', model, err); + _this.onSelectionChanged(); + }, + wait: true + }); + } + }); + } + }); + }, + + redraw: function () { + this.dataTable(); + }, + dataTable: function () { + var oldSort = [ + [1, 'asc'] + ]; + if (this.oTable) { + oldSort = this.oTable.fnSettings().aaSorting; + this.oTable.fnDestroy(); + } + this.oTable = this.$table.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + {'bSortable': false, 'aTargets': [0, 6, 7, 8, 9, 10, 11, 12]} + ] + }); + this.$el.find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + }, + + editSelectedProductInstanceACL: function () { + var templateSelected; + var _this = this; + _(_this.listItemViews).each(function (view) { + if (view.isChecked()) { + templateSelected = view.model; + } + }); + + var aclEditView = new ACLEditView({ + editMode: true, + acl: templateSelected.get('acl') + }); + + aclEditView.setTitle(templateSelected.getSerialNumber()); + window.document.body.appendChild(aclEditView.render().el); + + aclEditView.openModal(); + aclEditView.on('acl:update', function () { + + var acl = aclEditView.toList(); + + templateSelected.updateACL({ + acl: acl || {userEntries: {}, groupEntries: {}}, + success: function () { + templateSelected.set('acl', acl); + aclEditView.closeModal(); + }, + error: function(error){ + aclEditView.onError(error); + } + }); + }); + + return false; + } + + }); + + return ProductInstancesListView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_list_item.js b/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_list_item.js new file mode 100644 index 0000000000..c4393aa403 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/product-instances/product_instances_list_item.js @@ -0,0 +1,138 @@ +/*global define,App,$*/ +define([ + 'backbone', + 'mustache', + 'text!templates/product-instances/product_instances_list_item.html', + 'views/product-instances/product_instance_modal', + 'common-objects/utils/date' +], function (Backbone, Mustache, template, ProductInstanceModalView, date) { + 'use strict'; + var ProductInstancesListItemView = Backbone.View.extend({ + + events: { + 'click input[type=checkbox]': 'selectionChanged', + 'click td.reference': 'openEditView', + 'click td.has-path-to-path-link i.fa-exchange': 'openEditViewOnPathToPathLinkTab', + 'click td.has-path-data i.fa-asterisk': 'openEditViewOnPathDataTab', + 'click td.has-attached-files i': 'openEditViewOnFilesTab' + }, + + tagName: 'tr', + + initialize: function () { + this._isChecked = false; + this.listenTo(this.model,'change',this.render.bind(this)); + this.listenTo(this.model,'sync',this.render.bind(this)); + + }, + + render: function () { + this.$el.html(Mustache.render(template, { + model: this.model, + i18n: App.config.i18n, + bomUrl: this.model.getBomUrl(), + sceneUrl:this.model.getSceneUrl(), + zipUrl: this.model.getZipUrl(), + isReadOnly: this.model.isReadOnly(), + isFullAccess: this.model.isFullAccess() + })); + + this.$checkbox = this.$('input[type=checkbox]'); + if (this.isChecked()) { + this.check(); + this.trigger('selectionChanged', this); + } + + this.bindUserPopover(); + date.dateHelper(this.$('.date-popover')); + this.trigger('rendered', this); + + var zipUrl = this.model.getZipUrl(); + this.$('.download-zip').popover({ + title: ''+App.config.i18n.DOWNLOAD_ZIP+'
                                    ', + animation: true, + html: true, + trigger: 'manual', + content: ''+App.config.i18n.CAD_FILE+' | ' + + ''+App.config.i18n.LINKS+' | ' + + ''+App.config.i18n.EVERYTHING+'', + placement: 'top' + }).click(function (e) { + $(this).popover('show'); + e.stopPropagation(); + e.preventDefault(); + return false; + }); + + return this; + }, + + selectionChanged: function () { + this._isChecked = this.$checkbox.prop('checked'); + this.trigger('selectionChanged', this); + }, + + isChecked: function () { + return this._isChecked; + }, + + check: function () { + this.$checkbox.prop('checked', true); + this._isChecked = true; + }, + + bindUserPopover: function () { + this.$('.author-popover').userPopover(this.model.getUpdateAuthor(), this.model.getSerialNumber(), 'left'); + }, + + unCheck: function () { + this.$checkbox.prop('checked', false); + this._isChecked = false; + }, + + openEditView: function () { + var model = this.model; + model.fetch().success(function () { + var view = new ProductInstanceModalView({model: model}); + view.render(); + window.document.body.appendChild(view.el); + }); + }, + + openEditViewOnPathToPathLinkTab: function () { + var model = this.model; + model.fetch().success(function () { + var view = new ProductInstanceModalView({model: model}); + window.document.body.appendChild(view.el); + view.render(); + view.activePathToPathLinkTab(); + + }.bind(this)); + }, + + openEditViewOnPathDataTab: function () { + var model = this.model; + model.fetch().success(function () { + var view = new ProductInstanceModalView({model: model}); + window.document.body.appendChild(view.el); + view.render(); + view.activePathDataTab(); + + }.bind(this)); + }, + + openEditViewOnFilesTab: function () { + var model = this.model; + model.fetch().success(function () { + var view = new ProductInstanceModalView({model: model}); + window.document.body.appendChild(view.el); + view.render(); + view.activeFilesTab(); + + }.bind(this)); + } + + }); + + return ProductInstancesListItemView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/product/product_content.js b/docdoku-web-front/app/product-management/js/views/product/product_content.js new file mode 100644 index 0000000000..5be53efa0b --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/product/product_content.js @@ -0,0 +1,147 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'collections/configuration_items', + 'common-objects/collections/part_collection', + 'models/configuration_item', + 'text!templates/product/product_content.html', + 'views/product/product_list', + 'views/product/product_creation_view', + 'views/baselines/baseline_creation_view', + 'text!common-objects/templates/buttons/snap_button.html', + 'text!common-objects/templates/buttons/delete_button.html', + 'text!common-objects/templates/buttons/udf_button.html', + 'common-objects/views/alert', + 'common-objects/views/udf/user_defined_function' +], function (Backbone, Mustache, ConfigurationItemCollection, PartCollection, ConfigurationItem, template, ProductListView, ProductCreationView, BaselineCreationView, snapButton, deleteButton, udfButton, AlertView, UserDefinedFunctionView) { + 'use strict'; + var ProductContentView = Backbone.View.extend({ + partials: { + snapButton: snapButton, + deleteButton: deleteButton, + udfButton: udfButton + }, + + events: { + 'click button.new-product': 'newProduct', + 'click button.delete': 'deleteProduct', + 'click button.udf': 'openUdfView', + 'click button.new-baseline': 'createBaseline' + }, + + initialize: function () { + _.bindAll(this); + }, + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n}, this.partials)); + this.bindDomElements(); + + if(!this.configurationItemCollection){ + this.configurationItemCollection = new ConfigurationItemCollection(); + } + + if(this.productListView){ + this.productListView.remove(); + } + + this.productListView = new ProductListView({ + el: this.$('#product_table'), + collection: this.configurationItemCollection + }).render(); + + this.bindEvent(); + + this.partsCollection = new PartCollection(); + this.partsCollection.on('page-count:fetch',this.checkForPartCount); + this.partsCollection.fetchPageCount(); + + return this; + }, + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.snapBaselineButton = this.$('.new-baseline'); + this.deleteButton = this.$('.delete'); + }, + bindEvent: function(){ + this.delegateEvents(); + this.productListView.on('error', this.onError); + this.productListView.on('warning', this.onWarning); + this.productListView.on('info', this.onInfo); + this.productListView.on('delete-button:display', this.changeDeleteButtonDisplay); + this.productListView.on('create-baseline-button:display', this.changeSnapBaselineButtonDisplay); + }, + + newProduct: function () { + var productCreationView = new ProductCreationView(); + window.document.body.appendChild(productCreationView.render().el); + productCreationView.on('product:created',this.configurationItemCollection.push,this.configurationItemCollection); + productCreationView.openModal(); + }, + + createBaseline: function () { + var baselineCreationView = new BaselineCreationView({ + model: this.productListView.getSelectedProduct() + }); + window.document.body.appendChild(baselineCreationView.render().el); + baselineCreationView.on('warning', this.onWarning); + baselineCreationView.on('info', this.onInfo); + baselineCreationView.openModal(); + }, + + deleteProduct: function () { + this.productListView.deleteSelectedProducts(); + }, + + onInfo:function(message){ + this.$notifications.append(new AlertView({ + type: 'info', + message: message + }).render().$el); + }, + + changeSnapBaselineButtonDisplay: function (state) { + this.snapBaselineButton.toggle(state); + }, + + changeDeleteButtonDisplay: function (state) { + this.deleteButton.toggle(state); + }, + + onError:function(model, error){ + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + onWarning:function(model, error){ + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'warning', + message: errorMessage + }).render().$el); + }, + + openUdfView:function(){ + var view = new UserDefinedFunctionView(); + view.render(); + document.body.appendChild(view.el); + view.openModal(); + }, + + checkForPartCount:function(){ + if(!this.partsCollection.pageCount){ + this.$notifications.append(new AlertView({ + type: 'info', + message: App.config.i18n.CREATE_PART_BEFORE_PRODUCT + }).render().$el); + } + } + + }); + return ProductContentView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/product/product_creation_view.js b/docdoku-web-front/app/product-management/js/views/product/product_creation_view.js new file mode 100644 index 0000000000..19dd275c9f --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/product/product_creation_view.js @@ -0,0 +1,136 @@ +/*global _,$,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/product/product_creation_view.html', + 'models/configuration_item', + 'common-objects/views/alert' +], function (Backbone, Mustache, template, ConfigurationItem, AlertView) { + 'use strict'; + var ProductCreationView = Backbone.View.extend({ + + events: { + 'click .modal-footer .btn-primary': 'interceptSubmit', + 'submit #product_creation_form': 'onSubmitForm', + 'hidden #product_creation_modal': 'onHidden' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + this.bindTypeahead(); + this.$('input[required]').customValidity(App.config.i18n.REQUIRED_FIELD); + return this; + }, + + setRootPart:function(part){ + this.$inputPartNumber.val(part.getNumber()); + this.$inputPartName.val(part.getName()); + this.$inputPart.val(part.getName() + ' < ' + part.getNumber() + ' >'); + return this; + }, + + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.$modal = this.$('#product_creation_modal'); + this.$inputPartNumber = this.$('#inputPartNumber'); + this.$inputPart = this.$('#inputPart'); + this.$inputPartName = this.$('#inputPartName'); + this.$inputProductId = this.$('#inputProductId'); + this.$inputDescription = this.$('#inputDescription'); + }, + + bindTypeahead: function () { + var map = {}; + var that = this; + this.$inputPart.typeahead({ + source: function (query, process) { + var partNumbers = []; + + $.getJSON(App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/numbers?q=' + query, function (data) { + _(data).each(function (d) { + var label = d.partName + ' < ' + d.partNumber + ' >'; + partNumbers.push(label); + map[label] = d; + }); + process(partNumbers); + }); + }, + updater: function(item) { + that.$inputPartName.val(map[item].partName); + that.$inputPartNumber.val(map[item].partNumber); + return item; + } + }); + }, + + interceptSubmit : function(){ + this.isValid = ! this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSubmitForm: function (e) { + + this.$notifications.empty(); + + if(this.isValid){ + this.model = new ConfigurationItem({ + id: this.$inputProductId.val(), + workspaceId: App.config.workspaceId, + description: this.$inputDescription.val(), + designItemNumber: this.$inputPartNumber.val(), + designItemName: this.$inputPartName.val() + }); + + if (!this.model.getDesignItemNumber()) { + this.onError(App.config.i18n.PART_NOT_FOUND); + return false; + } + + this.model.save({}, { + wait: true, + success: this.onProductCreated, + error: this.onError + }); + this.model.fetch(); + } + + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onProductCreated: function () { + this.trigger('product:created', this.model); + this.closeModal(); + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + } + + }); + + return ProductCreationView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/product/product_details_view.js b/docdoku-web-front/app/product-management/js/views/product/product_details_view.js new file mode 100644 index 0000000000..4b23c10592 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/product/product_details_view.js @@ -0,0 +1,94 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/product/product_details.html', + 'views/baselines/baseline_list', + 'views/baselines/baseline_detail_view', + 'common-objects/views/alert' +], function (Backbone, Mustache, template, BaselineListView, BaselineDetailView, AlertView) { + + 'use strict'; + + var ProductDetailsView = Backbone.View.extend({ + + events: { + 'submit #product_details_form': 'onSubmitForm', + 'hidden #product_details_modal': 'onHidden', + 'close-modal-request':'closeModal' + }, + + template: Mustache.parse(template), + + initialize: function () { + + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, model: this.model})); + this.bindDomElements(); + this.initBaselinesView(); + return this; + }, + + bindDomElements: function () { + this.$notifications = this.$el.find('.notifications').first(); + this.$modal = this.$('#product_details_modal'); + this.$tabs = this.$('.nav-tabs li'); + this.$tabBaselines = this.$('#tab-baselines'); + }, + + onSubmitForm: function (e) { + var _this = this; + var baselines = this.baselineListView.getCheckedBaselines(); + + if (baselines.length) { + this.model.deleteBaselines(baselines,{ + success: _this.closeModal.bind(this), + error: _this.onError.bind(this) + }); + } + + else{ + _this.closeModal(); + } + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + initBaselinesView: function () { + this.baselineListView = new BaselineListView({}, {productId: this.model.getId()}).render(); + this.$tabBaselines.append(this.baselineListView.$el); + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + + this.$notifications.append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + }, + + activateTab: function (index) { + this.$tabs.eq(index).children().tab('show'); + } + + }); + + return ProductDetailsView; + +}); diff --git a/docdoku-web-front/app/product-management/js/views/product/product_list.js b/docdoku-web-front/app/product-management/js/views/product/product_list.js new file mode 100644 index 0000000000..b803941334 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/product/product_list.js @@ -0,0 +1,206 @@ +/*global _,define,bootbox,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/product/product_list.html', + 'views/product/product_list_item' +], function (Backbone, Mustache, template, ProductListItemView) { + 'use strict'; + var ProductListView = Backbone.View.extend({ + + events: { + 'click .toggle-checkboxes': 'toggleSelection' + }, + + removeSubviews: function () { + _(this.listItemViews).invoke('remove'); // Invoke remove for each views in listItemViews + this.listItemViews = []; + }, + + initialize: function () { + _.bindAll(this); + this.listenTo(this.collection, 'reset', this.resetList); + this.listenTo(this.collection, 'add', this.addNewProduct); + this.listItemViews = []; + this.$el.on('remove', this.removeSubviews); + }, + + render: function () { + var _this = this; + this.oTable=null; + this.collection.fetch({ + reset: true, + error:function(collection,err){ + _this.trigger('error', null, err); + } + }); + return this; + }, + + bindDomElements: function () { + this.$items = this.$('.items'); + this.$checkbox = this.$('.toggle-checkboxes'); + }, + + resetList: function () { + var _this = this; + this.removeSubviews(); + + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + + this.collection.each(function (model) { + _this.addProduct(model); + }); + this.dataTable(); + }, + + addNewProduct: function (model) { + this.addProduct(model, true); + this.redraw(); + }, + + addProduct: function (model, effect) { + var view = this.addProductView(model); + if (effect) { + view.$el.highlightEffect(); + } + }, + + removeProduct: function (model) { + this.removeProductView(model); + this.redraw(); + }, + + removeProductView: function (model) { + var viewToRemove = _(this.listItemViews).select(function (view) { + return view.model === model; + })[0]; + + if (viewToRemove) { + this.listItemViews = _(this.listItemViews).without(viewToRemove); + var row = viewToRemove.$el.get(0); + this.oTable.fnDeleteRow(this.oTable.fnGetPosition(row)); + viewToRemove.remove(); + } + + }, + + addProductView: function (model) { + var view = new ProductListItemView({model: model}).render(); + this.listItemViews.push(view); + this.$items.append(view.$el); + view.on('selectionChanged', this.onSelectionChanged); + view.on('rendered', this.redraw); + return view; + }, + + toggleSelection: function () { + if (this.$checkbox.is(':checked')) { + _(this.listItemViews).each(function (view) { + view.check(); + }); + } else { + _(this.listItemViews).each(function (view) { + view.unCheck(); + }); + } + this.onSelectionChanged(); + }, + + onSelectionChanged: function () { + + var checkedViews = _(this.listItemViews).select(function (view) { + return view.isChecked(); + }); + + if (checkedViews.length <= 0) { + this.onNoProductSelected(); + } else if (checkedViews.length === 1) { + this.onOneProductSelected(); + } else { + this.onSeveralProductsSelected(); + } + + }, + + onNoProductSelected: function () { + this.trigger('delete-button:display', false); + this.trigger('create-baseline-button:display', false); + }, + + onOneProductSelected: function () { + this.trigger('create-configuration-button:display', true); + this.trigger('create-baseline-button:display', true); + this.trigger('delete-button:display', true); + }, + + onSeveralProductsSelected: function () { + this.trigger('delete-button:display', true); + this.trigger('create-baseline-button:display', false); + }, + + getSelectedProduct: function () { + var model = null; + _(this.listItemViews).each(function (view) { + if (view.isChecked()) { + model = view.model; + } + }); + return model; + }, + + deleteSelectedProducts: function () { + var _this = this; + bootbox.confirm(App.config.i18n.CONFIRM_DELETE_PRODUCT, function(result){ + if(result){ + _(_this.listItemViews).each(function (view) { + if (view.isChecked()) { + view.model.destroy({ + dataType: 'text', // server doesn't send a json hash in the response body + success: function () { + _this.removeProduct(view.model); + _this.onSelectionChanged(); + }, + error: function (model, err) { + _this.trigger('error',model,err); + _this.onSelectionChanged(); + } + }); + } + }); + } + }); + }, + redraw: function () { + this.dataTable(); + }, + dataTable: function () { + var oldSort = [ + [0, 'asc'] + ]; + if (this.oTable) { + oldSort = this.oTable.fnSettings().aaSorting; + this.oTable.fnDestroy(); + } + this.oTable = this.$el.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + { 'bSortable': false, 'aTargets': [ 0, 2, 5, 6, 7 ] } + ] + }); + this.$el.parent().find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + } + + }); + + return ProductListView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/product/product_list_item.js b/docdoku-web-front/app/product-management/js/views/product/product_list_item.js new file mode 100644 index 0000000000..35956352d7 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/product/product_list_item.js @@ -0,0 +1,110 @@ +/*global define,App,$*/ +define([ + 'backbone', + 'mustache', + 'text!templates/product/product_list_item.html', + 'views/product/product_details_view', + 'common-objects/views/part/part_modal_view', + 'common-objects/models/part' +], function (Backbone, Mustache, template, ProductDetailsView, PartModalView, Part) { + 'use strict'; + var ProductListItemView = Backbone.View.extend({ + + events: { + 'click input[type=checkbox]': 'selectionChanged', + 'click td.product_id': 'openDetailsView', + 'click a.design_item': 'openPartView' + }, + + tagName: 'tr', + + initialize: function () { + this._isChecked = false; + }, + + render: function () { + + this.$el.html(Mustache.render(template, { + model: this.model, + bomUrl: this.model.getBomUrl(), + sceneUrl:this.model.getSceneUrl(), + zipUrl: this.model.getZipUrl(), + i18n: App.config.i18n + })); + this.$checkbox = this.$('input[type=checkbox]'); + this.trigger('rendered', this); + + var zipUrl = this.model.getZipUrl(); + this.$('.download-zip').popover({ + title: ''+App.config.i18n.DOWNLOAD_ZIP+'
                                    ', + animation: true, + html: true, + trigger: 'manual', + content: ''+App.config.i18n.CAD_FILE+' | ' + + ''+App.config.i18n.LINKS+' | ' + + ''+App.config.i18n.EVERYTHING+'', + placement: 'top' + }).click(function (e) { + $(this).popover('show'); + e.stopPropagation(); + e.preventDefault(); + return false; + }); + + this.$('.author-popover').userPopover(this.model.getAuthorLogin(), this.model.getId(), 'left'); + + return this; + }, + + selectionChanged: function () { + this._isChecked = this.$checkbox.prop('checked'); + this.trigger('selectionChanged', this); + }, + + isChecked: function () { + return this._isChecked; + }, + + check: function () { + this.$checkbox.prop('checked', true); + this._isChecked = true; + }, + + unCheck: function () { + this.$checkbox.prop('checked', false); + this._isChecked = false; + }, + + openDetailsView: function () { + var that = this; + var pdv = new ProductDetailsView({model: that.model}); + pdv.on('pathToPathLink:remove', function() { + that.syncProduct(); + }); + window.document.body.appendChild(pdv.render().el); + pdv.openModal(); + }, + + syncProduct: function() { + var that = this; + this.model.fetch().success(function() { + that.render(); + }).error(function() { + }); + }, + + openPartView:function(){ + var part = new Part({partKey:this.model.getDesignItemNumber() + '-' +this.model.getDesignItemLatestVersion()}); + + part.fetch().success(function () { + var partModalView = new PartModalView({ + model: part + }); + partModalView.show(); + }); + } + + }); + + return ProductListItemView; +}); diff --git a/docdoku-web-front/app/product-management/js/views/query_builder.js b/docdoku-web-front/app/product-management/js/views/query_builder.js new file mode 100644 index 0000000000..e82e738cd5 --- /dev/null +++ b/docdoku-web-front/app/product-management/js/views/query_builder.js @@ -0,0 +1,641 @@ +/*global _,$,bootbox,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/query_builder.html', + 'selectize', + 'query-builder-options', + 'common-objects/views/alert', + 'collections/configuration_items', + 'common-objects/collections/product_instances', + 'common-objects/views/prompt' +], function (Backbone, Mustache, template, selectize, queryBuilderOptions, AlertView, ConfigurationItemCollection, ProductInstances, PromptView) { + 'use strict'; + var QueryBuilderView = Backbone.View.extend({ + + events: { + 'click .search-button': 'onSearch', + 'click .save-button': 'onSave', + 'change select.query-list':'onSelectQueryChange', + 'click .delete-selected-query':'deleteSelectedQuery', + 'click .reset-button' : 'onReset', + 'click .clear-select-badge': 'onClearSelect', + 'click .clear-where-badge': 'onClearWhere', + 'click .clear-order-by-badge': 'onClearOrderBy', + 'click .clear-group-by-badge': 'onClearGroupBy', + 'click .clear-context-badge' : 'onClearContext', + 'click .export-excel-button': 'onExport' + }, + + delimiter: ',', + + init: function () { + + this.selectizeAvailableOptions = _.clone(queryBuilderOptions.fields); + + this.queryBuilderFilters = _.clone(queryBuilderOptions.filters); + + this.selectizeOptions = { + plugins: ['remove_button','drag_drop', 'optgroup_columns'], + persist: true, + delimiter:this.delimiter, + optgroupField: 'group', + optgroupLabelField: 'name', + optgroupValueField: 'id', + optgroups: _.clone(queryBuilderOptions.groups), + + valueField: 'value', + searchField: ['name'], + options: null, + render: { + item: function(item, escape) { + return '
                                    ' + escape(item.name) + '
                                    '; + }, + option: function(item, escape) { + return '
                                    ' + escape(item.name) + '
                                    '; + } + } + }; + + }, + + fetchQueries: function (queryName) { + this.queries = []; + var queries = this.queries; + + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/queries'; + + var $select = this.$selectQuery; + var selected = this.$selectQuery.val(); + $select.empty(); + $select.append(''); + + var fillOption = function(q){ + queries.push(q); + $select.append(''); + }; + + return $.getJSON(url,function(data){ + data.sort(function(a,b){ + return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1; + }); + data.map(fillOption); + + if (typeof queryName === 'string') { + var selectedQuery = _.find(data, function (query) { + return query.name === queryName; + }); + selected = selectedQuery.id; + } + $select.val(selected); + }); + + }, + + clear:function(){ + var selectSelectize = this.$select[0].selectize; + var orderBySelectize = this.$orderBy[0].selectize; + var groupBySelectize = this.$groupBy[0].selectize; + + selectSelectize.clear(); + orderBySelectize.clear(true); + orderBySelectize.clearOptions(); + groupBySelectize.clear(true); + groupBySelectize.clearOptions(); + + this.onClearContext(); + + this.$deleteQueryButton.hide(); + this.$exportQueryButton.hide(); + }, + + onSelectQueryChange:function(e){ + + this.clear(); + + var selectSelectize = this.$select[0].selectize; + var orderBySelectize = this.$orderBy[0].selectize; + var groupBySelectize = this.$groupBy[0].selectize; + var contextSelectize = this.$context[0].selectize; + + if(e.target.value){ + var query = _.findWhere(this.queries,{id: parseInt(e.target.value,10)}); + if (query.queryRule) { + if (query.queryRule.rules.length === 0) { + this.$where.queryBuilder('reset'); + } else { + if (query.queryRule && query.queryRule.rules) { + var rules = query.queryRule.rules; + for (var i=0; i + + + + + DocDokuPLM - Product structure + + + + + + + + + + + + + +
                                    +
                                    +
                                    +
                                    +
                                    + + + + + diff --git a/docdoku-web-front/app/product-structure/js/app.js b/docdoku-web-front/app/product-structure/js/app.js new file mode 100644 index 0000000000..791beb5be5 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/app.js @@ -0,0 +1,433 @@ +/*global define,App,dat,$*/ +define([ + 'backbone', + 'mustache', + 'views/search_view', + 'views/parts_tree_view', + 'views/bom_view', + 'views/collaborative_view', + 'views/part_metadata_view', + 'views/part_instance_view', + 'views/export_scene_modal_view', + 'views/control_navigation_view', + 'views/control_modes_view', + 'views/control_transform_view', + 'views/control_markers_view', + 'views/control_layers_view', + 'views/control_options_view', + 'views/control_clipping_view', + 'views/control_explode_view', + 'views/control_measure_view', + 'views/baselines/baseline_select_view', + 'dmu/SceneManager', + 'dmu/collaborativeController', + 'dmu/InstancesManager', + 'text!templates/content.html', + 'common-objects/models/part', + 'views/path_data_modal', + 'views/path_to_path_link_modal', + 'common-objects/views/alert' +], function (Backbone, Mustache, SearchView, PartsTreeView, BomView, CollaborativeView, PartMetadataView, PartInstanceView, ExportSceneModalView, ControlNavigationView, ControlModesView, ControlTransformView, ControlMarkersView, ControlLayersView, ControlOptionsView, ControlClippingView, ControlExplodeView, ControlMeasureView, BaselineSelectView, SceneManager, CollaborativeController, InstancesManager, template, Part, PathDataModalView, PathToPathLinkModalView, AlertView) { + + 'use strict'; + + var AppView = Backbone.View.extend({ + el: '#content', + + events: { + 'click #scene_view_btn': 'sceneButton', + 'click #bom_view_btn': 'bomButton', + 'click #export_scene_btn': 'exportScene', + 'click #fullscreen_scene_btn': 'fullScreenScene', + 'click #path_data_btn': 'openPathDataModal', + 'click #path_to_path_link_btn' : 'openPathToPathLinkModal' + }, + + inBomMode: false, + + initialize: function () { + + }, + + render: function () { + + this.$el.html(Mustache.render(template, { + productId: App.config.productId, + contextPath: App.config.contextPath, + i18n: App.config.i18n + })).show(); + + this.bindDomElements(); + this.menuResizable(); + + App.sceneManager = new SceneManager(); + App.instancesManager = new InstancesManager(); + App.collaborativeController = new CollaborativeController(); + App.collaborativeView = new CollaborativeView().render(); + + App.controlModesView = new ControlModesView().render(); + App.controlTransformView = new ControlTransformView().render(); + App.partMetadataView = new PartMetadataView({model: new Backbone.Model()}); + App.controlNavigationView = new ControlNavigationView().render(); + + App.$ControlsContainer.append(App.collaborativeView.$el); + App.$ControlsContainer.append(App.controlNavigationView.$el); + App.$ControlsContainer.append(App.controlModesView.$el); + App.$ControlsContainer.append(App.controlTransformView.$el); + App.$ControlsContainer.append(App.partMetadataView.$el); + + App.$ControlsContainer.append(new ControlOptionsView().render().$el); + App.$ControlsContainer.append(new ControlClippingView().render().$el); + App.$ControlsContainer.append(new ControlExplodeView().render().$el); + App.$ControlsContainer.append(new ControlMarkersView().render().$el); + App.$ControlsContainer.append(new ControlLayersView().render().$el); + App.$ControlsContainer.append(new ControlMeasureView().render().$el); + + this.pathToPathLinkButton.hide(); + this.pathDataModalButton.hide(); + + this.initDebugShortcut(); + try { + App.sceneManager.init(); + this.bindDatGUIControls(); + } catch (ex) { + console.error('Got exception in dmu'); + App.log(ex); + this.onNoWebGLSupport(); + } + + return this; + }, + + initDebugShortcut:function(){ + var k = [191,68,69, 66, 85, 71], // :debug or /debug on macosx + n = 0; + $(document).keydown(function (e) { + if (e.keyCode === k[n++]) { + if (n === k.length) { + App.setDebug(!App.debug); + n = 0; + return false; + } + } + else { + n = 0; + } + }); + }, + + initModules:function(){ + + App.searchView = new SearchView().render(); + App.partsTreeView = new PartsTreeView({resultPathCollection: App.searchView.collection}).render(); + App.bomView = new BomView().render(); + App.baselineSelectView = new BaselineSelectView({el: '#config_spec_container'}).render(); + + this.bomControls.append(App.bomView.bomHeaderView.$el); + + this.listenEvents(); + + App.partsTreeView.once('collection:fetched',function(){ + App.appView.trigger('app:ready'); + }); + + return this; + + }, + + bindDomElements: function () { + this.$contentContainer = this.$('#product-content'); + this.$productMenu = this.$('#product-menu'); + this.sceneModeButton = this.$('#scene_view_btn'); + this.bomModeButton = this.$('#bom_view_btn'); + this.exportSceneButton = this.$('#export_scene_btn'); + this.pathDataModalButton = this.$('#path_data_btn'); + this.pathToPathLinkButton = this.$('#path_to_path_link_btn'); + this.bomControls = this.$('.bom-controls'); + this.dmuControls = this.$('.dmu-controls'); + App.$ControlsContainer = this.$('#side_controls_container'); + App.$SceneContainer = this.$('#scene_container'); + }, + + menuResizable: function () { + this.$productMenu.resizable({ + containment: this.$el, + handles: 'e', + autoHide: true, + stop: function (e, ui) { + var parent = ui.element.parent(); + var percent = ui.element.width() / parent.width() * 100; + ui.element.css({ + width: percent + '%', + height: '100%' + }); + ui.element.toggleClass('alpha', Math.floor(percent) > 15); + } + }); + }, + + bomMode: function () { + this.$contentContainer.attr('class', 'bom-mode'); + this.$productMenu.attr('class', 'bom-mode'); + this.bomModeButton.addClass('active'); + this.sceneModeButton.removeClass('active'); + }, + + sceneMode: function () { + this.$contentContainer.attr('class', 'scene-mode'); + this.$productMenu.attr('class', 'scene-mode'); + this.bomModeButton.removeClass('active'); + this.sceneModeButton.addClass('active'); + App.sceneManager.onContainerShown(); + }, + + listenEvents: function () { + App.baselineSelectView.on('config_spec:changed', this.onConfigSpecChange, this); + Backbone.Events.on('object:selected', this.onObjectSelected, this); + Backbone.Events.on('selection:reset', this.onResetSelection, this); + Backbone.Events.on('part:saved', this.refreshTree, this); + Backbone.Events.on('path:selected', this.updateDisplayPathToPathLinkButton, this); + Backbone.Events.on('path-data:clicked', this.onPathDataClicked, this); + this.listenTo(App.bomView,'checkbox:change',App.partsTreeView.uncheckAll); + this.listenTo(App.bomView.bomHeaderView,'alert',this.alert); + }, + + alert: function(params) { + this.$('.notifications').first().append(new AlertView(params).render().$el); + }, + + updateDisplayPathToPathLinkButton: function(pathSelected){ + + App.bomView.uncheckAll(pathSelected); + this.pathSelected = pathSelected; + + if (pathSelected.length === 2) { + if(pathSelected[0].isSubstituteOf(pathSelected[1]) || pathSelected[1].isSubstituteOf(pathSelected[0])){ + this.pathToPathLinkButton.hide(); + }else{ + this.pathToPathLinkButton.show(); + } + } else { + this.pathToPathLinkButton.hide(); + } + + if(App.baselineSelectView.isSerialNumberSelected()){ + if (pathSelected.length === 1) { + this.pathDataModalButton.show(); + this.checkedComponent = pathSelected[0]; + } else { + this.pathDataModalButton.hide(); + this.checkedComponent = null; + } + } else { + this.pathDataModalButton.hide(); + this.checkedComponent = null; + } + + }, + + openPathToPathLinkModal:function(){ + var pathToPathLinkModal = new PathToPathLinkModalView({ + pathSelected : this.pathSelected, + productId : App.config.productId, + serialNumber : App.baselineSelectView.isSerialNumberSelected() ? App.config.productConfigSpec.substring(3) : null, + baselineId : App.baselineSelectView.isBaselineSelected() ? App.config.productConfigSpec : null + }).render(); + window.document.body.appendChild(pathToPathLinkModal.el); + pathToPathLinkModal.openModal(); + }, + + updateBom: function (showRoot) { + if (showRoot) { + App.bomView.showRoot(App.partsTreeView.componentSelected); + } else { + App.bomView.updateContent(App.partsTreeView.componentSelected); + } + }, + + sceneButton: function () { + App.router.navigate(App.config.workspaceId + '/' + App.config.productId + '/config-spec/' + App.config.productConfigSpec + '/scene', {trigger: true}); + }, + + bomButton: function () { + App.router.navigate(App.config.workspaceId + '/' + App.config.productId + '/config-spec/' + App.config.productConfigSpec + '/bom', {trigger: true}); + }, + + setSpectatorView: function () { + this.$('.side_control_group:not(.part_metadata_container)').hide(); + }, + + leaveSpectatorView: function () { + this.$('.side_control_group:not(.part_metadata_container)').show(); + }, + + transformControlMode: function () { + this.$('#view_buttons').find('button').removeClass('active'); + }, + + leaveTransformControlMode: function () { + App.controlTransformView.render(); + }, + + updateTreeView: function (arrayPaths) { + App.partsTreeView.setSmartPaths(arrayPaths); + }, + + onComponentSelected: function (showRoot) { + this.exportSceneButton.show(); + + if (App.partsTreeView.componentSelected) { + this.updateBom(showRoot); + this.showPartMetadata(); + App.sceneManager.setPathForIFrame(App.partsTreeView.componentSelected.getPath()); + } + }, + + exportScene: function () { + // Def url + var splitUrl = window.location.href.split('/'); + var urlRoot = splitUrl[0] + '//' + splitUrl[2]; + + var iframeSrc = urlRoot + '/visualization/#product/' + App.config.workspaceId + '/' + App.config.productId + + '/' + App.sceneManager.cameraObject.position.x + + '/' + App.sceneManager.cameraObject.position.y + + '/' + App.sceneManager.cameraObject.position.z; + + if (App.partsTreeView.componentSelected.getPath()) { + iframeSrc += '/' + App.partsTreeView.componentSelected.getEncodedPath(); + } else { + iframeSrc += '/-1'; + } + + iframeSrc += '/' + App.config.productConfigSpec; + + // Open modal + var esmv = new ExportSceneModalView({iframeSrc: iframeSrc}); + window.document.body.appendChild(esmv.render().el); + esmv.openModal(); + }, + + openPathDataModal:function(){ + var pathDataModal = new PathDataModalView({ + serialNumber: App.config.productConfigSpec.substr(3), + path : this.checkedComponent.getEncodedPath() + }); + window.document.body.appendChild(pathDataModal.el); + pathDataModal.initAndOpenModal(); + this.listenTo(pathDataModal,'path-data:created',this.refreshTree.bind(this)); + }, + + onPathDataClicked:function(pathSelected){ + this.checkedComponent = pathSelected; + this.openPathDataModal(); + }, + + fullScreenScene: function () { + App.sceneManager.requestFullScreen(); + }, + + refreshTree: function () { + App.partsTreeView.refreshAll(); + }, + + showPartMetadata: function () { + App.partMetadataView.setModel(App.partsTreeView.componentSelected).render(); + }, + + onNoWebGLSupport: function () { + this.crashWithMessage(App.config.i18n.NO_WEBGL); + }, + + onConfigSpecChange: function (configSpec) { + this.setConfigSpec(configSpec); + if (App.collaborativeController) { + App.collaborativeController.sendConfigSpec(configSpec); + } + }, + + setConfigSpec: function (configSpec) { + App.config.productConfigSpec = configSpec || App.config.productConfigSpec; + + if(App.collaborativeView.roomKey){ + App.router.navigate(App.config.workspaceId + '/' + App.config.productId + '/config-spec/' + App.config.productConfigSpec + '/room/' + App.collaborativeView.roomKey, {trigger: false}); + }else{ + App.router.navigate(App.config.workspaceId + '/' + App.config.productId + '/config-spec/' + App.config.productConfigSpec + '/bom', {trigger: false}); + } + + App.sceneManager.clear(); + App.instancesManager.clear(); + App.partsTreeView.refreshAll(); + this.updateBom(); + }, + + onObjectSelected: function (object) { + + var partKey = object.partIterationId.substr(0, object.partIterationId.lastIndexOf('-')); + var part = new Part({partKey: partKey}); + + part.fetch({ + success: function () { + // Search the part in the tree + App.searchView.trigger('instance:selected', object.path); + App.controlNavigationView.setObject(object); + App.controlTransformView.setObject(object).render(); + App.partMetadataView.setModel(part).render(); + } + }); + + }, + + onResetSelection: function () { + App.searchView.trigger('selection:reset'); + App.partMetadataView.reset(); + App.controlNavigationView.reset(); + App.controlTransformView.object = undefined; + App.controlTransformView.reset(); + this.exportSceneButton.hide(); + }, + + bindDatGUIControls: function () { + // Dat.gui controls + var gui = new dat.GUI({autoPlace: false}); + var valuesControllers = []; + this.$el.append(gui.domElement); + + valuesControllers.push(gui.add(App.WorkerManagedValues, 'maxInstances').min(0).max(5000).step(1)); + valuesControllers.push(gui.add(App.WorkerManagedValues, 'maxAngle').min(0).max(Math.PI).step(0.01)); + valuesControllers.push(gui.add(App.WorkerManagedValues, 'maxDist').min(1).max(100000).step(100)); + valuesControllers.push(gui.add(App.WorkerManagedValues, 'minProjectedSize').min(0).max(window.innerHeight).step(1)); + + valuesControllers.push(gui.add(App.WorkerManagedValues, 'angleRating').min(0).max(1).step(0.01)); + valuesControllers.push(gui.add(App.WorkerManagedValues, 'distanceRating').min(0).max(1).step(0.01)); + valuesControllers.push(gui.add(App.WorkerManagedValues, 'volRating').min(0).max(1).step(0.01)); + + valuesControllers.push(gui.add(App.SceneOptions, 'grid')); + valuesControllers.push(gui.add(App.SceneOptions, 'rotateSpeed').min(0).max(10).step(0.01)); + valuesControllers.push(gui.add(App.SceneOptions, 'zoomSpeed').min(0).max(10).step(0.01)); + valuesControllers.push(gui.add(App.SceneOptions, 'panSpeed').min(0).max(10).step(0.01)); + + var ambientLightColorController = gui.addColor(App.SceneOptions, 'ambientLightColor'); + ambientLightColorController.onChange(App.sceneManager.updateAmbientLight); + valuesControllers.push(ambientLightColorController); + + var cameraLight1ColorController = gui.addColor(App.SceneOptions, 'cameraLight1Color'); + cameraLight1ColorController.onChange(App.sceneManager.updateCameraLight1); + valuesControllers.push(cameraLight1ColorController); + + var cameraLight2ColorController = gui.addColor(App.SceneOptions, 'cameraLight2Color'); + cameraLight2ColorController.onChange(App.sceneManager.updateCameraLight2); + valuesControllers.push(cameraLight2ColorController); + + return this; + }, + + crashWithMessage: function (htmlMessage) { + App.$SceneContainer.html('' + htmlMessage + ''); + App.$ControlsContainer.hide(); + this.dmuControls.hide(); + } + + }); + + return AppView; +}); diff --git a/docdoku-web-front/app/product-structure/js/collections/layer_collection.js b/docdoku-web-front/app/product-structure/js/collections/layer_collection.js new file mode 100644 index 0000000000..9c5db64f32 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/collections/layer_collection.js @@ -0,0 +1,38 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'models/layer' +], function (Backbone, Layer) { + 'use strict'; + var LayerCollection = Backbone.Collection.extend({ + + model: Layer, + + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + App.config.productId + '/layers'; + }, + + setAllShown: function (allShown) { + this.each(function (layer) { + layer.setShown(allShown); + }); + }, + + areInEditingMarkers: function () { + return this.where({editingMarkers: true}); + }, + + setAllEditingMarkers: function (editingMarkers) { + _.each(this.areInEditingMarkers(), function (layer) { + layer.setEditingMarkers(editingMarkers); + }, this); + }, + + onEmpty: function () { + App.sceneManager.stopMarkerCreationMode(); + } + + }); + + return LayerCollection; +}); diff --git a/docdoku-web-front/app/product-structure/js/collections/marker_collection.js b/docdoku-web-front/app/product-structure/js/collections/marker_collection.js new file mode 100644 index 0000000000..4bef9a41a7 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/collections/marker_collection.js @@ -0,0 +1,29 @@ +/*global define*/ +define([ + 'backbone', + 'models/marker' +], function (Backbone, Marker) { + + 'use strict'; + + var MarkerCollection = Backbone.Collection.extend({ + + model: Marker, + + url: function () { + return this.urlLayer + '/markers'; + }, + + onScene: function () { + return this.where({onScene: true}); + }, + + notOnScene: function () { + return this.where({onScene: false}); + } + + }); + + return MarkerCollection; + +}); diff --git a/docdoku-web-front/app/product-structure/js/collections/part_collection.js b/docdoku-web-front/app/product-structure/js/collections/part_collection.js new file mode 100644 index 0000000000..2d8d4e157c --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/collections/part_collection.js @@ -0,0 +1,28 @@ +/*global define*/ +define([ + 'backbone', + 'common-objects/models/part' +], function (Backbone, Part) { + 'use strict'; + var PartList = Backbone.Collection.extend({ + + model: Part, + + className: 'PartList', + + initialize: function () { + this.filterUrl = undefined; + }, + + setFilterUrl: function (url) { + this.filterUrl = url; + }, + + url: function () { + return this.filterUrl; + } + + }); + + return PartList; +}); diff --git a/docdoku-web-front/app/product-structure/js/collections/result_path_collection.js b/docdoku-web-front/app/product-structure/js/collections/result_path_collection.js new file mode 100644 index 0000000000..f16798a916 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/collections/result_path_collection.js @@ -0,0 +1,31 @@ +/*global define,App*/ +define([ + 'backbone', + 'models/result_path' +], function (Backbone, ResultPath) { + 'use strict'; + var ResultPathCollection = Backbone.Collection.extend({ + + model: ResultPath, + + contains: function (partUsageLinkId) { + return this.some(function (resultPath) { + return resultPath.contains(partUsageLinkId); + }); + }, + + url: function () { + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + App.config.productId + '/paths?configSpec='+App.config.productConfigSpec+'&search=' + encodeURIComponent(this.searchString); + + if(App.config.diverge){ + url += '&diverge=true'; + } + + return url; + } + + }); + + return ResultPathCollection; + +}); diff --git a/docdoku-web-front/app/product-structure/js/dmu/InstancesManager.js b/docdoku-web-front/app/product-structure/js/dmu/InstancesManager.js new file mode 100644 index 0000000000..5b51bf1860 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/dmu/InstancesManager.js @@ -0,0 +1,438 @@ +/*global _,$,define,App,THREE,Worker*/ +define(['dmu/LoaderManager', 'async', 'backbone'], + function (LoaderManager, async, Backbone) { + + 'use strict'; + + /** + * This class handles instances management. + * + * Dialog to sceneManager : add and remove objects + * Dialog from sceneManager : init and update camera and frustum + * + * Dialog with worker : + * - insert and remove instances from tree, + * - update frustum and camera + * + * Dialog from worker + * - show/hide instances and update instances quality + * + * */ + + var InstancesManager = function () { + + var _this = this; + + var timestamp = Date.now(); + + Backbone.Events.on('part:saved', function () { + timestamp = Date.now(); + }, this); + + this.xhrQueue = null; + this.loadQueue = null; + this.aborted = 0; + this.alreadySameQuality = 0; + this.xhrsDone = 0; + + var instancesIndexed = {}; + var loadedInstances = []; + var loaderManager = new LoaderManager({progressBar: true}); + var loaderIndicator = $('#product_title').find('img.loader'); + var timer = null; + var evalRunning = false; + + var worker = new Worker(App.config.contextPath + '/product-structure/js/workers/InstancesWorker.js'); + + var workerMessages = { + stats: function (stats) { + _this.workerStats = stats; + }, + + directives: function (directives) { + _this.aborted += _this.xhrQueue.tasks.length; + _this.xhrQueue.kill(); + + _(directives).each(function (directive) { + var instance = _this.getInstance(directive.id); + if (directive.nowait && directive.quality === undefined) { + App.sceneManager.removeObjectById(directive.id); + instance.qualityLoaded = undefined; + worker.postMessage({ + fn: 'setQuality', + obj: {id: instance.id, quality: instance.qualityLoaded} + }); + } else { + instance.directiveQuality = directive.quality; + _this.xhrQueue.push(directive); + } + }); + + + setTimeout(function () { + evalRunning = false; + }, 500); + } + }; + + worker.addEventListener('message', function (message) { + if (typeof workerMessages[message.data.fn] === 'function') { + workerMessages[message.data.fn](message.data.obj); + } else { + App.log('%c Unrecognized command : \n\t' + message.data, 'IM'); + } + }, false); + + + function loadProcess(directive, callback) { + + var instance = _this.getInstance(directive.id); + + if (!instance) { + setTimeout(callback, 0); + return; + } + + if (directive.quality === undefined) { + + // don't unload edited objects + if (App.sceneManager.editedObjects.indexOf(instance.id) !== -1) { + _this.aborted++; + worker.postMessage({fn: 'setQuality', obj: {id: instance.id, quality: instance.qualityLoaded}}); + setTimeout(callback, 0); + return; + } + + App.sceneManager.removeObjectById(instance.id); + instance.qualityLoaded = undefined; + worker.postMessage({fn: 'setQuality', obj: {id: instance.id, quality: instance.qualityLoaded}}); + setTimeout(callback, 0); + return; + } + + if (directive.quality === instance.qualityLoaded) { + _this.alreadySameQuality++; + setTimeout(callback, 0); + return; + } + + // Load the instance + var quality = App.config.contextPath + '/' + instance.files[directive.quality].fullName; + + var texturePath = quality.substring(0, quality.lastIndexOf('/')); + loaderManager.parseFile(quality, texturePath, { + success: function (object3d) { + + _this.xhrsDone++; + + loadedInstances.push({ + id: directive.id, + partIterationId: instance.partIterationId, + path: instance.path, + quality: directive.quality, + object3d: object3d + }); + + instance.qualityLoaded = directive.quality; + worker.postMessage({fn: 'setQuality', obj: {id: instance.id, quality: instance.qualityLoaded}}); + + callback(); + } + }); + } + + function adaptMatrix(matrix) { + return new THREE.Matrix4(matrix[0], matrix[1], matrix[2], matrix[3], + matrix[4], matrix[5], matrix[6], matrix[7], + matrix[8], matrix[9], matrix[10], matrix[11], + matrix[12], matrix[13], matrix[14], matrix[15]); + } + + function updateWorker() { + if (evalRunning) { + return; + } + evalRunning = true; + var sceneContext = App.sceneManager.getControlsContext(); + App.log('%c Updating worker', 'IM'); + worker.postMessage({ + fn: 'context', + obj: { + camera: sceneContext.camPos, + target: sceneContext.target || {}, + WorkerManagedValues: App.WorkerManagedValues, + debug: App.debug + } + }); + + if (App.router) { + App.router.updateRoute(sceneContext); + } + } + + function onSuccessLoadPath(instances) { + _.each(instances, function (instance) { + if (instancesIndexed[instance.id]) { + worker.postMessage({fn: 'check', obj: instance.id}); + } else { + + instancesIndexed[instance.id] = instance; + instance.matrix = adaptMatrix(instance.matrix); + + var min = new THREE.Vector3(instance.xMin, instance.yMin, instance.zMin); + var max = new THREE.Vector3(instance.xMax, instance.yMax, instance.zMax); + var box = new THREE.Box3(min, max).applyMatrix4(instance.matrix); + + var cog = box.center(); + + // Allow parts that don't have box to be displayed + var radius = box.size().length() || 0.01; + + + worker.postMessage({ + fn: 'addInstance', + obj: { + instanceRow: instance, + id: instance.id, + box: box, + cog: cog, + radius: radius, + qualities: instance.qualities, + checked: true + } + }); + } + }); + + _this.planNewEval(); + loaderIndicator.hide(); + } + + function getTimestamp() { + return timestamp || Date.now(); + } + + function loadPath(path, callback) { + + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + + App.config.productId + '/instances' + + '?configSpec=' + App.config.productConfigSpec + '&path=' + path + '×tamp=' + getTimestamp(); + + if (App.config.diverge) { + url += '&diverge=true'; + } + + $.ajax({ + url: url, + type: 'GET', + success: function (instances) { + onSuccessLoadPath(instances); + if (callback) { + callback(instances); + } + } + }); + } + + function loadPaths(paths, callback) { + + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + + App.config.productId + '/instances'; + + if (App.config.diverge) { + url += '&diverge=true'; + } + + $.ajax({ + url: url, + type: 'POST', + contentType: 'application/json', + dataType: 'json', + data: JSON.stringify({ + configSpec: App.config.productConfigSpec, + paths: paths + }), + success: function (instances) { + onSuccessLoadPath(instances); + if (callback) { + callback(instances); + } + } + }); + + } + + function unLoadPath(path, callback) { + + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + + App.config.productId + '/instances' + + '?configSpec=' + App.config.productConfigSpec + '&path=' + path + '×tamp=' + getTimestamp(); + + if (App.config.diverge) { + url += '&diverge=true'; + } + + $.ajax({ + url: url, + type: 'GET', + success: function (instances) { + _.each(instances, function (instance) { + worker.postMessage({fn: 'unCheck', obj: instance.id}); + }); + _this.planNewEval(); + callback(); + } + }); + } + + this.loadQueue = async.queue(function (directive, callback) { + if (directive.process === 'load') { + loadPaths(directive.paths, callback); + } else if (directive.process === 'loadOne') { + loadPath(directive.path, callback); + } else { + unLoadPath(directive.path, callback); + } + }, 1); + + this.loadQueue.drain = function () { + App.log('Load Queue %c All paths have been processed', 'IM'); + }; + this.loadQueue.empty = function () { + App.log('Load Queue %c Empty Queue', 'IM'); + }; + this.loadQueue.saturated = function () { + App.log('Load Queue %c Saturated Queue', 'IM'); + }; + + + this.xhrQueue = async.queue(loadProcess, 4); + + this.xhrQueue.drain = function () { + App.log('XHR Queue %c All items have been processed', 'IM'); + }; + this.xhrQueue.empty = function () { + App.log('XHR Queue %c Empty Queue', 'IM'); + }; + this.xhrQueue.saturated = function () { + App.log('XHR Queue %c Saturated Queue', 'IM'); + }; + + this.getLoadedGeometries = function (n) { + return loadedInstances.splice(0, n || 1); + }; + + this.loadComponent = function (component) { + loaderIndicator.show(); + var path = component.getEncodedPath(); + if (path) { + _this.loadQueue.push({'process': 'loadOne', 'path': [component.getEncodedPath()]}); + } + }; + + this.loadComponentsByPaths = function (paths) { + loaderIndicator.show(); + var directive = { + process: 'load', + paths: [] + }; + _.each(paths, function (path) { + directive.paths.push(path); + }); + _this.loadQueue.push(directive); + }; + + this.unLoadComponent = function (component) { + var path = component.getEncodedPath(); + if (path) { + _this.loadQueue.push({'process': 'unload', 'path': component.getEncodedPath()}); + } + }; + + this.unLoadComponentsByPaths = function (pathsToUnload) { + _(pathsToUnload).each(function (path) { + _this.loadQueue.push({'process': 'unload', 'path': path}); + }); + }; + + this.clear = function () { + App.log('%c Clearing Scene', 'IM'); + + _this.xhrQueue.kill(); + _this.loadQueue.kill(); + + _(_(instancesIndexed).pluck('id')).map(App.sceneManager.removeObjectById); + + worker.postMessage({fn: 'clear', obj: null}); + + instancesIndexed = {}; + loadedInstances = []; + }; + + this.planNewEval = function () { + clearTimeout(timer); + if (!evalRunning) { + updateWorker(); + } else { + timer = setTimeout(updateWorker, 500); + } + }; + + this.getInstance = function (instanceId) { + return instancesIndexed[instanceId]; + }; + + // Method called from product visualization iframe + this.loadProduct = function(pathToLoad, callback){ + + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + + App.config.productId + '/instances' + + '?configSpec=' + App.config.productConfigSpec + '&path=' + pathToLoad + '×tamp=' + getTimestamp(); + + $.ajax({ + url: url, + type: 'GET', + success: function (instances) { + onSuccessLoadPath(instances); + callback(); + } + }); + + }; + + // Method called from assembly visualization iframe + this.loadAssembly = function(partRevisionKey, callback){ + + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/' + + partRevisionKey + '/instances'; + + $.ajax({ + url: url, + type: 'GET', + success: function (instances) { + onSuccessLoadPath(instances); + callback(); + } + + }); + + }; + + this.computeGlobalBBox = function(){ + + var box = new THREE.Box3(new THREE.Vector3(0,0,0),new THREE.Vector3(0,0,0)); + + _.each(instancesIndexed, function (instance) { + var min = new THREE.Vector3(instance.xMin, instance.yMin, instance.zMin); + var max = new THREE.Vector3(instance.xMax, instance.yMax, instance.zMax); + var instanceBox = new THREE.Box3(min, max).applyMatrix4(instance.matrix); + box.union(instanceBox); + }); + + return box; + }; + + }; + + return InstancesManager; + }); diff --git a/docdoku-web-front/app/product-structure/js/dmu/LayerManager.js b/docdoku-web-front/app/product-structure/js/dmu/LayerManager.js new file mode 100644 index 0000000000..da487bbfb0 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/dmu/LayerManager.js @@ -0,0 +1,168 @@ +/*global define,App,THREE*/ +define([ + 'collections/layer_collection', + 'models/layer', + 'views/layers-list-view', + 'views/marker_info_modal_view' +], function (LayerCollection, Layer, LayersListView, MarkerInfoModalView) { + 'use strict'; + + var STATE = { FULL: 0, TRANSPARENT: 1}; + + var LayerManager = function () { + this.meshs = []; + this.state = STATE.FULL; + this.markerStateControl = App.$ControlsContainer ? App.$ControlsContainer.find('#markerState i') : []; + this.layersCollection = new LayerCollection(); + this.markerScale = new THREE.Vector3(1, 1, 1); + this.markers = []; + }; + + LayerManager.prototype = { + + renderList: function () { + App.layersListView = new LayersListView({collection: this.layersCollection}); + App.layersListView.render(); + }, + + removeAllMeshesFromMarkers: function () { + var cid; + var currentMesh; + for (cid in this.meshs) { + currentMesh = this.meshs[cid]; + App.sceneManager.scene.remove(currentMesh); + } + + App.sceneManager.reDraw(); + }, + + addMeshFromMarker: function (marker, material) { + // set up the sphere vars + var radius = 50, + segments = 16, + rings = 16; + + var markerMesh = new THREE.Mesh( + new THREE.SphereGeometry( + radius, + segments, + rings + ), + material + ); + + markerMesh.position.set(marker.getX(), marker.getY(), marker.getZ()); + markerMesh.markerId = marker.cid; + + // add the sphere to the scene + App.sceneManager.scene.add(markerMesh); + App.sceneManager.reDraw(); + + // rescale the marker to the others markers scale + markerMesh.scale.copy(this.markerScale); + + //save the mesh for further reuse + this.meshs[marker.cid] = markerMesh; + this.markers[marker.cid] = marker; + + markerMesh.geometry.dynamic = true; + + marker.set('onScene', true); + }, + + onMarkerClicked: function (markerId) { + var marker = this.markers[markerId]; + this.showPopup(marker); + }, + + removeMeshFromMarker: function (marker) { + this._removeMesh(marker.cid); + marker.set('onScene', false); + }, + + _removeMesh: function (cid) { + App.sceneManager.scene.remove(this.meshs[cid]); + App.sceneManager.reDraw(); + delete this.meshs[cid]; + }, + + createLayer: function (name) { + var layer; + + var randomColor = + Math.ceil((Math.random() * (0xF))).toString(16) + + Math.ceil((Math.random() * (0xF))).toString(16) + + Math.ceil((Math.random() * (0xF))).toString(16) + + Math.ceil((Math.random() * (0xF))).toString(16) + + Math.ceil((Math.random() * (0xF))).toString(16) + + Math.ceil((Math.random() * (0xF))).toString(16); + + if (name) { + layer = new Layer({ + name: name, + color: randomColor + }); + } else { + layer = new Layer({ + color: randomColor + }); + } + + this.layersCollection.create(layer, {success: function () { + App.collaborativeController.sendLayersRefresh('create layer'); + }}); + return layer; + }, + + removeLayer: function (layer) { + this.layersCollection.remove(layer); + }, + + removeAllLayers: function () { + this.layersCollection.each(function (layer) { + layer.trigger('remove'); + }); + this.layersCollection.reset({silent: true}); + }, + + rescaleMarkers: function () { + for (var cid in this.meshs) { + var currentMesh = this.meshs[cid]; + currentMesh.scale.copy(this.markerScale); + } + App.sceneManager.reDraw(); + }, + + changeMarkerState: function () { + switch (this.state) { + case STATE.FULL : + this.markerStateControl.removeClass('fa-circle').addClass('fa-circle-o'); + this.changeOpacityOnMarker(0.4); + this.state = STATE.TRANSPARENT; + break; + case STATE.TRANSPARENT : + this.markerStateControl.removeClass('fa-circle-o').addClass('fa-circle'); + this.changeOpacityOnMarker(1); + this.state = STATE.FULL; + break; + } + }, + + changeOpacityOnMarker: function (opacity) { + for (var cid in this.meshs) { + this.meshs[cid].material.opacity = opacity; + } + App.sceneManager.reDraw(); + }, + + + showPopup: function (marker) { + var mimv = new MarkerInfoModalView({model: marker}); + window.document.body.appendChild(mimv.render().el); + mimv.openModal(); + } + + }; + + return LayerManager; +}); diff --git a/docdoku-web-front/app/product-structure/js/dmu/LoaderManager.js b/docdoku-web-front/app/product-structure/js/dmu/LoaderManager.js new file mode 100644 index 0000000000..c8efc31749 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/dmu/LoaderManager.js @@ -0,0 +1,212 @@ +/*global _,define,THREE*/ +define(['views/progress_bar_view'], function (ProgressBarView) { + 'use strict'; + var LoaderManager = function (options) { + + this.ColladaLoader = null; + this.STLLoader = null; + this.BinaryLoader = null; + this.OBJLoader = null; + this.JSONLoader = null; + + _.extend(this, options); + + if (this.progressBar) { + this.listenXHRProgress(); + } + }; + + var defaultMaterial = new THREE.MeshLambertMaterial({color:new THREE.Color(0x62697B)}); + + function setShadows(object){ + object.traverse( function ( o ) { + if ( o instanceof THREE.Mesh) { + o.castShadow = true; + o.receiveShadow = true; + } + }); + } + + function updateMaterial(object){ + object.traverse( function ( o ) { + if ( o instanceof THREE.Mesh && !o.material.name) { + o.material = defaultMaterial; + } + }); + } + + /* + * Parse all meshes geometries in collada object given by COlladaLoader + * */ + function getMeshGeometries(collada, geometries) { + if (collada) { + _.each(collada.children, function (child) { + if (child instanceof THREE.Mesh && child.geometry) { + geometries.push(child.geometry); + } + getMeshGeometries(child, geometries); + }); + } + } + + LoaderManager.prototype = { + + listenXHRProgress: function () { + + // Override xhr open prototype + var pbv = new ProgressBarView().render(); + var xhrCount = 0; + var _xhrOpen = XMLHttpRequest.prototype.open; + + XMLHttpRequest.prototype.open = function () { + + if (arguments[1].indexOf('/api/files/') === 0) { + + var totalAdded = false, + totalLoaded = 0, + xhrLength = 0; + + this.addEventListener('loadstart', function () { + xhrCount++; + }, false); + + this.addEventListener('progress', function (pe) { + + if (xhrLength === 0) { + xhrLength = pe.total; + } + + if (totalAdded === false) { + pbv.addTotal(xhrLength); + totalAdded = true; + } + + pbv.addLoaded(pe.loaded - totalLoaded); + totalLoaded = pe.loaded; + + }, false); + + this.addEventListener('loadend', function () { + xhrCount--; + setTimeout(function () { + pbv.removeXHRData(xhrLength); + }, 20); + }, false); + } + + return _xhrOpen.apply(this, arguments); + }; + }, + + + + parseFile: function (filename, texturePath, callbacks) { + + + var extension = filename.substr(filename.lastIndexOf('.') + 1).toLowerCase(); + + switch (extension) { + + case 'obj' : + + if (this.OBJLoader === null) { + this.OBJLoader = new THREE.OBJLoader(); + } + + this.OBJLoader.load(filename, texturePath+'/attachedfiles/', function ( object ) { + setShadows(object); + updateMaterial(object); + callbacks.success(object); + }); + + + break; + + case 'dae': + + if (this.ColladaLoader === null) { + this.ColladaLoader = new THREE.ColladaLoader(); + } + + this.ColladaLoader.load(filename, function (collada) { + + var geometries = [], combined = new THREE.Geometry(); + getMeshGeometries(collada.scene, geometries); + + // Merge all sub meshes into one + _.each(geometries, function (geometry) { + combined.merge(geometry); + }); + + combined.dynamic = false; + combined.mergeVertices(); + + combined.computeBoundingSphere(); + var object = new THREE.Object3D(); + object.add(new THREE.Mesh(combined)); + setShadows(object); + updateMaterial(object); + callbacks.success(object); + + }); + + break; + + case 'stl': + if (this.STLLoader === null) { + this.STLLoader = new THREE.STLLoader(); + } + + this.STLLoader.load(filename, function(geometry){ + var object = new THREE.Object3D(); + object.add(new THREE.Mesh(geometry)); + setShadows(object); + updateMaterial(object); + callbacks.success(object); + }); + + break; + + // Used for json files only (no referenced buffers) + case 'json': + if (this.JSONLoader === null) { + this.JSONLoader = new THREE.JSONLoader(); + } + + this.JSONLoader.load(filename, function (geometry, materials) { + geometry.dynamic = false; + var object = new THREE.Object3D(); + object.add(new THREE.Mesh(geometry,new THREE.MeshFaceMaterial(materials))); + setShadows(object); + callbacks.success(object); + }, texturePath+'/attachedfiles/'); + + break; + + // Used for binary json files only (referenced buffers - bin file) + case 'js': + + if (this.BinaryLoader === null) { + this.BinaryLoader = new THREE.BinaryLoader(); + } + + this.BinaryLoader.load(filename, function (geometry, materials) { + var _material = new THREE.MeshPhongMaterial({color: materials[0].color, overdraw: true }); + geometry.dynamic = false; + var object = new THREE.Object3D(); + object.add(new THREE.Mesh(geometry,_material)); + setShadows(object); + callbacks.success(object); + }, texturePath); + + break; + + + default: + break; + + } + } + }; + return LoaderManager; +}); diff --git a/docdoku-web-front/app/product-structure/js/dmu/MeasureTool.js b/docdoku-web-front/app/product-structure/js/dmu/MeasureTool.js new file mode 100644 index 0000000000..e6544e0440 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/dmu/MeasureTool.js @@ -0,0 +1,58 @@ +/*global define,App,THREE*/ +define(function(){ + + 'use strict'; + + var MeasureTool = function(callbacks){ + + var material = new THREE.LineBasicMaterial({ + color: 0xf47922 + }); + var geometry = new THREE.Geometry(); + + this.line = new THREE.Line(geometry, material); + this.points = [null, null]; + this.callbacks = callbacks; + }; + + MeasureTool.prototype.onClick = function(point){ + if(this.points[0] === null) { + this.setFirstPoint(point); + }else if(this.points[1]=== null) { + this.setSecondPoint(point); + } + }; + + MeasureTool.prototype.setFirstPoint = function(point){ + this.line.geometry.vertices[0] = point; + this.line.geometry.vertices[1] = point; + this.line.geometry.verticesNeedUpdate = true; + this.points[0] = point.clone(); + this.callbacks.onFirstPoint(); + }; + + MeasureTool.prototype.setVirtualPoint = function(point){ + this.line.geometry.vertices[1] = point; + this.line.geometry.verticesNeedUpdate = true; + }; + + MeasureTool.prototype.setSecondPoint = function(point){ + this.line.geometry.vertices[1] = point; + this.points[1] = point.clone(); + this.line.geometry.verticesNeedUpdate = true; + App.sceneManager.drawMeasure(this.points); + this.callbacks.onSecondPoint(); + + this.clear(); + }; + + MeasureTool.prototype.clear = function(){ + this.points = [null, null]; + }; + + MeasureTool.prototype.hasOnlyFirstPoint = function(){ + return this.points[0] && !this.points[1]; + }; + + return MeasureTool; +}); diff --git a/docdoku-web-front/app/product-structure/js/dmu/SceneManager.js b/docdoku-web-front/app/product-structure/js/dmu/SceneManager.js new file mode 100644 index 0000000000..173318a2bf --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/dmu/SceneManager.js @@ -0,0 +1,1262 @@ +/*global _,define,App,THREE,TWEEN,Stats,requestAnimationFrame,Element*/ +define([ + 'backbone', + 'views/marker_create_modal_view', + 'views/blocker_view', + 'dmu/LayerManager', + 'dmu/MeasureTool', + 'common-objects/utils/date' +], function (Backbone, MarkerCreateModalView, BlockerView, LayerManager, MeasureTool, date) { + 'use strict'; + var SceneManager = function (pOptions) { + var _this = this; + + var browserSupportPointerLock = 'pointerLockElement' in document || + 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document; + var options = pOptions || {}; + _.extend(this, options); + var isMoving = false; + var currentLayer = null; + var explosionValue = 0; + var projector = new THREE.Projector(); + var controlsObject = null; // Switching controls means different camera management + var clock = new THREE.Clock(); + var needsRedraw = false; + var objects = {}; + var selectionBox = null; + var objectMarkedForSelection = null; + var controlChanged = false; + var editedObjectsColoured = false; + var transformControls = null; + + var measureTool = null; + var measures = []; + var measuresPoints = []; + var measureTexts = []; + + var materialEditedObject = new THREE.MeshPhongMaterial({transparent: false, color: new THREE.Color(0x08B000)}); + + this.stateControl = null; + this.STATECONTROL = {PLC: 0, TBC: 1, ORB: 2}; + this.scene = new THREE.Scene(); + this.renderer = null; + this.cameraObject = null; // Represent the eye + this.layerManager = null; + this.editedObjects = []; + this.editedObjectsLeft = []; + + // Stat + this.switches = 0; + this.adds = 0; + this.onScene = 0; + + function render() { + _this.scene.updateMatrixWorld(); + _this.renderer.render(_this.scene, _this.cameraObject); + } + + function initDOM() { + _this.$container = App.$SceneContainer.find('#container'); + _this.$container[0].setAttribute('tabindex', '-1'); + _this.$blocker = new BlockerView().render().$el; + _this.$container.append(_this.$blocker); + } + + function initRenderer() { + _this.renderer = new THREE.WebGLRenderer({preserveDrawingBuffer: true, alpha: true}); + _this.renderer.setSize(_this.$container.width(), _this.$container.height()); + _this.$container.append(_this.renderer.domElement); + } + + function initLayerManager() { + if (!_.isUndefined(App.config.productId)) { + _this.layerManager = new LayerManager(); + _this.layerManager.rescaleMarkers(); + _this.layerManager.renderList(); + } + } + + function initMeasureTool() { + measureTool = new MeasureTool({ + onFirstPoint: function () { + _this.scene.add(measureTool.line); + }, + onSecondPoint: function () { + _this.scene.remove(measureTool.line); + }, + onCancelled: function () { + _this.scene.remove(measureTool.line); + } + }); + } + + function initAxes() { + var axes = new THREE.AxisHelper(100); + axes.position.set(0, 0, 0); + _this.scene.add(axes); + var arrow = new THREE.ArrowHelper(new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 0, 0), 100); + arrow.position.set(200, 0, 400); + _this.scene.add(arrow); + } + + function initStats() { + _this.stats = new Stats(); + var statsElement = _this.stats.domElement; + App.$SceneContainer.append(statsElement); + statsElement.id = 'statsWin'; + statsElement.className = 'statsWinMaximized'; + var statsArrow = document.createElement('i'); + statsArrow.id = 'statsArrow'; + statsArrow.className = 'fa fa-chevron-down'; + statsElement.insertBefore(statsArrow, statsElement.firstChild); + statsArrow.onclick = function () { + var statsClass = _this.stats.domElement.classList; + statsClass.toggle('statsWinMinimized'); + statsClass.toggle('statsWinMaximized'); + }; + } + + function initGrid() { + var size = 500000, step = 2500; + var geometry = new THREE.Geometry(); + var material = new THREE.LineBasicMaterial({vertexColors: THREE.VertexColors}); + var color1 = new THREE.Color(0x444444), color2 = new THREE.Color(0x888888); + for (var i = -size; i <= size; i += step) { + geometry.vertices.push(new THREE.Vector3(-size, 0, i)); + geometry.vertices.push(new THREE.Vector3(size, 0, i)); + geometry.vertices.push(new THREE.Vector3(i, 0, -size)); + geometry.vertices.push(new THREE.Vector3(i, 0, size)); + var color = i === 0 ? color1 : color2; + geometry.colors.push(color, color, color, color); + } + _this.grid = new THREE.Line(geometry, material, THREE.LinePieces); + } + + + function initSelectionBox() { + selectionBox = new THREE.BoxHelper(); + selectionBox.material.depthTest = true; + selectionBox.material.transparent = true; + selectionBox.visible = false; + selectionBox.overdraw = true; + _this.scene.add(selectionBox); + } + + function addLightsToCamera(camera) { + + var dirLight1 = new THREE.DirectionalLight(App.SceneOptions.cameraLight1Color); + dirLight1.position.set(200, 200, 1000).normalize(); + dirLight1.name = 'CameraLight1'; + camera.add(dirLight1); + camera.add(dirLight1.target); + + var dirLight2 = new THREE.DirectionalLight( App.SceneOptions.cameraLight2Color, 1 ); + dirLight2.color.setHSL( 0.1, 1, 0.95 ); + dirLight2.position.set( -1, 1.75, 1 ); + dirLight2.position.multiplyScalar( 50 ); + dirLight2.name='CameraLight2'; + camera.add( dirLight2 ); + + dirLight2.castShadow = true; + + dirLight2.shadowMapWidth = 2048; + dirLight2.shadowMapHeight = 2048; + + var d = 50; + + dirLight2.shadowCameraLeft = -d; + dirLight2.shadowCameraRight = d; + dirLight2.shadowCameraTop = d; + dirLight2.shadowCameraBottom = -d; + + dirLight2.shadowCameraFar = 3500; + dirLight2.shadowBias = -0.0001; + dirLight2.shadowDarkness = 0.35; + + var hemiLight = new THREE.HemisphereLight( App.SceneOptions.ambientLightColor, App.SceneOptions.ambientLightColor, 0.6 ); + hemiLight.color.setHSL( 0.6, 1, 0.6 ); + hemiLight.groundColor.setHSL( 0.095, 1, 0.75 ); + hemiLight.position.set( 0, 0, 500 ); + hemiLight.name='AmbientLight'; + camera.add( hemiLight ); + } + + function handleResize() { + _this.cameraObject.aspect = _this.$container.innerWidth() / _this.$container.innerHeight(); + _this.cameraObject.updateProjectionMatrix(); + _this.renderer.setSize(_this.$container.innerWidth(), _this.$container.innerHeight()); + controlsObject.handleResize(); + _this.reDraw(); + } + + function setSelectionBoxOnMesh(mesh) { + selectionBox.update(mesh); + selectionBox.visible = true; + } + + function unsetSelectionBox() { + selectionBox.visible = false; + } + + /** + * Controls management + */ + function createPointerLockControls() { + _this.pointerLockCamera = new THREE.PerspectiveCamera(45, _this.$container.width() / _this.$container.height(), App.SceneOptions.cameraNear, App.SceneOptions.cameraFar); + _this.pointerLockControls = new THREE.PointerLockControls(_this.pointerLockCamera); + addLightsToCamera(_this.pointerLockCamera); + } + + function createOrbitControls() { + _this.orbitCamera = new THREE.PerspectiveCamera(45, _this.$container.width() / _this.$container.height(), App.SceneOptions.cameraNear, App.SceneOptions.cameraFar); + _this.orbitCamera.position.copy(App.SceneOptions.defaultCameraPosition); + addLightsToCamera(_this.orbitCamera); + _this.orbitControls = new THREE.OrbitControls(_this.orbitCamera, _this.$container[0]); + } + + function createTrackBallControls() { + _this.trackBallCamera = new THREE.PerspectiveCamera(45, _this.$container.width() / _this.$container.height(), App.SceneOptions.cameraNear, App.SceneOptions.cameraFar); + _this.trackBallCamera.position.copy(App.SceneOptions.defaultCameraPosition); + addLightsToCamera(_this.trackBallCamera); + _this.trackBallControls = new THREE.TrackballControls(_this.trackBallCamera, _this.$container[0]); + _this.trackBallControls.keys = [65 /*A*/, 83 /*S*/, 68 /*D*/]; + } + + function initControls() { + createPointerLockControls(); + createTrackBallControls(); + createOrbitControls(); + } + + function createTransformControls() { + transformControls = new THREE.TransformControls(_this.$container[0]); + transformControls.addEventListener('change', _this.reDraw); + } + + function isPointerLock() { + return (document.pointerLockElement === _this.$container[0] || + document.mozPointerLockElement === _this.$container[0] || + document.webkitPointerLockElement === _this.$container[0]); + } + + function pointerLockChange() { + _this.pointerLockControls.enabled = isPointerLock(); + if (_this.pointerLockControls.enabled) { + _this.$blocker.hide(); + _this.pointerLockControls.bindEvents(); + } else { + _this.$blocker.show(); + _this.pointerLockControls.unbindEvents(); + } + } + + /** + * Lock the pointer (when you click on container) and Hide the gray screen + */ + function bindPointerLock() { + if (_this.stateControl !== _this.STATECONTROL.PLC || _this.pointerLockControls.enabled || isPointerLock()) { + return; + } + _this.$blocker.hide(); + + // Ask the browser to lock the pointer + _this.$container[0].requestPointerLock = (_this.$container[0].requestPointerLock) || + (_this.$container[0].mozRequestPointerLock) || + (_this.$container[0].webkitRequestPointerLock); + + _this.$container[0].requestPointerLock(); + } + + function deleteAllControls() { + _this.trackBallControls.removeEventListener('change'); + _this.trackBallControls.unbindEvents(); + _this.scene.remove(_this.trackBallCamera); + _this.trackBallControls.enabled = false; + + _this.pointerLockControls.removeEventListener('change'); + _this.scene.remove(_this.pointerLockControls.getObject()); + _this.pointerLockControls.enabled = false; + _this.pointerLockControls.unbindEvents(); + _this.$blocker.hide(); + if (browserSupportPointerLock) { + // Hook pointer lock state change events + document.removeEventListener('pointerlockchange', pointerLockChange, false); + document.removeEventListener('mozpointerlockchange', pointerLockChange, false); + document.removeEventListener('webkitpointerlockchange', pointerLockChange, false); + _this.$container[0].removeEventListener('click', bindPointerLock, false); + } + + _this.orbitControls.removeEventListener('change'); + _this.scene.remove(_this.orbitControls.getObject()); + _this.orbitControls.enabled = false; + _this.orbitControls.unbindEvents(); + + _this.deleteTransformControls(); + } + + /** + * Scene options control + */ + function onControlChange() { + App.instancesManager.planNewEval(); + controlChanged = true; + _this.reDraw(); + } + + function applyExplosionValue(object) { + if (!object.absoluteCentroid) { + var mesh = object.children[0]; + mesh.geometry.computeBoundingBox(); + mesh.geometry.computeBoundingSphere(); + var instance = App.instancesManager.getInstance(object.uuid); + object.absoluteCentroid = mesh.geometry.boundingBox.center().clone().applyMatrix4(instance.matrix); + } + + // Replace before translating + object.position.x = object.initialPosition.x; + object.position.y = object.initialPosition.y; + object.position.z = object.initialPosition.z; + // Translate instance + if (explosionValue !== 0) { + object.translateX(object.absoluteCentroid.x * explosionValue); + object.translateY(object.absoluteCentroid.y * explosionValue); + object.translateZ(object.absoluteCentroid.z * explosionValue); + } + object.updateMatrix(); + } + + function showGrid() { + if (_this.grid.added) { + return; + } + _this.grid.added = true; + _this.scene.add(_this.grid); + _this.reDraw(); + } + + function removeGrid() { + if (!_this.grid.added) { + return; + } + _this.grid.added = false; + _this.scene.remove(_this.grid); + _this.reDraw(); + } + + function watchSceneOptions() { + if (App.SceneOptions.grid) { + showGrid(); + } else { + removeGrid(); + } + } + + function updateMeasures() { + _.each(measureTexts, function (text) { + text.rotation.copy(_this.cameraObject.rotation); + }); + } + + /** + * Scene mouse events + */ + function onMouseEnter() { + } + + function onMouseLeave() { + isMoving = false; + } + + function onKeyDown() { + } + + function onKeyUp() { + + } + + function onMouseDown() { + isMoving = false; + } + + function onSceneMouseWheel() { + } + + function onSceneMouseMove(e) { + + if (_this.measureState && measureTool.hasOnlyFirstPoint()) { + var vector = new THREE.Vector3( + ((e.clientX - _this.$container.offset().left) / _this.$container[0].offsetWidth ) * 2 - 1, + -((e.clientY - _this.$container.offset().top) / _this.$container[0].offsetHeight ) * 2 + 1, + 0.5 + ); + projector.unprojectVector(vector, _this.cameraObject); + measureTool.setVirtualPoint(vector); + _this.reDraw(); + } + + isMoving = true; + + } + + function getMeshes() { + var meshes = []; + _this.scene.traverse(function (object) { + if (object instanceof THREE.Object3D) { + _.each(object.children, function (child) { + if (child instanceof THREE.Mesh) { + meshes.push(child); + } + }); + } + }); + return meshes; + } + + function onMouseUp(event) { + + event.preventDefault(); + + if (isMoving) { + return false; + } + + // RayCaster to get the clicked mesh + var vector = new THREE.Vector3( + ((event.clientX - _this.$container.offset().left) / _this.$container[0].offsetWidth ) * 2 - 1, + -((event.clientY - _this.$container.offset().top) / _this.$container[0].offsetHeight ) * 2 + 1, + 0.5 + ); + + var cameraPosition = controlsObject.getObject().position; + + var object = _this.cameraObject; + projector.unprojectVector(vector, object); + + var ray = new THREE.Raycaster(cameraPosition, vector.sub(cameraPosition).normalize()); + var intersects = ray.intersectObjects(getMeshes(), false); + + if (intersects.length > 0) { + + var clickedObject = intersects[0]; + var mesh = clickedObject.object; + var object3D = mesh.parent; + if (object3D.partIterationId) { + if (_this.markerCreationMode) { + var mcmv = new MarkerCreateModalView({ + model: currentLayer, + intersectPoint: intersects[0].point + }); + document.body.appendChild(mcmv.render().el); + mcmv.openModal(); + } + else if (_this.measureState) { + measureTool.onClick(clickedObject.point.clone()); + } + else { + objectMarkedForSelection = object3D.uuid; + setSelectionBoxOnMesh(mesh); + Backbone.Events.trigger('object:selected', object3D); + } + } else if (mesh.markerId) { + _this.layerManager.onMarkerClicked(mesh.markerId); + } + } + else { + if (!App.sceneManager.transformControlsEnabled()) { + Backbone.Events.trigger('selection:reset'); + objectMarkedForSelection = null; + unsetSelectionBox(); + } + } + + _this.reDraw(); + } + + function bindMouseAndKeyEvents() { + _this.$container[0].addEventListener('mousedown', onMouseDown, false); + _this.$container[0].addEventListener('mouseup', onMouseUp, false); + _this.$container[0].addEventListener('mouseover', onMouseEnter, false); + _this.$container[0].addEventListener('mouseout', onMouseLeave, false); + _this.$container[0].addEventListener('keydown', onKeyDown, false); + _this.$container[0].addEventListener('keyup', onKeyUp, false); + _this.$container[0].addEventListener('mousemove', onSceneMouseMove, false); + _this.$container[0].addEventListener('mousewheel', onSceneMouseWheel, false); + } + + function removeObject(objectId) { + var object = objects[objectId]; + delete objects[objectId]; + + if (_this.editedObjects.indexOf(objectId) !== -1) { + _this.editedObjectsLeft.push({ + uuid: objectId, + position: object.position.clone(), + rotation: object.rotation.clone(), + scale: object.scale.clone() + }); + _this.editedObjects = _(_this.editedObjects).without(objectId); + if (transformControls !== null && transformControls.enabled && transformControls.getObject() === object) { + _this.deleteTransformControls(); + } + } + + if (!object) { + return; + } + + if (objectMarkedForSelection === objectId) { + Backbone.Events.trigger('selection:reset'); + objectMarkedForSelection = null; + unsetSelectionBox(); + } + _this.scene.remove(object); + + App.log('%c object removed', 'SM'); + + _this.reDraw(); + } + + function saveMaterials(object){ + _.each(object.children,function(o){ + if(o instanceof THREE.Mesh){ + o.initialMaterial = o.material; + saveMaterials(o); + } + }); + } + + function setEditedMaterials(object){ + _.each(object.children,function(o){ + if(o instanceof THREE.Mesh){ + o.material = materialEditedObject; + setEditedMaterials(o); + } + }); + } + + function restoreInitialMaterials(object){ + _.each(object.children,function(o){ + if(o instanceof THREE.Mesh){ + o.material = o.initialMaterial; + restoreInitialMaterials(o); + } + }); + } + + function processLoadedStuff() { + + var loadedStuff = App.instancesManager.getLoadedGeometries(10); + + loadedStuff.forEach(function (stuff) { + + var instance = App.instancesManager.getInstance(stuff.id); + + if (instance) { + + var oldObject = objects[stuff.id]; + var newObject = stuff.object3d; + + if (oldObject) { + _this.switches++; + _this.scene.remove(oldObject); + } else { + _this.adds++; + _this.onScene++; + } + + objects[stuff.id] = newObject; + + newObject.uuid = stuff.id; + newObject.partIterationId = stuff.partIterationId; + newObject.path = stuff.path; + + newObject.applyMatrix(instance.matrix); + + newObject.initialPosition = { + x: newObject.position.x, + y: newObject.position.y, + z: newObject.position.z + }; + newObject.initialRotation = { + x: newObject.rotation.x, + y: newObject.rotation.y, + z: newObject.rotation.z + }; + + newObject.initialScale = { + x: newObject.scale.x, + y: newObject.scale.y, + z: newObject.scale.z + }; + + if (objectMarkedForSelection === newObject.uuid) { + setSelectionBoxOnMesh(newObject.children[0]); + } + + applyExplosionValue(newObject); + + saveMaterials(newObject); + + var potentiallyEdited = _(_this.editedObjectsLeft).findWhere({uuid: newObject.uuid}); + + if (potentiallyEdited) { + + newObject.position.copy(potentiallyEdited.position); + newObject.rotation.copy(potentiallyEdited.rotation); + newObject.scale.copy(potentiallyEdited.scale); + + if (editedObjectsColoured) { + setEditedMaterials(newObject); + } + + _this.editedObjectsLeft = _(_this.editedObjectsLeft).without(potentiallyEdited); + _this.editedObjects.push(potentiallyEdited.uuid); + + } + + _this.scene.add(newObject); + + } + _this.reDraw(); + }); + } + + /** + * Animation + */ + function cameraAnimation(target, duration, position) { + var curTar = controlsObject.target; + var endTarPos = target; + + new TWEEN.Tween(curTar) + .to({x: endTarPos.x, y: endTarPos.y, z: endTarPos.z}, duration) + .interpolation(TWEEN.Interpolation.CatmullRom) + .easing(TWEEN.Easing.Quintic.InOut) + .onUpdate(_this.reDraw) + .start(); + + if (position) { + var endCamPos = position; + var camera = _this.cameraObject; + var curCamPos = camera.position; + + new TWEEN.Tween(curCamPos) + .to({x: endCamPos.x, y: endCamPos.y, z: endCamPos.z}, duration) + .interpolation(TWEEN.Interpolation.CatmullRom) + .easing(TWEEN.Easing.Quintic.InOut) + .onUpdate(_this.reDraw) + .start(); + } + } + + function resetCameraAnimation(target, duration, position, camUp) { + + // Not working with pointer lock controls, pointer lock doesn't have a target + // TODO : We must reset it an other way + if (controlsObject instanceof THREE.PointerLockControls) { + return; + } + + var curTar = controlsObject.target; + var curCamUp = _this.cameraObject.up; + var endTarPos = target; + + + var endCamPos = position; + var camera = _this.cameraObject; + var curCamPos = camera.position; + + var tween1 = new TWEEN.Tween(curTar) + .to({x: endTarPos.x, y: endTarPos.y, z: endTarPos.z}, duration) + .interpolation(TWEEN.Interpolation.CatmullRom) + .easing(TWEEN.Easing.Linear.None) + .onUpdate(_this.reDraw); + + + var tween2 = new TWEEN.Tween(curCamPos) + .to({x: endCamPos.x, y: endCamPos.y, z: endCamPos.z}, duration) + .interpolation(TWEEN.Interpolation.CatmullRom) + .easing(TWEEN.Easing.Linear.None) + .onUpdate(_this.reDraw); + + var tween3 = new TWEEN.Tween(curCamUp) + .to({x: camUp.x, y: camUp.y, z: camUp.z}, duration) + .interpolation(TWEEN.Interpolation.CatmullRom) + .easing(TWEEN.Easing.Linear.None) + .onUpdate(_this.reDraw); + + tween1.start(); + tween2.start(); + tween3.start(); + } + + /** + * Collaborative Mode + */ + this.setEditedObjects = function (editedObjectsInfos) { + + var arrayId = _.pluck(editedObjectsInfos, 'uuid'); + + // cancel transformations for objects which are no longer edited + + var diff = _.difference(_this.editedObjects, arrayId); + + _.each(diff, function (uuid) { + var object = objects[uuid]; + if (_this.editedObjects.lastIndexOf(uuid) !== -1) { + _this.cancelTransformation(object); + } + }); + + // update the list + _this.editedObjects = arrayId; + + // update properties of edited objects + _.each(editedObjectsInfos, function (val) { + var object = objects[val.uuid]; + if (!object) { + _this.editedObjects = _.without(_this.editedObjects, val.uuid); + _this.editedObjectsLeft.push(val); + } else { + object.position.copy(val.position); + object.rotation.copy(val.rotation); + object.scale.copy(val.scale); + + if (editedObjectsColoured) { + setEditedMaterials(object); + } + } + + }); + _this.reDraw(); + }; + this.setEditedObjectsColor = function (colour) { + if (editedObjectsColoured !== colour) { + if (editedObjectsColoured) { + _this.cancelColourEditedObjects(); + } else { + _this.colourEditedObjects(); + } + } + }; + + /** + * Animation loop : + * Update controls, scene objects and animations + * Render at the end + * */ + //Main UI loop + function animate() { + requestAnimationFrame(animate); + // Update controls + controlsObject.update(clock.getDelta()); + + processLoadedStuff(); + + + // Update with SceneOptions + watchSceneOptions(); + + // Update potential animation + TWEEN.update(); + // Sometimes needs a reFrame + if (needsRedraw) { + needsRedraw = false; + if (_this.transformControlsEnabled()) { + transformControls.update(); + } + _this.stats.update(); + updateMeasures(); + render(); + } + if (controlChanged && App.collaborativeController) { + App.collaborativeController.sendCameraInfos(); + controlChanged = false; + } + } + + this.init = function () { + _.bindAll(_this); + initDOM(); + initControls(); + initLayerManager(); + initMeasureTool(); + initAxes(); + initGrid(); + initSelectionBox(); + initRenderer(); + initStats(); + window.addEventListener('resize', handleResize, false); + bindMouseAndKeyEvents(); + + if (App.SceneOptions.transformControls) { + createTransformControls(); + } + + _this.setTrackBallControls(); + + animate(); + }; + + this.reDraw = function () { + needsRedraw = true; + }; + + this.flyTo = function (object) { + + var mesh = object; + + if (object instanceof THREE.Object3D) { + mesh = object.children[0]; + } + + var boundingBox = mesh.geometry.boundingBox; + var cog = boundingBox.center().clone().applyMatrix4(object.matrix); + var size = boundingBox.size(); + var radius = Math.max(size.x, size.y, size.z); + var camera = _this.cameraObject; + var dir = new THREE.Vector3().copy(cog).sub(camera.position).normalize(); + var distance = radius ? radius * 2 : 1000; + distance = distance < App.SceneOptions.cameraNear ? App.SceneOptions.cameraNear + 100 : distance; + var endCamPos = new THREE.Vector3().copy(cog).sub(dir.multiplyScalar(distance)); + cameraAnimation(cog, 2000, endCamPos); + }; + + this.lookAt = function (object) { + var mesh = object.children[0]; + var boundingBox = mesh.geometry.boundingBox; + var cog = boundingBox.center().clone().applyMatrix4(object.matrix); + cameraAnimation(cog, 2000); + }; + + this.resetCameraPlace = function () { + var camPos = App.SceneOptions.defaultCameraPosition; + resetCameraAnimation(new THREE.Vector3(0, 0, 0), 1000, camPos, new THREE.Vector3(0, 1, 0)); + }; + + /** + * Context API + */ + + this.getControlsContext = function () { + return { + target: controlsObject.getTarget(), + camPos: controlsObject.getCamPos(), + camOrientation: _this.cameraObject.up + }; + }; + + this.setControlsContext = function (context) { + _this.cameraObject.position.copy(context.camPos); + controlsObject.target.copy(context.target); + _this.cameraObject.up.copy(context.camOrientation); + _this.reDraw(); + }; + + // If transformControls are enabled return the mode (translation, rotation, scaling), null otherwise + this.getTransformControlsMode = function () { + if (transformControls.enabled) { + return transformControls.getMode(); + } else { + return null; + } + }; + + this.transformControlsEnabled = function () { + var enabled = false; + if (transformControls !== null && transformControls.enabled) { + enabled = true; + } + return enabled; + }; + + this.enableControlsObject = function () { + if (controlsObject) { + controlsObject.enabled = true; + } + }; + + this.disableControlsObject = function () { + if (controlsObject) { + controlsObject.enabled = false; + } + }; + + /** + * Scene option control + */ + this.startMarkerCreationMode = function (layer) { + _this.markerCreationMode = true; + currentLayer = layer; + App.$SceneContainer.addClass('markersCreationMode'); + }; + + this.stopMarkerCreationMode = function () { + _this.markerCreationMode = false; + currentLayer = null; + App.$SceneContainer.removeClass('markersCreationMode'); + }; + + this.requestFullScreen = function () { + _this.renderer.domElement.parentNode.requestFullscreen = + (_this.renderer.domElement.parentNode.requestFullscreen) || + (_this.renderer.domElement.parentNode.mozRequestFullScreen) || + (_this.renderer.domElement.parentNode.webkitRequestFullScreen); + _this.renderer.domElement.parentNode.requestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + }; + + this.explodeScene = function (v) { + App.collaborativeController.sendExplodeValue(v); + // this could be adjusted + explosionValue = v * 0.1; + _(_this.scene.children).each(function (object) { + if (object instanceof THREE.Object3D && object.partIterationId) { + applyExplosionValue(object); + } + }); + _this.reDraw(); + }; + + this.drawMeasure = function (points) { + var material = new THREE.LineBasicMaterial({ + color: 0xf47922 + }); + var geometry = new THREE.Geometry(); + geometry.vertices.push(points[0]); + geometry.vertices.push(points[1]); + var line = new THREE.Line(geometry, material); + + var dist = points[0].distanceTo(points[1]); + var distText = (dist / 1000).toFixed(3) + ' m'; + + var size = dist / 100 + 10; + + var textGeo = new THREE.TextGeometry(distText, {size: size, height: 2, font: 'helvetiker'}); + var textMaterial = new THREE.MeshBasicMaterial({color: 0xf47922}); + var text = new THREE.Mesh(textGeo, textMaterial); + + text.position.copy(new THREE.Vector3().addVectors(points[0], points[1]).multiplyScalar(0.5)); + text.rotation.copy(_this.cameraObject.rotation); + + measures.push(line); + measureTexts.push(text); + _this.scene.add(line); + _this.scene.add(text); + + measuresPoints.push(points); + App.collaborativeController.sendMeasure(); + Backbone.Events.trigger('measure:drawn'); + _this.reDraw(); + }; + + this.clearMeasures = function () { + _.each(measures, function (line) { + _this.scene.remove(line); + }); + _.each(measureTexts, function (text) { + _this.scene.remove(text); + }); + measures = []; + measureTexts = []; + measuresPoints = []; + App.collaborativeController.sendMeasure(); + _this.reDraw(); + }; + + this.setMeasureState = function (state) { + App.$SceneContainer.toggleClass('measureMode', state); + _this.measureState = state; + + if (!state) { + measureTool.callbacks.onCancelled(); + } + }; + + this.takeScreenShot = function () { + + var imageSource = _this.renderer.domElement.toDataURL('image/png'); + var filename = App.config.productId + '-' + date.formatTimestamp(App.config.i18n._DATE_SHORT_FORMAT, Date.now()); + + var save = document.createElement('a'); + save.href = imageSource; + save.download = filename; + var event = document.createEvent('MouseEvents'); + event.initMouseEvent( + 'click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null + ); + save.dispatchEvent(event); + + }; + + this.setCameraNear = function (n) { + _this.cameraObject.near = n; + _this.cameraObject.updateProjectionMatrix(); + _this.reDraw(); + + }; + + this.colourEditedObjects = function () { + editedObjectsColoured = true; + _.each(_this.editedObjects, function (uuid) { + setEditedMaterials(objects[uuid]); + }); + App.collaborativeController.sendColourEditedObjects(); + _this.reDraw(); + }; + + this.cancelColourEditedObjects = function () { + + editedObjectsColoured = false; + + _.each(_this.editedObjects, function (uuid) { + restoreInitialMaterials(objects[uuid]); + }); + + App.collaborativeController.sendColourEditedObjects(); + + _this.reDraw(); + }; + + this.setPointerLockControls = function () { + if (_this.stateControl === _this.STATECONTROL.PLC || !browserSupportPointerLock) { + return; + } + + _this.stateControl = _this.STATECONTROL.PLC; + deleteAllControls(); + _this.$blocker.show(); + + _this.cameraObject = _this.pointerLockCamera; + controlsObject = _this.pointerLockControls; + + _this.pointerLockControls.addEventListener('change', onControlChange); + + // Hook pointer lock state change events + document.addEventListener('pointerlockchange', pointerLockChange, false); + document.addEventListener('mozpointerlockchange', pointerLockChange, false); + document.addEventListener('webkitpointerlockchange', pointerLockChange, false); + _this.$container[0].addEventListener('click', bindPointerLock, false); + + _this.scene.add(_this.pointerLockControls.getObject()); + _this.reDraw(); + }; + + this.setTrackBallControls = function () { + if (_this.stateControl === _this.STATECONTROL.TBC && controlsObject.enabled) { + return; + } + + _this.stateControl = _this.STATECONTROL.TBC; + deleteAllControls(); + + controlsObject = _this.trackBallControls; + _this.cameraObject = _this.trackBallCamera; + + _this.trackBallControls.enabled = true; + _this.trackBallControls.addEventListener('change', onControlChange); + _this.trackBallControls.bindEvents(); + _this.scene.add(_this.trackBallCamera); + + handleResize(); + _this.reDraw(); + }; + + this.setOrbitControls = function () { + + if (_this.stateControl === _this.STATECONTROL.ORB && controlsObject.enabled) { + return; + } + + _this.stateControl = _this.STATECONTROL.ORB; + deleteAllControls(); + + controlsObject = _this.orbitControls; + controlsObject.enabled = true; + + _this.cameraObject = _this.orbitCamera; + + controlsObject.addEventListener('change', onControlChange); + controlsObject.bindEvents(); + _this.scene.add(_this.orbitCamera); + + handleResize(); + _this.reDraw(); + }; + + this.setTransformControls = function (mesh, mode) { + transformControls.setCamera(_this.cameraObject); + controlsObject.enabled = false; + transformControls.enabled = true; + transformControls.attach(mesh); + if (!_.contains(_this.editedObjects, mesh.uuid)) { + _this.editedObjects.push(mesh.uuid); + if (editedObjectsColoured) { + _this.colourEditedObjects(); + } + App.log('%c Mesh added : \n\t' + this.editedObjects, 'SM'); + App.collaborativeController.sendEditedObjects(); + } + transformControls.bindEvents(); + if (typeof(mode) !== 'undefined') { + switch (mode) { + case 'translate' : + transformControls.setMode('translate'); + break; + case 'rotate': + transformControls.setMode('rotate'); + break; + case 'scale': + transformControls.setMode('scale'); + } + } + App.appView.transformControlMode(); + _this.scene.add(transformControls); + _this.reDraw(); + }; + + this.leaveTransformMode = function () { + if (_this.stateControl === _this.STATECONTROL.TBC) { + _this.setTrackBallControls(); + } else if (_this.stateControl === _this.STATECONTROL.ORB) { + _this.setOrbitControls(); + } + }; + + this.deleteTransformControls = function () { + if (transformControls !== null && transformControls.enabled) { + _this.scene.remove(transformControls); + transformControls.unbindEvents(); + transformControls.detach(); + transformControls.enabled = false; + App.appView.leaveTransformControlMode(); + _this.reDraw(); + } + }; + + this.cancelTransformation = function (object) { + + new TWEEN.Tween(object.position) + .to({x: object.initialPosition.x, y: object.initialPosition.y, z: object.initialPosition.z}, 2000) + .interpolation(TWEEN.Interpolation.CatmullRom) + .easing(TWEEN.Easing.Quintic.InOut) + .onUpdate(function () { + _this.reDraw(); + transformControls.update(); + }) + .start(); + new TWEEN.Tween(object.rotation) + .to({x: object.initialRotation.x, y: object.initialRotation.y, z: object.initialRotation.z}, 2000) + .interpolation(TWEEN.Interpolation.CatmullRom) + .easing(TWEEN.Easing.Quintic.InOut) + .onUpdate(function () { + _this.reDraw(); + transformControls.update(); + }) + .start(); + new TWEEN.Tween(object.scale) + .to({x: object.initialScale.x, y: object.initialScale.y, z: object.initialScale.z}, 2000) + .interpolation(TWEEN.Interpolation.CatmullRom) + .easing(TWEEN.Easing.Quintic.InOut) + .onUpdate(function () { + _this.reDraw(); + transformControls.update(); + }) + .start(); + _this.editedObjects = _.without(_this.editedObjects, object.uuid); + restoreInitialMaterials(object); + App.log('%c Mesh removed', 'SM'); + App.collaborativeController.sendEditedObjects(); + + _this.reDraw(); + _this.leaveTransformMode(); + }; + + /** + * Scene mouse events + */ + + this.setPathForIFrame = function (pathForIFrame) { + _this.pathForIFrameLink = pathForIFrame; + }; + + this.clear = function () { + + }; + + this.removeObjectById = function (objectId) { + removeObject(objectId); + }; + + this.getObject = function (uuid) { + return objects[uuid]; + }; + + this.getEditedObjectsColoured = function () { + return editedObjectsColoured; + }; + + this.updateAmbientLight = function (color) { + _this.cameraObject.getObjectByName('AmbientLight').color.set(color); + _this.reDraw(); + }; + + this.updateCameraLight1 = function (color) { + _this.cameraObject.getObjectByName('CameraLight1').color.set(color); + _this.reDraw(); + }; + + this.updateCameraLight2 = function (color) { + _this.cameraObject.getObjectByName('CameraLight2').color.set(color); + _this.reDraw(); + }; + + this.createLayerMaterial = function (color) { + return new THREE.MeshLambertMaterial({ + color: color, + transparent: true + }); + }; + + this.onContainerShown = function () { + handleResize(); + }; + + this.getMeasures = function () { + return measuresPoints; + }; + + this.setMeasures = function (measuresPoints) { + _this.clearMeasures(); + _.each(measuresPoints, function (points) { + var point0 = new THREE.Vector3(points[0].x, points[0].y, points[0].z); + var point1 = new THREE.Vector3(points[1].x, points[1].y, points[1].z); + + _this.drawMeasure([point0, point1]); + }); + }; + + this.bestFitView = function(){ + + var box = App.instancesManager.computeGlobalBBox(); + var size = box.size(); + + if(size.length()){ + var cog = box.center().clone(); + var radius = Math.max(size.x, size.y, size.z); + var camera = _this.cameraObject; + var dir = new THREE.Vector3().copy(cog).sub(camera.position).normalize(); + var distance = radius ? radius / 2 : 1000; + distance = distance < App.SceneOptions.cameraNear ? App.SceneOptions.cameraNear + 100 : distance; + var endCamPos = new THREE.Vector3().copy(cog).sub(dir.multiplyScalar(distance)); + cameraAnimation(cog, 2000, endCamPos); + _this.cameraObject.far = radius * 2; + + _this.cameraObject.updateProjectionMatrix(); + } + + }; + + }; + + return SceneManager; +}); diff --git a/docdoku-web-front/app/product-structure/js/dmu/collaborativeController.js b/docdoku-web-front/app/product-structure/js/dmu/collaborativeController.js new file mode 100644 index 0000000000..822c9cbed8 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/dmu/collaborativeController.js @@ -0,0 +1,282 @@ +/*global _,define,App*/ +define([ + 'common-objects/websocket/channelListener', + 'common-objects/websocket/channelMessagesType' +], function (ChannelListener, ChannelMessagesType) { + 'use strict'; + function CollaborativeController() { + var collaborativeListener; + var _this = this; + + function onCreateMessage(message) { + App.collaborativeView.roomCreated(message.key, message.remoteUser); + } + + function onJoinMessage(message) { + App.collaborativeView.setRoomKey(message.key); + App.appView.setConfigSpec(message.messageBroadcast.configSpec); + App.appView.updateTreeView(message.messageBroadcast.smartPath); + App.sceneManager.setControlsContext(message.messageBroadcast.cameraInfos); + App.sceneManager.setEditedObjects(message.messageBroadcast.editedObjects); + App.sceneManager.setEditedObjectsColor(message.messageBroadcast.colourEditedObjects); + App.$ControlsContainer.find('#slider-explode').val(message.messageBroadcast.explode); + App.sceneManager.explodeScene(message.messageBroadcast.explode); + } + + function onContextMessage(message) { + if (App.collaborativeView.roomKey === message.key) { + if (message.messageBroadcast.master === '') { + App.collaborativeView.setLastMaster(message.messageBroadcast.lastMaster); + } else { + App.collaborativeView.setMaster(message.messageBroadcast.master); + } + App.collaborativeView.setUsers(message.messageBroadcast.users); + App.collaborativeView.setPendingUsers(message.messageBroadcast.pendingUsers); + } + } + + function onKick() { + App.appView.leaveSpectatorView(); + App.sceneManager.enableControlsObject(); + App.collaborativeView.reset(); + } + + function onKickUserMessage() { + App.appView.crashWithMessage(App.config.i18n.COLLABORATIVE_KICKED); + onKick(); + } + + function onKickNotInvitedMessage() { + App.appView.crashWithMessage(App.config.i18n.COLLABORATIVE_NOT_INVITED); + onKick(); + } + + function onCollaborativeMessage(message) { + if (App.collaborativeView.roomKey === message.key) { + if (message.messageBroadcast.cameraInfos) { + App.sceneManager.setControlsContext(message.messageBroadcast.cameraInfos); + } else if (message.messageBroadcast.smartPath) { + App.appView.updateTreeView(message.messageBroadcast.smartPath); + } else if (message.messageBroadcast.editedObjects) { + App.sceneManager.setEditedObjects(message.messageBroadcast.editedObjects); + } else if (message.messageBroadcast.configSpec) { + App.appView.setConfigSpec(message.messageBroadcast.configSpec); + App.baselineSelectView.refresh(); + } else if (message.messageBroadcast.colourEditedObjects !== undefined) { + App.sceneManager.setEditedObjectsColor(message.messageBroadcast.colourEditedObjects); + } else if (message.messageBroadcast.explode) { + App.$ControlsContainer.find('#slider-explode').val(message.messageBroadcast.explode); + App.sceneManager.explodeScene(message.messageBroadcast.explode); + } else if (message.messageBroadcast.clipping) { + _this.setClippingValue(message.messageBroadcast.clipping); + } else if (message.messageBroadcast.layers) { + if (message.messageBroadcast.layers === 'layer:remove') { + App.sceneManager.layerManager.removeAllMeshesFromMarkers(); + } + App.layersListView.refreshLayers(); + } else if (message.messageBroadcast.markers) { + if (message.messageBroadcast.markers === 'remove marker') { + App.sceneManager.layerManager.removeAllMeshesFromMarkers(); + } + App.layersListView.refreshLayers(); + } else if (message.messageBroadcast.measures) { + App.sceneManager.setMeasures(message.messageBroadcast.measures); + } + } + } + + collaborativeListener = new ChannelListener({ + + messagePattern: /^COLLABORATIVE_.+/, + + isApplicable: function (messageType) { + return messageType.match(this.messagePattern) !== null; + }, + + onMessage: function (message) { + switch (message.type) { + case ChannelMessagesType.COLLABORATIVE_CREATE: + onCreateMessage(message); + break; + case ChannelMessagesType.COLLABORATIVE_JOIN: + onJoinMessage(message); + break; + case ChannelMessagesType.COLLABORATIVE_CONTEXT: + onContextMessage(message); + break; + case ChannelMessagesType.COLLABORATIVE_KICK_USER: + onKickUserMessage(message); + break; + case ChannelMessagesType.COLLABORATIVE_KICK_NOT_INVITED: + onKickNotInvitedMessage(message); + break; + case ChannelMessagesType.COLLABORATIVE_COMMANDS: + onCollaborativeMessage(message); + break; + default: + break; + } + }, + onStatusChanged: function (/*status*/) { + // only for compliance + } + }); + App.mainChannel.addChannelListener(collaborativeListener); + + this.setClippingValue = function (value) { + App.$ControlsContainer.find('slider-clipping').val(value); + var max = App.SceneOptions.cameraFar * 3 / 4; + var percentage = value * Math.log(max) / 100; // cross product to set a value to pass to the exponential function + App.sceneManager.setCameraNear(Math.exp(percentage)); + }; + this.sendJoinRequest = function (key) { + App.mainChannel.sendJSON({ + type: ChannelMessagesType.COLLABORATIVE_JOIN, + key: key, + remoteUser: '' + }); + }; + this.sendSmartPath = function (value) { + + if (App.collaborativeView && App.collaborativeView.isMaster) { + App.mainChannel.sendJSON({ + type: ChannelMessagesType.COLLABORATIVE_COMMANDS, + key: App.collaborativeView.roomKey, + remoteUser: '', + messageBroadcast: { + smartPath: value + } + }); + } + }; + this.sendConfigSpec = function (value) { + value = (value) ? value : App.config.productConfigSpec; + if (App.collaborativeView && App.collaborativeView.isMaster) { + App.mainChannel.sendJSON({ + type: ChannelMessagesType.COLLABORATIVE_COMMANDS, + key: App.collaborativeView.roomKey, + remoteUser: '', + messageBroadcast: { + configSpec: value + } + }); + } + }; + this.sendLayersRefresh = function (subject) { + if (App.collaborativeView && App.collaborativeView.isMaster) { + var message = { + type: ChannelMessagesType.COLLABORATIVE_COMMANDS, + key: App.collaborativeView.roomKey, + messageBroadcast: { + layers: subject + }, + remoteUser: '' + }; + App.mainChannel.sendJSON(message); + } + }; + this.sendMarkersRefresh = function (subject) { + if (App.collaborativeView && App.collaborativeView.isMaster) { + var message = { + type: ChannelMessagesType.COLLABORATIVE_COMMANDS, + key: App.collaborativeView.roomKey, + messageBroadcast: { + markers: subject + }, + remoteUser: '' + }; + App.mainChannel.sendJSON(message); + } + }; + this.sendCameraInfos = function () { + if (App.collaborativeView && App.collaborativeView.isMaster) { + var message = { + type: ChannelMessagesType.COLLABORATIVE_COMMANDS, + key: App.collaborativeView.roomKey, + messageBroadcast: { + cameraInfos: App.sceneManager.getControlsContext() + }, + remoteUser: '' + }; + App.mainChannel.sendJSON(message); + } + }; + this.sendClippingValue = function (value) { + if (App.collaborativeView && App.collaborativeView.isMaster) { + var message = { + type: ChannelMessagesType.COLLABORATIVE_COMMANDS, + key: App.collaborativeView.roomKey, + messageBroadcast: { + clipping: value + }, + remoteUser: '' + }; + App.mainChannel.sendJSON(message); + } + }; + this.sendEditedObjects = function () { + if (App.collaborativeView && App.collaborativeView.isMaster) { + var editedObjects = []; + _.each(App.sceneManager.editedObjects, function (uuid) { + var object = App.sceneManager.getObject(uuid); + editedObjects.push({ + uuid: uuid, + position: object.position, + scale: object.scale, + rotation: object.rotation + }); + }); + var message = { + type: ChannelMessagesType.COLLABORATIVE_COMMANDS, + key: App.collaborativeView.roomKey, + messageBroadcast: { + editedObjects: editedObjects + }, + remoteUser: '' + }; + App.mainChannel.sendJSON(message); + } + }; + this.sendColourEditedObjects = function () { + if (App.collaborativeView && App.collaborativeView.isMaster) { + var message = { + type: ChannelMessagesType.COLLABORATIVE_COMMANDS, + key: App.collaborativeView.roomKey, + messageBroadcast: { + colourEditedObjects: App.sceneManager.getEditedObjectsColoured() + }, + remoteUser: '' + }; + App.mainChannel.sendJSON(message); + } + }; + this.sendExplodeValue = function (value) { + if (App.collaborativeView && App.collaborativeView.isMaster) { + var message = { + type: ChannelMessagesType.COLLABORATIVE_COMMANDS, + key: App.collaborativeView.roomKey, + messageBroadcast: { + explode: value + }, + remoteUser: '' + }; + App.mainChannel.sendJSON(message); + } + }; + this.sendMeasure = function () { + if (App.collaborativeView && App.collaborativeView.isMaster) { + var message = { + type: ChannelMessagesType.COLLABORATIVE_COMMANDS, + key: App.collaborativeView.roomKey, + messageBroadcast: { + measures: App.sceneManager.getMeasures() + }, + remoteUser: '' + }; + App.mainChannel.sendJSON(message); + } + }; + } + + return CollaborativeController; +}); diff --git a/docdoku-web-front/app/product-structure/js/frameRouter.js b/docdoku-web-front/app/product-structure/js/frameRouter.js new file mode 100644 index 0000000000..0862837729 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/frameRouter.js @@ -0,0 +1,72 @@ +/*global define,App,$*/ +define([ + 'backbone', + 'common-objects/common/singleton_decorator' +], function (Backbone, singletonDecorator) { + 'use strict'; + var Router = Backbone.Router.extend({ + + routes: { + 'product/:workspaceId/:productId/:cameraX/:cameraY/:cameraZ/:pathToLoad(/:configSpec)': 'loadProduct', + 'assembly/:workspaceId/:partRevisionKey/:cameraX/:cameraY/:cameraZ': 'loadAssembly' + }, + + loadProduct: function (workspaceId, productId, cameraX, cameraY, cameraZ, pathToLoad, configSpec) { + + if (pathToLoad && productId) { + + App.config.workspaceId = workspaceId; + App.config.productId = productId; + App.config.productConfigSpec = configSpec || 'wip'; + + App.config.defaultCameraPosition = { + x: parseFloat(cameraX || 0), + y: parseFloat(cameraY || 0), + z: parseFloat(cameraZ || 0) + }; + + App.instancesManager.clear(); + App.sceneManager.clear(); + App.sceneManager.resetCameraPlace(); + $('#container').css({opacity:0}); + App.instancesManager.loadProduct(pathToLoad,function(){ + $('#content').remove(); + $('#container').css({opacity:1}); + setTimeout(App.instancesManager.planNewEval,100); + }); + + } + }, + + loadAssembly:function(workspaceId, partRevisionKey, cameraX, cameraY, cameraZ){ + + if (partRevisionKey) { + + App.config.workspaceId = workspaceId; + + App.config.defaultCameraPosition = { + x: parseFloat(cameraX || 0), + y: parseFloat(cameraY || 0), + z: parseFloat(cameraZ || 0) + }; + + App.instancesManager.clear(); + App.sceneManager.clear(); + App.sceneManager.resetCameraPlace(); + $('#container').css({opacity:0}); + App.instancesManager.loadAssembly(partRevisionKey,function(){ + $('#content').remove(); + $('#container').css({opacity:1}); + setTimeout(App.instancesManager.planNewEval,100); + }); + + } + }, + + updateRoute: function () { + /* nothing to do, but needs to be present*/ + } + }); + Router = singletonDecorator(Router); + return Router; +}); diff --git a/docdoku-web-front/app/product-structure/js/models/component_module.js b/docdoku-web-front/app/product-structure/js/models/component_module.js new file mode 100644 index 0000000000..bdf2378e84 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/models/component_module.js @@ -0,0 +1,344 @@ +/*global _,define,App*/ +define(['backbone', 'common-objects/utils/date'], + function (Backbone, date) { + 'use strict'; + var ComponentModule = {}; + + ComponentModule.Model = Backbone.Model.extend({ + + initialize: function () { + if(this.isAssembly()||App.config.linkType){ + this.children = new ComponentModule.Collection([], { parentUsageLinkId: this.getPartUsageLinkId(), path: this.getPath() }); + } + _.bindAll(this); + }, + + defaults: { + name: null, + number: null, + version: null, + description: null, + author: null, + authorLogin: null, + iteration: null, + standardPart: false, + partUsageLinkId: null, + amount: 0, + components: [], + assembly: false, + mail: null, + checkOutDate: null, + checkOutUser: null + }, + + getId: function () { + return this.getNumber() + '-' + this.getVersion() + '-' + this.getIteration(); + }, + + isCheckout: function () { + return !_.isNull(this.attributes.checkOutDate); + }, + + getCheckoutUser: function () { + return this.get('checkOutUser'); + }, + + getCheckoutDate: function () { + return this.get('checkOutDate'); + }, + + getFormattedCheckoutDate: function () { + if (this.isCheckout()) { + return date.formatTimestamp( + App.config.i18n._DATE_FORMAT, + this.getCheckoutDate() + ); + } else { + return false; + } + }, + + isCheckoutByConnectedUser: function () { + return this.isCheckout() ? this.getCheckoutUser().login === App.config.login : false; + }, + + hasUnreadModificationNotifications: function () { + // TODO: get the modification notification collection + return _.select(this.get('notifications') || [], function(notif) { + return !notif.acknowledged; + }).length; + }, + + isAssembly: function () { + return this.get('assembly'); + }, + + isLeaf: function () { + return !this.isAssembly(); + }, + + getPartUsageLinkId: function () { + return this.get('partUsageLinkId'); + }, + + getPath: function () { + return this.get('path'); + }, + + getAmount: function () { + return this.get('amount'); + }, + + getUnit: function () { + return this.get('unit'); + }, + + getName: function () { + return this.get('name'); + }, + + getNumber: function () { + return this.get('number'); + }, + + getVersion: function () { + return this.get('version'); + }, + + getDescription: function () { + return this.get('description'); + }, + + getAuthor: function () { + return this.get('author'); + }, + + getAuthorLogin: function () { + return this.get('authorLogin'); + }, + + getIteration: function () { + return this.get('iteration') !== 0 ? this.get('iteration') : null; + }, + + isLastIteration: function (iterationNumber) { + // return TRUE if the iteration is the very last (check or uncheck) + return this.get('lastIterationNumber') === iterationNumber; + }, + + isStandardPart: function () { + return this.get('standardPart'); + }, + + isForbidden: function () { + return this.get('accessDeny'); + }, + + isReleased : function(){ + return this.get('released'); + }, + + isObsolete : function(){ + return this.get('obsolete'); + }, + + isSubstitute : function(){ + return this.get('substitute'); + }, + + hasSubstitutes : function(){ + var substitutesIds = this.getSubstituteIds(); + return substitutesIds && substitutesIds.length; + }, + + getSubstituteIds : function(){ + return this.get('substituteIds'); + }, + + isSubstituteOf:function(otherComponent){ + if(!this.hasSubstitutes()){ + return false; + } + if(!this.isOnSameBasePath(otherComponent)){ + return false; + } + return this.getSubstituteIds().indexOf(otherComponent.getPartUsageLinkId()) !== -1; + }, + + getBasePath:function(){ + var path = this.getPath(); + var lastDash = path.lastIndexOf('-'); + if(lastDash !== -1){ + return path.substr(0,lastDash); + }else{ + return path; + } + }, + + isOnSameBasePath:function(otherComponent){ + return this.getBasePath() === otherComponent.getBasePath(); + }, + + hasPathData : function(){ + return this.get('hasPathData'); + }, + + isOptional : function(){ + return this.get('optional'); + }, + isVirtual : function(){ + return this.get('virtual'); + }, + getPartUsageLinkReferenceDescription:function(){ + return this.get('partUsageLinkReferenceDescription'); + }, + + getInstancesUrl: function () { + return this.getProductUrl() + '/instances?configSpec=' + App.config.productConfigSpec + '&path=' + this.getEncodedPath(); + }, + + getEncodedPath : function(){ + return this.getPath(); + }, + + getProductUrl: function() { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + App.config.productId; + }, + getUrlForBom: function () { + + var url; + + if (this.isAssembly()) { + url = this.getProductUrl() + + '/bom?configSpec=' + App.config.productConfigSpec + + '&path=' + this.getEncodedPath(); + + if(App.config.diverge) { + url += '&diverge=true'; + } + + } else { + url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/' + this.getNumber() + '-' + this.getVersion(); + } + + return url; + }, + + getRootUrlForBom: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/parts/' + this.getNumber() + '-' + this.getVersion(); + }, + + cascadeCheckin: function(callback, iterationNote) { + var that = this; + return $.ajax({ + context: this, + type: 'PUT', + data:JSON.stringify({iterationNote:iterationNote}), + contentType: 'application/json; charset=utf-8', + url: that.getProductUrl() + '/cascade-checkin?path='+this.getEncodedPath() + '&configSpec='+App.config.productConfigSpec, + success:callback + }); + }, + + cascadeCheckout: function(callback) { + var that = this; + return $.ajax({ + context: this, + type: 'PUT', + url: that.getProductUrl() + '/cascade-checkout?path='+this.getEncodedPath() + '&configSpec='+App.config.productConfigSpec, + success:callback + }); + }, + + cascadeUndoCheckout: function(callback) { + var that = this; + return $.ajax({ + context: this, + type: 'PUT', + url: that.getProductUrl() + '/cascade-undocheckout?path='+this.getEncodedPath() + '&configSpec='+App.config.productConfigSpec, + success:callback + }); + }, + + checkin: function() { + var that = this; + return $.ajax({ + context: this, + type: 'PUT', + url: that.getRootUrlForBom() + '/checkin' + }); + }, + + checkout: function() { + var that = this; + return $.ajax({ + context: this, + type: 'PUT', + url: that.getRootUrlForBom() + '/checkout' + }); + }, + + undocheckout: function() { + var that = this; + return $.ajax({ + context: this, + type: 'PUT', + url: that.getRootUrlForBom() + '/undocheckout' + }); + } + + }); + + ComponentModule.Collection = Backbone.Collection.extend({ + model: ComponentModule.Model, + + initialize: function (models, options) { + this.isRoot = _.isUndefined(options.isRoot) ? false : options.isRoot; + + if (!this.isRoot) { + this.parentUsageLinkId = options.parentUsageLinkId; + this.path = options.path; + } else if(!App.config.linkType){ + this.parentUsageLinkId = '-1'; + this.path = '-1'; + } + + }, + + url: function () { + var path = this.path; + + var url = this.urlBase() + '/filter?configSpec=' + App.config.productConfigSpec + '&depth=1'; + + if(path){ + url+= '&path=' + path; + } + + if(App.config.linkType){ + url += '&linkType='+App.config.linkType; + } + + if(App.config.diverge) { + url += '&diverge=true'; + } + + return url; + }, + + urlBase: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + App.config.productId; + }, + + parse: function (response) { + if (response) { + if (this.isRoot) { + response.path = response.partUsageLinkId; + return [response]; + } else { + return response.components; + } + } + } + }); + return ComponentModule; + }); diff --git a/docdoku-web-front/app/product-structure/js/models/layer.js b/docdoku-web-front/app/product-structure/js/models/layer.js new file mode 100644 index 0000000000..4598d39144 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/models/layer.js @@ -0,0 +1,151 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'models/marker', + 'collections/marker_collection' +], function (Backbone, Marker, MarkerCollection) { + 'use strict'; + var Layer = Backbone.Model.extend({ + + defaults: { + name: App.config.i18n.NEW_LAYER, + shown: true, + editingName: false, + editingMarkers: false + }, + + toJSON: function () { + return _.pick(this.attributes, 'id', 'name', 'color'); + }, + + setEditingName: function (editingName) { + this.set('editingName', editingName); + }, + + getColor: function () { + return this.get('color'); + }, + setColor: function (color) { + this.material.color.set(parseInt('0x' + color, 16)); + this.material.needsUpdate = true; + return this.set('color', color); + }, + + toggleEditingMarkers: function () { + if (this.get('editingMarkers')) { + this.collection.setAllEditingMarkers(false); + } else { + this.collection.setAllEditingMarkers(false); + this.setEditingMarkers(true); + } + }, + + setEditingMarkers: function (editingMarkers) { + this.set('editingMarkers', editingMarkers); + if (editingMarkers) { + App.sceneManager.startMarkerCreationMode(this); + } else { + App.sceneManager.stopMarkerCreationMode(this); + } + }, + + toggleShow: function () { + var shown = !this.get('shown'); + this.setShown(shown); + }, + + setShown: function (shown) { + this.set('shown', shown); + if (shown) { + this._addAllMarkersToScene(); + } else { + this._removeAllMarkersFromScene(); + } + }, + + initialize: function () { + this.material = App.sceneManager.createLayerMaterial(parseInt('0x' + this.get('color'), 16)); + this.markers = new MarkerCollection(); + this.markers.on('add', this._addMarkerToScene, this); + this.markers.on('remove', this._removeMarkerFromScene, this); + this.markers.on('reset', this._onResetMarkers, this); + this.on('remove', this._removeAllMarkersFromScene, this); + this.on('change:id', this._setMarkersUrl); + if (!this.isNew()) { + this._setMarkersUrl(); + this.markers.fetch({reset: true}); + } + + _.bindAll(this); + }, + + _setMarkersUrl: function () { + this.markers.urlLayer = this.url(); + }, + + getMarkers: function () { + return this.markers; + }, + + countMarkers: function () { + return this.getMarkers().length; + }, + + getMaterial: function () { + return this.material; + }, + + createMarker: function (title, description, x, y, z) { + var marker = new Marker({ + title: title, + description: description, + x: x, + y: y, + z: z + }); + this.getMarkers().create(marker, {success: function () { + App.collaborativeController.sendMarkersRefresh('create marker'); + }}); + + return marker; + }, + + removeMarker: function (marker) { + this.getMarkers().remove(marker); + }, + + removeAllMarkers: function () { + this._removeAllMarkersFromScene(); + this.getMarkers().reset(); + }, + + _addAllMarkersToScene: function () { + _.each(this.getMarkers().notOnScene(), this._addMarkerToScene, this); + }, + + _addMarkerToScene: function (marker) { + App.sceneManager.layerManager.addMeshFromMarker(marker, this.material); + }, + + _removeAllMarkersFromScene: function () { + _.each(this.getMarkers().onScene(), this._removeMarkerFromScene, this); + }, + + _removeMarkerFromScene: function (marker) { + App.sceneManager.layerManager.removeMeshFromMarker(marker); + }, + + getHexaColor: function () { + return '#' + this.get('color'); + }, + + _onResetMarkers: function () { + this._removeAllMarkersFromScene(); + this.getMarkers().each(this._addMarkerToScene, this); + } + + }); + + return Layer; + +}); diff --git a/docdoku-web-front/app/product-structure/js/models/marker.js b/docdoku-web-front/app/product-structure/js/models/marker.js new file mode 100644 index 0000000000..abdb1b3869 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/models/marker.js @@ -0,0 +1,36 @@ +/*global define,_*/ +define(['backbone'], function (Backbone) { + + 'use strict'; + + var Marker = Backbone.Model.extend({ + + toJSON: function () { + return _.pick(this.attributes, 'id', 'title', 'description', 'x', 'y', 'z'); + }, + + getX: function () { + return this.get('x'); + }, + + getY: function () { + return this.get('y'); + }, + + getZ: function () { + return this.get('z'); + }, + + getTitle: function () { + return this.get('title'); + }, + + getDescription: function () { + return this.get('description'); + } + + }); + + return Marker; + +}); diff --git a/docdoku-web-front/app/product-structure/js/models/path_data_master.js b/docdoku-web-front/app/product-structure/js/models/path_data_master.js new file mode 100644 index 0000000000..4f77cda297 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/models/path_data_master.js @@ -0,0 +1,88 @@ +/*global define,_,App*/ +define(['backbone', + 'common-objects/collections/path_data_iterations' +], function (Backbone, PathDataIterations) { + + 'use strict'; + + var PathDataMaster = Backbone.Model.extend({ + + defaults: { + pathDataIterations: [] + }, + + initialize: function (data) { + this.path = data.path; + this.serialNumber = data.serialNumber; + this.iterations = new PathDataIterations(data.pathDataIterations); + this.iterations.setPathDataMaster(this); + this.iteration = this.getLastIteration(); + _.bindAll(this); + }, + + url: function () { + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + App.config.productId + '/product-instances/' + this.serialNumber + '/pathdata/' + this.getPath(); + }, + + parse: function (data) { + if (data) { + this.iterations = new PathDataIterations(data.pathDataIterations); + this.iterations.setPathDataMaster(this); + delete data.pathDataIterations; + return data; + } + }, + + getId: function () { + return this.get('id'); + }, + + getIterations: function () { + return this.iterations; + }, + + getPartAttributes:function(){ + return this.get('partAttributes'); + }, + getPartAttributeTemplates:function(){ + return this.get('partAttributeTemplates'); + }, + getLastIteration: function () { + if (this.getIterations().length > 0) { + return this.getIterations().last(); + } else { + return null; + } + }, + + getPath: function () { + return this.get('path'); + }, + + getPartLinks: function () { + return this.get('partLinksList').partLinks; + }, + + setPath: function (path) { + this.set('path', path); + }, + + setSerialNumber: function (serialNumber) { + this.set('serialNumber', serialNumber); + }, + + getSerialNumber: function () { + return this.get('serialNumber'); + }, + + hasIterations: function () { + return this.get('pathDataIterations').length > 0; + } + + + }); + return PathDataMaster; + +}); + + diff --git a/docdoku-web-front/app/product-structure/js/models/product_instance_data.js b/docdoku-web-front/app/product-structure/js/models/product_instance_data.js new file mode 100644 index 0000000000..9672b3f87f --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/models/product_instance_data.js @@ -0,0 +1,71 @@ +/*global define,App*/ +define(['backbone'], function (Backbone) { + + 'use strict'; + + var ProductInstanceDataModel = Backbone.Model.extend({ + + defaults: { + attachedFiles: [], + instanceAttributes: [] + }, + + getId: function(){ + return this.get('id'); + }, + + getDocumentLinked: function(){ + return this.get('linkedDocuments'); + }, + + setDocumentLinked: function(linkedDocuments){ + this.set('linkedDocuments', linkedDocuments); + }, + + getAttributes: function(){ + return this.get('instanceAttributes'); + }, + + setAttributes: function(attributes){ + this.set('instanceAttributes', attributes); + }, + + getPath: function(){ + return this.get('path'); + }, + + setPath: function(path){ + this.set('path', path); + }, + + getDescription: function(){ + return this.get('description'); + }, + + setDescription: function(description){ + this.set('description', description); + }, + + //Files related : + getAttachedFiles : function(){ + return this.get('attachedFiles'); + }, + + setAttachedFiles : function(attachedFiles){ + this.set('attachedFiles', attachedFiles); + }, + + getUploadBaseUrl : function(serialNumber){ + return App.config.contextPath + '/api/files/' + App.config.workspaceId + '/product-instances/' + serialNumber + '/' + App.config.productId + '/pathdata/' + this.getId()+'/'; + }, + + getDeleteBaseUrl: function(serialNumber){ + return App.config.contextPath + '/api/files/' + App.config.workspaceId + '/product-instances/' + serialNumber + '/' + App.config.productId + '/pathdata/' + this.getId()+'/'; + } + + }); + return ProductInstanceDataModel; + +}); + + diff --git a/docdoku-web-front/app/product-structure/js/models/result_path.js b/docdoku-web-front/app/product-structure/js/models/result_path.js new file mode 100644 index 0000000000..1d7d41d4f4 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/models/result_path.js @@ -0,0 +1,27 @@ +/*global define,_*/ +'use strict'; +define(['backbone'], function (Backbone) { + + var ResultPath = Backbone.Model.extend({ + + contains: function (partUsageLinkId) { + return _.indexOf(this.partUsageLinks, partUsageLinkId) !== -1; + }, + + parse: function (response) { + if (response) { + var linkIds = response.path.substr(3).split('-'); + linkIds.unshift('-1'); + this.partUsageLinks = linkIds; + } + }, + + toJSON: function () { + return _.pick(this.attributes, 'path'); + } + + }); + + return ResultPath; + +}); diff --git a/docdoku-web-front/app/product-structure/js/permalinkApp.js b/docdoku-web-front/app/product-structure/js/permalinkApp.js new file mode 100644 index 0000000000..f075adf253 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/permalinkApp.js @@ -0,0 +1,230 @@ +/*global define,App,THREE,requestAnimationFrame,_,$*/ +define(function () { + + 'use strict'; + + function PermalinkApp(filename, width, height) { + + var container = document.getElementById('container'); + var $container = $(container); + var scene = new THREE.Scene(); + var camera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, App.SceneOptions.cameraNear, App.SceneOptions.cameraFar); + var control; + var renderer = new THREE.WebGLRenderer({alpha: true}); + var texturePath = filename.substring(0, filename.lastIndexOf('/')); + var extension = filename.substr(filename.lastIndexOf('.') + 1).toLowerCase(); + + function addLightsToCamera(camera) { + var dirLight1 = new THREE.DirectionalLight(App.SceneOptions.cameraLight1Color); + dirLight1.position.set(200, 200, 1000).normalize(); + dirLight1.name = 'CameraLight1'; + camera.add(dirLight1); + camera.add(dirLight1.target); + + var dirLight2 = new THREE.DirectionalLight( App.SceneOptions.cameraLight2Color, 1 ); + dirLight2.color.setHSL( 0.1, 1, 0.95 ); + dirLight2.position.set( -1, 1.75, 1 ); + dirLight2.position.multiplyScalar( 50 ); + dirLight2.name='CameraLight2'; + camera.add( dirLight2 ); + + dirLight2.castShadow = true; + + dirLight2.shadowMapWidth = 2048; + dirLight2.shadowMapHeight = 2048; + + var d = 50; + + dirLight2.shadowCameraLeft = -d; + dirLight2.shadowCameraRight = d; + dirLight2.shadowCameraTop = d; + dirLight2.shadowCameraBottom = -d; + + dirLight2.shadowCameraFar = 3500; + dirLight2.shadowBias = -0.0001; + dirLight2.shadowDarkness = 0.35; + + var hemiLight = new THREE.HemisphereLight( App.SceneOptions.ambientLightColor, App.SceneOptions.ambientLightColor, 0.6 ); + hemiLight.color.setHSL( 0.6, 1, 0.6 ); + hemiLight.groundColor.setHSL( 0.095, 1, 0.75 ); + hemiLight.position.set( 0, 0, 500 ); + hemiLight.name='AmbientLight'; + camera.add( hemiLight ); + + } + + camera.position.copy(App.SceneOptions.defaultCameraPosition); + addLightsToCamera(camera); + + renderer.setSize(width, height); + container.appendChild(renderer.domElement); + scene.add(camera); + scene.updateMatrixWorld(); + control = new THREE.TrackballControls(camera, container); + + function centerOn(mesh) { + mesh.geometry.computeBoundingBox(); + var boundingBox = mesh.geometry.boundingBox; + var cog = new THREE.Vector3().copy(boundingBox.center()); + var size = boundingBox.size(); + var radius = Math.max(size.x, size.y, size.z); + var distance = radius ? radius * 2 : 1000; + distance = (distance < App.SceneOptions.cameraNear) ? App.SceneOptions.cameraNear + 100 : distance; + camera.position.set(cog.x + distance, cog.y, cog.z + distance); + control.target.copy(cog); + } + + function render() { + scene.updateMatrixWorld(); + renderer.render(scene, camera); + } + + function animate() { + requestAnimationFrame(animate); + control.update(); + render(); + } + + function getMeshGeometries(collada, geometries) { + if (collada) { + _.each(collada.children, function (child) { + if (child instanceof THREE.Mesh && child.geometry) { + geometries.push(child.geometry); + } + getMeshGeometries(child, geometries); + }); + } + } + + var defaultMaterial = new THREE.MeshLambertMaterial({color:new THREE.Color(0x62697B)}); + + function setShadows(object){ + object.traverse( function ( o ) { + if ( o instanceof THREE.Mesh) { + o.castShadow = true; + o.receiveShadow = true; + } + }); + } + + function updateMaterial(object){ + object.traverse( function ( o ) { + if ( o instanceof THREE.Mesh && !o.material.name) { + o.material = defaultMaterial; + } + }); + } + + function onParseSuccess(object) { + scene.add(object); + centerOn(object.children[0]); + } + + function handleResize() { + camera.aspect = $container.innerWidth() / $container.innerHeight(); + camera.updateProjectionMatrix(); + renderer.setSize($container.innerWidth(), $container.innerHeight()); + control.handleResize(); + } + + window.addEventListener('resize', handleResize, false); + + switch (extension) { + + case 'dae': + + var colladaLoader = new THREE.ColladaLoader(); + + colladaLoader.load(filename, function (collada) { + + var geometries = [], combined = new THREE.Geometry(); + getMeshGeometries(collada.scene, geometries); + + // Merge all sub meshes into one + _.each(geometries, function (geometry) { + THREE.GeometryUtils.merge(combined, geometry); + }); + + combined.dynamic = false; + combined.mergeVertices(); + + combined.computeBoundingSphere(); + var object = new THREE.Object3D(); + object.add(new THREE.Mesh(combined)); + setShadows(object); + updateMaterial(object); + onParseSuccess(object); + + }); + + break; + + case 'stl': + var stlLoader = new THREE.STLLoader(); + + stlLoader.load(filename, function (geometry) { + var object = new THREE.Object3D(); + object.add(new THREE.Mesh(geometry)); + setShadows(object); + updateMaterial(object); + onParseSuccess(object); + }); + + break; + + // Used for json files only (no referenced buffers) + case 'json': + var jsonLoader = new THREE.JSONLoader(); + + jsonLoader.load(filename, function (geometry, materials) { + geometry.dynamic = false; + var object = new THREE.Object3D(); + object.add(new THREE.Mesh(geometry,new THREE.MeshFaceMaterial(materials))); + setShadows(object); + onParseSuccess(object); + }, texturePath+'/attachedfiles/'); + + break; + + // Used for binary json files only (referenced buffers - bin file) + case 'js': + var binaryLoader = new THREE.BinaryLoader(); + + binaryLoader.load(filename, function (geometry, materials) { + var _material = new THREE.MeshPhongMaterial({color: materials[0].color, overdraw: true}); + geometry.dynamic = false; + var object = new THREE.Object3D(); + object.add(new THREE.Mesh(geometry,_material)); + setShadows(object); + onParseSuccess(object); + }, texturePath); + + break; + + case 'obj' : + + var OBJLoader = new THREE.OBJLoader(); + + OBJLoader.load(filename, texturePath + '/attachedfiles/', function (object) { + setShadows(object); + updateMaterial(object); + onParseSuccess(object); + }); + + break; + + default: + break; + + } + + control.addEventListener('change', function () { + render(); + }); + + animate(); + handleResize(); + } + + return PermalinkApp; +}); diff --git a/docdoku-web-front/app/product-structure/js/router.js b/docdoku-web-front/app/product-structure/js/router.js new file mode 100644 index 0000000000..5be5f2102d --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/router.js @@ -0,0 +1,90 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/common/singleton_decorator' +], +function (Backbone, singletonDecorator) { + 'use strict'; + var Router = Backbone.Router.extend({ + + routes: { + ':workspaceId/:productId': 'defaults', + ':workspaceId/:productId/config-spec/:configSpecType': 'defaults', + ':workspaceId/:productId/config-spec/:configSpecType/scene(/camera/:camera)(/target/:target)(/up/:up)': 'scene', + ':workspaceId/:productId/config-spec/:configSpecType/bom': 'bom', + ':workspaceId/:productId/config-spec/:configSpecType/room/:key': 'joinCollaborative' + }, + + defaults: function (workspaceId, productId, configSpecType) { + if(!configSpecType){ + configSpecType = 'wip'; + } + this.navigate(workspaceId+'/'+productId+'/config-spec/'+configSpecType+'/scene',{trigger:true}); + }, + + scene:function(workspaceId, productId, configSpecType, camera, target, up){ + App.config.productConfigSpec = configSpecType; + App.appView.sceneMode(); + if (camera && target && up) { + var c = camera.split(';'); + var t = target.split(';'); + var u = up.split(';'); + App.sceneManager.setControlsContext({ + target: {x:parseFloat(t[0]),y: parseFloat(t[1]),z: parseFloat(t[2])}, + camPos: {x:parseFloat(c[0]),y: parseFloat(c[1]),z: parseFloat(c[2])}, + camOrientation: {x:parseFloat(u[0]),y:parseFloat(u[1]),z: parseFloat(u[2])} + }); + } + }, + + bom:function(workspaceId, productId, configSpecType){ + App.config.productConfigSpec = configSpecType; + App.appView.bomMode(); + App.appView.once('app:ready',function() { + App.partsTreeView.$el.trigger('load:root'); + }); + }, + + joinCollaborative: function (workspaceId, productId, configSpecType, key) { + App.config.productConfigSpec = configSpecType; + App.appView.sceneMode(); + if (!App.collaborativeView.isMaster) { + App.appView.once('app:ready',function(){ + App.collaborativeController.sendJoinRequest(key); + }); + } + }, + + updateRoute: function (context) { + + if(!App.collaborativeView.roomKey){ + + var c = context.camPos.toArray(); + var t = context.target.toArray(); + var u = context.camOrientation.toArray(); + var positionPrecision = 2; + + this.navigate( + App.config.workspaceId + '/' + App.config.productId+ + '/config-spec/' + App.config.productConfigSpec + + '/scene/camera/' + + c[0].toFixed(positionPrecision) + ';' + + c[1].toFixed(positionPrecision) + ';' + + c[2].toFixed(positionPrecision) + + '/target/' + + t[0].toFixed(positionPrecision) + ';' + + t[1].toFixed(positionPrecision) + ';' + + t[2].toFixed(positionPrecision) + + '/up/' + + u[0].toFixed(positionPrecision) + ';' + + u[1].toFixed(positionPrecision) + ';' + + u[2].toFixed(positionPrecision),{ + trigger: false + }); + + } + } + }); + Router = singletonDecorator(Router); + return Router; +}); diff --git a/docdoku-web-front/app/product-structure/js/templates/baselines/baseline_select.html b/docdoku-web-front/app/product-structure/js/templates/baselines/baseline_select.html new file mode 100644 index 0000000000..ec3bd2ec23 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/baselines/baseline_select.html @@ -0,0 +1,17 @@ + + + + + + + diff --git a/docdoku-web-front/app/product-structure/js/templates/blocker.html b/docdoku-web-front/app/product-structure/js/templates/blocker.html new file mode 100644 index 0000000000..66d7811082 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/blocker.html @@ -0,0 +1,5 @@ +
                                    + {{i18n.BLOCKER_TITLE}} +
                                    + {{i18n.BLOCKER_TEXT}} +
                                    diff --git a/docdoku-web-front/app/product-structure/js/templates/bom.html b/docdoku-web-front/app/product-structure/js/templates/bom.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docdoku-web-front/app/product-structure/js/templates/bom_content.html b/docdoku-web-front/app/product-structure/js/templates/bom_content.html new file mode 100644 index 0000000000..6e1855da44 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/bom_content.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + +
                                    {{i18n.PART_NUMBER}}{{i18n.VERSION}}{{i18n.ITERATION}}{{i18n.TYPE}}{{i18n.PART_NAME}}{{i18n.AUTHOR}}{{i18n.MODIFICATION_DATE}}{{i18n.LIFECYCLE_STATE}}{{i18n.CHECKOUT_BY}}{{i18n.ACL}}
                                    diff --git a/docdoku-web-front/app/product-structure/js/templates/bom_header.html b/docdoku-web-front/app/product-structure/js/templates/bom_header.html new file mode 100644 index 0000000000..94211b0da8 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/bom_header.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docdoku-web-front/app/product-structure/js/templates/bom_item.html b/docdoku-web-front/app/product-structure/js/templates/bom_item.html new file mode 100644 index 0000000000..6192968466 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/bom_item.html @@ -0,0 +1,59 @@ + + + {{#model.hasUnreadModificationNotifications}} + + {{/model.hasUnreadModificationNotifications}} + + + {{#model.isCheckout}} + {{#model.isCheckoutByConnectedUser}}{{/model.isCheckoutByConnectedUser}} + {{^model.isCheckoutByConnectedUser}}{{/model.isCheckoutByConnectedUser}} + {{/model.isCheckout}} + {{^model.isCheckout}} + {{#model.isReleased}}{{/model.isReleased}} + {{^model.isReleased}} + {{#model.isObsolete}}{{/model.isObsolete}} + {{^model.isObsolete}}{{/model.isObsolete}} + {{/model.isReleased}} + {{/model.isCheckout}} + + {{#model.isLastIterationAssembly}} + + {{/model.isLastIterationAssembly}} + {{^model.isLastIterationAssembly}} + + {{/model.isLastIterationAssembly}} + + {{model.getNumber}} + +{{model.getVersion}} +{{#model.getLastIteration}} + {{id}} + {{/model.getLastIteration}} + {{^model.getLastIteration}} + - + {{/model.getLastIteration}} + +{{model.getType}} +{{model.getName}} +{{model.getAuthorName}} + +{{model.getFormattedModificationDate}} +{{model.getLifeCycleState}} +{{model.getCheckOutUserName}} + + {{#model.isReadOnly}} + + {{/model.isReadOnly}} + {{#model.isFullAccess}} + + {{/model.isFullAccess}} + + + + + + {{#model.hasLastIterationAttachedFiles}} + + {{/model.hasLastIterationAttachedFiles}} + diff --git a/docdoku-web-front/app/product-structure/js/templates/collaborative_view.html b/docdoku-web-front/app/product-structure/js/templates/collaborative_view.html new file mode 100644 index 0000000000..cdc9fb188b --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/collaborative_view.html @@ -0,0 +1,47 @@ + + +
                                    + +
                                    +

                                    + +

                                    + +
                                      +
                                    • + + {{master}} +
                                    • + + {{^users}} + {{^pendingUsers}} +
                                    • {{i18n.NO_PARTICIPANT}}
                                    • + {{/pendingUsers}} + {{/users}} + + {{#users}} +
                                    • + + {{ . }} + + + + +
                                    • + {{/users}} + + {{#pendingUsers}} +
                                    • + + {{ . }} + + + +
                                    • + {{/pendingUsers}} +
                                    +
                                    +
                                    diff --git a/docdoku-web-front/app/product-structure/js/templates/content.html b/docdoku-web-front/app/product-structure/js/templates/content.html new file mode 100644 index 0000000000..cfbb82993b --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/content.html @@ -0,0 +1,65 @@ +
                                    +
                                    +
                                    + + + +
                                    + + +
                                    + +
                                    +
                                    + +
                                    + +
                                    + +
                                    + + +
                                    + +
                                    + + +
                                    +
                                    + +
                                    + +
                                    + +
                                    + + + +
                                    + +
                                    + +
                                    +
                                    + +
                                    +
                                    +
                                    + +
                                    +
                                    +
                                    + +
                                    diff --git a/docdoku-web-front/app/product-structure/js/templates/control_clipping.html b/docdoku-web-front/app/product-structure/js/templates/control_clipping.html new file mode 100644 index 0000000000..66c0bce77b --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/control_clipping.html @@ -0,0 +1,2 @@ + + diff --git a/docdoku-web-front/app/product-structure/js/templates/control_cutplan.html b/docdoku-web-front/app/product-structure/js/templates/control_cutplan.html new file mode 100644 index 0000000000..038fef6be1 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/control_cutplan.html @@ -0,0 +1,15 @@ + + +
                                    + + + +
                                    + +
                                    + +
                                    + +
                                    + +
                                    diff --git a/docdoku-web-front/app/product-structure/js/templates/control_explode.html b/docdoku-web-front/app/product-structure/js/templates/control_explode.html new file mode 100644 index 0000000000..b4c4c7e035 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/control_explode.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/docdoku-web-front/app/product-structure/js/templates/control_layers.html b/docdoku-web-front/app/product-structure/js/templates/control_layers.html new file mode 100644 index 0000000000..3b9dfa5c4a --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/control_layers.html @@ -0,0 +1,7 @@ + + +
                                    + +
                                    \ No newline at end of file diff --git a/docdoku-web-front/app/product-structure/js/templates/control_markers.html b/docdoku-web-front/app/product-structure/js/templates/control_markers.html new file mode 100644 index 0000000000..0d6e83e006 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/control_markers.html @@ -0,0 +1,9 @@ + +
                                    + + + +
                                    \ No newline at end of file diff --git a/docdoku-web-front/app/product-structure/js/templates/control_measure.html b/docdoku-web-front/app/product-structure/js/templates/control_measure.html new file mode 100644 index 0000000000..64e80e54a4 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/control_measure.html @@ -0,0 +1,8 @@ + + +
                                    +
                                    + +
                                    + +
                                    diff --git a/docdoku-web-front/app/product-structure/js/templates/control_modes.html b/docdoku-web-front/app/product-structure/js/templates/control_modes.html new file mode 100644 index 0000000000..7cee197f24 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/control_modes.html @@ -0,0 +1,9 @@ + +
                                    + + + +
                                    \ No newline at end of file diff --git a/docdoku-web-front/app/product-structure/js/templates/control_navigation.html b/docdoku-web-front/app/product-structure/js/templates/control_navigation.html new file mode 100644 index 0000000000..22532a0f9d --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/control_navigation.html @@ -0,0 +1,11 @@ + +
                                    + + + + + +
                                    diff --git a/docdoku-web-front/app/product-structure/js/templates/control_options.html b/docdoku-web-front/app/product-structure/js/templates/control_options.html new file mode 100644 index 0000000000..fb53c9e078 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/control_options.html @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/docdoku-web-front/app/product-structure/js/templates/control_transform.html b/docdoku-web-front/app/product-structure/js/templates/control_transform.html new file mode 100644 index 0000000000..c833eb7dc0 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/control_transform.html @@ -0,0 +1,9 @@ + + +
                                    + + + +
                                    + + \ No newline at end of file diff --git a/docdoku-web-front/app/product-structure/js/templates/controls_infos_modal.html b/docdoku-web-front/app/product-structure/js/templates/controls_infos_modal.html new file mode 100644 index 0000000000..2721a396b5 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/controls_infos_modal.html @@ -0,0 +1,109 @@ + diff --git a/docdoku-web-front/app/product-structure/js/templates/export_scene_modal.html b/docdoku-web-front/app/product-structure/js/templates/export_scene_modal.html new file mode 100644 index 0000000000..29107c710a --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/export_scene_modal.html @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/docdoku-web-front/app/product-structure/js/templates/layer_controls.html b/docdoku-web-front/app/product-structure/js/templates/layer_controls.html new file mode 100644 index 0000000000..af5be5e108 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/layer_controls.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/docdoku-web-front/app/product-structure/js/templates/layer_item.html b/docdoku-web-front/app/product-structure/js/templates/layer_item.html new file mode 100644 index 0000000000..fff443b6cf --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/layer_item.html @@ -0,0 +1,8 @@ + +
                                    +

                                    + {{attributes.name}} ({{countMarkers}}) +

                                    + + + diff --git a/docdoku-web-front/app/product-structure/js/templates/marker_create_modal.html b/docdoku-web-front/app/product-structure/js/templates/marker_create_modal.html new file mode 100644 index 0000000000..48517a69da --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/marker_create_modal.html @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/docdoku-web-front/app/product-structure/js/templates/marker_info_modal.html b/docdoku-web-front/app/product-structure/js/templates/marker_info_modal.html new file mode 100644 index 0000000000..775d19313b --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/marker_info_modal.html @@ -0,0 +1,13 @@ + diff --git a/docdoku-web-front/app/product-structure/js/templates/nav_list_action_bar.html b/docdoku-web-front/app/product-structure/js/templates/nav_list_action_bar.html new file mode 100644 index 0000000000..16414b487b --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/nav_list_action_bar.html @@ -0,0 +1,27 @@ + + diff --git a/docdoku-web-front/app/product-structure/js/templates/part_instance.html b/docdoku-web-front/app/product-structure/js/templates/part_instance.html new file mode 100644 index 0000000000..0c15d05562 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/part_instance.html @@ -0,0 +1,22 @@ + +
                                    + + + + +
                                    + + + + +
                                    + + + +
                                    + + diff --git a/docdoku-web-front/app/product-structure/js/templates/part_meta_data.html b/docdoku-web-front/app/product-structure/js/templates/part_meta_data.html new file mode 100644 index 0000000000..f6de6924fa --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/part_meta_data.html @@ -0,0 +1,22 @@ + + +
                                    + +
                                    {{i18n.PART_NAME}} :
                                    +
                                    {{model.getName}}
                                    + +
                                    {{i18n.VERSION}} :
                                    +
                                    {{model.getVersion}}
                                    + +
                                    {{i18n.ITERATION}} :
                                    +
                                    {{model.getIteration}}
                                    + +
                                    {{i18n.DESCRIPTION}} :
                                    +
                                    {{model.getDescription}}
                                    + +
                                    {{i18n.AUTHOR}} :
                                    +
                                    {{model.getAuthor}}
                                    + +
                                    +
                                    diff --git a/docdoku-web-front/app/product-structure/js/templates/path_to_path_link_item.html b/docdoku-web-front/app/product-structure/js/templates/path_to_path_link_item.html new file mode 100644 index 0000000000..00b5baa913 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/path_to_path_link_item.html @@ -0,0 +1,69 @@ +
                                    + + diff --git a/docdoku-web-front/app/product-structure/js/templates/path_to_path_link_modal.html b/docdoku-web-front/app/product-structure/js/templates/path_to_path_link_modal.html new file mode 100644 index 0000000000..d064fc2dff --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/path_to_path_link_modal.html @@ -0,0 +1,25 @@ + + + diff --git a/docdoku-web-front/app/product-structure/js/templates/product-instance-data-modal.html b/docdoku-web-front/app/product-structure/js/templates/product-instance-data-modal.html new file mode 100644 index 0000000000..dda7dbb1a5 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/product-instance-data-modal.html @@ -0,0 +1,58 @@ + + + diff --git a/docdoku-web-front/app/product-structure/js/templates/progress_bar.html b/docdoku-web-front/app/product-structure/js/templates/progress_bar.html new file mode 100644 index 0000000000..5cf810b040 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/progress_bar.html @@ -0,0 +1 @@ +
                                    diff --git a/docdoku-web-front/app/product-structure/js/templates/shortcuts.html b/docdoku-web-front/app/product-structure/js/templates/shortcuts.html new file mode 100644 index 0000000000..f21e8712f0 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/templates/shortcuts.html @@ -0,0 +1 @@ +{{i18n.SHORTCUTS_TITLE}} \ No newline at end of file diff --git a/docdoku-web-front/app/product-structure/js/views/baselines/baseline_select_view.js b/docdoku-web-front/app/product-structure/js/views/baselines/baseline_select_view.js new file mode 100644 index 0000000000..5a6dfbef00 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/baselines/baseline_select_view.js @@ -0,0 +1,238 @@ +/*global _,define,App,$*/ +define([ + 'backbone', + 'mustache', + 'common-objects/collections/baselines', + 'common-objects/collections/product_instances', + 'text!templates/baselines/baseline_select.html' +], function (Backbone, Mustache, Baselines, ProductInstances, template) { + 'use strict'; + + var BaselineSelectView = Backbone.View.extend({ + + events:{ + 'change #config_spec_type_selector_list' : 'onTypeChanged', + 'change #latest_selector_list' : 'changeLatest', + 'change #baseline_selector_list' : 'changeBaseline', + 'change #product_instance_selector_list' : 'changeInstance', + 'change #path_to_path_link_selector_list' : 'changePathToPathLink', + 'click .toggle_substitutes': 'toggleSubstitutes' + }, + + availableFilters:['wip','latest','latest-released'], + + type:'product', + + initialize:function(){ + this.baselineCollection = new Baselines({},{type : this.type, productId : App.config.productId}); + this.listenToOnce(this.baselineCollection,'reset',this.onBaselineCollectionReset); + this.productInstanceCollection = new ProductInstances({},{productId : App.config.productId}); + this.listenToOnce(this.productInstanceCollection,'reset',this.onProductInstanceCollectionReset); + this.showSubstitutes = true; + }, + + render:function(){ + + this.$el.html(Mustache.render(template, {i18n:App.config.i18n})); + this.bindDomElements(); + + this.$selectBaselineSpec.hide(); + this.$selectProdInstSpec.hide(); + + this.baselineCollection.fetch({reset:true}); + this.productInstanceCollection.fetch({reset:true}); + + if(_.contains(this.availableFilters,App.config.productConfigSpec)){ + this.$selectLatestFilter.val(App.config.productConfigSpec); + } + + this.fetchPathToPathLinkTypes(); + + return this; + }, + + bindDomElements:function(){ + // Main selector + this.$selectConfSpec = this.$('#config_spec_type_selector_list'); + // Sub selectors + this.$selectLatestFilter = this.$('#latest_selector_list'); + this.$selectBaselineSpec = this.$('#baseline_selector_list'); + this.$selectProdInstSpec = this.$('#product_instance_selector_list'); + this.$selectPathToPathLink = this.$('#path_to_path_link_selector_list'); + this.$divergeSwitchContainer = this.$('#diverge-selector'); + this.$toggleSubstitutes = this.$('.toggle_substitutes'); + }, + + onBaselineCollectionReset:function(){ + + var selected; + + this.baselineCollection.each(function(baseline){ + this.$selectBaselineSpec.append(''); + if(App.config.productConfigSpec === ''+baseline.getId()){ + selected = baseline; + } + },this); + + this.$selectConfSpec.find('[value="baseline"]').prop('disabled',!this.baselineCollection.size()); + + if(selected){ + this.$selectConfSpec.val('baseline'); + this.$selectProdInstSpec.hide(); + this.$selectLatestFilter.hide(); + this.$selectBaselineSpec.val(selected.getId()).show(); + this.$divergeSwitchContainer.hide(); + this.setDescription(selected.getDescription()); + } + }, + + onProductInstanceCollectionReset:function(){ + var selected; + this.productInstanceCollection.each(function(productInstance){ + this.$selectProdInstSpec.append(''); + if(App.config.productConfigSpec === 'pi-' + productInstance.getSerialNumber()){ + selected = productInstance; + } + },this); + + this.$selectConfSpec.find('[value="serial-number"]').prop('disabled',!this.productInstanceCollection.size()); + + if(selected){ + this.$selectConfSpec.val('serial-number'); + this.$selectBaselineSpec.hide(); + this.$selectLatestFilter.hide(); + this.$divergeSwitchContainer.hide(); + this.$selectProdInstSpec.val('pi-'+selected.getSerialNumber()).show(); + this.setDescription(''); + } + }, + + onTypeChanged:function(){ + this.$selectConfSpec.show(); + App.config.linkType = null; + var selectedType = this.$selectConfSpec.val(); + + if (selectedType==='latest-filters') { + this.changeLatest(); + this.fetchPathToPathLinkTypes(); + + } else if (selectedType==='baseline') { + this.changeBaseline(); + + } else if (selectedType==='serial-number') { + this.changeInstance(); + } + }, + + isSerialNumberSelected:function(){ + return this.$selectConfSpec.val() === 'serial-number'; + }, + + isBaselineSelected:function(){ + return this.$selectConfSpec.val() === 'baseline'; + }, + + changeLatest:function(){ + this.$selectBaselineSpec.hide(); + this.$selectProdInstSpec.hide(); + this.$selectLatestFilter.show(); + this.$divergeSwitchContainer.show(); + this.trigger('config_spec:changed', this.$selectLatestFilter.val()); + this.setDescription(''); + }, + + changeBaseline:function(){ + this.$selectProdInstSpec.hide(); + this.$selectLatestFilter.hide(); + this.$selectBaselineSpec.show(); + this.$divergeSwitchContainer.hide(); + + var baseline = this.baselineCollection.findWhere({id:parseInt(this.$selectBaselineSpec.val(),10)}); + this.setDescription(baseline ? baseline.getDescription() : ''); + + App.config.linkType = null; + this.fetchPathToPathLinkTypes(); + this.trigger('config_spec:changed', this.$selectBaselineSpec.val()); + }, + + changeInstance:function(){ + this.$selectBaselineSpec.hide(); + this.$selectLatestFilter.hide(); + this.$selectProdInstSpec.show(); + this.$divergeSwitchContainer.hide(); + + this.setDescription(''); + + App.config.linkType = null; + this.fetchPathToPathLinkTypes(); + this.trigger('config_spec:changed', this.$selectProdInstSpec.val()); + }, + + fetchPathToPathLinkTypes:function(){ + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + App.config.productId ; + + var selectedType = this.$selectConfSpec.val(); + if (selectedType === 'serial-number') { + url+= '/product-instances/' + this.$selectProdInstSpec.val().substr(3); + } else if (selectedType === 'baseline') { + url+= '/baselines/' + this.$selectBaselineSpec.val(); + } + url+= '/path-to-path-links-types'; + + var $select = this.$selectPathToPathLink; + $select.empty(); + $select.append(''); + + $.getJSON(url).success(function(data){ + data.map(function(link){ + $select.append(''); + }); + }); + }, + + changePathToPathLink:function(e){ + App.config.linkType = e.target.value; + this.trigger('config_spec:changed', App.config.productConfigSpec); + }, + + setDescription:function(desc){ + this.$('.description').text(desc); + }, + + refresh:function(){ + var selectedConfigSpecOption = this.$('option[value="'+App.config.productConfigSpec+'"]'); + + if(selectedConfigSpecOption){ + this.$selectBaselineSpec.hide(); + this.$selectLatestFilter.hide(); + this.$selectProdInstSpec.hide(); + + selectedConfigSpecOption.parent().val(App.config.productConfigSpec).show(); + + if(_.contains(this.availableFilters,App.config.productConfigSpec)){ + this.$selectConfSpec.val('latest-filters'); + }else if(App.config.productConfigSpec.match(/^pi-/)){ + this.$selectConfSpec.val('serial-number'); + }else{ + this.$selectConfSpec.val('baseline'); + } + } + }, + + toggleSubstitutes: function () { + if (this.showSubstitutes) { + this.$toggleSubstitutes.html(App.config.i18n.HIDE_SUBSTITUTES); + } else { + this.$toggleSubstitutes.html(App.config.i18n.SHOW_SUBSTITUTES); + } + + this.showSubstitutes = !this.showSubstitutes; + + App.config.diverge = !App.config.diverge; + this.trigger('config_spec:changed', App.config.productConfigSpec); + } + + }); + + return BaselineSelectView; +}); diff --git a/docdoku-web-front/app/product-structure/js/views/blocker_view.js b/docdoku-web-front/app/product-structure/js/views/blocker_view.js new file mode 100644 index 0000000000..f0dc78428f --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/blocker_view.js @@ -0,0 +1,26 @@ +/*global define,App*/ +define( + [ + 'backbone', + 'mustache', + 'text!templates/blocker.html' + ], function (Backbone, Mustache, template) { + + 'use strict'; + + var BlockerView = Backbone.View.extend({ + + tagName: 'div', + + id: 'blocker', + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + return this; + } + + }); + + return BlockerView; + + }); diff --git a/docdoku-web-front/app/product-structure/js/views/bom_content_view.js b/docdoku-web-front/app/product-structure/js/views/bom_content_view.js new file mode 100644 index 0000000000..68a17a1327 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/bom_content_view.js @@ -0,0 +1,228 @@ +/*global _,define,App,window,$*/ +define([ + 'backbone', + 'mustache', + 'async', + 'views/bom_item_view', + 'text!templates/bom_content.html', + 'collections/part_collection', + 'common-objects/views/prompt', + 'common-objects/views/security/acl_edit' +], function (Backbone, Mustache, Async, BomItemView, template, PartList, PromptView, ACLEditView) { + 'use strict'; + var BomContentView = Backbone.View.extend({ + + el: '#bom_table_container', + + events: { + 'change th > input': 'onHeaderSelectionChanged', + 'change td > input': 'onItemSelectionChanged' + }, + + initialize: function () { + _.bindAll(this); + this.itemViews = []; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.table = this.$('table'); + this.tbody = this.$('tbody'); + return this; + }, + + onHeaderSelectionChanged: function (e) { + this.setCheckAll(e.target.checked); + this.notifySelectionChanged(); + }, + + onItemSelectionChanged: function () { + this.notifySelectionChanged(); + }, + + notifySelectionChanged: function () { + this.trigger('itemSelectionChanged', this.checkedViews()); + }, + + update: function (component) { + this.itemViews = []; + if (component && !component.isVirtual()) { + this.partsCollection = new PartList(); + this.partsCollection.setFilterUrl(component.getUrlForBom()); + this.listenTo(this.partsCollection, 'reset', this.addAllBomItem); + this.partsCollection.fetch({reset: true}); + } + }, + + showRoot: function (rootComponent) { + this.itemViews = []; + if (rootComponent && !rootComponent.isVirtual()) { + this.partsCollection = new PartList(); + this.partsCollection.setFilterUrl(rootComponent.getRootUrlForBom()); + this.listenTo(this.partsCollection, 'reset', this.addAllBomItem); + this.partsCollection.fetch({reset: true}); + } + }, + + addAllBomItem: function (parts) { + this.render(); + parts.each(this.addBomItem, this); + this.notifySelectionChanged(); + this.dataTable(); + }, + + setCheckAll: function(status) { + _.invoke(this.itemViews, 'setSelectionState', status); + }, + + checkedViews: function () { + return _.filter(this.itemViews, function (itemView) { + return itemView.isChecked(); + }); + }, + + addBomItem: function (part) { + var bomItemView = new BomItemView({model: part}).render(); + this.listenTo(bomItemView.model, 'change', this.notifySelectionChanged); + this.itemViews.push(bomItemView); + this.tbody.append(bomItemView.el); + }, + + actionCheckout: function () { + var listViews = this.checkedViews(); + + var ajaxes = []; + _(listViews).each(function (view) { + ajaxes.push(view.model.checkout()); + }); + $.when.apply($, ajaxes).then(this.onSuccess); + + + return false; + }, + + actionUndocheckout: function () { + var listViews = this.checkedViews(); + + var ajaxes = []; + _(listViews).each(function (view) { + ajaxes.push(view.model.undocheckout()); + }); + $.when.apply($, ajaxes).then(this.onSuccess); + + + return false; + }, + + actionCheckin: function () { + var self = this; + var listViews = this.checkedViews(); + var promptView = new PromptView(); + promptView.setPromptOptions(App.config.i18n.REVISION_NOTE, App.config.i18n.REVISION_NOTE_PROMPT_LABEL, App.config.i18n.REVISION_NOTE_PROMPT_OK, App.config.i18n.REVISION_NOTE_PROMPT_CANCEL); + window.document.body.appendChild(promptView.render().el); + promptView.openModal(); + + self.listenTo(promptView, 'prompt-ok', function (args) { + var iterationNote = args[0]; + if (_.isEqual(iterationNote, '')) { + iterationNote = null; + } + + Async.each(listViews, function (view, callback) { + view.model.getLastIteration().save({ + iterationNote: iterationNote + }).success(function () { + view.model.checkin().success(callback); + }); + }, + function (err) { + if (!err) { + self.onSuccess(); + } + }); + + }); + + this.listenTo(promptView, 'prompt-cancel', function () { + var ajaxes = []; + _(listViews).each(function (view) { + ajaxes.push(view.model.checkin()); + }); + $.when.apply($, ajaxes).then(this.onSuccess); + }); + return false; + }, + + onSuccess: function () { + Backbone.Events.trigger('part:saved'); + }, + + actionUpdateACL: function () { + var _this = this; + + var selectedPart = this.checkedViews()[0].model; + + var aclEditView = new ACLEditView({ + editMode: true, + acl: selectedPart.get('acl') + }); + + aclEditView.setTitle(selectedPart.getPartKey()); + + window.document.body.appendChild(aclEditView.render().el); + + aclEditView.openModal(); + + aclEditView.on('acl:update', function () { + + var acl = aclEditView.toList(); + + selectedPart.updateACL({ + acl: acl || {userEntries: {}, groupEntries: {}}, + success: function () { + selectedPart.set('acl', acl); + aclEditView.closeModal(); + }, + error: _this.onError + }); + + }); + }, + + dataTable: function () { + var oldSort = [ + [0, 'asc'] + ]; + if (this.oTable) { + if (this.oTable.fnSettings()) { + oldSort = this.oTable.fnSettings().aaSorting; + } + } + this.oTable = this.table.dataTable({ + aaSorting: oldSort, + bDestroy: true, + iDisplayLength: -1, + oLanguage: { + sSearch: '', + sEmptyTable: App.config.i18n.NO_DATA, + sZeroRecords: App.config.i18n.NO_FILTERED_DATA + }, + sDom: 'ft', + aoColumnDefs: [ + {'bSortable': false, 'aTargets': [0, 1, 11, 12, 13]}, + {'sType': App.config.i18n.DATE_SORT, 'aTargets': [8]}, + {'sType': 'strip_html', 'aTargets': [2]} + ] + }); + this.$el.parent().find('.dataTables_filter input').attr('placeholder', App.config.i18n.FILTER); + }, + + onError: function (model, error) { + var errorMessage = error ? error.responseText : model; + window.alert(errorMessage); + } + + }); + + return BomContentView; +}); diff --git a/docdoku-web-front/app/product-structure/js/views/bom_header_view.js b/docdoku-web-front/app/product-structure/js/views/bom_header_view.js new file mode 100644 index 0000000000..2f3234ef60 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/bom_header_view.js @@ -0,0 +1,279 @@ +/*global define,App,_*/ +define([ + 'backbone', + 'mustache', + 'text!templates/bom_header.html', + 'common-objects/views/prompt' +], function (Backbone, Mustache, template, PromptView) { + 'use strict'; + var BomHeaderView = Backbone.View.extend({ + + events: { + 'click .checkout-group .checkout': 'actionCheckout', + 'click .checkout-group .undocheckout': 'actionUndocheckout', + 'click .checkout-group .checkin': 'actionCheckin', + 'click .cascade-checkout-group button.checkout': 'actionPBSCheckout', + 'click .cascade-checkout-group button.undocheckout': 'actionPBSUndocheckout', + 'click .cascade-checkout-group button.checkin': 'actioPBSnCheckin', + 'click .cascade-checkout-group .cascade a.checkout': 'actionCascadeCheckout', + 'click .cascade-checkout-group .cascade a.undocheckout': 'actionCascadeUndocheckout', + 'click .cascade-checkout-group .cascade a.checkin': 'actionCascadeCheckin', + 'click .edit-acl': 'actionUpdateACL' + }, + + actionCheckout: function () { + this.trigger('actionCheckout'); + }, + + actionUndocheckout: function () { + this.trigger('actionUndocheckout'); + }, + + actionCheckin: function () { + this.trigger('actionCheckin'); + }, + + actionUpdateACL: function () { + this.trigger('actionUpdateACL'); + }, + + cascadeSuccess: function(params) { + App.partsTreeView.refreshAll(); + this.trigger('alert',{type: 'info', message: this.formatResponse(params)}); + }, + + formatResponse: function(params) { + //TODO: might be better to format the response on the server + return App.config.i18n.CASCADE_RESULT + ' : '+params.succeedAttempts + ' / ' + (params.failedAttempts + params.succeedAttempts) + ' ' + App.config.i18n.DONE; + }, + + onSuccess: function() { + Backbone.Events.trigger('part:saved'); + }, + + actionPBSCheckout: function () { + var ajaxes = []; + _(App.partsTreeView.checkedPath).each(function (component) { + ajaxes.push(component.checkout()); + }); + $.when.apply($, ajaxes).then(this.onSuccess); + }, + + actionPBSUndocheckout: function () { + var ajaxes = []; + _(App.partsTreeView.checkedPath).each(function (component) { + ajaxes.push(component.undocheckout()); + }); + $.when.apply($, ajaxes).then(this.onSuccess); + }, + + actioPBSnCheckin: function () { + var ajaxes = []; + _(App.partsTreeView.checkedPath).each(function (component) { + ajaxes.push(component.checkin()); + }); + $.when.apply($, ajaxes).then(this.onSuccess); + }, + + actionCascadeCheckout: function() { + App.partsTreeView.checkedPath[0].cascadeCheckout(this.cascadeSuccess); + }, + + actionCascadeUndocheckout: function() { + App.partsTreeView.checkedPath[0].cascadeUndoCheckout(this.cascadeSuccess); + + }, + + actionCascadeCheckin: function() { + + var _this = this; + var promptView = new PromptView(); + + promptView.specifyInput('textarea'); + promptView.setPromptOptions(App.config.i18n.REVISION_NOTE, App.config.i18n.PART_REVISION_NOTE_PROMPT_LABEL, App.config.i18n.REVISION_NOTE_PROMPT_OK, App.config.i18n.REVISION_NOTE_PROMPT_CANCEL); + window.document.body.appendChild(promptView.render().el); + promptView.openModal(); + + this.listenTo(promptView, 'prompt-ok', function (args) { + var iterationNote = args[0]; + if (_.isEqual(iterationNote, '')) { + iterationNote = null; + } + App.partsTreeView.checkedPath[0].cascadeCheckin(_this.cascadeSuccess,iterationNote); + }); + + this.listenTo(promptView, 'prompt-cancel', function () { + App.partsTreeView.checkedPath[0].cascadeCheckin(_this.cascadeSuccess,null); + }); + + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.append(Mustache.render(template, {i18n: App.config.i18n})); + this.checkoutGroup = this.$('.checkout-group'); + this.checkoutButton = this.$('.checkout-group .checkout'); + this.undoCheckoutButton = this.$('.checkout-group .undocheckout'); + this.checkinButton = this.$('.checkout-group .checkin'); + this.cascadeCheckoutGroup = this.$('.cascade-checkout-group'); + this.cascadeCheckout = this.$('.cascade-checkout-group .cascade-button-checkout'); + this.cascadeUndocheckout = this.$('.cascade-checkout-group .cascade-button-undocheckout'); + this.cascadeCheckin = this.$('.cascade-checkout-group .cascade-button-checkin'); + this.pbsCheckout = this.$('.cascade-checkout-group .checkout'); + this.pbsUndocheckout = this.$('.cascade-checkout-group .undocheckout'); + this.pbsCheckin = this.$('.cascade-checkout-group .checkin'); + this.aclButton = this.$('.edit-acl'); + return this; + }, + + onPathChange: function(list) { + if(App.config.productConfigSpec === 'wip') { + switch (list.length) { + case 0: + this.onNoPathSelected(); + break; + case 1: + this.onOnePathSelected(list[0]); + break; + default: + this.onSeveralPathSelected(list); + break; + } + } else { + this.cascadeCheckoutGroup.hide(); + } + + }, + onNoPathSelected: function() { + this.cascadeCheckoutGroup.hide(); + }, + + onOnePathSelected: function(component) { + if(!component.isReleased() && !component.isObsolete()) { + this.cascadeCheckoutGroup.css('display', 'inline-block'); + } + this.updatePathActionsButton(this.getPermission(component),true); + }, + + onSeveralPathSelected: function(list) { + this.cascadeCheckoutGroup.css('display', 'inline-block'); + var perm = this.getPermissionFromSeveral(list); + if(perm) { + this.updatePathActionsButton(perm,false); + } else { + this.cascadeCheckoutGroup.hide(); + } + }, + + updatePathActionsButton: function (permission,dropdownStatus) { + this.pbsCheckout.prop('disabled', !permission.canCheckout); + this.cascadeCheckout.prop('disabled', (!permission.canCheckout || !dropdownStatus)); + this.pbsUndocheckout.prop('disabled', !permission.canUndoAndCheckin); + this.cascadeUndocheckout.prop('disabled', (!permission.canUndoAndCheckin || !dropdownStatus)); + this.pbsCheckin.prop('disabled', !permission.canUndoAndCheckin); + this.cascadeCheckin.prop('disabled', (!permission.canUndoAndCheckin || !dropdownStatus)); + }, + + onSelectionChange: function (checkedViews) { + switch (checkedViews.length) { + case 0: + this.onNoComponentSelected(); + break; + case 1: + this.onOneComponentSelected(checkedViews[0].model); + break; + default: + this.onSeveralComponentsSelected(checkedViews); + break; + } + + }, + + hideCheckGroup: function () { + this.checkoutGroup.hide(); + }, + + hideACLButton: function () { + this.aclButton.hide(); + }, + + onNoComponentSelected: function () { + this.hideCheckGroup(); + this.aclButton.hide(); + }, + + onOneComponentSelected: function (component) { + if (!component.isReleased() && !component.isObsolete()) { + this.checkoutGroup.show(); + } + if (App.config.workspaceAdmin || component.getAuthorLogin() === App.config.login) { + this.aclButton.show(); + } + + this.updateActionsButton(this.getPermission(component)); + + }, + + getPermission: function (component) { + if (component.isCheckout()) { + if (component.isCheckoutByConnectedUser()) { + return { + canCheckout: false, + canUndoAndCheckin: true + }; + } else { + return { + canCheckout: false, + canUndoAndCheckin: false + }; + } + } else { + return { + canCheckout: true, + canUndoAndCheckin: false + }; + } + }, + + getPermissionFromSeveral: function(list) { + var noneReleased = true; + var noneObsolete = true; + var permission = this.getPermission(list[0]); + var samePermission = true; + var that = this; + _.each(list, function (component) { + var permComponent = that.getPermission(component); + samePermission = samePermission && (permission.canCheckout === permComponent.canCheckout && + permComponent.canUndoAndCheckin === permComponent.canUndoAndCheckin); + noneReleased = noneReleased && !component.isReleased(); + noneObsolete = noneObsolete && !component.isObsolete(); + }); + return (noneReleased && noneObsolete && samePermission) ? permission : null; + }, + + onSeveralComponentsSelected: function (listComponent) { + var permission = this.getPermissionFromSeveral(_.pluck(listComponent,'model')); + if (permission !== null) { + this.checkoutGroup.show(); + this.updateActionsButton(permission); + } else { + this.hideCheckGroup(); + } + + this.hideACLButton(); + }, + + updateActionsButton: function (permission) { + this.checkoutButton.prop('disabled', !permission.canCheckout); + this.undoCheckoutButton.prop('disabled', !permission.canUndoAndCheckin); + this.checkinButton.prop('disabled', !permission.canUndoAndCheckin); + } + + }); + + return BomHeaderView; + +}); diff --git a/docdoku-web-front/app/product-structure/js/views/bom_item_view.js b/docdoku-web-front/app/product-structure/js/views/bom_item_view.js new file mode 100644 index 0000000000..e1b23fe099 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/bom_item_view.js @@ -0,0 +1,86 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/bom_item.html', + 'common-objects/views/part/part_modal_view', + 'common-objects/views/share/share_entity', + 'common-objects/utils/date' +], function (Backbone, Mustache, template, PartModalView, ShareView, date) { + 'use strict'; + var BomItemView = Backbone.View.extend({ + + tagName: 'tr', + + events: { + 'click td.modification_notification i': 'toPartModalOnNotificationsTab', + 'click .part_number': 'onPartClicked', + 'click td.part-revision-share i': 'sharePart', + 'click td.part-attached-files i': 'toPartModalOnFilesTab' + }, + + initialize: function () { + this.listenTo(this.model, 'sync, reset, change', this.render); + }, + + render: function () { + this.$el.html(Mustache.render(template, {model: this.model, i18n: App.config.i18n})); + this.$input = this.$('input'); + this.$('.author-popover').userPopover(this.model.getAuthorLogin(), this.model.getName(), 'left'); + if (this.model.isCheckout()) { + this.$('.checkout-user-popover').userPopover(this.model.getCheckOutUserLogin(), this.model.getNumber(), 'left'); + } + date.dateHelper(this.$('.date-popover')); + return this; + }, + + onPartClicked: function () { + var self = this; + self.model.fetch().success(function () { + new PartModalView({ + model: self.model + }); + }); + }, + + toPartModalOnNotificationsTab: function () { + var model = this.model; + model.fetch().success(function () { + var partModalView = new PartModalView({ + model: model + }); + partModalView.show(); + partModalView.activateNotificationsTab(); + }); + }, + + toPartModalOnFilesTab: function () { + var model = this.model; + model.fetch().success(function () { + var partModalView = new PartModalView({ + model: model + }); + partModalView.show(); + partModalView.activateFileTab(); + }); + }, + + sharePart: function () { + var shareView = new ShareView({model: this.model, entityType: 'parts'}); + window.document.body.appendChild(shareView.render().el); + shareView.openModal(); + }, + + isChecked: function () { + return this.$input[0].checked; + }, + + setSelectionState: function (state) { + this.$input[0].checked = state; + } + + }); + + return BomItemView; + +}); diff --git a/docdoku-web-front/app/product-structure/js/views/bom_view.js b/docdoku-web-front/app/product-structure/js/views/bom_view.js new file mode 100644 index 0000000000..5284485f60 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/bom_view.js @@ -0,0 +1,42 @@ +/*global define*/ +define(['backbone', 'views/bom_header_view', 'views/bom_content_view'], function (Backbone, BomHeaderView, BomContentView) { + 'use strict'; + var BomView = Backbone.View.extend({ + + render: function () { + this.bomContentView = new BomContentView().render(); + this.bomHeaderView = new BomHeaderView().render(); + this.listenTo(this.bomContentView, 'itemSelectionChanged',this.onSelectionChange); + this.listenTo(this.bomHeaderView, 'actionCheckout', this.bomContentView.actionCheckout); + this.listenTo(this.bomHeaderView, 'actionUndocheckout', this.bomContentView.actionUndocheckout); + this.listenTo(this.bomHeaderView, 'actionCheckin', this.bomContentView.actionCheckin); + this.listenTo(this.bomHeaderView, 'actionUpdateACL', this.bomContentView.actionUpdateACL); + return this; + }, + + onSelectionChange: function(list) { + this.trigger('checkbox:change'); + this.bomHeaderView.onSelectionChange(list); + }, + + uncheckAll: function(list) { + if(list.length) { + this.bomContentView.setCheckAll(false); + this.bomHeaderView.onSelectionChange([]); + } + this.bomHeaderView.onPathChange(list); + }, + + updateContent: function (component) { + this.bomContentView.update(component); + }, + + showRoot: function (component) { + this.bomContentView.showRoot(component); + } + + }); + + return BomView; + +}); diff --git a/docdoku-web-front/app/product-structure/js/views/collaborative_view.js b/docdoku-web-front/app/product-structure/js/views/collaborative_view.js new file mode 100644 index 0000000000..4044eee8e7 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/collaborative_view.js @@ -0,0 +1,221 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/collaborative_view.html', + 'common-objects/websocket/channelMessagesType' +], function (Backbone, Mustache, template, ChannelMessagesType) { + 'use strict'; + var CollaborativeView = Backbone.View.extend({ + + events: { + 'click a#collaborative_create': 'create', + 'click i.collaborative_kick': 'kick', + 'click i.collaborative_give_hand': 'giveHand', + 'click i.collaborative_withdraw_invitation': 'withdrawInvitation', + 'click i#collaborative_exit': 'exit', + 'click li': 'toggleExpandCommandsOnParticipant' + }, + + + initialize: function () { + Backbone.Events.on('SendCollaborativeInvite', this.sendInvite, this); + }, + + render: function () { + this.$el.html(Mustache.render(template, {master: this.master, roomKey: this.roomKey, users: this.users, pendingUsers: this.pendingUsers, i18n: App.config.i18n})); + if (!this.roomKey) { + this.$('a#collaborative_create').show(); + this.$('#collaborative_room').hide(); + } else { + this.$('a#collaborative_create').hide(); + this.$('#collaborative_room').show(); + if (this.isMaster) { + App.headerView.removeActionDisabled(); + App.appView.leaveSpectatorView(); + App.sceneManager.enableControlsObject(); + this.reduceAllCommands(); + } else { + App.headerView.addActionDisabled(); + App.sceneManager.disableControlsObject(); + App.appView.setSpectatorView(); + this.$('.fa-chevron-right').hide(); + this.$('.fa-chevron-left').hide(); + this.$('.collaborative_kick').hide(); + this.$('.collaborative_give_hand').hide(); + this.$('.collaborative_withdraw_invitation').hide(); + if (this.noMaster) { + this.$('i#collaborative_master').removeClass('master'); + this.$('i#collaborative_master').addClass('no-master'); + } + } + } + return this; + }, + create: function () { + App.mainChannel.sendJSON({ + type: ChannelMessagesType.COLLABORATIVE_CREATE, + remoteUser: '' + }); + }, + + sendInvite: function (user) { + if (this.isMaster) { + App.mainChannel.sendJSON({ + type: ChannelMessagesType.COLLABORATIVE_INVITE, + key: this.roomKey, + messageBroadcast: { + url: '#' + App.config.workspaceId + '/' + App.config.productId + '/config-spec/' + App.config.productConfigSpec, + context: App.config.workspaceId + }, + remoteUser: user + }); + } + }, + + invite: function () { + Backbone.Events.trigger('collaboration:invite'); + }, + + kick: function (e) { + var name = e.currentTarget.parentElement.id.substr(12); + App.mainChannel.sendJSON({ + type: ChannelMessagesType.COLLABORATIVE_KICK_USER, + key: this.roomKey, + remoteUser: name + }); + }, + + withdrawInvitation: function (e) { + var name = e.currentTarget.parentElement.id.substr(12); + App.mainChannel.sendJSON({ + type: ChannelMessagesType.COLLABORATIVE_WITHDRAW_INVITATION, + key: this.roomKey, + remoteUser: name, + messageBroadcast: { + context: App.config.workspaceId + } + }); + }, + + giveHand: function (e) { + var name = e.currentTarget.parentElement.id.substr(12); + App.mainChannel.sendJSON({ + type: ChannelMessagesType.COLLABORATIVE_GIVE_HAND, + key: this.roomKey, + remoteUser: name + }); + this.setMaster(name); + + //App.sceneManager.disableControlsObject(); + //App.appView.setSpectatorView(); + }, + + exit: function () { + if (this.isMaster) { + App.mainChannel.sendJSON({ + type: ChannelMessagesType.COLLABORATIVE_KILL, + key: this.roomKey, + remoteUser: 'null' + }); + } else { + App.mainChannel.sendJSON({ + type: ChannelMessagesType.COLLABORATIVE_EXIT, + key: this.roomKey, + remoteUser: 'null' + }); + App.appView.leaveSpectatorView(); + App.sceneManager.enableControlsObject(); + } + this.reset(); + }, + + roomCreated: function (key, master) { + this.setRoomKey(key); + this.setMaster(master); + + App.collaborativeController.sendSmartPath(App.partsTreeView.getSmartPath()); + App.collaborativeController.sendCameraInfos(); + App.collaborativeController.sendEditedObjects(); + App.collaborativeController.sendColourEditedObjects(); + App.collaborativeController.sendExplodeValue(App.$ControlsContainer.find('#slider-explode').val()); + + window.location.hash = [App.config.workspaceId , App.config.productId, 'config-spec', App.config.productConfigSpec, 'room', this.roomKey].join('/'); + + this.invite(); + }, + + reset: function () { + this.setRoomKey(null); + this.setMaster(null); + this.setMaster(null); + this.setUsers(null); + this.setPendingUsers(null); + }, + + setRoomKey: function (key) { + this.roomKey = key; + this.render(); + }, + + setMaster: function (master) { + this.master = master; + this.noMaster = false; + this.isMaster = (this.master === App.config.login); + this.render(); + }, + + setLastMaster: function (lastMaster) { + this.master = lastMaster; + this.noMaster = true; + this.isMaster = (this.lastMaster === App.config.login); + this.render(); + }, + + setUsers: function (users) { + this.users = users; + this.render(); + }, + + setPendingUsers: function (pendingUsers) { + this.pendingUsers = pendingUsers; + this.render(); + }, + + toggleExpandCommandsOnParticipant: function (e) { + if (this.isMaster) { + var el = e.currentTarget; + if (this.$(el).find('.fa-chevron-right').is(':visible')) { + this.expandCommandsOnParticipant(el); + } else { + this.reduceCommandsOnParticipant(el); + } + } + }, + + expandCommandsOnParticipant: function (el) { + this.$(el).find('.fa-chevron-right').hide(); + this.$(el).find('.fa-chevron-left').show(); + this.$(el).find('.collaborative_give_hand').show(); + this.$(el).find('.collaborative_kick').show(); + this.$(el).find('.collaborative_withdraw_invitation').show(); + }, + + reduceCommandsOnParticipant: function (el) { + this.$(el).find('.fa-chevron-right').show(); + this.$(el).find('.fa-chevron-left').hide(); + this.$(el).find('.collaborative_give_hand').hide(); + this.$(el).find('.collaborative_kick').hide(); + this.$(el).find('.collaborative_withdraw_invitation').hide(); + }, + + reduceAllCommands: function () { + this.$('.fa-chevron-right').show(); + this.$('.fa-chevron-left').hide(); + this.$('.collaborative_give_hand').hide(); + this.$('.collaborative_kick').hide(); + this.$('.collaborative_withdraw_invitation').hide(); + } + }); + return CollaborativeView; +}); diff --git a/docdoku-web-front/app/product-structure/js/views/component_views.js b/docdoku-web-front/app/product-structure/js/views/component_views.js new file mode 100644 index 0000000000..feaef7d2a9 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/component_views.js @@ -0,0 +1,412 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'common-objects/models/part', + 'common-objects/views/part/part_modal_view' +], function (Backbone, Part, PartModalView) { + 'use strict'; + var expandedViews = []; + var ComponentViews = {}; + + var nodeTemplate = _.template( + '<%if(path){%>'+ + '' + + '<%}%>'+ + '<%if(!isLock && !isForbidden) {%>' + + '<%if(isNode) {%>' + + '
                                    ' + + '<%}%>' + + '<%if(path){%>'+ + 'checked="checked"<%}%>>' + + '' + + '<%}%>'+ + '<%} else {%>' + + 'checked="checked"<%}%>>' + + '<%}%>' + + '' + + '<%}else{%>' + + '<%= number %>' + + '<%}%>' + + '' + + '<%if(isForbidden) {%> ' + + '' + + '<%} else if(isCheckoutByAnotherUser && isLastIteration) {%> ' + + '' + + '<%} else if(isCheckoutByConnectedUser && isLastIteration) {%> ' + + ' ' + + '<%} else if(isReleased){%> ' + + '' + + '<%} else if(isObsolete){%> ' + + '' + + '<%} else if(path) {%> ' + + '' + + '<%}%>'+ + '<%if(hasUnreadModificationNotifications) {%> ' + + '' + + '<%}%>' + + '<%if(hasPathData) {%> ' + + '' + + '<%}%>' + + '<%if (partUsageLinkReferenceDescription) {%> <%= partUsageLinkReferenceDescription %> <%}%>' + ); + + ComponentViews.Components = Backbone.View.extend({ + + tagName: 'ul', + + initialize: function () { + this.options.parentView.append(this.el); + this.componentViews = []; + this.expandedViews = []; + if (this.collection.isEmpty()) { + this.listenTo(this.collection, 'reset', this.addAllComponentsView) + .listenTo(this.collection, 'add', this.addComponentView); + this.collection.fetch({reset: true}); + } else { + this.addAllComponentsView(); + } + }, + + addAllComponentsView: function () { + this.collection.each(this.addComponentView, this); + }, + + addComponentView: function (component) { + + var isLast = component === this.collection.last(); + + var optionsForComponentView = { + model: component, + isLast: isLast, + checkedAtInit: this.options.parentChecked || App.partsTreeView.smartPath.indexOf(component.getPath()) !== -1, + resultPathCollection: this.options.resultPathCollection + }; + + var componentView = component.isAssembly() || App.config.linkType ? new ComponentViews.Assembly(optionsForComponentView) : new ComponentViews.Leaf(optionsForComponentView); + + this.$el.append(componentView.render().el); + + // expand the assembly if it was expanded before redraw && _.contains(expandedViews,component.attributes.number) + if (component.isAssembly() && _.contains(expandedViews, component.attributes.number)) { + componentView.onToggleExpand(); + } + + this.componentViews.push(componentView); + }, + + fetchAll: function () { + this.$el.empty(); + this.collection.fetch({reset: true}); + }, + + setChecked: function(status) { + this.$el.find('.selectable-part-checkbox').prop('checked',status); + } + + }); + + ComponentViews.Leaf = Backbone.View.extend({ + + tagName: 'li', + + events: { + 'click a': 'onComponentSelected', + 'change input.load-3D:first': 'onLoad3D', + 'change input.selectable-part-checkbox:first': 'selectPart', + 'click .openModal:first': 'onEditPart', + 'click > .fa-asterisk': 'openPathDataModal' + }, + + initialize: function () { + _.bindAll(this, ['onLoad3D']); + this.listenTo(this.options.resultPathCollection, 'reset', this.onAllResultPathAdded); + this.$el.attr('id', 'path_' + String(this.model.getEncodedPath())); + this.isForbidden = this.model.isForbidden(); + this.isLock = this.model.isCheckout() && this.model.isLastIteration(this.model.get('iteration')) && !this.model.isCheckoutByConnectedUser(); + }, + + onAllResultPathAdded: function () { + var isInResultPaths = this.options.resultPathCollection.contains(this.model.attributes.partUsageLinkId); + this.$el.toggleClass('resultPath',isInResultPaths); + }, + + selectPart:function(e){ + this.$el.trigger('checkbox:selected', [e.target.checked,this.model]); + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + onLoad3D: function (event) { + if (event.target.checked) { + App.instancesManager.loadComponent(this.model); + } + else { + App.instancesManager.unLoadComponent(this.model); + } + }, + + render: function () { + + var data = { + isNode:false, + number: this.model.attributes.number, + name: this.model.attributes.name, + amount: this.model.getAmount(), + version: this.model.getVersion(), + iteration: this.model.getIteration(), + isLastIteration: this.model.isLastIteration( this.model.getIteration()), + unit: this.model.getUnit(), + checkedAtInit: this.options.checkedAtInit, + isForbidden: this.model.isForbidden(), + isCheckoutByAnotherUser: this.model.isCheckout() && !this.model.isCheckoutByConnectedUser(), + isCheckoutByConnectedUser: this.model.isCheckout() && this.model.isCheckoutByConnectedUser(), + hasUnreadModificationNotifications: this.model.hasUnreadModificationNotifications(), + isReleased: this.model.isReleased(), + isObsolete: this.model.isObsolete(), + isLock: this.isLock, + partUsageLinkReferenceDescription: this.model.getPartUsageLinkReferenceDescription(), + isSubstitute: this.model.isSubstitute(), + isOptional:this.model.isOptional(), + hasSubstitutes : this.model.hasSubstitutes(), + hasPathData:this.model.hasPathData(), + path:this.model.getEncodedPath() + }; + + this.$el.html(nodeTemplate(data)); + + this.input = this.$('input.load-3D').first(); + this.checkbox = this.$('.selectable-part-checkbox'); + + //If the ComponentViews is checked + if(this.options.checkedAtInit){ + App.instancesManager.loadComponent(this.model); + } + + if (this.options.isLast) { + this.$el.addClass('last'); + } + + this.onAllResultPathAdded(); + + return this; + }, + + onComponentSelected: function (e) { + e.stopPropagation(); + this.$('>a').trigger('component:selected', [this.model, this.$el]); + + }, + + onEditPart: function () { + var model = new Part({partKey: this.model.getNumber() + '-' + this.model.getVersion()}); + var iteration = this.model.getIteration(); + + model.fetch().success(function () { + new PartModalView({ + model: model, + iteration: iteration, + productId: App.config.productId, + productConfigSpec: ['wip','latest','latest-released'].indexOf(App.config.productConfigSpec)===-1 ? App.config.productConfigSpec : null + }).show(); + }); + + }, + + openPathDataModal : function () { + Backbone.Events.trigger('path-data:clicked',this.model); + } + }); + + ComponentViews.Assembly = Backbone.View.extend({ + + tagName: 'li', + + className: 'expandable', + + events: { + 'click a:first': 'onComponentSelected', + 'click .openModal:first': 'onEditPart', + 'change input.load-3D:first': 'onLoad3D', + 'change input.selectable-part-checkbox:first': 'selectPart', + 'click .hitarea:first': 'onToggleExpand', + 'click > .fa-asterisk': 'openPathDataModal' + }, + + initialize: function () { + this.isExpanded = false; + _.bindAll(this, ['onLoad3D']); + this.listenTo(this.options.resultPathCollection, 'reset', this.onAllResultPathAdded); + this.$el.attr('id', 'path_' + this.model.getEncodedPath()); + this.isForbidden = this.model.isForbidden(); + this.isLock = this.model.isCheckout() && this.model.isLastIteration(this.model.get('iteration')) && !this.model.isCheckoutByConnectedUser(); + }, + + onAllResultPathAdded: function () { + var isInResultPaths = this.options.resultPathCollection.contains(this.model.attributes.partUsageLinkId); + this.$el.toggleClass('resultPath',isInResultPaths); + }, + + selectPart:function(e){ + this.$el.trigger('checkbox:selected', [e.target.checked,this.model]); + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + openPathDataModal : function () { + Backbone.Events.trigger('path-data:clicked',this.model); + }, + onLoad3D: function (event) { + if (event) { + if (event.target.checked) { + App.instancesManager.loadComponent(this.model); + } + else { + App.instancesManager.unLoadComponent(this.model); + } + } + }, + + render: function () { + + var data = { + isNode:true, + number: this.model.attributes.number, + name: this.model.attributes.name, + version: this.model.getVersion(), + iteration: this.model.getIteration(), + isLastIteration: this.model.isLastIteration( this.model.getIteration()), + amount: this.model.getAmount(), + unit: this.model.getUnit(), + checkedAtInit: this.options.checkedAtInit, + isForbidden: this.isForbidden, + isCheckoutByAnotherUser: this.model.isCheckout() && !this.model.isCheckoutByConnectedUser(), + isCheckoutByConnectedUser: this.model.isCheckout() && this.model.isCheckoutByConnectedUser(), + hasUnreadModificationNotifications: this.model.hasUnreadModificationNotifications(), + isReleased: this.model.isReleased(), + isObsolete: this.model.isObsolete(), + isLock: this.isLock, + partUsageLinkReferenceDescription: this.model.getPartUsageLinkReferenceDescription(), + isSubstitute: this.model.isSubstitute(), + isOptional:this.model.isOptional(), + hasSubstitutes : this.model.hasSubstitutes(), + hasPathData:this.model.hasPathData(), + path:this.model.getEncodedPath() + }; + + this.$el.html(nodeTemplate(data)); + + this.input = this.$('input.load-3D').first(); + this.checkbox = this.$('.selectable-part-checkbox'); + + //If the ComponentViews is checked + if(this.options.checkedAtInit && (!App.collaborativeView || !App.collaborativeView.roomKey)){ + App.instancesManager.loadComponent(this.model); + } + + if (data.isForbidden || data.isLock) { + this.$el.removeClass('expandable'); + } + + if (this.options.isLast) { + if (data.isForbidden || data.isLock) { + this.$el.addClass('last'); + } else { + this.$el.addClass('lastExpandable') + .children('.hitarea') + .addClass('lastExpandable-hitarea'); + } + } + + this.onAllResultPathAdded(); + + + if (_.contains(this.expandedViews, this.model.attributes.number)) { + this.onToggleExpand(); + } + + return this; + }, + + onToggleExpand: function () { + if (!this.hasChildrenNodes()) { + new ComponentViews.Components({ + collection: this.model.children, + parentView: this.$el, + parentChecked: this.isChecked(), + resultPathCollection: this.options.resultPathCollection + }); + } + this.toggleExpand(); + }, + + toggleExpand: function () { + if (!this.isForbidden && !this.isLock) { + this.$el.toggleClass('expandable collapsable') + .children('.hitarea') + .toggleClass('expandable-hitarea collapsable-hitarea'); + + if (this.options.isLast) { + this.$el.toggleClass('lastExpandable lastCollapsable') + .children('.hitarea') + .toggleClass('lastExpandable-hitarea lastCollapsable-hitarea'); + } + + this.isExpanded = !this.isExpanded; + + var childrenNode = this.$('>ul'); + + if (this.isExpanded) { + childrenNode.show(); + expandedViews.push(this.model.attributes.number); + } else { + childrenNode.hide(); + expandedViews = _(expandedViews).without(this.model.attributes.number); + } + } + }, + + hasChildrenNodes: function () { + var childrenNode = this.$('>ul'); + return childrenNode.length > 0; + }, + + onComponentSelected: function (e) { + e.stopPropagation(); + this.$('>a').trigger('component:selected', [this.model, this.$el]); + }, + + isChecked: function () { + return this.input.prop('checked'); + }, + + onEditPart: function () { + var model = new Part({partKey: this.model.getNumber() + '-' + this.model.getVersion()}); + var iteration = this.model.getIteration(); + + model.fetch().success(function () { + new PartModalView({ + model: model, + iteration: iteration, + productId: App.config.productId, + productConfigSpec: ['wip','latest','latest-released'].indexOf(App.config.productConfigSpec)===-1 ? App.config.productConfigSpec : null + }).show(); + }); + + } + }); + + return ComponentViews; +}); diff --git a/docdoku-web-front/app/product-structure/js/views/control_clipping_view.js b/docdoku-web-front/app/product-structure/js/views/control_clipping_view.js new file mode 100644 index 0000000000..e1a72482eb --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/control_clipping_view.js @@ -0,0 +1,41 @@ +/*global define,App*/ +define( + [ + 'backbone', + 'mustache', + 'text!templates/control_clipping.html' + ], + function (Backbone, Mustache, template) { + + 'use strict'; + + var ControlOptionsView = Backbone.View.extend({ + + className: 'side_control_group', + + events: { + 'input input#slider-clipping': 'clipping' + }, + + initialize: function () { + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + return this; + }, + + clipping: function (e) { + var value = e.target.value; + App.collaborativeController.sendClippingValue(value); + // I remove the clipping for the last quarter of the scene to be more accurate + var max = App.SceneOptions.cameraFar * 3 / 4; + var percentage = value * Math.log(max) / 100; // cross product to set a value to pass to the exponential function + App.sceneManager.setCameraNear(Math.exp(percentage)); + } + + }); + + return ControlOptionsView; + + }); diff --git a/docdoku-web-front/app/product-structure/js/views/control_explode_view.js b/docdoku-web-front/app/product-structure/js/views/control_explode_view.js new file mode 100644 index 0000000000..b46b27d048 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/control_explode_view.js @@ -0,0 +1,36 @@ +/*global define,App*/ +define( + [ + 'backbone', + 'mustache', + 'text!templates/control_explode.html' + ], + function (Backbone, Mustache, template) { + + 'use strict'; + + var ControlOptionsView = Backbone.View.extend({ + + className: 'side_control_group', + + events: { + 'input input#slider-explode': 'explode' + }, + + initialize: function () { + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + return this; + }, + + explode: function (e) { + App.sceneManager.explodeScene(e.target.value); + } + + }); + + return ControlOptionsView; + + }); diff --git a/docdoku-web-front/app/product-structure/js/views/control_layers_view.js b/docdoku-web-front/app/product-structure/js/views/control_layers_view.js new file mode 100644 index 0000000000..b4b3571b73 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/control_layers_view.js @@ -0,0 +1,27 @@ +/*global define,App*/ +define( + [ + 'backbone', + 'mustache', + 'text!templates/control_layers.html' + ], function (Backbone, Mustache, template) { + + 'use strict'; + + var ControlLayersView = Backbone.View.extend({ + + className: 'side_control_group', + + initialize: function () { + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + return this; + } + + }); + + return ControlLayersView; + + }); diff --git a/docdoku-web-front/app/product-structure/js/views/control_markers_view.js b/docdoku-web-front/app/product-structure/js/views/control_markers_view.js new file mode 100644 index 0000000000..23aea763cf --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/control_markers_view.js @@ -0,0 +1,42 @@ +/*global define,App*/ +define([ 'backbone', 'mustache', 'text!templates/control_markers.html'], function (Backbone, Mustache, template) { + + 'use strict'; + + var ControlMarkersView = Backbone.View.extend({ + + className: 'side_control_group', + + events: { + 'click button#markerZoomLess': 'markerZoomLess', + 'click button#markerState': 'markerState', + 'click button#markerZoomMore': 'markerZoomMore' + }, + + initialize: function () { + }, + + markerZoomLess: function () { + App.sceneManager.layerManager.markerScale.addScalar(-App.sceneManager.layerManager.markerScale.x/2); + App.sceneManager.layerManager.rescaleMarkers(); + }, + markerState: function () { + App.sceneManager.layerManager.changeMarkerState(); + }, + + markerZoomMore: function () { + App.sceneManager.layerManager.markerScale.addScalar(App.sceneManager.layerManager.markerScale.x/2); + App.sceneManager.layerManager.rescaleMarkers(); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + return this; + } + + + }); + + return ControlMarkersView; + +}); diff --git a/docdoku-web-front/app/product-structure/js/views/control_measure_view.js b/docdoku-web-front/app/product-structure/js/views/control_measure_view.js new file mode 100644 index 0000000000..e2fee8790c --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/control_measure_view.js @@ -0,0 +1,50 @@ +/*global _,define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/control_measure.html' +], +function (Backbone, Mustache, template) { + 'use strict'; + var MeasureOptionsView = Backbone.View.extend({ + + className: 'side_control_group', + + events: { + 'click .clear-measures-btn':'onClearClicked' + }, + + initialize: function () { + _.bindAll(this); + this.state = false; + Backbone.Events.on('measure:drawn', this.displayClearButton); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.$switch = this.$('.measure-switch'); + this.$switch.bootstrapSwitch(); + this.$switch.bootstrapSwitch('setState', this.state); + this.$switch.on('switch-change', this.switchMeasureState); + return this; + }, + + switchMeasureState: function (e, data) { + this.state = data.value; + App.sceneManager.setMeasureState(data.value); + }, + + onClearClicked: function(/*e*/){ + App.sceneManager.clearMeasures(); + this.$('.clear-measures-btn').removeClass('display'); + }, + + displayClearButton: function(){ + this.$('.clear-measures-btn').addClass('display'); + } + + }); + + return MeasureOptionsView; + +}); diff --git a/docdoku-web-front/app/product-structure/js/views/control_modes_view.js b/docdoku-web-front/app/product-structure/js/views/control_modes_view.js new file mode 100644 index 0000000000..3000f8af61 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/control_modes_view.js @@ -0,0 +1,57 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'views/shortcuts_view', + 'text!templates/control_modes.html' +], function (Backbone, Mustache, ShortcutsView, template) { + 'use strict'; + var ControlModesView = Backbone.View.extend({ + className: 'side_control_group', + + events: { + 'click button#flying_mode_view_btn': 'flyingView', + 'click button#tracking_mode_view_btn': 'trackingView', + 'click button#orbit_mode_view_btn': 'orbitView' + }, + + flyingView: function () { + if(App.sceneManager){ + App.sceneManager.setPointerLockControls(); + } + this.$flyingModeButton.addClass('active'); + }, + + trackingView: function () { + if(App.sceneManager){ + App.sceneManager.setTrackBallControls(); + } + this.$trackingModeButton.addClass('active'); + }, + + orbitView: function () { + if(App.sceneManager){ + App.sceneManager.setOrbitControls(); + } + this.$orbitModeButton.addClass('active'); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.shortcutsview = new ShortcutsView().render(); + this.$('.nav-header').after(this.shortcutsview.$el); + this.bindDomElements(); + return this; + }, + + bindDomElements: function(){ + this.$flyingModeButton = this.$('button#flying_mode_view_btn'); + this.$orbitModeButton = this.$('button#orbit_mode_view_btn'); + this.$trackingModeButton = this.$('button#tracking_mode_view_btn'); + } + + }); + + return ControlModesView; + +}); diff --git a/docdoku-web-front/app/product-structure/js/views/control_navigation_view.js b/docdoku-web-front/app/product-structure/js/views/control_navigation_view.js new file mode 100644 index 0000000000..76ca4d7c2a --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/control_navigation_view.js @@ -0,0 +1,55 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/control_navigation.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var ControlNavigationView = Backbone.View.extend({ + + className: 'side_control_group', + + events: { + 'click button#fly_to': 'flyTo', + 'click button#look_at_or_best_fit': 'lookAtOrBestFit', + 'click button#reset_camera': 'resetCamera' + }, + + setObject: function (object) { + this.$('button#look_at_or_best_fit').attr('title', App.config.i18n.LOOK_AT); + this.$('button#fly_to').removeAttr('disabled'); + this.object = object; + }, + + reset: function () { + this.$('button#look_at_or_best_fit').attr('title', App.config.i18n.BEST_FIT_VIEW); + this.$('button#fly_to').attr('disabled', 'disabled'); + this.object = null; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.reset(); + return this; + }, + + flyTo: function () { + App.sceneManager.flyTo(this.object); + }, + + lookAtOrBestFit: function () { + if(this.object){ + App.sceneManager.lookAt(this.object); + }else{ + App.sceneManager.bestFitView(); + } + }, + + resetCamera: function () { + App.sceneManager.resetCameraPlace(); + } + + }); + + return ControlNavigationView; +}); diff --git a/docdoku-web-front/app/product-structure/js/views/control_options_view.js b/docdoku-web-front/app/product-structure/js/views/control_options_view.js new file mode 100644 index 0000000000..427bd72788 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/control_options_view.js @@ -0,0 +1,47 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/control_options.html' +], function (Backbone, Mustache, template) { + 'use strict'; + + var ControlOptionsView = Backbone.View.extend({ + className: 'side_control_group', + + events: { + 'click button#gridSwitch': 'gridSwitch', + 'click button#screenshot': 'takeScreenShot', + 'click button#show_edited_meshes': 'showEditedMeshes' + }, + + gridSwitch: function () { + var gridSwitch = this.$('#gridSwitch'); + gridSwitch.toggleClass('active'); + App.SceneOptions.grid = !!gridSwitch.hasClass('active'); + }, + + takeScreenShot: function () { + App.sceneManager.takeScreenShot(); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + return this; + }, + + showEditedMeshes: function () { + var showEditedMeshes = this.$('#show_edited_meshes'); + showEditedMeshes.toggleClass('active'); + if (showEditedMeshes.hasClass('active')) { + App.sceneManager.colourEditedObjects(); + } else { + App.sceneManager.cancelColourEditedObjects(); + } + } + + + }); + + return ControlOptionsView; +}); diff --git a/docdoku-web-front/app/product-structure/js/views/control_transform_view.js b/docdoku-web-front/app/product-structure/js/views/control_transform_view.js new file mode 100644 index 0000000000..fb11367413 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/control_transform_view.js @@ -0,0 +1,69 @@ +/*global define,App*/ +define( + [ + 'backbone', + 'mustache', + 'text!templates/control_transform.html' + ], function (Backbone, Mustache, template) { + + 'use strict'; + + var ControlTransformView = Backbone.View.extend({ + + className: 'side_control_group', + + events: { + 'click #transform_mode_view_btn > button': 'transformView', + 'click button#cancel_transformation': 'cancelTransformation' + }, + + initialize: function () { + this.object = undefined; + }, + + setObject: function (object) { + if (App.sceneManager.transformControlsEnabled()) { + App.sceneManager.deleteTransformControls(this.object); + App.sceneManager.setTransformControls(object); + } + this.$('button').removeAttr('disabled'); + this.object = object; + return this; + }, + + reset: function () { + + // TransformControls enabled + if (App.sceneManager.transformControlsEnabled()) { + var mode = App.sceneManager.getTransformControlsMode(); + this.$('button#' + mode).addClass('active'); + } + else if (!this.object) { + this.$('button').attr('disabled', 'disabled'); + } + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.reset(); + return this; + }, + + transformView: function (e) { + var modeSelected = e.currentTarget.id; + + if (modeSelected === App.sceneManager.getTransformControlsMode()) { + App.sceneManager.leaveTransformMode(); + } else { + App.sceneManager.setTransformControls(this.object, modeSelected); + } + }, + + cancelTransformation: function () { + App.sceneManager.cancelTransformation(this.object); + } + + }); + + return ControlTransformView; + }); diff --git a/docdoku-web-front/app/product-structure/js/views/controls_infos_modal_view.js b/docdoku-web-front/app/product-structure/js/views/controls_infos_modal_view.js new file mode 100644 index 0000000000..3e30e2c3b0 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/controls_infos_modal_view.js @@ -0,0 +1,52 @@ +/*global define,App,_*/ +define([ + 'backbone', + 'mustache', + 'text!templates/controls_infos_modal.html' + ], + + function (Backbone, Mustache, template) { + + 'use strict'; + + var ControlsInfosModalView = Backbone.View.extend({ + + events: { + 'hidden #controlsInfosModal': 'onHidden' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + if (this.options.isPLC) { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, isPLC: true})); + } else if (this.options.isTBC) { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, isTBC: true})); + } else if (this.options.isORB) { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, isORC: true})); + } + + this.$modal = this.$('#controlsInfosModal'); + + return this; + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + } + + }); + + return ControlsInfosModalView; + } +); diff --git a/docdoku-web-front/app/product-structure/js/views/export_scene_modal_view.js b/docdoku-web-front/app/product-structure/js/views/export_scene_modal_view.js new file mode 100644 index 0000000000..a7e9dca9a9 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/export_scene_modal_view.js @@ -0,0 +1,52 @@ +/*global define,App,_*/ +define([ + 'backbone', + 'mustache', + 'text!templates/export_scene_modal.html' + ], function (Backbone, Mustache, template) { + + 'use strict'; + + var ExportSceneModalView = Backbone.View.extend({ + + events: { + 'hidden #exportSceneModal': 'onHidden', + 'click textarea': 'onClickTextArea' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.$modal = this.$('#exportSceneModal'); + this.$textarea = this.$('textarea'); + this.$link = this.$('.frame-link'); + return this; + }, + + openModal: function () { + this.$modal.modal('show'); + this.$textarea.text(''); + this.$link.attr('href',this.options.iframeSrc); + + }, + + onClickTextArea: function () { + this.$textarea.select(); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + } + + }); + + return ExportSceneModalView; + } +); diff --git a/docdoku-web-front/app/product-structure/js/views/layer-header-view.js b/docdoku-web-front/app/product-structure/js/views/layer-header-view.js new file mode 100644 index 0000000000..9edd405509 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/layer-header-view.js @@ -0,0 +1,41 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/layer_controls.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var LayerHeaderView = Backbone.View.extend({ + tagName: 'div', + className: 'btn-group', + + events: { + 'click button.toggleAllShow': 'toggleAllShow', + 'click button.addLayer': 'addLayer' + }, + + initialize: function () { + this.allShown = true; + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.$el.toggleClass('shown', this.allShown); + return this; + }, + + toggleAllShow: function () { + this.allShown = !this.allShown; + this.render(); + this.$el.trigger('layers:setAllShown', this.allShown); + }, + + addLayer: function () { + this.$el.trigger('layers:addLayer'); + } + + }); + + return LayerHeaderView; + +}); diff --git a/docdoku-web-front/app/product-structure/js/views/layer-item-view.js b/docdoku-web-front/app/product-structure/js/views/layer-item-view.js new file mode 100644 index 0000000000..7d5cfafa02 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/layer-item-view.js @@ -0,0 +1,98 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/layer_item.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var LayerItemView = Backbone.View.extend({ + tagName: 'li', + + events: { + 'click i.start': 'toggleShow', + 'dblclick': 'startEditingName', + 'blur .edit': 'stopEditingName', + 'keypress .edit': 'stopEditingNameOnEnter', + 'click i.end': 'toggleEditingMarkers', + 'click i.fa-times': 'removeLayer', + 'change input[type="color"]':'colorChanged' + }, + + initialize: function () { + this.listenTo(this.model, 'destroy', this.remove) + .listenTo(this.model, 'change:editingName change:editingMarkers change:shown change:color', this.render) + .listenTo(this.model.getMarkers(), 'add remove reset', this.render); + }, + + render: function () { + this.$el.html(Mustache.render(template, this.model)); + this.$el.toggleClass('shown', this.model.get('shown')); + var editingName = this.model.get('editingName'); + this.$el.toggleClass('editingName', editingName); + this.input = this.$('.edit'); + if (editingName) { + this.input.focus(); + } + this.$el.toggleClass('editingMarkers', this.model.get('editingMarkers')); + return this; + }, + + toggleShow: function () { + this.model.toggleShow(); + }, + + toggleEditingMarkers: function () { + this.model.toggleEditingMarkers(); + }, + + startEditingName: function () { + this.model.setEditingName(true); + }, + + stopEditingName: function () { + var value = this.input.val(); + if (this.model.get('name') !== value) { + this.model.save({ + name: value, + editingName: false + }, + {success: function () { + App.collaborativeController.sendLayersRefresh('edit layer name'); + }} + ); + } else { + this.model.set('editingName', false); + } + }, + + stopEditingNameOnEnter: function (e) { + if (e.keyCode === 13) { + this.input[0].blur(); + } + }, + + removeLayer: function () { + var collection = this.model.collection; + this.model.setEditingMarkers(false); + this.model.destroy({ + dataType: 'text', // server doesn't send a json hash in the response body + success: function () { + App.collaborativeController.sendLayersRefresh('layer:remove'); + } + }); + if (collection.length === 0) { + collection.onEmpty(); + } + + }, + colorChanged:function(e){ + this.model.setColor(e.target.value.replace('#','')); + App.sceneManager.reDraw(); + this.model.save(); + } + + }); + + return LayerItemView; + +}); diff --git a/docdoku-web-front/app/product-structure/js/views/layers-list-view.js b/docdoku-web-front/app/product-structure/js/views/layers-list-view.js new file mode 100644 index 0000000000..ad9cfd64d4 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/layers-list-view.js @@ -0,0 +1,87 @@ +/*global define,App*/ +define([ + 'backbone', + 'views/layer-item-view', + 'views/layer-header-view' +], function (Backbone, LayerItemView, LayerHeaderView) { + 'use strict'; + var LayersListView = Backbone.View.extend({ + el: 'div#layer-wrapper', + + events: { + 'layers:setAllShown': 'setAllShown', + 'layers:addLayer': 'addLayer' + }, + + initialize: function () { + this.listContainer = this.$('nav > ul'); + this.listenTo(this.collection, 'add', this.addOne) + .listenTo(this.collection, 'remove', this.onRemove) + .listenTo(this.collection, 'reset', this.addAll); + this.collection.fetch({reset: true}); + }, + + refreshLayers: function () { + App.layersListView.collection.fetch({reset: true}); + }, + + refreshMarkers: function () { + App.layersListView.collection.invoke('_removeAllMarkersFromScene'); + App.layersListView.collection.fetch({reset: true}); + }, + + addAll: function () { + this.listContainer.empty(); + if (this.collection.length > 0) { + this.collection.each(this.addOne, this); + } else { + this.addEmptyView(); + } + }, + + onRemove: function () { + if (this.collection.length <= 0) { + this.listContainer.empty(); + this.addEmptyView(); + } + }, + + addOne: function (layer) { + /* if this is the first layer, remove the empty view */ + if (this.collection.length === 1) { + this.listContainer.empty(); + } + var layerItemView = new LayerItemView({model: layer}); + this.listContainer.prepend(layerItemView.render().el); + }, + + render: function () { + this.$el.prepend(new LayerHeaderView().render().el); + if (this.collection.isEmpty()) { + this.addEmptyView(); + } + return this; + }, + + templateEmptyView: '
                                  • ' + App.config.i18n.NO_LAYERS + '
                                  • ', + + addEmptyView: function () { + this.listContainer.append(this.templateEmptyView); + }, + + setAllShown: function (e, allShown) { + e.stopPropagation(); + this.collection.setAllShown(allShown); + }, + + addLayer: function (e) { + e.stopPropagation(); + var layer = App.sceneManager.layerManager.createLayer(); + layer.set('editingName', true); + } + + }); + + return LayersListView; + +}); diff --git a/docdoku-web-front/app/product-structure/js/views/marker_create_modal_view.js b/docdoku-web-front/app/product-structure/js/views/marker_create_modal_view.js new file mode 100644 index 0000000000..397b53be6c --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/marker_create_modal_view.js @@ -0,0 +1,56 @@ +/*global define,App,_*/ +define([ + 'backbone', + 'mustache', + 'text!templates/marker_create_modal.html' + ], + + function (Backbone, Mustache, template) { + + 'use strict'; + + var MarkerCreateModalView = Backbone.View.extend({ + + events: { + 'submit form#save-marker': 'saveMarker', + 'hidden #creationMarkersModal': 'onHidden' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.$modal = this.$('#creationMarkersModal'); + this.$markersModalInputName = this.$('input[name=makerName]'); + this.$markersModalInputDescription = this.$('textarea'); + + return this; + }, + + saveMarker: function (e) { + this.model.createMarker(this.$markersModalInputName.val(), this.$markersModalInputDescription.val(), this.options.intersectPoint.x, this.options.intersectPoint.y, this.options.intersectPoint.z); + this.closeModal(); + e.preventDefault(); + return false; + }, + + openModal: function () { + this.$modal.modal('show'); + this.$markersModalInputName.focus(); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + } + + }); + + return MarkerCreateModalView; + } +); diff --git a/docdoku-web-front/app/product-structure/js/views/marker_info_modal_view.js b/docdoku-web-front/app/product-structure/js/views/marker_info_modal_view.js new file mode 100644 index 0000000000..5779763edf --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/marker_info_modal_view.js @@ -0,0 +1,56 @@ +/*global define,App,_*/ +define([ + 'backbone', + 'mustache', + 'text!templates/marker_info_modal.html' +], function (Backbone, Mustache, template) { + 'use strict'; + var MarkerInfoModalView = Backbone.View.extend({ + + events: { + 'click .destroy-marker-btn': 'destroyMarker', + 'hidden #markerModal': 'onHidden' + }, + + initialize: function () { + _.bindAll(this); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n, title: this.model.getTitle()})); + this.$modal = this.$('#markerModal'); + this.$('#markerDesc').html(this.model.getDescription().nl2br()); + + return this; + }, + + destroyMarker: function () { + if (this.model) { + this.model.destroy({ + dataType: 'text', // server doesn't send a json hash in the response body + success: function () { + if(App.collaborativeController){ + App.collaborativeController.sendMarkersRefresh('remove marker'); + } + } + }); + } + this.closeModal(); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + } + + }); + + return MarkerInfoModalView; +}); diff --git a/docdoku-web-front/app/product-structure/js/views/part_instance_view.js b/docdoku-web-front/app/product-structure/js/views/part_instance_view.js new file mode 100644 index 0000000000..01d81ccffd --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/part_instance_view.js @@ -0,0 +1,69 @@ +/*global define,App*/ +define(['backbone', 'mustache', 'text!templates/part_instance.html'], +function (Backbone, Mustache, template) { + 'use strict'; + var PartInstanceView = Backbone.View.extend({ + + tagName: 'div', + + id: 'part_instance_container', + + events: { + 'click button#fly_to': 'flyTo', + 'click button#look_at': 'lookAt', + 'click #transform_mode_view_btn > button': 'transformView', + 'click button#cancel_transformation': 'cancelTransformation' + }, + + className: 'side_control_group', + + initialize: function () { + + }, + + setObject: function (object) { + if (App.sceneManager.transformControlsEnabled()) { + App.sceneManager.deleteTransformControls(this.object); + App.sceneManager.setTransformControls(object); + + } + this.object = object; + return this; + }, + + render: function () { + this.$el.html(Mustache.render(template, {object: this.object, i18n: App.config.i18n})); + if (App.sceneManager.transformControlsEnabled()) { + var mode = App.sceneManager.getTransformControlsMode(); + this.$('button#' + mode).addClass('active'); + + } + return this; + }, + + reset: function () { + if (! App.sceneManager.transformControlsEnabled()) { + this.$el.empty(); + } + }, + + flyTo: function () { + App.sceneManager.flyTo(this.object); + }, + + lookAt: function () { + App.sceneManager.lookAt(this.object); + }, + + transformView: function (e) { + App.sceneManager.setTransformControls(this.object, e.currentTarget.id); + }, + + cancelTransformation: function () { + App.sceneManager.cancelTransformation(this.object); + } + + }); + + return PartInstanceView; +}); diff --git a/docdoku-web-front/app/product-structure/js/views/part_metadata_view.js b/docdoku-web-front/app/product-structure/js/views/part_metadata_view.js new file mode 100644 index 0000000000..127a3dbed2 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/part_metadata_view.js @@ -0,0 +1,45 @@ +/*global define,App*/ + +define([ 'backbone', 'mustache', 'text!templates/part_meta_data.html'], function (Backbone, Mustache, template) { + + 'use strict'; + + var PartMetadataView = Backbone.View.extend({ + + tagName: 'div', + + events: { + 'click .author-join': 'authorClicked' + }, + + className: 'side_control_group part_metadata_container', + + initialize: function () { + this.listenTo(this.model, 'change', this.render); + }, + + setModel: function (model) { + this.model = model; + return this; + }, + + render: function () { + var permalink = this.model.getPermalink ? this.model.getPermalink() : ('/parts/#' + App.config.workspaceId + '/' + this.model.getNumber() + '/' + this.model.getVersion()); + this.$el.html(Mustache.render(template, {model: this.model, i18n: App.config.i18n, permalink: permalink})); + return this; + }, + + authorClicked: function () { + if (this.model.getAuthorLogin() !== App.config.login) { + Backbone.Events.trigger('NewChatSession', {remoteUser: this.model.getAuthorLogin(), context: this.model.getNumber()}); + } + }, + + reset: function () { + this.$el.empty(); + } + + }); + + return PartMetadataView; +}); diff --git a/docdoku-web-front/app/product-structure/js/views/parts_tree_view.js b/docdoku-web-front/app/product-structure/js/views/parts_tree_view.js new file mode 100644 index 0000000000..f97994ada1 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/parts_tree_view.js @@ -0,0 +1,235 @@ +/*global _,define,App*/ +define(['backbone', 'models/component_module', 'views/component_views' +], function (Backbone, ComponentModule, ComponentViews) { + 'use strict'; + + var PartsTreeView = Backbone.View.extend({ + el: '#product_nav_list', + + events: { + 'change input': 'checkChildrenInputs', + 'change li': 'checkParentsInputs', + 'component:selected a': 'onComponentSelected', + 'click #product_title .product_title': 'onProductTitleClicked', + 'load:root': 'onProductTitleClicked', + 'click .fa-refresh': 'refreshProductView', + 'click .fa-quote-right': 'toggleComment', + 'checkbox:selected': 'onCheckboxSelected' + }, + + checkedPath : [], + + initialize: function () { + _.bindAll(this); + }, + + uncheckAll: function() { + this.componentViews.setChecked(false); + this.checkedPath.length = 0; + Backbone.Events.trigger('path:selected', this.checkedPath); + }, + + onCheckboxSelected:function(e, checked, model){ + if(checked){ + this.checkedPath.push(model); + }else{ + this.checkedPath.splice(this.checkedPath.indexOf(model), 1); + } + Backbone.Events.trigger('path:selected', this.checkedPath); + }, + + setSelectedComponent: function (component) { + this.componentSelected = component; + }, + + onProductTitleClicked: function () { + this.setTitleSelected(true); + this.setSelectedComponent(this.rootComponent); + App.appView.onComponentSelected(true); + }, + + setTitleSelected:function(selected){ + if(selected){ + this.$('li.active').removeClass('active'); + this.$('#product_title .product_title').addClass('active'); + }else{ + this.$('#product_title .product_title').removeClass('active'); + } + }, + + refreshProductView: function(){ + this.refreshAll(); + }, + + render: function () { + var self = this; + + this.rootCollection = new ComponentModule.Collection([], { isRoot: true }); + + this.smartPath = []; + + this.rootComponent = undefined; + + this.listenTo(this.rootCollection, 'reset', function (collection) { + + self.rootComponent = collection.first(); + + if(!self.componentSelected){ + self.setSelectedComponent(self.rootComponent); + } + + self.trigger('collection:fetched'); + + self.onProductTitleClicked(); + + }); + + this.componentViews = new ComponentViews.Components({ + collection: this.rootCollection, + resultPathCollection: this.options.resultPathCollection, + parentView: this.$el, + parentChecked: false + }); + + return this; + }, + + getSmartPath: function () { + return this.smartPath; + }, + + checkChildrenInputs: function (event) { + var inputs = event.target.parentNode.querySelectorAll('input.load-3D.available'); + for (var i = 0; i < inputs.length; i++) { + inputs[i].checked = event.target.checked; + // on retire les fils du smartPath + this.removeFromSmartPath(inputs[i].id.substring(5)); + } + }, + + // Set smartPaths while checking parents + checkParentsInputs: function (event) { + var relativeInput = event.currentTarget.querySelector('input.load-3D'); + relativeInput.checked = event.target.checked; + var childrenUl = event.currentTarget.querySelector('ul'); + + if (childrenUl !== null) { + // Check children + var tempArray = []; + var inputsChecked = 0; + + for (var i = 0; i < childrenUl.childNodes.length; i++) { + var li = childrenUl.childNodes[i]; + if (li.querySelector('input.load-3D') && li.querySelector('input.load-3D').checked) { + inputsChecked++; + // add children into a temporary array + tempArray.push(li.id.substring(5)); + } + // remove children from path + this.removeFromSmartPath(li.id.substring(5)); + } + if (inputsChecked === childrenUl.childNodes.length) { + // if all children are checked add the node + this.addToSmartPath(relativeInput.parentNode.id.substring(5)); + } else { + relativeInput.checked = false; + // remove the node and add children checked + this.removeFromSmartPath(relativeInput.parentNode.id.substring(5)); + this.smartPath = this.smartPath.concat(tempArray); + } + } else { + // Check leaves + if (relativeInput.checked) { + // if checked add leaf + this.addToSmartPath(relativeInput.parentNode.id.substring(5)); + } else { + // if not remove it + this.removeFromSmartPath(relativeInput.parentNode.id.substring(5)); + } + } + + if (relativeInput.parentNode.id === 'path_-1') { + // Root node : master send the new smartPaths + App.collaborativeController.sendSmartPath(this.smartPath); + } + }, + + addToSmartPath: function (p) { + this.removeFromSmartPath(p); + this.smartPath.push(p); + }, + removeFromSmartPath: function (p) { + this.smartPath = _.filter(this.smartPath, function (e) { + return e !== p; + }); + }, + + setSmartPaths: function (arrayPaths) { + + arrayPaths = (arrayPaths) ? arrayPaths : []; + var pathsToLoad = _.difference(arrayPaths, this.smartPath); + var pathsToUnload = _.difference(this.smartPath, arrayPaths); + + // Remove child path of path to load from the pathsToUnload + pathsToUnload = _.filter(pathsToUnload,function(unloadPath){ + var isChildOfALoadedPath = false; + _.each(pathsToLoad,function(loadPath){ + if(loadPath==='null'){ + isChildOfALoadedPath = true; + }else{ + var isChildOfThis = unloadPath.indexOf(loadPath)===0; + isChildOfALoadedPath = isChildOfALoadedPath || isChildOfThis; + } + }); + return ! isChildOfALoadedPath; + }); + + // We have to unload path before load it because some path to unload can be child of path to load + if (pathsToUnload.length !== 0) { + App.log('%c Path to unload : \n\t'+pathsToUnload, 'PTV'); + App.instancesManager.unLoadComponentsByPaths(pathsToUnload); + } + if (pathsToLoad.length !== 0) { + App.log('%c Paths to load : \n\t'+pathsToLoad, 'PTV'); + App.instancesManager.loadComponentsByPaths(pathsToLoad); + } + + this.smartPath = arrayPaths; + this.setCheckboxes(); + }, + + setCheckboxes: function () { + this.$('li input.load-3D').prop('checked', false); + _.each(this.smartPath, function (path) { + this.$('li[id^="path_' + path + '"] input.load-3D').prop('checked', true); + },this); + }, + + onComponentSelected: function (e, componentModel, li) { + this.setTitleSelected(false); + e.stopPropagation(); + this.$('li.active').removeClass('active'); + li.addClass('active'); + this.setSelectedComponent(componentModel); + App.appView.onComponentSelected(); + }, + + refreshAll: function () { + this.checkedPath = []; + Backbone.Events.trigger('path:selected', this.checkedPath); + this.rootCollection.path = App.config.linkType ? null : '-1'; + this.componentViews.fetchAll(); + this.onProductTitleClicked(); + App.instancesManager.clear(); + App.collaborativeController.sendSmartPath(App.partsTreeView.getSmartPath()); + App.collaborativeController.sendConfigSpec(App.config.productConfigSpec); + }, + + toggleComment:function(){ + this.$el.toggleClass('displayComment'); + } + + }); + + return PartsTreeView; +}); diff --git a/docdoku-web-front/app/product-structure/js/views/path_data_modal.js b/docdoku-web-front/app/product-structure/js/views/path_data_modal.js new file mode 100644 index 0000000000..8fa25a7eb3 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/path_data_modal.js @@ -0,0 +1,366 @@ +/*global define,App,_,$*/ +define([ + 'backbone', + 'mustache', + 'text!templates/product-instance-data-modal.html', + 'models/path_data_master', + 'common-objects/models/path_data_iteration', + 'common-objects/views/attributes/attributes', + 'common-objects/views/file/file_list', + 'common-objects/views/linked/linked_documents', + 'common-objects/collections/linked/linked_document_collection', + 'common-objects/collections/file/attached_file_collection', + 'common-objects/models/attribute', + 'common-objects/views/alert' + ], function (Backbone, Mustache, template, PathDataMaster, PathDataIteration, AttributesView, FileListView, LinkedDocumentsView, LinkedDocumentCollection, AttachedFileCollection, Attribute, AlertView) { + + 'use strict'; + + var PathDataModalView = Backbone.View.extend({ + + className: 'modal hide product-instance-data-modal in', + + events: { + 'hidden': 'onHidden', + 'submit #form-deliverable-data': 'onSave', + 'click .cancel-button': 'closeModal', + 'click .save-button': 'interceptSubmit', + 'click .new-iteration': 'saveAndCreateNewIteration', + 'click a#previous-iteration': 'onPreviousIteration', + 'click a#next-iteration': 'onNextIteration' + }, + + initialize: function () { + this.path = this.options.path;// ? this.options.path : '-1'; + this.serialNumber = this.options.serialNumber; + _.bindAll(this); + }, + + onPreviousIteration: function () { + if (this.iterations.hasPreviousIteration(this.iteration)) { + this.switchIteration(this.iterations.previous(this.iteration)); + } + return false; + }, + + onNextIteration: function () { + if (this.iterations.hasNextIteration(this.iteration)) { + this.switchIteration(this.iterations.next(this.iteration)); + } + return false; + }, + + switchIteration: function (iteration) { + this.iteration = iteration; + this.iteration.setSerialNumber(this.serialNumber); + this.iteration.setId(this.pathDataId); + + var activeTabIndex = this.getActiveTabIndex(); + this.render(); + this.activateTab(activeTabIndex); + }, + + render: function () { + this.editMode = false; + var dataIteration = null; + if (this.iterations) { + this.editMode = this.iteration.getIteration() === this.model.getIterations().size(); + var hasNextIteration = this.iterations.hasNextIteration(this.iteration); + var hasPreviousIteration = this.iterations.hasPreviousIteration(this.iteration); + dataIteration = { + iteration: this.iteration.getIteration(), + iterationNote: this.iteration.getIterationNote(), + iterations: this.model.getIterations().size(), + hasNextIteration: hasNextIteration, + hasPreviousIteration: hasPreviousIteration, + i18n: App.config.i18n, + editMode: this.editMode + }; + } + this.$el.html(Mustache.render(template, dataIteration || {editMode: true, i18n: App.config.i18n})); + this.bindDOMElements(); + this.buildTabs(); + }, + + bindDOMElements: function () { + this.$modal = this.$el; + this.$tabs = this.$('.nav-tabs li'); + }, + + getActiveTabIndex: function () { + return this.$tabs.filter('.active').index(); + }, + activateTab: function (index) { + this.$tabs.eq(index).children().tab('show'); + }, + initAndOpenModal: function () { + + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + App.config.productId + '/product-instances/' + this.serialNumber + '/pathdata/' + this.path; + var self = this; + + $.ajax({ + type: 'GET', + url: url, + success: function (data) { + self.isNew = !data.id; + data.serialNumber = self.serialNumber; + data.path = self.path; + self.pathDataId = data.id; + self.model = new PathDataMaster(data); + + if (self.isNew) { + self.$('.delete-button').hide(); + self.$('.title-tab-file').hide(); + self.$('.title-tab-link').hide(); + self.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + self.$modal = self.$('.modal.product-instance-data-modal'); + self.$tabs = self.$('.nav-tabs li'); + } else { + self.iteration = self.model.getLastIteration(); + self.iterations = self.model.getIterations(); + self.iteration.setSerialNumber(self.serialNumber); + self.iteration.setPath(self.path); + self.iteration.setId(self.model.id); + } + self.render(); + self.openModal(); + } + + }); + + return this; + }, + + buildTabs: function () { + var partLinks = this.model.getPartLinks(); + var $pathDescription = this.$('.path-description'); + _.each(partLinks, function (partLink) { + var text = partLink.name + ' < ' + partLink.number + ' >'; + if(partLink.referenceDescription){ + text+=' ('+partLink.referenceDescription+')'; + } + $pathDescription.append(text + ' '); + }); + + this.$('.fa.fa-long-arrow-right').last().remove(); + + this.buildAttributesTab(); + this.addPartAttributes(); + + if (this.iteration) { + this.buildLinkedDocumentTab(); + this.buildFileTab(); + } + + }, + buildAttributesTab: function () { + var self = this; + this.attributesView = new AttributesView({ + el: this.$('#pathDataAttributes') + }); + this.attributesView.setEditMode(!this.iteration || this.iteration.getIteration() === this.model.getIterations().size()); + this.attributesView.render(); + + if (this.isNew) { + this.addPartAttributeTemplatesAsAttributes(); + + } else { + _.each(this.iteration.getInstanceAttributes(), function (item) { + self.attributesView.addAndFillAttribute(new Attribute(item)); + }); + } + }, + + buildLinkedDocumentTab: function () { + this.linkedDocuments = new LinkedDocumentCollection(this.iteration !== null ? this.iteration.getDocumentLinked() : []); + this.linkedDocumentsView = new LinkedDocumentsView({ + editMode: this.editMode, + commentEditable: true, + collection: this.linkedDocuments + }); + this.linkedDocumentsView.render(); + this.$('#tab-link').html(this.linkedDocumentsView.$el); + }, + + buildFileTab: function () { + + var filesMapping = _.map(this.iteration !== null ? this.iteration.getAttachedFiles() : [], function (fullName) { + return { + fullName: fullName, + shortName: _.last(fullName.split('/')), + created: true + }; + }); + + this.attachedFiles = new AttachedFileCollection(filesMapping); + + this.fileListView = new FileListView({ + deleteBaseUrl: this.iteration.getDeleteBaseUrl(), + uploadBaseUrl: this.iteration.getUploadBaseUrl(), + collection: this.attachedFiles, + editMode: this.editMode + }); + + this.fileListView.render(); + + this.$('#tab-files').html(this.fileListView.$el); + }, + interceptSubmit: function () { + this.isValid = !this.$('.tabs').invalidFormTabSwitcher(); + }, + + onSave: function (e) { + if (this.isValid) { + if (!this.iteration) { + var self = this; + this.iterations = this.model.iterations; + this.iteration = new PathDataIteration(this.model.attributes); + this.iterations.add(this.iteration); + this.iteration.setIteration(this.iterations.size()); + this.iteration.setInstanceAttributes(this.attributesView.collection.toJSON()); + this.iteration.setIterationNote(this.$('.description-input').val()); + + //POST + var creationURL = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + App.config.productId + '/product-instances/' + this.serialNumber + '/pathdata/' + this.iteration.getPath() + '/new'; + $.ajax({ + type: 'POST', + url: creationURL, + data: JSON.stringify(this.iteration.toJSON()), + contentType: 'application/json', + success: function (data) { + self.model = new PathDataMaster(data); + self.iteration = self.model.getLastIteration(); + self.iterations = self.model.getIterations(); + self.iteration.setSerialNumber(self.serialNumber); + self.iteration.setPath(self.path); + self.iteration.setId(self.model.id); + self.trigger('path-data:created'); + self.closeModal(); + }, + error: function (errorMessage) { + self.$('#alerts').append(new AlertView({ + type: 'error', + title: errorMessage.statusText, + message: errorMessage.responseText + }).render().$el); + } + }); + + } else { + this.updateIteration(this.closeModal); + } + } + e.preventDefault(); + e.stopPropagation(); + return false; + }, + + updateIteration: function (callback) { + var self = this; + this.iteration.setId(this.model.getId()); + this.iteration.setInstanceAttributes(this.attributesView.collection.toJSON()); + this.iteration.setIterationNote(this.$('.description-input').val()); + this.iteration.setDocumentLinked(this.linkedDocumentsView.collection.toJSON()); + this.iteration.set({ + attachedFiles: this.attachedFiles + }); + + //PUT + var updateURL = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + App.config.productId + '/product-instances/' + this.serialNumber + '/pathdata/' + this.iteration.getId() + '/iterations/' + this.iteration.getIteration(); + $.ajax({ + type: 'PUT', + url: updateURL, + data: JSON.stringify(this.iteration.toJSON()), + contentType: 'application/json', + success: function () { + callback(); + }, + error: function (errorMessage) { + self.$('#alerts').append(new AlertView({ + type: 'error', + title: errorMessage.statusText, + message: errorMessage.responseText + }).render().$el); + } + }); + + this.fileListView.deleteFilesToDelete(); + }, + + createIteration: function () { + this.lasIteration = this.model.getLastIteration(); + this.iteration = new PathDataIteration(this.model.attributes); + this.iterations.add(this.iteration); + this.iteration.setIteration(this.iterations.size()); + this.iteration.setInstanceAttributes(this.lasIteration.getInstanceAttributes()); + this.iteration.setIterationNote(''); + this.iteration.setDocumentLinked(this.lasIteration.getDocumentLinked()); + this.iteration.set({ + attachedFiles: this.lasIteration.getAttachedFiles() + }); + + //POST + var url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + App.config.productId + '/product-instances/' + this.serialNumber + '/pathdata/' + this.iteration.getId(); + var self = this; + $.ajax({ + type: 'POST', + url: url, + data: JSON.stringify(this.iteration.toJSON()), + contentType: 'application/json', + success: function (data) { + self.model = new PathDataMaster(data); + self.iteration = self.model.getLastIteration(); + self.iterations = self.model.getIterations(); + self.iteration.setSerialNumber(self.serialNumber); + self.iteration.setPath(self.path); + self.iteration.setId(self.model.id); + self.render(); + }, + error: function (errorMessage) { + self.$('#alerts').append(new AlertView({ + type: 'error', + message: errorMessage + }).render().$el); + } + }); + }, + + saveAndCreateNewIteration: function () { + this.updateIteration(this.createIteration); + }, + + addPartAttributes: function () { + + var attributesView = new AttributesView({el: this.$('#partAttributes')}); + attributesView.setEditMode(false); + attributesView.render(); + + _.each(this.model.getPartAttributes(), function (item) { + attributesView.addAndFillAttribute(new Attribute(item)); + }); + }, + + addPartAttributeTemplatesAsAttributes: function () { + var self = this; + _.each(this.model.getPartAttributeTemplates(), function (item) { + self.attributesView.addAndFillAttribute(new Attribute(item)); + }); + }, + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + }, + + onHidden: function () { + this.remove(); + } + + }); + + return PathDataModalView; + } +); diff --git a/docdoku-web-front/app/product-structure/js/views/path_to_path_link_item.js b/docdoku-web-front/app/product-structure/js/views/path_to_path_link_item.js new file mode 100644 index 0000000000..4cd04e608a --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/path_to_path_link_item.js @@ -0,0 +1,210 @@ +/*global define,App,$*/ +define([ + 'backbone', + 'mustache', + 'text!templates/path_to_path_link_item.html', + 'common-objects/views/alert' +], function (Backbone, Mustache, template, AlertView){ + 'use strict'; + + var PathToPathLinkItemView = Backbone.View.extend({ + + className:'well', + + events:{ + 'click .invert-source-target' : 'onInvertSourceTarget', + 'click .delete-item' : 'onDeleteItem', + 'change select.type-select': 'onSelectType', + 'focus input.add-type-input': 'onInputType' + }, + + initialize: function(){ + this.model = this.options.model; + this.creationMode = this.model.creationMode; + this.deleted = false; + + if(!this.model.pathToPath){ + this.model.pathToPath = { + source: this.model.sourceModel.getEncodedPath(), + target: this.model.targetModel.getEncodedPath() + }; + } + }, + + render: function () { + + var data = { + i18n: App.config.i18n, + creationMode : this.model.creationMode, + editionMode : this.model.editionMode, + canSuppress : ['wip','latest','latest-released'].indexOf(App.config.productConfigSpec)!==-1, + source : this.model.sourceModel, + target : this.model.targetModel, + availableType: this.model.availableType, + description : this.model.pathToPath.description, + type : this.model.pathToPath.type, + sourceComponents:this.model.sourceComponents, + targetComponents:this.model.targetComponents + }; + + this.$el.html(Mustache.render(template, data)); + this.$('.link-source .fa-long-arrow-right').last().remove(); + this.$('.link-target .fa-long-arrow-right').last().remove(); + this.bindDOMElements(); + + return this; + }, + + bindDOMElements:function(){ + }, + + onInvertSourceTarget: function(){ + var copy = this.model.sourceModel; + this.model.sourceModel = this.model.targetModel; + this.model.targetModel = copy; + this.model.pathToPath.source = this.model.sourceModel.getEncodedPath(); + this.model.pathToPath.target = this.model.targetModel.getEncodedPath(); + var copyComponents = this.model.sourceComponents; + this.model.sourceComponents = this.model.targetComponents; + this.model.targetComponents = copyComponents; + this.render(); + }, + + onDeleteItem: function() { + this.remove(); + this.deleted = true; + }, + + onSelectType: function () { + this.$('.add-type-input').val(''); + }, + + onInputType: function () { + this.$('.type-select').val(''); + }, + + save: function (callback) { + if (this.deleted) { + if (this.creationMode) { + callback(); + } else { + this.onDelete(callback); + } + + } else if (this.model.editionMode && !this.creationMode) { + this.onPut(callback); + + } else if (this.creationMode) { + if (this.determineType()) { + this.onPost(callback); + } else { + this.showNotification('error', App.config.i18n.YOU_CANNOT_CREATE_LINK_WITHOUT_TYPE); + } + + } else { + callback(); + } + }, + + onDelete : function (callback) { + var self = this; + var urlToDelete = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.model.productId + '/path-to-path-links/' + this.model.pathToPath.id; + + $.ajax({ + type: 'DELETE', + url: urlToDelete, + contentType: 'application/json', + success: function() { + self.remove(); + callback(); + }, + error: function(errorMessage) { + self.showNotification('error', errorMessage.responseText); + } + }); + }, + + onPost: function (callback) { + var self = this; + var urlToPost = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.model.productId + '/path-to-path-links'; + + this.model.pathToPath.description = this.$('.path-to-path-description').val(); + + var data = { + sourcePath : this.model.pathToPath.source, + targetPath : this.model.pathToPath.target, + type : this.model.pathToPath.type, + description : this.model.pathToPath.description + }; + + $.ajax({ + type: 'POST', + url: urlToPost, + dataType:'json', + contentType: 'application/json', + data : JSON.stringify(data), + success: function(pathToPathLink) { + self.model.pathToPath.id = pathToPathLink.id; + self.model.creationMode = false; + self.creationMode = false; + self.render(); + callback(); + }, + error: function(errorMessage) { + self.showNotification('error', errorMessage.responseText); + } + }); + + }, + + onPut: function (callback) { + var description = this.$('.path-to-path-description').val(); + + if (description !== this.model.pathToPath.description) { + this.model.pathToPath.description = description; + var self = this; + var urlToPut = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.model.productId + '/path-to-path-links/' + this.model.pathToPath.id; + + var data = { + sourcePath : this.model.pathToPath.source, + targetPath : this.model.pathToPath.target, + type : this.model.pathToPath.type, + description : this.model.pathToPath.description + }; + + $.ajax({ + type: 'PUT', + url: urlToPut, + dataType:'json', + contentType: 'application/json', + data : JSON.stringify(data), + success: function() { + self.model.creationMode = false; + callback(); + }, + error: function(errorMessage) { + self.showNotification('error', errorMessage.responseText); + } + }); + + } else { + callback(); + } + }, + + determineType: function () { + this.model.pathToPath.type = this.$('.type-select').val() !== ''? this.$('.type-select').val() : this.$('.add-type-input').val(); + return this.model.pathToPath.type; + }, + + showNotification: function (type, message) { + this.$('.error-div').append(new AlertView({ + type: type, + message: message + }).render().$el); + } + + }); + + return PathToPathLinkItemView; +}); diff --git a/docdoku-web-front/app/product-structure/js/views/path_to_path_link_modal.js b/docdoku-web-front/app/product-structure/js/views/path_to_path_link_modal.js new file mode 100644 index 0000000000..08c5e521cc --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/path_to_path_link_modal.js @@ -0,0 +1,223 @@ +/*global define,App,_,$*/ +define([ + 'backbone', + 'mustache', + 'async', + 'text!templates/path_to_path_link_modal.html', + 'views/path_to_path_link_item', + 'common-objects/views/alert' +], function (Backbone, Mustache, Async, template, PathToPathLinkItemView, AlertView){ + 'use strict'; + + var PathToPathLinkModalView = Backbone.View.extend({ + + className:'modal hide path-to-path-link-modal', + + events: { + 'hidden': 'onHidden', + 'click .add-path-to-path-link-btn': 'onAddPathToPathLink', + 'submit form': 'onSavePathToPathLinks', + 'click .cancel-button': 'closeModal' + }, + + initialize: function () { + this.pathSelected = this.options.pathSelected; + this.productId = this.options.productId; + this.serialNumber = this.options.serialNumber; + this.baselineId = this.options.baselineId; + }, + + bindDOMElements:function(){ + this.$modal = this.$el; + }, + + render: function () { + this.pathToPathLinkItemViews = []; + + var self = this; + + var sourcePath = this.pathSelected[0].getEncodedPath(); + var targetPath = this.pathSelected[1].getEncodedPath(); + var getComponents = this.getComponents.bind(this); + + getComponents(sourcePath,function(sourceComponents){ + getComponents(targetPath,function(targetComponents){ + + self.sourceComponents = sourceComponents; + self.targetComponents = targetComponents; + + self.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + editionMode:['wip','latest','latest-released'].indexOf(App.config.productConfigSpec)!==-1 + })); + + self.getExistingPathToPathAndType(); + + }); + }); + + this.bindDOMElements(); + return this; + }, + + getComponents:function(path,next){ + $.ajax(this.getUrlForComponents(path),{success:next}); + }, + + getUrlForComponents : function(path){ + return App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.productId + '/decode-path/' + path; + }, + + getUrlForAvailableType: function(){ + + var url = ''; + + if(this.serialNumber) { + url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.productId + '/product-instances/' + this.serialNumber + '/path-to-path-links-types'; + } + else if(this.baselineId){ + url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.productId + '/baselines/' + this.baselineId + '/path-to-path-links-types'; + } + else{ + url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.productId + '/path-to-path-links-types'; + } + + return url; + }, + + getUrlForExistingPathToPathLink: function(){ + var url = ''; + + if(this.serialNumber){ + url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.productId + '/product-instances/' + this.serialNumber + '/path-to-path-links/source/' + this.pathSelected[0].getEncodedPath() + '/target/' + this.pathSelected[1].getEncodedPath(); + } + else if(this.baselineId){ + url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.productId + '/baselines/' + this.baselineId + '/path-to-path-links/source/' + this.pathSelected[0].getEncodedPath() + '/target/' + this.pathSelected[1].getEncodedPath(); + } + else{ + url = App.config.contextPath + '/api/workspaces/' + App.config.workspaceId + '/products/' + this.productId + '/path-to-path-links/source/' + this.pathSelected[0].getEncodedPath() + '/target/' + this.pathSelected[1].getEncodedPath(); + } + + return url; + }, + + getExistingPathToPathAndType:function(){ + var self = this; + + var urlForAvailableTypes = this.getUrlForAvailableType(); + + this.existingPathToPathLinkCollection = []; + this.availableType = []; + + $.ajax({ + type : 'GET', + url : urlForAvailableTypes, + contentType: 'application/json', + success: function(pathToPathLinks){ + _.each(pathToPathLinks, function(pathToPathLink){ + self.availableType.push(pathToPathLink.type); + }); + self.getExistingPathToPath(); + }, + error: function(errorMessage){ + self.$('#path-to-path-link-alerts').append(new AlertView({ + type: 'error', + message: errorMessage.responseText + }).render().$el); + } + }); + }, + + getExistingPathToPath: function(){ + var self = this; + var url = this.getUrlForExistingPathToPathLink(); + $.ajax({ + type : 'GET', + url : url, + contentType: 'application/json', + success: function(pathToPathLinks){ + + _.each(pathToPathLinks, function(pathToPathLink){ + self.existingPathToPathLinkCollection.push({ + sourceModel : self.pathSelected[0].getEncodedPath() === pathToPathLink.sourcePath ? self.pathSelected[0] : self.pathSelected[1], + targetModel : self.pathSelected[1].getEncodedPath() === pathToPathLink.targetPath ? self.pathSelected[1] : self.pathSelected[0], + pathToPath : pathToPathLink, + creationMode : false, + editionMode : !self.baselineId && !self.serialNumber, + availableType : self.availableType, + productId : self.productId, + serialNumber : self.serialNumber, + sourceComponents:pathToPathLink.sourceComponents, + targetComponents:pathToPathLink.targetComponents + }); + }); + + _.each(self.existingPathToPathLinkCollection, function(pathToPathLink){ + var pathToPathLinkItemView = new PathToPathLinkItemView({model:pathToPathLink}).render(); + self.$('#path-to-path-links').append(pathToPathLinkItemView.el); + self.pathToPathLinkItemViews.push(pathToPathLinkItemView); + }); + + }, + error: function(errorMessage){ + self.$('#path-to-path-link-alerts').append(new AlertView({ + type: 'error', + message: errorMessage.responseText + }).render().$el); + } + }); + }, + + onSavePathToPathLinks: function(e) { + var _this = this; + + Async.each(this.pathToPathLinkItemViews, function(pathToPathLinkItemView, callback) { + + pathToPathLinkItemView.save(callback); + + }, function(err) { + if (!err) { + _this.closeModal(); + } + }); + e.preventDefault(); + return false; + }, + + onAddPathToPathLink: function(){ + var newPathToPathLinkItemView = new PathToPathLinkItemView({ + model:{ + sourceModel : this.pathSelected[0], + targetModel : this.pathSelected[1], + creationMode : true, + editionMode : true, + availableType : this.availableType, + productId : this.productId, + serialNumber : this.serialNumber, + sourceComponents:this.sourceComponents, + targetComponents:this.targetComponents + } + }).render(); + + this.$('#path-to-path-links').append(newPathToPathLinkItemView.el); + this.pathToPathLinkItemViews.push(newPathToPathLinkItemView); + }, + + + openModal: function () { + this.$modal.modal('show'); + }, + + closeModal: function () { + this.$modal.modal('hide'); + App.baselineSelectView.fetchPathToPathLinkTypes(); + }, + + onHidden: function () { + this.remove(); + } + + }); + + return PathToPathLinkModalView; +}); diff --git a/docdoku-web-front/app/product-structure/js/views/progress_bar_view.js b/docdoku-web-front/app/product-structure/js/views/progress_bar_view.js new file mode 100644 index 0000000000..ac0365d70f --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/progress_bar_view.js @@ -0,0 +1,63 @@ +/*global define,_*/ +define([ + 'backbone', + 'text!templates/progress_bar.html' + ], + function (Backbone, template) { + + 'use strict'; + + var ProgressBar = Backbone.View.extend({ + + tagName: 'div', + el: '#progress_bar_container', + + initialize: function () { + _.bindAll(this); + this.total = 0; + this.loaded = 0; + }, + + render: function () { + this.$el.html(template); + this.$bar = this.$('.bar'); + this.$bar.width(0); + return this; + }, + + addTotal: function (total) { + this.$bar.show(); + this.total += total; + }, + + removeXHRData: function (totalToRemove) { + this.total -= totalToRemove; + this.loaded -= totalToRemove; + this.updateState(); + }, + + addLoaded: function (loaded) { + this.loaded += loaded; + this.updateState(); + }, + + percentValue: function () { + return this.total > 0 ? (this.loaded / this.total) * 100 : 0; + }, + + updateState: function () { + if (this.total) { + this.$bar.css('width', this.percentValue() + '%'); + } else { + this.hideProgressBar(); + } + }, + + hideProgressBar: function () { + this.$bar.hide(); + } + }); + + return ProgressBar; + } +); diff --git a/docdoku-web-front/app/product-structure/js/views/search_view.js b/docdoku-web-front/app/product-structure/js/views/search_view.js new file mode 100644 index 0000000000..1080bf4dfc --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/search_view.js @@ -0,0 +1,83 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/nav_list_action_bar.html', + 'collections/result_path_collection' +], function (Backbone, Mustache, template, ResultPathCollection) { + 'use strict'; + var SearchView = Backbone.View.extend({ + el: '#nav_list_action_bar', + + events: { + 'submit form#nav_list_search': 'onSearchSubmit' + }, + + initialize: function () { + this.collection = new ResultPathCollection(); + this.oppened = false; + this.on('instance:selected', this.onInstanceSelected); + this.on('selection:reset', this.onResetSelection); + }, + + bindDomElements: function () { + this.$helpLink = this.$('#nav_list_search_mini_icon i'); + this.$helpPopover = this.$('#nav_list_controls_help'); + }, + + initHelpPopover: function () { + var self = this; + var $link = this.$helpLink; + $link.popover({ + html: true, + placement: 'bottom', + title: App.config.i18n.SEARCH_OPTIONS, + trigger: 'manual', + container:'body', + content: function () { + return self.$helpPopover.html(); + } + }).click(function (e) { + $link.popover('show'); + e.stopPropagation(); + e.preventDefault(); + return false; + }); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + this.bindDomElements(); + this.initHelpPopover(); + return this; + }, + + onSearchSubmit: function (e) { + var searchString = e.target.children[0].value.trim(); + this.search(searchString); + e.preventDefault(); + return false; + }, + + search: function (partNumber) { + if (partNumber.length > 0) { + this.collection.searchString = partNumber.replace(/%/g, '.*'); + this.collection.fetch({reset: true}); + } else { + this.collection.reset(); + } + + return false; + }, + + onInstanceSelected: function (partNumber) { + this.search(partNumber); + }, + + onResetSelection: function () { + this.collection.reset(); + } + + }); + return SearchView; +}); diff --git a/docdoku-web-front/app/product-structure/js/views/shortcuts_view.js b/docdoku-web-front/app/product-structure/js/views/shortcuts_view.js new file mode 100644 index 0000000000..352a62e216 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/views/shortcuts_view.js @@ -0,0 +1,51 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/shortcuts.html', + 'views/controls_infos_modal_view' +], function (Backbone, Mustache, template, ControlsInfosModalView) { + + 'use strict'; + + var ShortcutsView = Backbone.View.extend({ + + tagName: 'div', + + id: 'shortcuts', + + events: { + 'click a': 'clicked' + }, + + initialize: function () { + + }, + + clicked: function () { + var cimv; + switch (App.sceneManager.stateControl) { + case App.sceneManager.STATECONTROL.PLC: + cimv = new ControlsInfosModalView({isPLC: true, isTBC: false, isORB: false}); + break; + case App.sceneManager.STATECONTROL.TBC: + cimv = new ControlsInfosModalView({isPLC: false, isTBC: true, isORB: false}); + break; + case App.sceneManager.STATECONTROL.ORB: + cimv = new ControlsInfosModalView({isPLC: false, isTBC: false, isORB: true}); + break; + } + window.document.body.appendChild(cimv.render().el); + cimv.openModal(); + }, + + render: function () { + this.$el.html(Mustache.render(template, {i18n: App.config.i18n})); + return this; + } + + }); + + return ShortcutsView; + +}); diff --git a/docdoku-web-front/app/product-structure/js/workers/DegradationLevelBalancer.js b/docdoku-web-front/app/product-structure/js/workers/DegradationLevelBalancer.js new file mode 100644 index 0000000000..5e3310535d --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/workers/DegradationLevelBalancer.js @@ -0,0 +1,65 @@ +/*global _,self*/ +'use strict'; +var DegradationLevelBalancer = {}; + +(function (DLB) { + + /* + * Spread qualities on maximum eligible parts + * + * input : list of sorted instances + * output : directives array [{instance:instance,quality:quality}, ...]; + * + * */ + + + DLB.apply = function (sorterResult) { + + var instancesList = sorterResult.sortedInstances; + var directives = {}; + + // Init all parts to undefined + _(instancesList).each(function (instance) { + directives[instance.id] = { + instance: instance, + quality: undefined + }; + }); + + // Take out maxInstances + var shortenList = instancesList.splice(0, self.WorkerManagedValues.maxInstances); + var shortenListLength = shortenList.length; + + var onScene = 0; + + var slices = { + 0: 10, + 1: 80, + 2: 10 + }; + + var offset = 0; + var percent = 0; + _(_.keys(slices)).each(function (degradationLevel) { + percent += slices[degradationLevel]; + var index = Math.ceil(shortenListLength * percent / 100); + _(shortenList.slice(offset, index)).each(function (instance) { + if (instance.globalRating !== -1) { + directives[instance.id] = { + instance: instance, + quality: Math.min(instance.qualities-1,degradationLevel) + }; + onScene++; + } + }); + offset = index; + }); + + return { + directives: _.values(directives).sort(function (a, b) { + return b.instance.globalRating - a.instance.globalRating; + }), + onScene: onScene + }; + }; +})(DegradationLevelBalancer); diff --git a/docdoku-web-front/app/product-structure/js/workers/InstancesSorter.js b/docdoku-web-front/app/product-structure/js/workers/InstancesSorter.js new file mode 100644 index 0000000000..9a90d6c203 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/workers/InstancesSorter.js @@ -0,0 +1,151 @@ +/*global _,WorkerManagedValues,AppWorker,Context,THREE*/ +var InstancesSorter = {}; + +(function (IS) { + 'use strict'; + var cameraDist = function (instance) { + return Math.min( + new THREE.Vector3().subVectors(instance.box.min, Context.camera).length(), + new THREE.Vector3().subVectors(instance.box.max, Context.camera).length(), + new THREE.Vector3().subVectors(instance.cog, Context.camera).length() + ); + }; + + var cameraAngle = function (instance) { + return Math.min( + new THREE.Vector3().subVectors(instance.box.min, Context.camera).normalize().angleTo(Context.ct), + new THREE.Vector3().subVectors(instance.box.max, Context.camera).normalize().angleTo(Context.ct), + new THREE.Vector3().subVectors(instance.cog, Context.camera).normalize().angleTo(Context.ct) + ); + }; + + IS.sort = function (instances) { + + /* + * Performs rating calculation + * */ + + var result = { + eligible: 0, + eliminated: 0, + minDist: 0, + maxDist: 0, + minAngle: 0, + maxAngle: 0, + minPSize: 0, + maxPSize: 0, + minRating: 0, + maxRating: 0, + sortedInstances: null + }; + + var minProjectedSize = WorkerManagedValues.minProjectedSize / 1000; + + // Evaluate global + _(instances).each(function (instance) { + + + /* + * No geometric data + * */ + if (!instance.cog) { + instance.globalRating = -1; + result.eliminated++; + return; + } + + /* + * Checked unchecked + * */ + + if (!instance.checked) { + instance.globalRating = -1; // eliminated + result.eliminated++; + return; + } + + /* + * Max distance/angle/projectedSize filtering + * */ + + var dist = cameraDist(instance); + if (dist > WorkerManagedValues.maxDist) { + instance.globalRating = -1; // eliminated + result.eliminated++; + return; + } + + var angle = cameraAngle(instance); + if (angle > WorkerManagedValues.maxAngle) { + instance.globalRating = -1; // eliminated + result.eliminated++; + return; + } + + var projectedSize = (instance.radius * 2) / dist; + + if (projectedSize < minProjectedSize) { + instance.globalRating = -1; // eliminated + result.eliminated++; + return; + } + + result.minDist = (result.minDist === 0) ? dist : Math.min(result.minDist, dist); + result.maxDist = Math.max(result.maxDist, dist); + result.minAngle = (result.minAngle === 0) ? angle : Math.min(result.minAngle, angle); + result.maxAngle = Math.max(result.maxAngle, angle); + result.minPSize = (result.minPSize === 0) ? projectedSize : Math.min(result.minPSize, projectedSize); + result.maxPSize = Math.max(result.maxPSize, projectedSize); + + instance.dist = dist; + instance.angle = angle; + instance.projectedSize = projectedSize; + instance.globalRating = 0; // eliglibe + result.eligible++; + + }); + + // calculate ratings + _(instances).each(function (instance) { + + if (instance.globalRating > -1) { + + var angleRating = 1 - instance.angle / WorkerManagedValues.maxAngle; + var distanceRating = 1 - (instance.dist - result.minDist) / (result.maxDist - result.minDist); + var volRating = (instance.projectedSize - result.minPSize) / (result.maxPSize - result.minPSize); + + angleRating *= WorkerManagedValues.angleRating; + distanceRating *= WorkerManagedValues.distanceRating; + volRating *= WorkerManagedValues.volRating; + + // Geometric mean (nth-root of n value's product) + //instance.globalRating = Math.pow(angleRating * distanceRating * volRating , 1/3); + + // Arithmetic mean + instance.globalRating = angleRating + distanceRating + volRating; + + result.minRating = Math.min(result.minRating, instance.globalRating); + result.maxRating = Math.max(result.maxRating, instance.globalRating); + + } + + }); + + // Sort instances on their global rating, descending + var sortedInstances = _.values(instances); + + sortedInstances.sort(function (a, b) { + return (b.globalRating - a.globalRating); + }); + + AppWorker.log('%c ' + JSON.stringify(result), 'IS'); + + result.sortedInstances = sortedInstances; + + + return result; + + }; + + +})(InstancesSorter); diff --git a/docdoku-web-front/app/product-structure/js/workers/InstancesWorker.js b/docdoku-web-front/app/product-structure/js/workers/InstancesWorker.js new file mode 100644 index 0000000000..6a1dc1feb9 --- /dev/null +++ b/docdoku-web-front/app/product-structure/js/workers/InstancesWorker.js @@ -0,0 +1,187 @@ +/*global _,self,THREE,InstancesSorter,DegradationLevelBalancer,importScripts*/ + +'use strict'; + +importScripts( + '../../../bower_components/underscore/underscore-min.js', + '../../../bower_components/threejs/build/three.min.js', + 'InstancesSorter.js', + 'DegradationLevelBalancer.js' +); + +// Index instances +var newData = false; +var instances = {}; +var instancesCount = 0; +var WorkerManagedValues = {}; +var debug = null; + +var AppWorker = { + log : function(message,type){ + + if(debug){ + type = type ? type : ''; + switch (type) { + case 'IS': + console.log('%c [Worker] IS '+message, 'background: #5D2B63; color: #bada55','background: none; color:inherit'); + break; + case 'DLB': + console.log('%c [Worker] DLB '+message, 'background: #5D2B63; color: #bada55','background: none; color:inherit'); + break; + default : + console.log('%c [Worker] '+message, 'background: #5D2B63; color: #bada55','background: none; color:inherit'); + break; + } + + } + } +}; + +function fixPrecision(v) { + + v.x = parseFloat(v.x).toFixed(2); + v.y = parseFloat(v.y).toFixed(2); + v.z = parseFloat(v.z).toFixed(2); +} + +var Context = { + // Previous camera + _camera: new THREE.Vector3(), + _target: new THREE.Vector3(), + // Latest camera + camera: new THREE.Vector3(), + target: new THREE.Vector3(), + // Vector on camera -> target + ct: new THREE.Vector3(), + + clear: function () { + + instances = {}; + instancesCount = 0; + newData = true; + + AppWorker.log('%c CLEARED'); + }, + addInstance: function (instance) { + + if (typeof(instances[instance.id]) === 'undefined') { + instancesCount++; + } + instances[instance.id] = instance; + newData = true; + }, + unCheckInstance: function (instanceId) { + + if(instances[instanceId]){ + instances[instanceId].checked = false; + } + newData = true; + }, + checkInstance: function (instanceId) { + + if(instances[instanceId]){ + instances[instanceId].checked = true; + } + newData = true; + }, + hasChanged: function (newContext) { + + debug = newContext.debug; + WorkerManagedValues = newContext.WorkerManagedValues; + //Lower precision, sometimes camera is moving by 1E-6 and triggers calculations + //Avoid this effect by fixing precision + fixPrecision(newContext.camera); + fixPrecision(newContext.target); + // Copy current context in previous context + Context._camera.copy(Context.camera); + Context._target.copy(Context.target); + // Detect camera move + var cameraMoved = !Context.camera.equals(newContext.camera) || !Context.target.equals(newContext.target); + if (newData || cameraMoved) { + // Set newContext as current + Context.camera.copy(newContext.camera); + Context.target.copy(newContext.target); + // Set the new direction of camera (looking a virtual point) + Context.ct.subVectors(Context.target, Context.camera).normalize(); + newData = false; + return true; + } + return false; + }, + + setQuality: function (instance) { + instances[instance.id].qualityLoaded = instance.quality; + }, + + evalContext: function (context) { + + if (Context.hasChanged(context)) { + AppWorker.log('%c Start a cycle'); + + var start = Date.now(); + // Apply ratings, determine which instances must be displayed in this context + var sorterResult = InstancesSorter.sort(instances); + + // Balancer will spread qualities and determine which instances will need to display first + var dlbResult = DegradationLevelBalancer.apply(sorterResult); + var directives = []; + + _.each(dlbResult.directives, function (directive) { + var instance = instances[directive.instance.id]; + if (instance.qualityLoaded !== directive.quality) { + directives.push({ + id: instance.id, + quality: directive.quality, + nowait: directive.quality === undefined && !instance.checked + }); + } + }); + + // Send directives + self.postMessage({fn: 'directives', obj: directives}); + + AppWorker.log('%c Cycle duration : ' + (Date.now() - start) + ' ms'); + } else { + AppWorker.log('%c Context didn\'t changed since last call'); + + self.postMessage({fn: 'directives', obj: []}); + } + } +}; + +// Lookup table for parent messages +var ParentMessages = { + context: function (context) { + + Context.evalContext(context); + }, + unCheck: function (nodeId) { + + Context.unCheckInstance(nodeId); + }, + check: function (nodeId) { + + Context.checkInstance(nodeId); + }, + clear: function () { + + Context.clear(); + }, + setQuality: function (instance) { + + Context.setQuality(instance); + }, + addInstance: function (instance) { + + Context.addInstance(instance); + } +}; + +self.addEventListener('message', function (message) { + + if (typeof ParentMessages[message.data.fn] === 'function') { + ParentMessages[message.data.fn](message.data.obj); + } else { + AppWorker.log('%c Unrecognized command : \n\t'+message.data); + } +}, false); diff --git a/docdoku-web-front/app/product-structure/main.js b/docdoku-web-front/app/product-structure/main.js new file mode 100644 index 0000000000..fdb6d92a1e --- /dev/null +++ b/docdoku-web-front/app/product-structure/main.js @@ -0,0 +1,224 @@ +/*global _,require,window*/ +var workspace = /^#([^\/]+)/.exec(window.location.hash); +if(!workspace){ + location.href = '../404?url='+window.location.href; + throw new Error('Cannot parse workspace in url'); +} + +var App = { + debug: false, + + setDebug:function(state){ + 'use strict'; + App.debug = state; + if(state){ + document.body.classList.add('debug'); + }else{ + document.body.classList.remove('debug'); + } + }, + + config:{ + workspaceId: decodeURIComponent(workspace[1]).trim() || null, + productId: decodeURIComponent(window.location.hash.split('/')[1]).trim() || null, + login: '', + groups: [], + contextPath: '', + locale: window.localStorage.getItem('locale') || 'en', + needAuthentication:true + }, + + WorkerManagedValues: { + maxInstances: 500, + maxAngle: Math.PI / 2, + maxDist: 5E10, + minProjectedSize: 0.000001,//100, + distanceRating: 0.6,//0.7, + angleRating: 0.4,//0.6,//0.5, + volRating: 1.0//0.7 + }, + + SceneOptions: { + grid: false, + zoomSpeed: 1.2, + rotateSpeed: 1.0, + panSpeed: 0.3, + cameraNear: 0.1, + cameraFar: 5E4, + defaultCameraPosition: {x: -1000, y: 800, z: 1100}, + defaultTargetPosition: {x: 0, y: 0, z: 0}, + ambientLightColor:0xffffff, + cameraLight1Color:0xbcbcbc, + cameraLight2Color:0xffffff, + transformControls:true + } + +}; + +App.log=function(message,colorType){ + 'use strict'; + if(App.debug){ + if(colorType){ + switch (colorType) { + case 'WS' : + window.console.log('%c [WS] ' + message, 'background: #222; color: #bada55','background: none; color:inherit'); + break; + case 'IM' : + window.console.log('%c [InstancesManager] ' + message, 'background: #206963; color: #bada55','background: none; color:inherit'); + break; + case 'SM' : + window.console.log('%c [SceneManager] ' + message, 'background: #275217; color: #bada55','background: none; color:inherit'); + break; + case 'PTV' : + window.console.log('%c [PartsTreeView] ' + message, 'background: #3C4C52; color: #bada55','background: none; color:inherit'); + break; + default : + window.console.log(message, 'background: #888; color: #bada55','background: none; color:inherit'); + break; + } + }else{ + window.console.log(message); + } + } +}; + +require.config({ + baseUrl: 'js', + shim: { + jqueryUI: { deps: ['jquery'], exports: 'jQuery' }, + effects: { deps: ['jquery'], exports: 'jQuery' }, + popoverUtils: { deps: ['jquery'], exports: 'jQuery' }, + inputValidity: { deps: ['jquery'], exports: 'jQuery' }, + bootstrap:{ deps: ['jquery','jqueryUI'], exports: 'jQuery' }, + datatables:{ deps: ['jquery'], exports: 'jQuery' }, + backbone: {deps: ['underscore', 'jquery'],exports: 'Backbone'}, + bootstrapCombobox:{deps:['jquery'],exports:'jQuery'}, + bootstrapSwitch:{deps:['jquery'],exports:'jQuery'}, + bootstrapDatepicker: {deps: ['jquery','bootstrap'], exports: 'jQuery'}, + pointerlockcontrols:{deps:['threecore'],exports:'THREE'}, + trackballcontrols:{deps:['threecore'],exports:'THREE'}, + orbitcontrols:{deps:['threecore'],exports:'THREE'}, + transformcontrols:{deps:['threecore'],exports:'THREE'}, + binaryloader:{deps:['threecore'],exports:'THREE'}, + colladaloader:{deps:['threecore'],exports:'THREE'}, + stlloader:{deps:['threecore'],exports:'THREE'}, + objloader:{deps:['threecore'],exports:'THREE'}, + mtlloader:{deps:['threecore'],exports:'THREE'}, + buffergeometryutils:{deps:['threecore'],exports:'THREE'}, + typeface : { deps: ['threecore'], exports: 'window' }, + selectize: { deps: ['jquery'], exports: 'jQuery' }, + datePickerLang: { deps: ['bootstrapDatepicker'], exports: 'jQuery'} + }, + paths: { + jquery: '../../bower_components/jquery/jquery', + backbone: '../../bower_components/backbone/backbone', + underscore: '../../bower_components/underscore/underscore', + mustache: '../../bower_components/mustache/mustache', + text: '../../bower_components/requirejs-text/text', + i18n: '../../bower_components/requirejs-i18n/i18n', + buzz: '../../bower_components/buzz/dist/buzz', + bootstrap:'../../bower_components/bootstrap/docs/assets/js/bootstrap', + datatables:'../../bower_components/datatables/media/js/jquery.dataTables', + unorm:'../../bower_components/unorm/lib/unorm', + moment:'../../bower_components/moment/min/moment-with-locales', + momentTimeZone:'../../bower_components/moment-timezone/builds/moment-timezone-with-data', + threecore:'../../bower_components/threejs/build/three', + jqueryUI: '../../bower_components/jqueryui/ui/jquery-ui', + async: '../../bower_components/async/lib/async', + tween:'../../bower_components/tweenjs/src/Tween', + bootstrapCombobox:'../../bower_components/bootstrap-combobox/js/bootstrap-combobox', + bootstrapSwitch:'../../bower_components/bootstrap-switch/static/js/bootstrap-switch', + bootstrapDatepicker:'../../bower_components/bootstrap-datepicker/js/bootstrap-datepicker', + date:'../../bower_components/date.format/date.format', + dat:'../../bower_components/dat.gui/dat.gui', + localization: '../../js/localization', + modules: '../../js/modules', + 'common-objects': '../../js/common-objects', + userPopover:'../../js/modules/user-popover-module/app', + effects:'../../js/utils/effects', + popoverUtils: '../../js/utils/popover.utils', + inputValidity: '../../js/utils/input-validity', + datatablesOsortExt: '../../js/utils/datatables.oSort.ext', + utilsprototype:'../../js/utils/utils.prototype', + pointerlockcontrols:'../../js/dmu/controls/PointerLockControls', + trackballcontrols:'../../js/dmu/controls/TrackballControls', + orbitcontrols:'../../js/dmu/controls/OrbitControls', + transformcontrols:'../../js/dmu/controls/TransformControls', + binaryloader:'../../js/dmu/loaders/BinaryLoader', + colladaloader:'../../js/dmu/loaders/ColladaLoader', + stlloader:'../../js/dmu/loaders/STLLoader', + objloader:'../../js/dmu/loaders/OBJLoader', + mtlloader:'../../js/dmu/loaders/MTLLoader', + buffergeometryutils: '../../js/dmu/utils/BufferGeometryUtils', + stats:'../../js/dmu/utils/Stats', + typeface:'../../js//lib/helvetiker_regular.typeface', + selectize: '../../bower_components/selectize/dist/js/standalone/selectize', + datePickerLang: '../../bower_components/bootstrap-datepicker/js/locales/bootstrap-datepicker.fr' + }, + + deps:[ + 'jquery', + 'underscore', + 'date', + 'jqueryUI', + 'bootstrap', + 'effects', + 'popoverUtils', + 'datatables', + 'datatablesOsortExt', + 'bootstrapCombobox', + 'bootstrapSwitch', + 'utilsprototype', + 'threecore', + 'pointerlockcontrols', + 'trackballcontrols', + 'orbitcontrols', + 'transformcontrols', + 'binaryloader', + 'colladaloader', + 'stlloader', + 'objloader', + 'mtlloader', + 'buffergeometryutils', + 'stats', + 'dat', + 'tween', + 'inputValidity', + 'typeface', + 'selectize', + 'datePickerLang' + ], + config: { + i18n: { + locale: (function(){ + 'use strict'; + try{ + return App.config.locale; + }catch(ex){ + return 'en'; + } + })() + } + } +}); + +require(['common-objects/contextResolver','i18n!localization/nls/common','i18n!localization/nls/product-structure'], + function (ContextResolver, commonStrings, productStructureStrings) { + 'use strict'; + App.config.i18n = _.extend(commonStrings,productStructureStrings); + ContextResolver.resolveServerProperties() + .then(ContextResolver.resolveAccount) + .then(ContextResolver.resolveWorkspaces) + .then(ContextResolver.resolveGroups) + .then(ContextResolver.resolveUser) + .then(function(){ + require(['backbone','app','router','common-objects/views/header','modules/all'],function(Backbone, AppView, Router,HeaderView,Modules){ + App.appView = new AppView().render(); + App.headerView = new HeaderView().render(); + App.router = Router.getInstance(); + App.coworkersView = new Modules.CoWorkersAccessModuleView().render(); + Backbone.history.start(); + App.appView.initModules(); + }); + }); + }); diff --git a/docdoku-web-front/app/sounds/incoming-call.ogg b/docdoku-web-front/app/sounds/incoming-call.ogg new file mode 100644 index 0000000000..132d9e0ef5 Binary files /dev/null and b/docdoku-web-front/app/sounds/incoming-call.ogg differ diff --git a/docdoku-web-front/app/sounds/notification.ogg b/docdoku-web-front/app/sounds/notification.ogg new file mode 100644 index 0000000000..e96c3a19eb Binary files /dev/null and b/docdoku-web-front/app/sounds/notification.ogg differ diff --git a/docdoku-web-front/app/visualization/index.html b/docdoku-web-front/app/visualization/index.html new file mode 100644 index 0000000000..b0567b4dba --- /dev/null +++ b/docdoku-web-front/app/visualization/index.html @@ -0,0 +1,42 @@ + + + + + + DocDokuPLM - Product structure + + + + + + + + + + + + +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    + +
                                    + + + + + diff --git a/docdoku-web-front/app/visualization/main.js b/docdoku-web-front/app/visualization/main.js new file mode 100644 index 0000000000..56c2806a9c --- /dev/null +++ b/docdoku-web-front/app/visualization/main.js @@ -0,0 +1,166 @@ +/*global $,require,window,_*/ +var App = { + debug: false, + + config:{ + workspaceId: decodeURIComponent(/^#(product|assembly)\/([^\/]+)/.exec(window.location.hash)[2]).trim() || null, + productId: decodeURIComponent(window.location.hash.split('/')[2]).trim() || null, + login: '', + groups: [], + contextPath: '', + locale: window.localStorage.getItem('locale') || 'en', + needAuthentication:true + }, + + WorkerManagedValues: { + maxInstances: 500, + maxAngle: Math.PI / 4, + maxDist: 100000, + minProjectedSize: 0.000001,//100, + distanceRating: 0.6,//0.7, + angleRating: 0.4,//0.6,//0.5, + volRating: 1.0//0.7 + }, + + SceneOptions: { + grid: false, + zoomSpeed: 1.2, + rotateSpeed: 1.0, + panSpeed: 0.3, + cameraNear: 1, + cameraFar: 5E4, + defaultCameraPosition: {x: -1000, y: 800, z: 1100}, + defaultTargetPosition: {x: 0, y: 0, z: 0}, + ambientLightColor:0xffffff, + cameraLight1Color:0xbcbcbc, + cameraLight2Color:0xffffff, + transformControls:false + } + +}; + +App.log=function(message,colorType){ + 'use strict'; + if(App.debug){ + if(colorType){ + switch (colorType) { + case 'WS' : + window.console.log('%c [WS] ' + message, 'background: #222; color: #bada55','background: none; color:inherit'); + break; + case 'IM' : + window.console.log('%c [InstancesManager] ' + message, 'background: #206963; color: #bada55','background: none; color:inherit'); + break; + case 'SM' : + window.console.log('%c [SceneManager] ' + message, 'background: #275217; color: #bada55','background: none; color:inherit'); + break; + case 'PTV' : + window.console.log('%c [PartsTreeView] ' + message, 'background: #3C4C52; color: #bada55','background: none; color:inherit'); + break; + default : + window.console.log(message, 'background: #888; color: #bada55','background: none; color:inherit'); + break; + } + }else{ + window.console.log(message); + } + } +}; + +require.config({ + baseUrl: '../product-structure/js', + shim: { + jqueryUI: { deps: ['jquery'], exports: 'jQuery' }, + bootstrap: { deps: ['jquery', 'jqueryUI'], exports: 'jQuery' }, + backbone: {deps: ['underscore', 'jquery'], exports: 'Backbone'}, + pointerlockcontrols: {deps: ['threecore'], exports: 'THREE'}, + trackballcontrols: {deps: ['threecore'], exports: 'THREE'}, + orbitcontrols: {deps: ['threecore'], exports: 'THREE'}, + binaryloader: {deps: ['threecore'], exports: 'THREE'}, + colladaloader: {deps: ['threecore'], exports: 'THREE'}, + stlloader: {deps: ['threecore'], exports: 'THREE'}, + objloader: {deps: ['threecore'], exports: 'THREE'}, + mtlloader:{deps:['threecore'],exports:'THREE'}, + buffergeometryutils: {deps: ['threecore'], exports: 'THREE'} + }, + paths: { + jquery: '../../bower_components/jquery/jquery', + jqueryUI: '../../bower_components/jqueryui/ui/jquery-ui', + backbone: '../../bower_components/backbone/backbone', + bootstrap:'../../bower_components/bootstrap/docs/assets/js/bootstrap', + underscore: '../../bower_components/underscore/underscore', + mustache: '../../bower_components/mustache/mustache', + text: '../../bower_components/requirejs-text/text', + i18n: '../../bower_components/requirejs-i18n/i18n', + threecore: '../../bower_components/threejs/build/three', + async: '../../bower_components/async/lib/async', + tween:'../../bower_components/tweenjs/src/Tween', + date:'../../bower_components/date.format/date.format', + dat:'../../bower_components/dat.gui/dat.gui', + localization: '../../js/localization', + moment:'../../bower_components/moment/min/moment-with-locales', + momentTimeZone:'../../bower_components/moment-timezone/builds/moment-timezone-with-data', + 'common-objects': '../../js/common-objects', + pointerlockcontrols: '../../js/dmu/controls/PointerLockControls', + trackballcontrols: '../../js/dmu/controls/TrackballControls', + orbitcontrols: '../../js/dmu/controls/OrbitControls', + binaryloader: '../../js/dmu/loaders/BinaryLoader', + colladaloader: '../../js/dmu/loaders/ColladaLoader', + buffergeometryutils: '../../js/dmu/utils/BufferGeometryUtils', + stlloader: '../../js/dmu/loaders/STLLoader', + objloader: '../../js/dmu/loaders/OBJLoader', + mtlloader: '../../js/dmu/loaders/MTLLoader', + stats:'../../js/dmu/utils/Stats', + utilsprototype:'../../js/utils/utils.prototype' + }, + + deps: [ + 'threecore', + 'pointerlockcontrols', + 'trackballcontrols', + 'orbitcontrols', + 'binaryloader', + 'colladaloader', + 'stlloader', + 'objloader', + 'mtlloader', + 'buffergeometryutils', + 'stats', + 'dat', + 'tween', + 'utilsprototype', + 'bootstrap' + ], + config: { + i18n: { + locale: (function(){ + 'use strict'; + try{ + return App.config.locale; + }catch(ex){ + return 'en'; + } + })() + } + } +}); + +require(['common-objects/contextResolver','i18n!localization/nls/common','i18n!localization/nls/product-structure'], +function (ContextResolver, commonStrings, productStructureStrings) { + 'use strict'; + App.config.i18n = _.extend(commonStrings,productStructureStrings); + ContextResolver.resolveServerProperties() + .then(ContextResolver.resolveAccount) + .then(ContextResolver.resolveWorkspaces) + .then(ContextResolver.resolveGroups) + .then(ContextResolver.resolveUser) + .then(function(){ + require(['backbone', 'frameRouter', 'dmu/SceneManager','dmu/InstancesManager'],function(Backbone, Router,SceneManager,InstancesManager){ + App.$SceneContainer = $('div#frameWorkspace'); + App.instancesManager = new InstancesManager(); + App.sceneManager = new SceneManager(); + App.sceneManager.init(); + App.router = Router.getInstance(); + Backbone.history.start(); + }); + }); +}); diff --git a/docdoku-web-front/app/webapp.properties.json b/docdoku-web-front/app/webapp.properties.json new file mode 100644 index 0000000000..97d1b48c31 --- /dev/null +++ b/docdoku-web-front/app/webapp.properties.json @@ -0,0 +1,3 @@ +{ + "contextRoot":"" +} diff --git a/docdoku-web-front/app/workspace-management/index.html b/docdoku-web-front/app/workspace-management/index.html new file mode 100644 index 0000000000..1c395bc772 --- /dev/null +++ b/docdoku-web-front/app/workspace-management/index.html @@ -0,0 +1,29 @@ + + + + + + DocDokuPLM - Workspace management + + + + + + + + + + + + + +
                                    +
                                    +
                                    +
                                    +
                                    + + + + + diff --git a/docdoku-web-front/app/workspace-management/js/app.js b/docdoku-web-front/app/workspace-management/js/app.js new file mode 100644 index 0000000000..b8322d2b5e --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/app.js @@ -0,0 +1,88 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/content.html', + 'views/workspace-edit', + 'views/workspace-creation', + 'views/workspace-users', + 'views/workspace-dashboard', + 'views/workspace-management-home', + 'views/admin-dashboard' +], function (Backbone, Mustache, template, WorkspaceEditView, WorkspaceCreationView, WorkspaceUsersView, WorkspaceDashboardView, WorkspaceManagementHomeView, AdminDashboardView) { + 'use strict'; + var AppView = Backbone.View.extend({ + + el: '#content', + + events: { + 'click .new-workspace':'navigateWorkspaceCreation', + 'click .workspace-management':'navigateWorkspaceManagement', + }, + + initialize: function () { + }, + + render: function () { + var isEditionRegex = new RegExp('#/workspace/'+App.config.workspaceId+'/edit','g'); + var isUsersRegex = new RegExp('#/workspace/'+App.config.workspaceId+'/users','g'); + var isDashboardRegex = new RegExp('#/workspace/'+App.config.workspaceId+'/dashboard','g'); + this.$el.html(Mustache.render(template, { + isAdmin:App.config.admin, + administratedWorkspaces:App.config.workspaces.administratedWorkspaces, + nonAdministratedWorkspaces:App.config.workspaces.nonAdministratedWorkspaces, + workspaceId:App.config.workspaceId, + i18n: App.config.i18n, + isCreation: window.location.hash === '#/create', + isEdition: window.location.hash.match(isEditionRegex) !== null, + isUsers: window.location.hash.match(isUsersRegex) !== null, + isDashboard: window.location.hash.match(isDashboardRegex) !== null, + })).show(); + return this; + }, + + navigateWorkspaceCreation:function(){ + window.location.hash = '#/create'; + }, + + navigateWorkspaceManagement:function(){ + window.location.hash = '#/'; + }, + + workspaceManagementHome : function(){ + var view = new WorkspaceManagementHomeView(); + view.render(); + this.$('#workspace-management-content').html(view.$el); + }, + + workspaceCreation : function(){ + var view = new WorkspaceCreationView(); + view.render(); + this.$('#workspace-management-content').html(view.$el); + }, + workspaceUsers : function(){ + var view = new WorkspaceUsersView(); + view.render(); + this.$('#workspace-management-content').html(view.$el); + }, + workspaceEdit : function(){ + var view = new WorkspaceEditView(); + view.render(); + this.$('#workspace-management-content').html(view.$el); + }, + workspaceDashboard : function(){ + var view = new WorkspaceDashboardView(); + view.render(); + this.$('#workspace-management-content').html(view.$el); + }, + adminDashboard:function(){ + var view = new AdminDashboardView(); + view.render(); + this.$('#workspace-management-content').html(view.$el); + + } + + }); + + return AppView; +}); diff --git a/docdoku-web-front/app/workspace-management/js/router.js b/docdoku-web-front/app/workspace-management/js/router.js new file mode 100644 index 0000000000..7f2e862e27 --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/router.js @@ -0,0 +1,63 @@ +/*global define,App*/ +define([ + 'backbone', + 'common-objects/common/singleton_decorator' +], +function (Backbone, singletonDecorator) { + 'use strict'; + var Router = Backbone.Router.extend({ + + routes: { + '': 'workspaceManagementHome', + 'create': 'workspaceCreation', + 'admin/dashboard':'adminDashboard', + 'workspace/:workspaceId/users': 'workspaceUsers', + 'workspace/:workspaceId/edit': 'workspaceEdit', + 'workspace/:workspaceId/dashboard': 'workspaceDashboard', + 'workspace/:workspaceId/*path': 'workspaceUsers' + }, + + refresh:function(){ + App.appView.render(); + App.headerView.render(); + }, + + workspaceManagementHome:function(){ + App.config.workspaceId = null; + this.refresh(); + App.appView.workspaceManagementHome(); + }, + + workspaceCreation:function(){ + App.config.workspaceId = null; + this.refresh(); + App.appView.workspaceCreation(); + }, + + workspaceUsers:function(workspaceId){ + App.config.workspaceId = workspaceId; + this.refresh(); + App.appView.workspaceUsers(); + }, + + workspaceEdit:function(workspaceId){ + App.config.workspaceId = workspaceId; + this.refresh(); + App.appView.workspaceEdit(); + }, + + workspaceDashboard:function(workspaceId){ + App.config.workspaceId = workspaceId; + this.refresh(); + App.appView.workspaceDashboard(); + }, + + adminDashboard:function(){ + this.refresh(); + App.appView.adminDashboard(); + } + + }); + + return singletonDecorator(Router); +}); diff --git a/docdoku-web-front/app/workspace-management/js/templates/admin-dashboard.html b/docdoku-web-front/app/workspace-management/js/templates/admin-dashboard.html new file mode 100644 index 0000000000..6b7ecf7017 --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/templates/admin-dashboard.html @@ -0,0 +1,24 @@ +
                                    + +
                                    +
                                    +

                                    {{i18n.DASHBOARD}}

                                    +
                                    +
                                    +

                                    {{i18n.DISK_USAGE}}

                                    +
                                    + +
                                    +

                                    {{i18n.DISK_USAGE_TOTAL}}

                                    +
                                    + +
                                    +

                                    {{i18n.ENTITIES}}

                                    +
                                    + +
                                    +
                                    +
                                    +
                                    diff --git a/docdoku-web-front/app/workspace-management/js/templates/content.html b/docdoku-web-front/app/workspace-management/js/templates/content.html new file mode 100644 index 0000000000..8aef6d1731 --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/templates/content.html @@ -0,0 +1,54 @@ +
                                    + +
                                    + +
                                    diff --git a/docdoku-web-front/app/workspace-management/js/templates/workspace-creation.html b/docdoku-web-front/app/workspace-management/js/templates/workspace-creation.html new file mode 100644 index 0000000000..7e48991d0a --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/templates/workspace-creation.html @@ -0,0 +1,43 @@ +
                                    + +
                                    +
                                    +
                                    +

                                    {{i18n.CREATE_WORKSPACE_SUBTITLE}}

                                    +

                                    {{i18n.ADMIN}}

                                    +

                                    {{i18n.CREATE_WORKSPACE_SIDE_TEXT}}

                                    +
                                    +
                                    + +
                                    + +
                                    +
                                    + +
                                    + +
                                    + +
                                    +
                                    + +
                                    + +
                                    + +
                                    + +
                                    + +
                                    +
                                    + +
                                    +
                                    +
                                    +
                                    diff --git a/docdoku-web-front/app/workspace-management/js/templates/workspace-dashboard.html b/docdoku-web-front/app/workspace-management/js/templates/workspace-dashboard.html new file mode 100644 index 0000000000..bad1d6a42b --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/templates/workspace-dashboard.html @@ -0,0 +1,51 @@ +
                                    + +
                                    +
                                    +

                                    {{i18n.DASHBOARD}}

                                    + +
                                    + +
                                    +

                                    {{i18n.DISK_USAGE}}

                                    +
                                    + +
                                    +

                                    {{i18n.DISK_USAGE_TOTAL}}

                                    +
                                    + +
                                    +

                                    {{i18n.ENTITIES}}

                                    +
                                    + +
                                    +
                                    + +
                                    +

                                    {{i18n.USERS}}

                                    +
                                    + +
                                    +
                                    + +
                                    +

                                    {{i18n.CHECKED_OUT_DOCUMENTS}}

                                    +
                                    + +
                                    +

                                    {{i18n.TOTAL}}

                                    +
                                    + +
                                    +

                                    {{i18n.CHECKED_OUT_PARTS}}

                                    +
                                    + +
                                    +

                                    {{i18n.TOTAL}}

                                    +
                                    + +
                                    + +
                                    diff --git a/docdoku-web-front/app/workspace-management/js/templates/workspace-edit.html b/docdoku-web-front/app/workspace-management/js/templates/workspace-edit.html new file mode 100644 index 0000000000..71c7d86dc5 --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/templates/workspace-edit.html @@ -0,0 +1,62 @@ +{{#workspace}} + +
                                    + + +
                                    +
                                    + +
                                    +

                                    {{i18n.EDIT_WORKSPACE_SUBTITLE}}

                                    + +
                                    + +
                                    +
                                    +

                                    + {{i18n.SAVED}} +

                                    +
                                    +
                                    + +
                                    + +
                                    + {{workspace.id}} +
                                    +
                                    + + +
                                    + +
                                    + +
                                    +
                                    + +
                                    + +
                                    + +
                                    + +
                                    + +
                                    +
                                    + +
                                    +
                                    +
                                    +
                                    +{{/workspace}} +{{#deleting}} +

                                    {{i18n.WORKSPACE_DELETING_TITLE}}

                                    {{i18n.WORKSPACE_DELETING_TEXT}}

                                    +{{/deleting}} diff --git a/docdoku-web-front/app/workspace-management/js/templates/workspace-item.html b/docdoku-web-front/app/workspace-management/js/templates/workspace-item.html new file mode 100644 index 0000000000..8b716c3e3d --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/templates/workspace-item.html @@ -0,0 +1,62 @@ +

                                    + {{#administrated}} + + + {{/administrated}} + {{^administrated}} + + {{/administrated}} + {{workspace.id}} + {{workspace.description}} +

                                    + +{{#administrated}} +
                                    + {{i18n.DASHBOARD}} + {{#isAdmin}} + {{i18n.INDEX_WORKSPACE}} + {{/isAdmin}} +
                                    +{{/administrated}} diff --git a/docdoku-web-front/app/workspace-management/js/templates/workspace-management-home.html b/docdoku-web-front/app/workspace-management/js/templates/workspace-management-home.html new file mode 100644 index 0000000000..74c3050a0e --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/templates/workspace-management-home.html @@ -0,0 +1,23 @@ +
                                    + {{#isAdmin}} +
                                    +
                                    + × +

                                    +

                                    {{i18n.ROOT_ADMIN}}

                                    +

                                    {{i18n.ROOT_ADMIN_MESSAGE}}

                                    +
                                    +
                                    + {{/isAdmin}} +
                                    + +
                                    +

                                    {{i18n.WORKSPACES_ADMINISTRATION}}

                                    + {{#isAdmin}} +

                                    {{i18n.ADMIN_DASHBOARD}}

                                    + {{/isAdmin}} +

                                    {{i18n.WORKSPACES_ADMINISTRATION_TEXT}}

                                    + +
                                    +
                                    +
                                    diff --git a/docdoku-web-front/app/workspace-management/js/templates/workspace-users.html b/docdoku-web-front/app/workspace-management/js/templates/workspace-users.html new file mode 100644 index 0000000000..d6f0cdabe0 --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/templates/workspace-users.html @@ -0,0 +1,160 @@ +
                                    + + + +
                                    + + + +
                                    + + + +
                                    + + + +
                                    + + + + + + + {{#groupMemberships.length}} + + {{/groupMemberships.length}} + + +
                                    +
                                    + +
                                    +
                                    + {{#groupMemberships}} +
                                    + + + + +

                                    + {{memberId}} +

                                    + + {{^users}} +

                                    {{i18n.NO_USER_IN_GROUP}}

                                    + {{/users}} + + {{#users.length}} + + + + + + + + + + + {{#users}} + + + + + + + {{/users}} + +
                                    {{i18n.LOGIN}}{{i18n.NAME}}{{i18n.EMAIL}}
                                    {{#isCurrentAdmin}} {{/isCurrentAdmin}}{{login}}{{name}}{{email}}
                                    + {{/users.length}} + {{i18n.DELETE}} +
                                    + {{/groupMemberships}} + +
                                    +
                                    +
                                    +
                                    +
                                    +

                                    + {{i18n.USERS}} +

                                    + {{^usersToManage}} +

                                    {{i18n.NO_USER_TO_MANAGE}}

                                    + {{/usersToManage}} + {{#usersToManage.length}} + + + + + + + + + + + + {{#usersToManage}} + + + + + + + + + + {{/usersToManage}} + +
                                    {{i18n.LOGIN}}{{i18n.NAME}}{{i18n.EMAIL}}{{i18n.ACCESS_RIGHTS}}
                                    {{#isCurrentAdmin}} {{/isCurrentAdmin}}{{login}}{{name}}{{email}} + {{^isCurrentAdmin}} + {{#membership}} +
                                    + +
                                    + {{/membership}} + {{/isCurrentAdmin}} + {{^membership}} + {{i18n.ENABLE_USER}} + {{/membership}} +
                                    + {{/usersToManage.length}} +
                                    +
                                    +
                                    diff --git a/docdoku-web-front/app/workspace-management/js/views/admin-dashboard.js b/docdoku-web-front/app/workspace-management/js/views/admin-dashboard.js new file mode 100644 index 0000000000..dd632e0188 --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/views/admin-dashboard.js @@ -0,0 +1,161 @@ +/*global define,App,nv,d3,bytesToSize,diskUsageTooltip*/ +define([ + 'backbone', + 'mustache', + 'text!templates/admin-dashboard.html', + 'common-objects/models/admin' +], function (Backbone, Mustache, template, Admin) { + 'use strict'; + + var AdminDashboardView = Backbone.View.extend({ + + events: {}, + + initialize: function () { + }, + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n + })); + + Admin.getDiskSpaceUsageStats() + .then(this.constructDiskUsageChart.bind(this)); + + var onData = function (data) { + return data; + }; + + $.when( + Admin.getUsersStats().then(onData), + Admin.getDocumentsStats().then(onData), + Admin.getProductsStats().then(onData), + Admin.getPartsStats().then(onData) + ).then(this.constructEntitiesChart.bind(this)); + + return this; + }, + + constructDiskUsageChart: function (diskUsage) { + + var diskUsageData = []; + var totalDiskUsage = 0; + + for (var key in diskUsage) { + //if(diskUsage[key]){ + diskUsageData.push({key: key, y: diskUsage[key], f: bytesToSize(diskUsage[key])}); + totalDiskUsage += diskUsage[key]; + // } + } + + var $chart = this.$('#admin_disk_usage_chart'); + $chart.parent().find('span.total').html(bytesToSize(totalDiskUsage)); + var width = $chart.width(); + var height = $chart.height(); + $chart.find('svg') + .attr('width', '100%') + .attr('height', '100%') + .attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height)) + .attr('preserveAspectRatio', 'xMinYMin') + .attr('transform', 'translate(' + Math.min(width, height) / 2 + ',' + Math.min(width, height) / 2 + ')'); + + nv.addGraph(function () { + + var chart; + + chart = nv.models.pieChart() + .x(function (d) { + return d.key; + }) + .y(function (d) { + return d.y; + }) + .showLabels(false) + .values(function (d) { + return d; + }) + .color(d3.scale.category10().range()) + .donut(false) + .tooltipContent(function (x, y, e) { + return diskUsageTooltip(x, e.point.f); + }); + + d3.select('#admin_disk_usage_chart svg') + .datum([diskUsageData]) + .transition().duration(1200) + .call(chart); + nv.utils.windowResize(chart.update); + + return chart; + }); + }, + + constructEntitiesChart: function (usersStats, docsStats, productsStats, partsStats) { + + var usersData = [], docsData = [], partsData = [], productsData = []; + + var total = 0; + var populate = function (stats, data) { + for (var key in stats) { + if (stats[key]) { + data.push({key: key, y: stats[key]}); + total++; + } + } + }; + + populate(usersStats, usersData); + populate(docsStats, docsData); + populate(productsStats, productsData); + populate(partsStats, partsData); + + if (total) { + + var $chart = this.$('#admin_users_chart'); + var width = $chart.width(); + var height = $chart.height(); + $chart.find('svg') + .attr('width', '100%') + .attr('height', '100%') + .attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height)) + .attr('preserveAspectRatio', 'xMinYMin') + .attr('transform', 'translate(' + Math.min(width, height) / 2 + ',' + Math.min(width, height) / 2 + ')'); + + + nv.addGraph(function () { + var chart = nv.models.multiBarHorizontalChart() + .x(function (d) { + return d.key; + }) + .y(function (d) { + return d.y; + }) + .showValues(true) + .tooltips(false) + .showControls(true); + + chart.yAxis + .tickFormat(d3.format(',f')); + + d3.select('#admin_users_chart svg') + .datum([ + {key: App.config.i18n.USERS, values: usersData}, + {key: App.config.i18n.PRODUCTS, values: productsData}, + {key: App.config.i18n.PARTS, values: partsData}, + {key: App.config.i18n.DOCUMENTS, values: docsData} + ]) + .transition().duration(500) + .call(chart); + + nv.utils.windowResize(chart.update); + + return chart; + }); + } + + } + + }); + + return AdminDashboardView; +}); diff --git a/docdoku-web-front/app/workspace-management/js/views/workspace-creation.js b/docdoku-web-front/app/workspace-management/js/views/workspace-creation.js new file mode 100644 index 0000000000..73024196a0 --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/views/workspace-creation.js @@ -0,0 +1,57 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/workspace-creation.html', + 'common-objects/models/workspace', + 'common-objects/views/alert' +], function (Backbone, Mustache, template, Workspace, AlertView) { + 'use strict'; + + var WorkspaceCreationView = Backbone.View.extend({ + + events: { + 'submit #workspace_creation_form':'onSubmit' + }, + + initialize: function () { + }, + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n + })); + this.$notifications = this.$('.notifications'); + return this; + }, + + onSubmit:function(e){ + var workspaceId = this.$('#workspace-id').val(); + var description = this.$('#description').val(); + var folderLocked = this.$('#folderLocked').is(':checked'); + + Workspace.createWorkspace({ + id:workspaceId, + description:description, + folderLocked:folderLocked + }).then(function(workspace){ + App.config.workspaces.administratedWorkspaces.push(workspace); + App.config.workspaces.allWorkspaces.push(workspace); + window.location.hash = '#/'; + },this.onError.bind(this)); + + e.preventDefault(); + return false; + }, + + onError:function(error){ + this.$notifications.append(new AlertView({ + type: 'error', + message: error.responseText + }).render().$el); + } + + }); + + return WorkspaceCreationView; +}); diff --git a/docdoku-web-front/app/workspace-management/js/views/workspace-dashboard.js b/docdoku-web-front/app/workspace-management/js/views/workspace-dashboard.js new file mode 100644 index 0000000000..17c93ebb6a --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/views/workspace-dashboard.js @@ -0,0 +1,353 @@ +/*global define,App,nv,d3,bytesToSize,diskUsageTooltip*/ +define([ + 'backbone', + 'mustache', + 'text!templates/workspace-dashboard.html', + 'common-objects/models/workspace' +], function (Backbone, Mustache, template, Workspace) { + 'use strict'; + + var MAX_DAYS = 30; + + function calculateDaysSinceTimestamp(timestamp) { + var days = parseInt(((((new Date().getTime() - timestamp) / 1000) / 60) / 60) / 24); + return days < MAX_DAYS ? days + 1 : MAX_DAYS; + } + + var WorkspaceDashboardView = Backbone.View.extend({ + + events: {}, + + initialize: function () { + }, + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + workspace: _.findWhere(App.config.workspaces.administratedWorkspaces, {id: App.config.workspaceId}) + })); + + Workspace.getDiskUsageStats(App.config.workspaceId) + .then(this.constructDiskUsageChart.bind(this)); + + Workspace.getUsersStats(App.config.workspaceId) + .then(this.constructUsersCharts.bind(this)); + + Workspace.getStatsOverView(App.config.workspaceId) + .then(this.constructEntitiesChart.bind(this)); + + Workspace.getCheckedOutDocumentsStats(App.config.workspaceId) + .then(this.constructCheckedOutDocsCharts.bind(this)); + + Workspace.getCheckedOutPartsStats(App.config.workspaceId) + .then(this.constructCheckedOutPartsCharts.bind(this)); + + return this; + }, + + constructDiskUsageChart: function (diskUsage) { + + var diskUsageData = []; + var totalDiskUsage = 0; + + var translates = { + documents: App.config.i18n.DOCUMENTS, + documentTemplates: App.config.i18n.DOCUMENT_TEMPLATES, + parts: App.config.i18n.PARTS, + partTemplates: App.config.i18n.PART_TEMPLATES + }; + + for (var key in diskUsage) { + diskUsageData.push({key: translates[key], y: diskUsage[key], f: bytesToSize(diskUsage[key])}); + totalDiskUsage += diskUsage[key]; + } + + if (diskUsageData.length === 0) { + diskUsageData.push({key: App.config.i18n.NO_DATA, y: 100, f: App.config.i18n.NO_DATA}); + } + + var $chart = this.$('#disk_usage_chart'); + var width = $chart.width(); + var height = $chart.height(); + $chart.find('svg') + .attr('width', '100%') + .attr('height', '100%') + .attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height)) + .attr('preserveAspectRatio', 'xMinYMin') + .attr('transform', 'translate(' + Math.min(width, height) / 2 + ',' + Math.min(width, height) / 2 + ')'); + + $chart.parent().find('span.total').html(bytesToSize(totalDiskUsage)); + + // TODO translate keys + + nv.addGraph(function () { + var chart; + + chart = nv.models.pieChart() + .x(function (d) { + return d.key; + }) + .y(function (d) { + return d.y; + }) + .showLabels(false) + .values(function (d) { + return d; + }) + .color(d3.scale.category10().range()) + + .donut(false) + .tooltipContent(function (x, y, e) { + return diskUsageTooltip(x, e.point.f); + }); + + d3.select('#disk_usage_chart svg') + .datum([diskUsageData]) + .transition().duration(1200) + .call(chart); + + return chart; + }); + }, + + constructEntitiesChart: function (entitiesCount) { + + var entitiesData = []; + + var usersCount = entitiesCount.users; + var documentsCount = entitiesCount.documents; + var productsCount = entitiesCount.products; + var partsCount = entitiesCount.parts; + + // TODO translate + entitiesData.push({key: App.config.i18n.USERS, y: usersCount}); + entitiesData.push({key: App.config.i18n.DOCUMENTS, y: documentsCount}); + entitiesData.push({key: App.config.i18n.PRODUCTS, y: productsCount}); + entitiesData.push({key: App.config.i18n.PARTS, y: partsCount}); + + + var $chart = this.$('#entities_chart'); + var width = $chart.width(); + var height = $chart.height(); + $chart.find('svg') + .attr('width', '100%') + .attr('height', '100%') + .attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height)) + .attr('preserveAspectRatio', 'xMinYMin') + .attr('transform', 'translate(' + Math.min(width, height) / 2 + ',' + Math.min(width, height) / 2 + ')'); + + nv.addGraph(function () { + var chart = nv.models.discreteBarChart() + .x(function (d) { + return d.key; + }) + .y(function (d) { + return d.y; + }) + .staggerLabels(true) + .tooltips(false) + .showValues(true); + + chart.yAxis.tickFormat(d3.format('.f')); + + d3.select('#entities_chart svg') + .datum([{key: 'entities', values: entitiesData}]) + .transition().duration(500) + .call(chart); + + nv.utils.windowResize(chart.update); + + return chart; + }); + }, + + constructCheckedOutDocsCharts: function (cod) { + + var codData = []; + var totalCod = 0; + + for (var user in cod) { + var documents = cod[user]; + var mapDayDoc = {}; + var userData = { + key: user, + values: [] + }; + for (var i = 0; i < MAX_DAYS + 1; i++) { + mapDayDoc[i] = 0; + } + for (var j = 0; j < documents.length; j++) { + mapDayDoc[calculateDaysSinceTimestamp(documents[j].date)]++; + totalCod++; + } + for (var day in mapDayDoc) { + if (mapDayDoc[day] > 0) { + userData.values.push({ + x: day, + y: mapDayDoc[day], + size: mapDayDoc[day] + }); + } + } + codData.push(userData); + } + + var $chart = this.$('#cod_chart'); + var width = $chart.width(); + var height = $chart.height(); + + $chart.find('svg') + .attr('width', '100%') + .attr('height', '100%') + .attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height)) + .attr('preserveAspectRatio', 'xMinYMin') + .attr('transform', 'translate(' + Math.min(width, height) / 2 + ',' + Math.min(width, height) / 2 + ')'); + $chart.parent().find('span.total').html(totalCod); + + if (codData.length) { + + nv.addGraph(function () { + var chart = nv.models.scatterChart() + .showDistX(true) + .showDistY(true) + .forceX([0, MAX_DAYS]) + .forceY([0, null]) + .color(d3.scale.category10().range()); + + chart.xAxis.tickFormat(d3.format('.f')); + chart.xAxis.axisLabel(App.config.i18n.CHART_AXIS_DAYS_NUMBER); + chart.yAxis.tickFormat(d3.format('.f')); + chart.yAxis.axisLabel(App.config.i18n.CHART_AXIS_DOCUMENTS_NUMBER); + + d3.select('#cod_chart svg') + .datum(codData) + .transition().duration(500) + .call(chart); + + nv.utils.windowResize(chart.update); + + return chart; + }); + + } else { + $chart.html(App.config.i18n.NO_DATA); + } + + + }, + + constructCheckedOutPartsCharts: function (cop) { + + var copData = []; + var totalCop = 0; + for (var user in cop) { + var parts = cop[user]; + var mapDayPart = {}; + var userData = { + key: user, + values: [] + }; + for (var i = 0; i < MAX_DAYS + 1; i++) { + mapDayPart[i] = 0; + } + for (var j = 0; j < parts.length; j++) { + mapDayPart[calculateDaysSinceTimestamp(parts[j].date)]++; + totalCop++; + } + for (var day in mapDayPart) { + if (mapDayPart[day] > 0) { + userData.values.push({ + x: day, + y: mapDayPart[day], + size: mapDayPart[day] + }); + } + } + copData.push(userData); + } + + var $chart = this.$('#cop_chart'); + var width = $chart.width(); + var height = $chart.height(); + $chart.find('svg') + .attr('width', '100%') + .attr('height', '100%') + .attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height)) + .attr('preserveAspectRatio', 'xMinYMin') + .attr('transform', 'translate(' + Math.min(width, height) / 2 + ',' + Math.min(width, height) / 2 + ')'); + + $chart.parent().find('span.total').html(totalCop); + + if (copData.length) { + nv.addGraph(function () { + var chart = nv.models.scatterChart() + .showDistX(true) + .showDistY(true) + .forceX([0, MAX_DAYS]) + .forceY([0, null]) + .color(d3.scale.category10().range()); + + chart.xAxis.tickFormat(d3.format('.f')); + chart.xAxis.axisLabel(App.config.i18n.CHART_AXIS_DAYS_NUMBER); + chart.yAxis.tickFormat(d3.format('.f')); + chart.yAxis.axisLabel(App.config.i18n.CHART_AXIS_PARTS_NUMBER); + + d3.select('#cop_chart svg') + .datum(copData) + .transition().duration(500) + .call(chart); + + nv.utils.windowResize(chart.update); + + return chart; + }); + + } else { + $chart.html(App.config.i18n.NO_DATA); + } + }, + + constructUsersCharts: function (usersInWorkspace, usersStats) { + + var usersAndGroupData = []; + var translates = { + users: App.config.i18n.USERS, + activeusers: App.config.i18n.ACTIVE_USERS, + inactiveusers: App.config.i18n.INACTIVE_USERS, + groups: App.config.i18n.GROUPS, + activegroups: App.config.i18n.ACTIVE_GROUPS, + inactivegroups: App.config.i18n.INACTIVE_GROUPS + }; + // TODO translate keys + for (var key in usersStats) { + usersAndGroupData.push({key: translates[key], y: usersStats[key]}); + } + + nv.addGraph(function () { + var chart = nv.models.discreteBarChart() + .x(function (d) { + return d.key; + }) + .y(function (d) { + return d.y; + }) + .staggerLabels(true) + .tooltips(true) + .showValues(true); + + chart.yAxis.tickFormat(d3.format('.f')); + + d3.select('#users_chart svg') + .datum([{key: 'entities', values: usersAndGroupData}]) + .transition().duration(500) + .call(chart); + + nv.utils.windowResize(chart.update); + return chart; + }); + + } + }); + + return WorkspaceDashboardView; +}); diff --git a/docdoku-web-front/app/workspace-management/js/views/workspace-edit.js b/docdoku-web-front/app/workspace-management/js/views/workspace-edit.js new file mode 100644 index 0000000000..8d48b8d22c --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/views/workspace-edit.js @@ -0,0 +1,82 @@ +/*global define,App,bootbox*/ +define([ + 'backbone', + 'mustache', + 'text!templates/workspace-edit.html', + 'common-objects/models/workspace', + 'common-objects/views/alert' +], function (Backbone, Mustache, template, Workspace, AlertView) { + 'use strict'; + + var WorkspaceEditView = Backbone.View.extend({ + + events: { + 'click .delete-workspace':'deleteWorkspace', + 'submit #workspace_update_form':'onSubmit' + }, + + initialize: function () { + }, + + render: function () { + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + workspace: _.findWhere(App.config.workspaces.administratedWorkspaces,{id:App.config.workspaceId}) + })); + this.$notifications = this.$('.notifications'); + return this; + }, + + onSubmit:function(e){ + + var description = this.$('#description').val(); + var folderLocked = this.$('#folderLocked').is(':checked'); + var $success = this.$('.success-update'); + $success.hide(); + + Workspace.updateWorkspace({ + id:App.config.workspaceId, + description:description, + folderLocked:folderLocked + }).then(function(){ + $success.show(); + var workspace = _.findWhere(App.config.workspaces.administratedWorkspaces,{id:App.config.workspaceId}); + workspace.description=description; + workspace.folderLocked=folderLocked; + },this.onError.bind(this)); + e.preventDefault(); + return false; + }, + + deleteWorkspace:function(){ + var _this = this; + bootbox.confirm( + '

                                    '+App.config.i18n.DELETE_WORKSPACE_QUESTION+'

                                    '+ + '

                                    '+App.config.i18n.DELETE_WORKSPACE_TEXT+'

                                    ', + App.config.i18n.CANCEL, + App.config.i18n.DELETE, + function(result){ + if(result){ + Workspace.deleteWorkspace(App.config.workspaceId) + .then(_this.onDeleteWorkspaceSuccess.bind(_this),_this.onError.bind(_this)); + } + }); + }, + + onError:function(error){ + this.$notifications.append(new AlertView({ + type: 'error', + message: error.responseText + }).render().$el); + }, + + onDeleteWorkspaceSuccess:function(){ + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + deleting: true + })); + } + }); + + return WorkspaceEditView; +}); diff --git a/docdoku-web-front/app/workspace-management/js/views/workspace-item.js b/docdoku-web-front/app/workspace-management/js/views/workspace-item.js new file mode 100644 index 0000000000..d269dd41a8 --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/views/workspace-item.js @@ -0,0 +1,60 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/workspace-item.html', + 'common-objects/models/workspace', + 'common-objects/models/admin' +], function (Backbone, Mustache, template, Workspace, Admin) { + 'use strict'; + + var WorkspaceItemView = Backbone.View.extend({ + + className:'well-large well home-workspace', + + events: { + 'click .index-workspace':'indexWorkspace' + }, + + initialize: function () { + }, + + render: function () { + + var _this = this; + + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + workspace:this.options.workspace, + isAdmin:App.config.admin, + administrated:this.options.administrated + })); + + Workspace.getStatsOverView(this.options.workspace.id).then(function(stats){ + _this.$('.documents-count').text(stats.documents); + _this.$('.parts-count').text(stats.parts); + _this.$('.users-count').text(stats.users); + _this.$('.products-count').text(stats.products); + }); + + return this; + }, + + newWorkspace:function(){ + window.location.href='#/create'; + }, + + indexWorkspace:function(e){ + var _this = this; + Admin.indexWorkspace(e.target.dataset.workspaceId) + .then(function(){ + _this.trigger('index-workspace-success', App.config.i18n.WORKSPACE_INDEXING); + },function(error){ + _this.trigger('index-workspace-error', error); + }); + } + + }); + + return WorkspaceItemView; +}); diff --git a/docdoku-web-front/app/workspace-management/js/views/workspace-management-home.js b/docdoku-web-front/app/workspace-management/js/views/workspace-management-home.js new file mode 100644 index 0000000000..ad0148adfd --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/views/workspace-management-home.js @@ -0,0 +1,72 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/workspace-management-home.html', + 'common-objects/models/workspace', + 'common-objects/views/alert', + 'views/workspace-item' +], function (Backbone, Mustache, template, Workspace, AlertView, WorkspaceItemView) { + 'use strict'; + + var WorkspaceManagementHomeView = Backbone.View.extend({ + + events: { + 'click .new-workspace':'newWorkspace' + }, + + initialize: function () { + }, + + render: function () { + var _this = this; + + this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + workspaceId:App.config.workspaceId, + isAdmin:App.config.admin + })); + + this.$notifications = this.$('.notifications'); + var $administratedWorkspaces = this.$('.administrated-workspaces'); + var $nonAdministratedWorkspaces = this.$('.non-administrated-workspaces'); + $administratedWorkspaces.empty(); + $nonAdministratedWorkspaces.empty(); + + _.each(App.config.workspaces.administratedWorkspaces,function(workspace){ + var view = new WorkspaceItemView({administrated:true,workspace:workspace}); + $administratedWorkspaces.append(view.render().$el); + _this.listenTo(view,'index-workspace-success',_this.onInfo.bind(_this)); + _this.listenTo(view,'index-workspace-error',_this.onError.bind(_this)); + }); + + _.each(App.config.workspaces.nonAdministratedWorkspaces,function(workspace){ + var view = new WorkspaceItemView({administrated:false,workspace:workspace}); + $nonAdministratedWorkspaces.append(view.render().$el); + }); + + return this; + }, + + newWorkspace:function(){ + window.location.href='#/create'; + }, + + onError:function(error){ + this.$notifications.append(new AlertView({ + type: 'error', + message: error.responseText + }).render().$el); + }, + + onInfo:function(message){ + this.$notifications.append(new AlertView({ + type: 'info', + message: message + }).render().$el); + } + + }); + + return WorkspaceManagementHomeView; +}); diff --git a/docdoku-web-front/app/workspace-management/js/views/workspace-users.js b/docdoku-web-front/app/workspace-management/js/views/workspace-users.js new file mode 100644 index 0000000000..d2362db8a6 --- /dev/null +++ b/docdoku-web-front/app/workspace-management/js/views/workspace-users.js @@ -0,0 +1,267 @@ +/*global define,App*/ +define([ + 'backbone', + 'mustache', + 'text!templates/workspace-users.html', + 'common-objects/models/workspace', + 'common-objects/views/alert' +], function (Backbone, Mustache,template, Workspace, AlertView) { + 'use strict'; + + var WorkspaceUsersView = Backbone.View.extend({ + + events: { + 'click .read-only':'readOnly', + 'change .toggle-checkboxes':'toggleCheckboxes', + 'click .toggle-checkbox':'toggleCheckbox', + 'change .toggle-checkbox':'toggleCheckboxChange', + 'click .delete-users':'deleteUsers', + 'click .delete-group':'deleteGroup', + 'click .add-user':'addUserForm', + 'click .add-group':'addGroupForm', + 'click .move-users':'moveUsers', + 'click .enable-users':'enableUsers', + 'click .enable-user':'enableUser', + 'click .disable-users':'disableUsers', + 'submit #workspace-add-user-form':'onAddUserFormSubmit', + 'submit #workspace-add-group-form':'onAddGroupFormSubmit', + 'click #workspace-add-user-form .cancel':'cancelAddUserForm', + 'click #workspace-add-group-form .cancel':'cancelAddGroupForm', + 'click .remove-users-from-group':'removeUsersFromGroup' + }, + + initialize: function () { + }, + + render: function () { + var _this = this; + _this.groupedUsers = []; + Workspace.getUsers(App.config.workspaceId) + .then(function(users) { + _this.users = users; + return App.config.workspaceId; + }) + .then(Workspace.getUsersMemberships) + .then(function(memberships) { + _this.memberships = memberships; + return App.config.workspaceId; + }) + .then(Workspace.getUserGroupsMemberships) + .then(function(groupMemberships){ + _this.groupMemberships = groupMemberships; + return { + groups: groupMemberships, + next: function (group,users) { + group.users = users; + _.each(users, function (user) { + user.isCurrentAdmin = user.login === App.config.login; + _this.groupedUsers.push({user: user}); + }); + return users; + } + }; + }) + .then(Workspace.getUsersInGroups) + .then(function(){ + _.each(_this.users,function(user){ + user.membership = _.select(_this.memberships,function(membership){ + return membership.member.login === user.login; + }).map(function(membership){ + return { login : membership.member.login, readOnly:membership.readOnly}; + })[0] || null; + user.groupsMemberships = _.select(_this.groupedUsers,function(groupMembership){ + return groupMembership.user.login === user.login; + }).map(function(groupMembership){ + return { login : groupMembership.user.login, readOnly:groupMembership.readOnly}; + })[0] || null; + user.isCurrentAdmin = App.config.login === user.login; + }); + }) + .then(function(){ + _this.$el.html(Mustache.render(template, { + i18n: App.config.i18n, + memberships:_this.memberships, + usersToManage:_this.users, + groupMemberships: _this.groupMemberships + })); + _this.bindDOMElements(); + }); + + return this; + }, + + bindDOMElements:function(){ + this.$addUserForm = this.$('#workspace-add-user-form'); + this.$addGroupForm = this.$('#workspace-add-group-form'); + this.$addUserFormButton = this.$('.add-user'); + this.$addGroupFormButton = this.$('.add-group'); + this.$notifications = this.$('.notifications'); + this.bindGroupSwitches(); + this.bindUserSwitches(); + }, + + bindGroupSwitches:function(){ + var _this = this; + this.$groupSwitch = this.$('.group-readonly-switch'); + this.$groupSwitch.bootstrapSwitch(); + this.$groupSwitch.on('switch-change', function (e,data) { + var memberId = e.target.dataset.memberId; + var fullAccess = data.value; + Workspace.setGroupAccess(App.config.workspaceId,{memberId:memberId,readOnly:!fullAccess}) + .then(function(){},_this.onError.bind(_this)); + }); + }, + + bindUserSwitches:function(){ + var _this = this; + this.$userSwitch = this.$('.user-readonly-switch'); + this.$userSwitch.bootstrapSwitch(); + this.$userSwitch.bootstrapSwitch('setOnLabel', App.config.i18n.FULL_ACCESS); + this.$userSwitch.bootstrapSwitch('setOffLabel', App.config.i18n.READONLY); + this.$userSwitch.on('switch-change', function (e,data) { + var login = e.target.dataset.login; + var fullAccess = data.value; + Workspace.setUsersMembership(App.config.workspaceId,{login:login,membership:fullAccess?'FULL_ACCESS':'READ_ONLY'}) + .then(function(){},_this.onError.bind(_this)); + }); + }, + + onError:function(error){ + this.$notifications.append(new AlertView({ + type: 'error', + message: error.responseText + }).render().$el); + }, + + cancelAddUserForm:function(){ + this.$addUserForm.addClass('hide'); + this.$addUserFormButton.show(); + }, + + cancelAddGroupForm:function(){ + this.$addGroupForm.addClass('hide'); + this.$addGroupFormButton.show(); + }, + + addUserForm:function(){ + this.$addUserForm.removeClass('hide'); + this.$addUserFormButton.hide(); + }, + addGroupForm:function(){ + this.$addGroupForm.removeClass('hide'); + this.$addGroupFormButton.hide(); + }, + onAddUserFormSubmit:function(e){ + var login = this.$('#workspace-add-user-form input[name="login"]').val().trim(); + if(login){ + Workspace.addUser(App.config.workspaceId,{login:login}) + .then(this.render.bind(this),this.onError.bind(this)); + } + e.preventDefault(); + return false; + }, + onAddGroupFormSubmit:function(e){ + var groupId = this.$('#workspace-add-group-form input[name="groupId"]').val().trim(); + if(groupId){ + Workspace.addGroup(App.config.workspaceId,{id:groupId}) + .then(this.render.bind(this),this.onError.bind(this)); + } + e.preventDefault(); + return false; + }, + + toggleButtons:function(){ + var hasUsers = this.$('tbody > tr > td:nth-child(1) > input[type="checkbox"]:checked').size() > 0; + var hasGroupUsers = this.$('table.group_user_table > tbody > tr > td:nth-child(1) > input[type="checkbox"]:checked').size() > 0; + + this.$('.show-if-users').toggle(hasUsers); + this.$('.show-if-group-users').toggle(hasGroupUsers); + }, + + deleteUsers:function(){ + var userLogins=[]; + this.$('tbody > tr > td:nth-child(1) > input[type="checkbox"]:checked') + .each(function(index,checkbox){ + userLogins.push(checkbox.dataset.login); + }); + + var usersToDelete = _.without(_.uniq(userLogins),App.config.login); + + Workspace.removeUsersFromWorkspace(App.config.workspaceId, usersToDelete) + .then(this.render.bind(this), this.onError.bind(this)); + + }, + + moveUsers:function(e){ + var groupId = e.target.dataset.groupId; + var userLogins=[]; + this.$('tbody > tr > td:nth-child(1) > input[type="checkbox"]:checked') + .each(function(index,checkbox){ + userLogins.push(checkbox.dataset.login); + }); + + var usersToMove = _.uniq(userLogins); + + Workspace.moveUsers(App.config.workspaceId, groupId, usersToMove) + .then(this.render.bind(this), this.onError.bind(this)); + + }, + + removeUsersFromGroup:function(){ + var promiseArray = []; + this.$('table.group_user_table > tbody > tr > td:nth-child(1) > input[type="checkbox"]:checked') + .each(function(index,checkbox){ + promiseArray.push(Workspace.removeUserFromGroup(App.config.workspaceId, checkbox.dataset.memberId, checkbox.dataset.login)); + }); + $.when.apply(undefined, promiseArray).then(this.render.bind(this), this.onError.bind(this)); + }, + + enableUser:function(e){ + Workspace.enableUser(App.config.workspaceId, {login: e.target.dataset.login}) + .then(this.render.bind(this), this.onError.bind(this)); + }, + + enableUsers:function(){ + var users=[]; + this.$('tbody > tr > td:nth-child(1) > input[type="checkbox"]:checked') + .each(function(index,checkbox){ + users.push({login:checkbox.dataset.login}); + }); + Workspace.enableUsers(App.config.workspaceId, users) + .then(this.render.bind(this), this.onError.bind(this)); + }, + + disableUsers:function(){ + var users=[]; + this.$('tbody > tr > td:nth-child(1) > input[type="checkbox"]:checked') + .each(function(index,checkbox){ + users.push({login:checkbox.dataset.login}); + }); + Workspace.disableUsers(App.config.workspaceId, users) + .then(this.render.bind(this), this.onError.bind(this)); + + }, + + deleteGroup:function(e){ + Workspace.removeGroup(App.config.workspaceId, e.target.dataset.groupId) + .then(this.render.bind(this), this.onError.bind(this)); + }, + + toggleCheckboxChange:function(){ + }, + + toggleCheckbox:function(e){ + console.log('click ' + e.target); + this.toggleButtons(); + }, + + toggleCheckboxes:function(e){ + var $table = $(e.target).parents('table'); + $table.find('tbody > tr > td:nth-child(1) > input[type="checkbox"]').prop('checked', e.target.checked).trigger('change'); + this.toggleButtons(); + } + + }); + + return WorkspaceUsersView; +}); diff --git a/docdoku-web-front/app/workspace-management/main.js b/docdoku-web-front/app/workspace-management/main.js new file mode 100644 index 0000000000..b4d23045c8 --- /dev/null +++ b/docdoku-web-front/app/workspace-management/main.js @@ -0,0 +1,164 @@ +/*global _,require,window*/ + + +var App = { + debug:false, + config:{ + login: '', + groups: [], + contextPath: '', + locale: window.localStorage.getItem('locale') || 'en', + needAuthentication:true + } +}; + +App.log=function(message){ + 'use strict'; + if(App.debug){ + window.console.log(message); + } +}; + +require.config({ + + baseUrl: 'js', + + shim: { + jqueryUI: { deps: ['jquery'], exports: 'jQuery' }, + effects: { deps: ['jquery'], exports: 'jQuery' }, + popoverUtils: { deps: ['jquery'], exports: 'jQuery' }, + inputValidity: { deps: ['jquery'], exports: 'jQuery' }, + bootstrap: { deps: ['jquery', 'jqueryUI'], exports: 'jQuery' }, + bootbox: { deps: ['jquery'], exports: 'jQuery' }, + datatables: { deps: ['jquery'], exports: 'jQuery' }, + unmask: { deps: ['jquery'], exports: 'jQuery' }, + unmaskConfig: { deps: ['unmask','jquery'], exports: 'jQuery' }, + bootstrapSwitch: { deps: ['jquery'], exports: 'jQuery'}, + bootstrapDatepicker: {deps: ['jquery','bootstrap'], exports: 'jQuery'}, + backbone: { deps: ['underscore', 'jquery'], exports: 'Backbone'}, + datePickerLang: { deps: ['bootstrapDatepicker'], exports: 'jQuery'}, + d3:{deps:[],exports:'window'}, + nvd3:{deps:['d3'],exports:'window'}, + legend:{deps:['nvd3','d3']}, + pie:{deps:['nvd3','d3']}, + pieChart:{deps:['nvd3','d3']}, + discreteBar:{deps:['nvd3','d3']}, + discreteBarChart:{deps:['nvd3','d3']}, + nvutils:{deps:['nvd3','d3']}, + tooltip:{deps:['nvd3','d3'],exports:'window'}, + fisheye:{deps:['nvd3','d3']}, + helpers:{deps:['nvd3','d3']}, + }, + + paths: { + jquery: '../../bower_components/jquery/jquery', + backbone: '../../bower_components/backbone/backbone', + underscore: '../../bower_components/underscore/underscore', + mustache: '../../bower_components/mustache/mustache', + text: '../../bower_components/requirejs-text/text', + i18n: '../../bower_components/requirejs-i18n/i18n', + buzz: '../../bower_components/buzz/dist/buzz', + bootstrap: '../../bower_components/bootstrap/docs/assets/js/bootstrap', + bootbox:'../../bower_components/bootbox/bootbox', + datatables: '../../bower_components/datatables/media/js/jquery.dataTables', + jqueryUI: '../../bower_components/jqueryui/ui/jquery-ui', + unmask:'../../bower_components/jquery-maskedinput/dist/jquery.maskedinput', + bootstrapSwitch:'../../bower_components/bootstrap-switch/static/js/bootstrap-switch', + bootstrapDatepicker:'../../bower_components/bootstrap-datepicker/js/bootstrap-datepicker', + date:'../../bower_components/date.format/date.format', + unorm:'../../bower_components/unorm/lib/unorm', + moment:'../../bower_components/moment/min/moment-with-locales', + momentTimeZone:'../../bower_components/moment-timezone/builds/moment-timezone-with-data', + unmaskConfig:'../../js/utils/jquery.maskedinput-config', + localization: '../../js/localization', + modules: '../../js/modules', + 'common-objects': '../../js/common-objects', + effects: '../../js/utils/effects', + popoverUtils: '../../js/utils/popover.utils', + inputValidity: '../../js/utils/input-validity', + datatablesOsortExt: '../../js/utils/datatables.oSort.ext', + utilsprototype: '../../js/utils/utils.prototype', + userPopover: '../../js/modules/user-popover-module/app', + async: '../../bower_components/async/lib/async', + datePickerLang: '../../bower_components/bootstrap-datepicker/js/locales/bootstrap-datepicker.fr', + d3:'../../js/lib/charts/nv3d/lib/d3.v2', + nvd3:'../../js/lib/charts/nv3d/nv.d3', + legend:'../../js/lib/charts/nv3d/src/models/legend', + pie:'../../js/lib/charts/nv3d/src/models/pie', + pieChart:'../../js/lib/charts/nv3d/src/models/pieChart', + discreteBar:'../../js/lib/charts/nv3d/src/models/discreteBar', + discreteBarChart:'../../js/lib/charts/nv3d/src/models/discreteBarChart', + nvutils:'../../js/lib/charts/nv3d/src/utils', + fisheye:'../../js/lib/charts/nv3d/lib/fisheye', + tooltip:'../../js/lib/charts/nv3d/custom/tooltip', + helpers:'../../js/lib/charts/helpers' + }, + + deps: [ + 'jquery', + 'underscore', + 'date', + 'bootstrap', + 'bootbox', + 'bootstrapSwitch', + 'jqueryUI', + 'effects', + 'popoverUtils', + 'inputValidity', + 'datatables', + 'datatablesOsortExt', + 'unmaskConfig', + 'utilsprototype', + 'datePickerLang', + 'd3', + 'nvd3', + 'legend', + 'pie', + 'pieChart', + 'discreteBar', + 'discreteBarChart', + 'nvutils', + 'fisheye', + 'tooltip', + 'helpers' + ], + config: { + i18n: { + locale: (function(){ + 'use strict'; + try{ + return App.config.locale; + }catch(ex){ + return 'en'; + } + })() + } + } +}); + +require(['common-objects/contextResolver','i18n!localization/nls/common','i18n!localization/nls/workspace-management'], + function (ContextResolver, commonStrings, workspaceManagementStrings) { + + 'use strict'; + + App.config.i18n = _.extend(commonStrings,workspaceManagementStrings); + + ContextResolver.resolveServerProperties() + .then(ContextResolver.resolveAccount) + .then(ContextResolver.resolveWorkspaces) + .then(function buildView(){ + require(['backbone','app','router','common-objects/views/header','modules/all'],function(Backbone, AppView, Router,HeaderView,Modules){ + + App.appView = new AppView(); + App.headerView = new HeaderView(); + + if(!App.config.admin){ + App.headerView.setCoWorkersView(Modules.CoWorkersAccessModuleView); + } + + App.router = Router.getInstance(); + Backbone.history.start(); + }); + }); + }); + diff --git a/docdoku-web-front/bower.json b/docdoku-web-front/bower.json new file mode 100644 index 0000000000..ec25324a99 --- /dev/null +++ b/docdoku-web-front/bower.json @@ -0,0 +1,40 @@ +{ + "name": "docdokuplm", + "version": "1.1.0", + "dependencies": { + "async": "~0.9.0", + "backbone": "~1.0.0", + "bootbox": "~3.3.0", + "bootstrap": "~2.3.2", + "bootstrap-combobox": "~1.1.6", + "bootstrap-datepicker": "~1.4.1", + "bootstrap-switch": "#1.7.0", + "buzz": "~1.1.4", + "dat.gui": "*", + "datatables": "1.9.4", + "date.format": "~1.2.3", + "fontawesome": "~4.2.0", + "jquery": "#1.9.1", + "jquery-maskedinput": "~1.3.1", + "jQuery-QueryBuilder": "~2.0.1", + "jqueryui": "1.9.2", + "lodash": "~2.4.1", + "modernizr": "~2.7.1", + "moment": "~2.9.0", + "moment-timezone": "~0.2.5", + "mustache": "~0.8.2", + "requirejs": "~2.1.10", + "requirejs-i18n": "~2.0.4", + "requirejs-text": "~2.0.10", + "selectize": "~0.12.0", + "threejs": "r68", + "tweenjs": "~16.3.4", + "underscore": "~1.4.2", + "unorm": "1.3.3" + }, + "devDependencies": {}, + "resolutions": { + "jquery": "1.9.1", + "bootstrap": "~2.3.2" + } +} diff --git a/docdoku-web-front/grunt/dev/jshint.js b/docdoku-web-front/grunt/dev/jshint.js new file mode 100644 index 0000000000..955a87cf2a --- /dev/null +++ b/docdoku-web-front/grunt/dev/jshint.js @@ -0,0 +1,24 @@ +module.exports = { + loadConf:function(config, grunt){ + config.jshint= { + options: { + jshintrc: '.jshintrc', + reporter: require('jshint-stylish') + }, + all: { + src:[ + 'Gruntfile.js', + 'app/**/*.js', + 'tests/js/**/*.js', + '!app/bower_components/**', + '!app/js/lib/**', + '!app/js/dmu/**' + ] + }, + current:{} + }; + }, + loadTasks:function(grunt){ + + } +}; diff --git a/docdoku-web-front/grunt/dev/server-front.js b/docdoku-web-front/grunt/dev/server-front.js new file mode 100644 index 0000000000..db1c92684e --- /dev/null +++ b/docdoku-web-front/grunt/dev/server-front.js @@ -0,0 +1,151 @@ +'use strict'; +var moduleName = 'serverFront'; + +var LIVERELOAD_PORT = 35730; +var SERVER_HOSTNAME = 'localhost'; +var SERVER_PORT = 9001; +var DEV_HOSTNAME = 'localhost'; +var DEV_PORT = 8989; +var lrSnippet = require('connect-livereload')({port: LIVERELOAD_PORT}); + +var mountFolder = function (connect, dir) { + return connect.static(require('path').resolve(dir)); +}; + +module.exports = { + + name: moduleName, + + loadConf: function (config, grunt) { + + config.watch = { + dev: { + options: { + nospawn: true, + livereload: LIVERELOAD_PORT + }, + files: [ + 'Gruntfile.js', + '{app,grunt}/**/*', + '!app/less/**/*' + ] + }, + tests: { + files: ['tests/js/**/*.js'], + tasks: ['execute:tests', 'watch:tests'] + }, + less: { + files: ['app/less/**/*.less'], + tasks: ['less', 'watch:less'] + } + }; + + config.connect = { + options: { + port: SERVER_PORT, + hostname: SERVER_HOSTNAME + }, + + livereload: { + options: { + middleware: function (connect) { + return [ + lrSnippet, + mountFolder(connect, '.tmp'), + mountFolder(connect, 'app') + ]; + } + } + }, + dist: { + options: { + middleware: function (connect) { + return [ + mountFolder(connect, 'dist') + ]; + } + } + }, + app: { + options: { + middleware: function (connect) { + return [ + mountFolder(connect, 'app') + ]; + } + } + } + }; + + config.open = { + server: { + path: 'http://localhost:<%= connect.options.port %>' + }, + dev: { + path: 'http://' + DEV_HOSTNAME + ':' + DEV_PORT + } + }; + + config.clean.server = '.tmp'; + }, + + loadTasks: function (grunt) { + + grunt.registerTask('serve', function (target) { + + if (target === 'dist') { + return grunt.task.run(['build', 'open:server', 'connect:dist:keepalive']); + } + + if (target === 'dist-no-build') { + return grunt.task.run(['open:server', 'connect:dist:keepalive']); + } + + if (target === 'less') { + return grunt.task.run(['less','watch:less']); + } + + if (target === 'tests') { + return grunt.task.run(['execute:tests','watch:tests']); + } + + if (target === 'noLiveReload') { + return grunt.task.run([ + 'clean:server', + 'less', + 'connect:app', + 'open:dev' + ]); + } + + // Default serve command + + grunt.task.run([ + 'clean:server', + 'less', + 'connect:livereload', + 'open:dev', + 'watch:dev' + ]); + + }); + + // On watch events, configure jshint to only run on changed file, run less task if file is a less source file + + var filesTask = { + js: function (filepath) { + grunt.config('jshint.current.src', filepath); + grunt.task.run(['jshint:current']); + } + }; + + grunt.event.on('watch', function (action, filepath) { + var extension = filepath.substr((~-filepath.lastIndexOf('.') >>> 0) + 2); + if (typeof filesTask[extension] === 'function') { + filesTask[extension](filepath); + } + }); + + } + +}; diff --git a/docdoku-web-front/grunt/dev/tests.js b/docdoku-web-front/grunt/dev/tests.js new file mode 100644 index 0000000000..2d150ea685 --- /dev/null +++ b/docdoku-web-front/grunt/dev/tests.js @@ -0,0 +1,22 @@ +var moduleName = 'tests'; + +module.exports = { + + name:moduleName, + + loadConf:function(config,grunt){ + config.execute = { + tests:{ + options:{ + cwd:'tests' + }, + src:['tests/run.js'] + } + }; + }, + + loadTasks:function(grunt){ + grunt.registerTask('test',['execute:tests']); + } + +}; diff --git a/docdoku-web-front/grunt/modules/account-management.js b/docdoku-web-front/grunt/modules/account-management.js new file mode 100644 index 0000000000..3dc1ca01b2 --- /dev/null +++ b/docdoku-web-front/grunt/modules/account-management.js @@ -0,0 +1,72 @@ +var moduleName = 'accountManagement'; + +module.exports = { + + loadConf:function(config, grunt){ + + config.copy.accountManagement=[]; + + config.less.accountManagement = { + options: { + strictImports: false, + paths: [ + 'app/less/c/' + ] + }, + files: { + 'app/account-management/main.css': 'app/less/account-management/style.less' + } + }; + + config.clean.accountManagement = ['dist/account-management/*']; + + config.requirejs.accountManagement = { + options: { + name: '../../account-management/main', + optimize: 'none', + preserveLicenseComments: false, + useStrict: true, + wrap: true, + inlineText: true, + baseUrl: 'app/account-management/js', + mainConfigFile: 'app/account-management/main.js', + out: 'dist/account-management/main.js', + paths: {localization: 'empty:'}, + findNestedDependencies: true + } + }; + + config.uglify.accountManagement = { + files: { + 'dist/account-management/main.js': ['dist/account-management/main.js'] + } + }; + + config.cssmin.accountManagement = { + files: { + 'dist/account-management/main.css': ['app/account-management/main.css'] + } + }; + config.usemin.accountManagement = { + html: ['dist/account-management/index.html'], + css: ['dist/account-management/main.css'], + options: { + dirs: ['dist/account-management'] + } + }; + config.htmlmin.accountManagement = { + files: [{ + expand: true, + cwd: 'app/account-management', + src: 'index.html', + dest: 'dist/account-management' + }] + }; + + }, + + loadTasks:function(grunt){ + + } + +}; diff --git a/docdoku-web-front/grunt/modules/change-management.js b/docdoku-web-front/grunt/modules/change-management.js new file mode 100644 index 0000000000..84bdae2bb7 --- /dev/null +++ b/docdoku-web-front/grunt/modules/change-management.js @@ -0,0 +1,72 @@ +var moduleName = 'changeManagement'; + +module.exports = { + + loadConf:function(config, grunt){ + + config.copy.changeManagement=[]; + + config.less.changeManagement = { + options: { + strictImports: false, + paths: [ + 'app/less/c/' + ] + }, + files: { + 'app/change-management/main.css': 'app/less/change-management/style.less' + } + }; + + config.clean.changeManagement = ['dist/change-management/*']; + + config.requirejs.changeManagement = { + options: { + name: '../../change-management/main', + optimize: 'none', + preserveLicenseComments: false, + useStrict: true, + wrap: true, + inlineText: true, + baseUrl: 'app/change-management/js', + mainConfigFile: 'app/change-management/main.js', + out: 'dist/change-management/main.js', + paths: {localization: 'empty:'}, + findNestedDependencies: true + } + }; + + config.uglify.changeManagement = { + files: { + 'dist/change-management/main.js': ['dist/change-management/main.js'] + } + }; + + config.cssmin.changeManagement = { + files: { + 'dist/change-management/main.css': ['app/change-management/main.css'] + } + }; + config.usemin.changeManagement = { + html: ['dist/change-management/index.html'], + css: ['dist/change-management/main.css'], + options: { + dirs: ['dist/change-management'] + } + }; + config.htmlmin.changeManagement = { + files: [{ + expand: true, + cwd: 'app/change-management', + src: 'index.html', + dest: 'dist/change-management' + }] + }; + + }, + + loadTasks:function(grunt){ + + } + +}; diff --git a/docdoku-web-front/grunt/modules/document-management.js b/docdoku-web-front/grunt/modules/document-management.js new file mode 100644 index 0000000000..7c341131ca --- /dev/null +++ b/docdoku-web-front/grunt/modules/document-management.js @@ -0,0 +1,72 @@ +var moduleName = 'documentManagement'; + +module.exports = { + + loadConf:function(config, grunt){ + + config.copy.documentManagement=[]; + + config.less.documentManagement = { + options: { + strictImports: false, + paths: [ + 'app/less/document-management/' + ] + }, + files: { + 'app/document-management/main.css': 'app/less/document-management/style.less' + } + }; + + config.clean.documentManagement = ['dist/document-management/*']; + + config.requirejs.documentManagement = { + options: { + name: '../../document-management/main', + optimize: 'none', + preserveLicenseComments: false, + useStrict: true, + wrap: true, + inlineText: true, + baseUrl: 'app/document-management/js', + mainConfigFile: 'app/document-management/main.js', + out: 'dist/document-management/main.js', + paths: {localization: 'empty:'}, + findNestedDependencies: true + } + }; + + config.uglify.documentManagement = { + files: { + 'dist/document-management/main.js': ['dist/document-management/main.js'] + } + }; + + config.cssmin.documentManagement = { + files: { + 'dist/document-management/main.css': ['app/document-management/main.css'] + } + }; + config.usemin.documentManagement = { + html: ['dist/document-management/index.html'], + css: ['dist/document-management/main.css'], + options: { + dirs: ['dist/document-management'] + } + }; + config.htmlmin.documentManagement = { + files: [{ + expand: true, + cwd: 'app/document-management', + src: 'index.html', + dest: 'dist/document-management' + }] + }; + + }, + + loadTasks:function(grunt){ + + } + +}; diff --git a/docdoku-web-front/grunt/modules/documents.js b/docdoku-web-front/grunt/modules/documents.js new file mode 100644 index 0000000000..0022200184 --- /dev/null +++ b/docdoku-web-front/grunt/modules/documents.js @@ -0,0 +1,72 @@ +var moduleName = 'documents'; + +module.exports = { + + loadConf:function(config, grunt){ + + config.copy.documents=[]; + + config.less.documents = { + options: { + strictImports: false, + paths: [ + 'app/less/documents/' + ] + }, + files: { + 'app/documents/main.css': 'app/less/documents/style.less' + } + }; + + config.clean.documents = ['dist/documents/*']; + + config.requirejs.documents = { + options: { + name: '../../documents/main', + optimize: 'none', + preserveLicenseComments: false, + useStrict: true, + wrap: true, + inlineText: true, + baseUrl: 'app/documents/js', + mainConfigFile: 'app/documents/main.js', + out: 'dist/documents/main.js', + paths: {localization: 'empty:'}, + findNestedDependencies: true + } + }; + + config.uglify.documents = { + files: { + 'dist/documents/main.js': ['dist/documents/main.js'] + } + }; + + config.cssmin.documents = { + files: { + 'dist/documents/main.css': ['app/documents/main.css'] + } + }; + config.usemin.documents = { + html: ['dist/documents/index.html'], + css: ['dist/documents/main.css'], + options: { + dirs: ['dist/documents'] + } + }; + config.htmlmin.documents = { + files: [{ + expand: true, + cwd: 'app/documents', + src: 'index.html', + dest: 'dist/documents' + }] + }; + + }, + + loadTasks:function(grunt){ + + } + +}; diff --git a/docdoku-web-front/grunt/modules/download.js b/docdoku-web-front/grunt/modules/download.js new file mode 100644 index 0000000000..3060acdc79 --- /dev/null +++ b/docdoku-web-front/grunt/modules/download.js @@ -0,0 +1,82 @@ +var moduleName = 'download'; + +module.exports = { + + loadConf:function(config, grunt){ + + config.less.download = { + options: { + strictImports: false, + paths: [ + 'app/less/c/' + ] + }, + files: { + 'app/download/main.css': 'app/less/download/style.less' + } + }; + + config.clean.download = ['dist/download/*']; + + config.copy.download = { + files: [{ + expand: true, + dot: false, + cwd: 'app', + dest: 'dist', + src: [ + 'download/dplm/**' + ] + }] + }; + + config.requirejs.download = { + options: { + name: '../../download/main', + optimize: 'none', + preserveLicenseComments: false, + useStrict: true, + wrap: true, + inlineText: true, + baseUrl: 'app/download/js', + mainConfigFile: 'app/download/main.js', + out: 'dist/download/main.js', + paths: {localization: 'empty:'}, + findNestedDependencies: true + } + }; + + config.uglify.download = { + files: { + 'dist/download/main.js': ['dist/download/main.js'] + } + }; + + config.cssmin.download = { + files: { + 'dist/download/main.css': ['app/download/main.css'] + } + }; + config.usemin.download = { + html: ['dist/download/index.html'], + css: ['dist/download/main.css'], + options: { + dirs: ['dist/download'] + } + }; + config.htmlmin.download = { + files: [{ + expand: true, + cwd: 'app/download', + src: 'index.html', + dest: 'dist/download' + }] + }; + + }, + + loadTasks:function(grunt){ + + } + +}; diff --git a/docdoku-web-front/grunt/modules/main.js b/docdoku-web-front/grunt/modules/main.js new file mode 100644 index 0000000000..4dfa6c9d0e --- /dev/null +++ b/docdoku-web-front/grunt/modules/main.js @@ -0,0 +1,72 @@ +var moduleName = 'index'; + +module.exports = { + + loadConf:function(config, grunt){ + + config.copy.main=[]; + + config.less.main = { + options: { + strictImports: false, + paths: [ + 'app/less/c/' + ] + }, + files: { + 'app/main/main.css': 'app/less/main/style.less' + } + }; + + config.clean.main = ['dist/index.html','dist/main.css']; + + config.requirejs.main = { + options: { + name: '../../main/main', + optimize: 'none', + preserveLicenseComments: false, + useStrict: true, + wrap: true, + inlineText: true, + baseUrl: 'app/main/js', + mainConfigFile: 'app/main/main.js', + out: 'dist/main/main.js', + paths: {localization: 'empty:'}, + findNestedDependencies: true + } + }; + + config.uglify.main = { + files: { + 'dist/main/main.js': ['dist/main/main.js'] + } + }; + + config.cssmin.main = { + files: { + 'dist/main/main.css': ['app/main/main.css'] + } + }; + config.usemin.main = { + html: ['dist/index.html'], + css: ['dist/main/main.css'], + options: { + dirs: ['dist'] + } + }; + config.htmlmin.main = { + files: [{ + expand: true, + cwd: 'app', + src: 'index.html', + dest: 'dist' + }] + }; + + }, + + loadTasks:function(grunt){ + + } + +}; diff --git a/docdoku-web-front/grunt/modules/parts.js b/docdoku-web-front/grunt/modules/parts.js new file mode 100644 index 0000000000..806fe648c8 --- /dev/null +++ b/docdoku-web-front/grunt/modules/parts.js @@ -0,0 +1,72 @@ +var moduleName = 'parts'; + +module.exports = { + + loadConf:function(config, grunt){ + + config.copy.parts=[]; + + config.less.parts = { + options: { + strictImports: false, + paths: [ + 'app/less/parts/' + ] + }, + files: { + 'app/parts/main.css': 'app/less/parts/style.less' + } + }; + + config.clean.parts = ['dist/parts/*']; + + config.requirejs.parts = { + options: { + name: '../../parts/main', + optimize: 'none', + preserveLicenseComments: false, + useStrict: true, + wrap: true, + inlineText: true, + baseUrl: 'app/parts/js', + mainConfigFile: 'app/parts/main.js', + out: 'dist/parts/main.js', + paths: {localization: 'empty:'}, + findNestedDependencies: true + } + }; + + config.uglify.parts = { + files: { + 'dist/parts/main.js': ['dist/parts/main.js'] + } + }; + + config.cssmin.parts = { + files: { + 'dist/parts/main.css': ['app/parts/main.css'] + } + }; + config.usemin.parts = { + html: ['dist/parts/index.html'], + css: ['dist/parts/main.css'], + options: { + dirs: ['dist/parts'] + } + }; + config.htmlmin.parts = { + files: [{ + expand: true, + cwd: 'app/parts', + src: 'index.html', + dest: 'dist/parts' + }] + }; + + }, + + loadTasks:function(grunt){ + + } + +}; diff --git a/docdoku-web-front/grunt/modules/product-management.js b/docdoku-web-front/grunt/modules/product-management.js new file mode 100644 index 0000000000..4b481012b2 --- /dev/null +++ b/docdoku-web-front/grunt/modules/product-management.js @@ -0,0 +1,72 @@ +var moduleName = 'productManagement'; + +module.exports = { + + loadConf:function(config, grunt){ + + config.copy.productManagement=[]; + + config.less.productManagement = { + options: { + strictImports: false, + paths: [ + 'app/less/c/' + ] + }, + files: { + 'app/product-management/main.css': 'app/less/product-management/style.less' + } + }; + + config.clean.productManagement = ['dist/product-management/*']; + + config.requirejs.productManagement = { + options: { + name: '../../product-management/main', + optimize: 'none', + preserveLicenseComments: false, + useStrict: true, + wrap: true, + inlineText: true, + baseUrl: 'app/product-management/js', + mainConfigFile: 'app/product-management/main.js', + out: 'dist/product-management/main.js', + paths: {localization: 'empty:'}, + findNestedDependencies: true + } + }; + + config.uglify.productManagement = { + files: { + 'dist/product-management/main.js': ['dist/product-management/main.js'] + } + }; + + config.cssmin.productManagement = { + files: { + 'dist/product-management/main.css': ['app/product-management/main.css'] + } + }; + config.usemin.productManagement = { + html: ['dist/product-management/index.html'], + css: ['dist/product-management/main.css'], + options: { + dirs: ['dist/product-management'] + } + }; + config.htmlmin.productManagement = { + files: [{ + expand: true, + cwd: 'app/product-management', + src: 'index.html', + dest: 'dist/product-management' + }] + }; + + }, + + loadTasks:function(grunt){ + + } + +}; diff --git a/docdoku-web-front/grunt/modules/product-structure.js b/docdoku-web-front/grunt/modules/product-structure.js new file mode 100644 index 0000000000..92c21254e7 --- /dev/null +++ b/docdoku-web-front/grunt/modules/product-structure.js @@ -0,0 +1,72 @@ +var moduleName = 'productStructure'; + +module.exports = { + + loadConf:function(config, grunt){ + + config.copy.productStructure=[]; + + config.less.productStructure = { + options: { + strictImports: false, + paths: [ + 'app/less/c/' + ] + }, + files: { + 'app/product-structure/main.css': 'app/less/product-structure/style.less' + } + }; + + config.clean.productStructure = ['dist/product-structure/*']; + + config.requirejs.productStructure = { + options: { + name: '../../product-structure/main', + optimize: 'none', + preserveLicenseComments: false, + useStrict: true, + wrap: true, + inlineText: true, + baseUrl: 'app/product-structure/js', + mainConfigFile: 'app/product-structure/main.js', + out: 'dist/product-structure/main.js', + paths: {localization: 'empty:'}, + findNestedDependencies: true + } + }; + + config.uglify.productStructure = { + files: { + 'dist/product-structure/main.js': ['dist/product-structure/main.js'] + } + }; + + config.cssmin.productStructure = { + files: { + 'dist/product-structure/main.css': ['app/product-structure/main.css'] + } + }; + config.usemin.productStructure = { + html: ['dist/product-structure/index.html'], + css: ['dist/product-structure/main.css'], + options: { + dirs: ['dist/product-structure'] + } + }; + config.htmlmin.productStructure = { + files: [{ + expand: true, + cwd: 'app/product-structure', + src: 'index.html', + dest: 'dist/product-structure' + }] + }; + + }, + + loadTasks:function(grunt){ + + } + +}; diff --git a/docdoku-web-front/grunt/modules/visualization.js b/docdoku-web-front/grunt/modules/visualization.js new file mode 100644 index 0000000000..bc96e6c738 --- /dev/null +++ b/docdoku-web-front/grunt/modules/visualization.js @@ -0,0 +1,72 @@ +var moduleName = 'visualization'; + +module.exports = { + + loadConf:function(config, grunt){ + + config.copy.visualization=[]; + + config.less.visualization = { + options: { + strictImports: false, + paths: [ + 'app/less/c/' + ] + }, + files: { + 'app/visualization/main.css': 'app/less/visualization/style.less' + } + }; + + config.clean.visualization = ['dist/visualization/*']; + + config.requirejs.visualization = { + options: { + name: '../../visualization/main', + optimize: 'none', + preserveLicenseComments: false, + useStrict: true, + wrap: true, + inlineText: true, + baseUrl: 'app/product-structure/js', + mainConfigFile: 'app/visualization/main.js', + out: 'dist/visualization/main.js', + paths: {localization: 'empty:'}, + findNestedDependencies: true + } + }; + + config.uglify.visualization = { + files: { + 'dist/visualization/main.js': ['dist/visualization/main.js'] + } + }; + + config.cssmin.visualization = { + files: { + 'dist/visualization/main.css': ['app/visualization/main.css'] + } + }; + config.usemin.visualization = { + html: ['dist/visualization/index.html'], + css: ['dist/visualization/main.css'], + options: { + dirs: ['dist/visualization'] + } + }; + config.htmlmin.visualization = { + files: [{ + expand: true, + cwd: 'app/visualization', + src: 'index.html', + dest: 'dist/visualization' + }] + }; + + }, + + loadTasks:function(grunt){ + + } + +}; diff --git a/docdoku-web-front/grunt/modules/workspace-management.js b/docdoku-web-front/grunt/modules/workspace-management.js new file mode 100644 index 0000000000..a0161dd464 --- /dev/null +++ b/docdoku-web-front/grunt/modules/workspace-management.js @@ -0,0 +1,72 @@ +var moduleName = 'workspaceManagement'; + +module.exports = { + + loadConf:function(config, grunt){ + + config.copy.workspaceManagement=[]; + + config.less.workspaceManagement = { + options: { + strictImports: false, + paths: [ + 'app/less/workspace-management/' + ] + }, + files: { + 'app/workspace-management/main.css': 'app/less/workspace-management/style.less' + } + }; + + config.clean.workspaceManagement = ['dist/workspace-management/*']; + + config.requirejs.workspaceManagement = { + options: { + name: '../../workspace-management/main', + optimize: 'none', + preserveLicenseComments: false, + useStrict: true, + wrap: true, + inlineText: true, + baseUrl: 'app/workspace-management/js', + mainConfigFile: 'app/workspace-management/main.js', + out: 'dist/workspace-management/main.js', + paths: {localization: 'empty:'}, + findNestedDependencies: true + } + }; + + config.uglify.workspaceManagement = { + files: { + 'dist/workspace-management/main.js': ['dist/workspace-management/main.js'] + } + }; + + config.cssmin.workspaceManagement = { + files: { + 'dist/workspace-management/main.css': ['app/workspace-management/main.css'] + } + }; + config.usemin.workspaceManagement = { + html: ['dist/workspace-management/index.html'], + css: ['dist/workspace-management/main.css'], + options: { + dirs: ['dist/workspace-management'] + } + }; + config.htmlmin.workspaceManagement = { + files: [{ + expand: true, + cwd: 'app/workspace-management', + src: 'index.html', + dest: 'dist/workspace-management' + }] + }; + + }, + + loadTasks:function(grunt){ + + } + +}; diff --git a/docdoku-web-front/grunt/tasks/build.js b/docdoku-web-front/grunt/tasks/build.js new file mode 100644 index 0000000000..4e0cf5cb3c --- /dev/null +++ b/docdoku-web-front/grunt/tasks/build.js @@ -0,0 +1,56 @@ +module.exports = { + + loadConf:function(config,grunt){ + config.clean.dist=['.tmp', 'dist']; + + config.compress = { + dist: { + options: { + archive: 'target/docdoku-web-front.zip' + }, + files: [ + {expand: true, cwd: 'dist/', src: ['**'], dest: ''} + ] + } + }; + + + }, + + loadTasks:function(grunt){ + + grunt.registerTask('build-module', function (module) { + return grunt.task.run([ + 'clean:'+module, + 'requirejs:'+module, + 'uglify:'+module, + 'cssmin:'+module, + 'htmlmin:'+module, + 'usemin:'+module, + 'copy:'+module + ]); + }); + + grunt.registerTask('build', [ + 'clean:dist', + 'less', + 'copy:libs', + 'copy:assets', + 'copy:properties', + 'copy:dmu', + 'copy:i18n', + 'build-module:main', + 'build-module:accountManagement', + 'build-module:workspaceManagement', + 'build-module:download', + 'build-module:documents', + 'build-module:parts', + 'build-module:documentManagement', + 'build-module:productManagement', + 'build-module:productStructure', + 'build-module:visualization', + 'build-module:changeManagement', + 'compress:dist' + ]); + } +}; diff --git a/docdoku-web-front/grunt/tasks/copy.js b/docdoku-web-front/grunt/tasks/copy.js new file mode 100644 index 0000000000..5f08205234 --- /dev/null +++ b/docdoku-web-front/grunt/tasks/copy.js @@ -0,0 +1,106 @@ +var moduleName = 'copy'; + +module.exports = { + + name:moduleName, + + loadConf:function(config,grunt){ + + config.copy.libs = { + files: [ + { + expand: true, + dot: false, + cwd: 'app', + dest: 'dist', + src: [ + 'bower_components/requirejs/require.js', + 'bower_components/modernizr/modernizr.js', + 'bower_components/jquery/jquery.min.*', + 'bower_components/underscore/underscore-min.js', + 'bower_components/threejs/build/three.min.js', + 'bower_components/tweenjs/src/Tween.js', + 'bower_components/bootstrap/docs/assets/js/bootstrap.min.js', + 'bower_components/backbone/backbone-min.js' + ] + } + ] + }; + config.copy.properties = { + files: [ + { + expand: true, + dot: false, + cwd: 'app', + dest: 'dist', + src: [ + 'webapp.properties.json', + ] + } + ] + }; + + + config.copy.assets = { + files: [ + { + expand: true, + dot: false, + cwd: 'app', + dest: 'dist', + src: [ + 'css/**', + 'images/**', + 'sounds/**', + 'fonts/**', + 'js/home/main.js', + 'js/lib/plugin-detect.js', + 'js/lib/empty.pdf', + 'js/lib/charts/**' + ] + },{ + expand: true, + dot: false, + cwd: 'app/bower_components/bootstrap/', + dest: 'dist', + src: [ + 'img/*' + ] + } + ] + }; + config.copy.dmu = { + files: [ + { + expand: true, + dot: false, + cwd: 'app', + dest: 'dist', + src: [ + 'product-structure/js/workers/*', + 'js/dmu/*' + ] + } + ] + }; + config.copy.i18n ={ + files: [ + { + expand: true, + dot: false, + cwd: 'app', + dest: 'dist', + src: [ + 'js/localization/nls/*', + 'js/localization/nls/fr/*', + 'js/localization/nls/es/*' + ] + } + ] + }; + }, + + loadTasks:function(grunt){ + } + +}; diff --git a/docdoku-web-front/index.html b/docdoku-web-front/index.html new file mode 100644 index 0000000000..d10cee026e --- /dev/null +++ b/docdoku-web-front/index.html @@ -0,0 +1,26 @@ + + + + + Mocha Spec Runner + + + +
                                    + + + + + + + + + + + + + diff --git a/docdoku-web-front/package.json b/docdoku-web-front/package.json new file mode 100644 index 0000000000..9407bdbc2d --- /dev/null +++ b/docdoku-web-front/package.json @@ -0,0 +1,50 @@ +{ + "name": "docdokuplm", + "private": true, + "description": "Web based application for DocDokuPLM server", + "version": "1.1.0", + "devDependencies": { + "connect-livereload": "~0.3.2", + "del": "^1.2.0", + "grunt": "~0.4.1", + "grunt-bower-requirejs": "~0.8.4", + "grunt-connect-proxy": "~0.2.0", + "grunt-contrib-clean": "~0.5.0", + "grunt-contrib-compress": "^1.3.0", + "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-connect": "~0.6.0", + "grunt-contrib-copy": "~0.5.0", + "grunt-contrib-cssmin": "~0.7.0", + "grunt-contrib-htmlmin": "~0.1.3", + "grunt-contrib-imagemin": "~0.5.0", + "grunt-contrib-jshint": "~0.8.0", + "grunt-contrib-jst": "~0.5.1", + "grunt-contrib-less": "latest", + "grunt-contrib-uglify": "~0.3.3", + "grunt-contrib-watch": "~0.5.3", + "grunt-execute": "^0.2.2", + "grunt-mocha": "~0.4.10", + "grunt-open": "~0.2.3", + "grunt-requirejs": "~0.4.0", + "grunt-requirejs-i18n": "^0.1.0", + "grunt-rev": "~0.1.0", + "grunt-usemin": "~0.1.13", + "jshint-stylish": "~0.1.5", + "load-grunt-tasks": "~0.3.0", + "time-grunt": "~0.2.10", + "underscore": "^1.7.0", + "xml2js": "^0.4.9", + "yargs": "^4.7.1" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "preclean": "npm install && bower install", + "prebuild": "npm install && bower install", + "predev": "npm install && bower install", + "clean": "grunt clean", + "build": "grunt build", + "dev":"grunt serve" + } +} diff --git a/docdoku-web-front/pom.xml b/docdoku-web-front/pom.xml new file mode 100644 index 0000000000..150a34f140 --- /dev/null +++ b/docdoku-web-front/pom.xml @@ -0,0 +1,110 @@ + + + + com.docdoku + docdoku-plm + 2.5-SNAPSHOT + + 4.0.0 + + docdoku-web-front + + + + + + + env-ci + + + + org.codehaus.mojo + exec-maven-plugin + 1.3.2 + + + clean-yo-dist + clean + + exec + + + . + npm + + run + clean + + + + + build-yo + generate-resources + + exec + + + . + npm + + run + build + + + + + + + + + + env-prod + + true + + + + + org.codehaus.mojo + exec-maven-plugin + 1.3.2 + + + clean-yo-dist + clean + + exec + + + . + npm + + run + clean + + + + + build-yo + generate-resources + + exec + + + . + npm + + run + build + + + + + + + + + + diff --git a/docdoku-web-front/tests/.gitignore b/docdoku-web-front/tests/.gitignore new file mode 100644 index 0000000000..d2162d8253 --- /dev/null +++ b/docdoku-web-front/tests/.gitignore @@ -0,0 +1,4 @@ +*.png +*.xml +*.log +*.wav diff --git a/docdoku-web-front/tests/README.md b/docdoku-web-front/tests/README.md new file mode 100644 index 0000000000..b71c30e343 --- /dev/null +++ b/docdoku-web-front/tests/README.md @@ -0,0 +1,19 @@ +# DocdokuPLM WebFront tests + +These tests are functional tests. The web application and api must be running. + +# Requirements + +* node +* npm +* casperjs + +# Running tests + +Edit config.js according to your environment (or use command line options) + + cd docdoku-plm/docdoku-web-front/ + npm install; + cd tests; + node run.js + diff --git a/docdoku-web-front/tests/config.js b/docdoku-web-front/tests/config.js new file mode 100644 index 0000000000..c7d3597c22 --- /dev/null +++ b/docdoku-web-front/tests/config.js @@ -0,0 +1,169 @@ +/*global module*/ +module.exports = { + + // URL + protocol: 'http', + domain: 'localhost', + port: '8080', + contextPath: '/', + + // Workspace authentication + workspace: 'test', + login: 'test', + pass: 'test', + + // Configuration + logLevel: 'warning', + debug: false, + verbose: true, + failFast:true, + xunit: 'results.xml', + waitOnRequest: false, + debugResponses:false, + debugRequests:false, + requestTimeOut: 1000, // ms + globalTimeout: 20, // minutes + soundOnTestsEnd:false, + + // Files to test + pre: [ + 'js/pre/start.js' + ], + post: [ + 'js/auth/logout.js' + ], + includes: [ + 'js/includes/vars.js', + 'js/includes/helpers.js' + ], + paths: [ + + // Login, erase potential data + 'js/auth/login.js', + 'js/pre/clean.js', + + // Content Type check + 'js/content-type/contentTypeCheck.js', + + // Workflow creation + 'js/change-management/role/roleCreation.js', + 'js/change-management/workflow/workflowCreation.js', + 'js/change-management/workflow/workflowDuplication.js', + + // Documents tags + 'js/document-management/tag/tagCreation.js', + 'js/document-management/tag/tagList.js', + + // Document templates + 'js/document-management/template/templateCreation.js', + + // Folder and document creation + 'js/document-management/folder/folderCreation.js', + 'js/document-management/document/documentCreationFromTemplate.js', + 'js/document-management/document/documentCreationWithWorkflow.js', + 'js/document-management/document/documentCreation.js', + 'js/document-management/document/documentsCreation.js', + 'js/document-management/document/documentUploadFile.js', + 'js/document-management/document/documentFilesRemove.js', + 'js/document-management/document/documentMultipleCheckin.js', + 'js/document-management/document/documentMultipleCheckout.js', + 'js/document-management/document/documentAddLink.js', + 'js/document-management/document/documentClickLink.js', + 'js/document-management/document/documentMultipleUndoCheckout.js', + 'js/document-management/document/documentCheckout.js', + 'js/document-management/document/documentCheckin.js', + 'js/document-management/document/documentRelease.js', + 'js/document-management/document/documentObsolete.js', + 'js/document-management/document/documentMultipleRelease.js', + + // Document sharing + 'js/document-management/share/sharedDocumentCreation.js', + 'js/document-management/share/publicSharedDocument.js', + 'js/document-management/share/privateSharedDocument.js', + 'js/document-management/share/expiredSharedDocument.js', + + + // Part templates + 'js/product-management/template/partTemplateCreation.js', + 'js/product-management/template/templateWithAttribute.js', + + // Part and assembly creation + 'js/product-management/part/partCreation.js', + 'js/product-management/part/showPartDetails.js', + 'js/product-management/part/partUploadNativeCadFile.js', + 'js/product-management/part/partAddLink.js', + 'js/product-management/part/partClickLink.js', + 'js/product-management/part/partCheckin.js', + 'js/product-management/part/partCheckout.js', + 'js/product-management/assembly/assemblyCreation.js', + 'js/product-management/assembly/assemblyCheck.js', + 'js/product-management/part/partCheckin.js', + 'js/product-management/part/partsMultipleCheckout.js', + 'js/product-management/part/partsMultipleCheckin.js', + 'js/product-management/part/partsMultipleCheckout.js', + 'js/product-management/part/partsMultipleUndoCheckout.js', + 'js/product-management/part/partRelease.js', + 'js/product-management/part/partObsolete.js', + 'js/product-management/part/partsMultipleRelease.js', + + // Part sharing + 'js/product-management/share/sharedPartCreation.js', + 'js/product-management/share/publicSharedPart.js', + 'js/product-management/share/expiredSharedPart.js', + 'js/product-management/share/privateSharedPart.js', + + // Product and baseline creation + 'js/product-management/product/productCreation.js', + 'js/product-management/pathToPathLink/pathToPathLinkCreation.js', + 'js/product-management/baseline/baselineCreation.js', + 'js/product-management/product-instance/productInstanceCreation.js', + + // Product structure + 'js/product-management/assembly/bomInspection.js', + 'js/product-management/assembly/instancesCheck.js', + 'js/product-management/product-instance/productInstanceData.js', + 'js/product-management/pathToPathLink/pathToPathLinkCheck.js', + 'js/product-management/part/checkUsedByList.js', + + // Change items creation + 'js/change-management/issue/issueCreation.js', + 'js/change-management/request/requestCreation.js', + 'js/change-management/order/orderCreation.js', + 'js/change-management/milestone/milestoneCreation.js', + + //LOV Creation + 'js/document-management/lov/lovCreation.js', + + // Attributes creation + 'js/common/attributes.js', + 'js/common/partFromTemplate.js', + + // Query builder + "js/product-management/queryBuilder/queryBuilderSearch.js", + + // Deletions + 'js/product-management/product-instance/productInstanceDeletion.js', + 'js/product-management/baseline/baselineDeletion.js', + 'js/product-management/product/productDeletion.js', + 'js/product-management/part/partDeletion.js', + 'js/product-management/part/partMultipleDeletion.js', + 'js/product-management/template/partTemplateDeletion.js', + 'js/document-management/lov/lovDeletion.js', + + 'js/document-management/tag/tagDeletion.js', + 'js/document-management/document/documentDeletion.js', + 'js/document-management/document/documentMultipleDeletion.js', + 'js/document-management/template/templateDeletion.js', + 'js/document-management/folder/folderDeletion.js', + + 'js/change-management/workflow/workflowDeletion.js', + 'js/change-management/issue/issueDeletion.js', + 'js/change-management/milestone/milestoneDeletion.js', + 'js/change-management/order/orderDeletion.js', + 'js/change-management/request/requestDeletion.js', + + //Create a document template with a LOV attribute, needs an empty list of documents template, and an empty list of LOV + 'js/document-management/lov/lovInTemplateCreation.js' + + ] +}; diff --git a/docdoku-web-front/tests/js/auth/login.js b/docdoku-web-front/tests/js/auth/login.js new file mode 100644 index 0000000000..660a041518 --- /dev/null +++ b/docdoku-web-front/tests/js/auth/login.js @@ -0,0 +1,69 @@ +/*global casper,homeUrl,login,pass,apiUrls*/ +casper.test.begin('Login tests suite', 3, function loginTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open app home page + */ + casper.then(function () { + return this.open(homeUrl); + }); + + /** + * Test to find the login form + */ + casper.then(function waitForLoginForm() { + return this.waitForSelector('form[id="login_form"]', function loginFormFound() { + this.test.assert(true, 'Login form found'); + }, function fail() { + this.capture('screenshot/login/waitForLoginForm-error.png'); + this.test.assert(false, 'Login form not found'); + }); + }); + + /** + * Fill the login form + */ + casper.then(function fillLoginForm() { + this.fill('form[id="login_form"]', { + 'login_form-login': login, + 'login_form-password': pass + }, false); + }); + + /** + * Submit the login form + */ + casper.then(function submitLoginForm() { + this.click('#login_form-login_button'); + }); + + /** + * We should be redirected on workspace menu + */ + casper.then(function waitForLogoutButton() { + return this.waitForSelector('#logout_link',function(){ + this.test.assert(true,'Logout link should be displayed'); + },function(){ + this.capture('screenshot/auth/login.png'); + this.test.assert(true,'Logout link should be displayed'); + }); + }); + + /** + * Check if we are connected to the api + */ + casper.then(function checkSessionState() { + return this.open(apiUrls.userInfo).then(function(response){ + this.test.assertEqual(response.status, 200, 'User "' + login + '" should log in'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/auth/logout.js b/docdoku-web-front/tests/js/auth/logout.js new file mode 100644 index 0000000000..7165d7c540 --- /dev/null +++ b/docdoku-web-front/tests/js/auth/logout.js @@ -0,0 +1,48 @@ +/*global casper,urls*/ +casper.test.begin('Logout tests suite', 1, function logoutTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Wait for disconnect link, and click it + */ + casper.then(function () { + return this.waitForSelector('#logout_link a', function onLogoutLinkReady() { + this.click('#logout_link a'); + }); + }); + + /** + * Test to find the login form + */ + casper.then(function checkForLoginForm() { + return this.waitForSelector('form[id="login_form"]', function loginFormFound() { + this.test.assert(true, 'Login form found'); + }); + }); + + /** + * Test session state + + casper.then(function checkResource(){ + console.log(apiUrls.userInfo) + this.open(apiUrls.userInfo, {method: 'GET'}).then(function (response) { + console.log(this.getPageContent()) + this.test.assert(response.status == 401,'We should get a 401 HTTP code'); + }); + }); + */ + casper.run(function () { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/change-management/issue/issueCreation.js b/docdoku-web-front/tests/js/change-management/issue/issueCreation.js new file mode 100644 index 0000000000..49f563bae4 --- /dev/null +++ b/docdoku-web-front/tests/js/change-management/issue/issueCreation.js @@ -0,0 +1,96 @@ +/*global casper,urls,workspace,changeItems*/ + +casper.test.begin('Change issue creation tests suite', 3, function changeIssueCreationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open change management URL + * */ + + casper.then(function () { + return this.open(urls.changeManagement); + }); + + /** + * Open change issues nav + */ + casper.then(function waitForChangeIssuesNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/issues"]', function clickOnChangeIssueNavLink() { + this.click('a[href="#' + workspace + '/issues"]'); + }, function fail() { + this.capture('screenshot/issueCreation/waitForChangeIssuesNavLink-error.png'); + this.test.assert(false, 'Change issue nav link can not be found'); + }); + }); + + /** + * Open issue creation modal + */ + casper.then(function openNewChangeIssueModal() { + return this.waitForSelector('.actions .new-issue', + function clickOnChangeIssueCreationLink() { + this.click('.actions .new-issue'); + }, function fail() { + this.capture('screenshot/issueCreation/openNewChangeIssueModal-error.png'); + this.test.assert(false, 'New issue button can not be found'); + } + ); + }); + + /** + * Try to create an issue without a name + */ + casper.then(function waitForChangeIssueCreationModal() { + return this.waitForSelector('#issue_creation_modal .modal-footer .btn-primary', function createIssueWithoutName() { + this.click('#issue_creation_modal .modal-footer .btn-primary'); + this.test.assertExists('#issue_creation_modal #inputIssueName:invalid', 'Should not create an issue without a name'); + }, function fail() { + this.capture('screenshot/issueCreation/waitForChangeIssueCreationModal-error.png'); + this.test.assert(false, 'Change issue modal can not be found'); + }); + }); + + /** + * Fill the form and create the issue + */ + casper.then(function fillAndSubmitChangeIssueCreationModal() { + this.waitForSelector('#issue_creation_modal input#inputIssueName', function fillForm() { + this.sendKeys('#issue_creation_modal input#inputIssueName', changeItems.changeIssue1.number, {reset: true}); + this.click('#issue_creation_modal .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/issueCreation/fillAndSubmitChangeIssueCreationModal-error.png'); + this.test.assert(false, 'Change issue modal can not be found'); + }); + }); + + /** + * Wait for modal to close + */ + casper.then(function waitForChangeIssueCreationModalToBeClosed() { + return this.waitWhileSelector('#issue_creation_modal', function modalClosed() { + this.test.assert(true, 'Modal is closed'); + }, function fail() { + this.capture('screenshot/issueCreation/checkForChangeIssueCreation-error.png'); + this.test.assert(false, 'Change issue creation modal not closed'); + }); + }); + + /** + * Verify the issue is in the list + */ + casper.then(function checkIssueIsCreated() { + return this.waitForSelector('#issue_table tr td.reference', function modalClosed() { + this.test.assertSelectorHasText('#issue_table tr td.reference', changeItems.changeIssue1.number); + }, function fail() { + this.capture('screenshot/issueCreation/checkForChangeIssueCreation-error.png'); + this.test.assert(false, 'Change issue not in the list'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/change-management/issue/issueDeletion.js b/docdoku-web-front/tests/js/change-management/issue/issueDeletion.js new file mode 100644 index 0000000000..5e5916ae6f --- /dev/null +++ b/docdoku-web-front/tests/js/change-management/issue/issueDeletion.js @@ -0,0 +1,82 @@ +/*global casper,urls,workspace*/ + +casper.test.begin('Change issue deletion tests suite', 2, function changeIssueDeletionTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open change management URL + * */ + + casper.then(function () { + return this.open(urls.changeManagement); + }); + + /** + * Open change issues nav + */ + casper.then(function waitForChangeIssuesNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/issues"]', function clickOnChangeIssueNavLink() { + this.click('a[href="#' + workspace + '/issues"]'); + }, function fail() { + this.capture('screenshot/issueDeletion/waitForChangeIssuesNavLink-error.png'); + this.test.assert(false, 'Change issue nav link can not be found'); + }); + }); + + /** + * Click the 'select all' checkbox + */ + casper.then(function waitForSelectAllCheckbox() { + return this.waitForSelector('#issue_table thead tr:first-child th:first-child input', function clickOnSelectAllCheckbox() { + this.click('#issue_table thead tr:first-child th:first-child input'); + }, function fail() { + this.capture('screenshot/issueDeletion/waitForSelectAllCheckbox-error.png'); + this.test.assert(false, 'Select all checkbox can not be found'); + }); + }); + + /** + * Wait for the delete button to appear + */ + casper.then(function waitForDeleteButton() { + return this.waitForSelector('.actions .delete', function clickOnDeleteButton() { + this.click('.actions .delete'); + this.test.assert(true, 'Delete button available'); + }, function fail() { + this.capture('screenshot/issueDeletion/waitForDeleteButton-error.png'); + this.test.assert(false, 'Select all checkbox can not be found'); + }); + }); + + /** + * Wait for the confirmation modal + */ + casper.then(function waitForConfirmationModal() { + return this.waitForSelector('.bootbox', function confirmDeletion() { + this.click('.bootbox .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/issueDeletion/waitForConfirmationModal-error.png'); + this.test.assert(false, 'Change issue deletion confirmation modal can not be found'); + }); + }); + + /** + * Assert that there's no more entries in the table + **/ + casper.then(function waitForTableToBeEmpty() { + return this.waitWhileSelector('#issue_table tbody tr:first-child td:first-child input', function onBaselineTableEmpty() { + this.test.assert(true, 'No more issues in the list'); + }, function fail() { + this.capture('screenshot/issueDeletion/waitForTableToBeEmpty-error.png'); + this.test.assert(false, 'Issue table still not empty'); + }); + }); + + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/change-management/milestone/milestoneCreation.js b/docdoku-web-front/tests/js/change-management/milestone/milestoneCreation.js new file mode 100644 index 0000000000..de89453230 --- /dev/null +++ b/docdoku-web-front/tests/js/change-management/milestone/milestoneCreation.js @@ -0,0 +1,117 @@ +/*global casper,urls,workspace,changeItems*/ + +casper.test.begin('Milestone creation tests suite', 5, function milestoneCreationTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open change management URL + * */ + + casper.then(function () { + return this.open(urls.changeManagement); + }); + + /** + * Open milestones nav + */ + casper.then(function waitForMilestonesNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/milestones"]', function clickOnMilestoneNavLink() { + this.click('a[href="#' + workspace + '/milestones"]'); + }, function fail() { + this.capture('screenshot/milestoneCreation/waitForMilestonesNavLink-error.png'); + this.test.assert(false, 'Milestone nav link can not be found'); + }); + }); + + /** + * Open milestone creation modal + */ + casper.then(function openNewMilestoneModal() { + return this.waitForSelector('.actions .new-milestone', + function clickOnMilestoneCreationLink() { + this.click('.actions .new-milestone'); + }, function fail() { + this.capture('screenshot/milestonCreation/openNewMilestoneModal-error.png'); + this.test.assert(false, 'New mileston button can not be found'); + } + ); + }); + + /** + * Try to create an milestone without a title + */ + casper.then(function waitForMilestoneCreationModal() { + return this.waitForSelector('#milestone_creation_modal.ready', function createMilestoneWithoutName() { + this.click('#milestone_creation_modal .modal-footer .btn-primary'); + this.test.assertExists('#milestone_creation_modal #inputMilestoneTitle:invalid', 'Should not create a milestone without a name'); + }, function fail() { + this.capture('screenshot/milestoneCreation/waitForMilestoneCreationModal-error.png'); + this.test.assert(false, 'Milestone modal can not be found'); + }); + }); + + /** + * Try to create an milestone without a date + */ + + casper.then(function waitForMilestoneCreationModal() { + return this.waitForSelector('#milestone_creation_modal .modal-footer .btn-primary', function createMilestoneWithoutDate() { + this.sendKeys('#milestone_creation_modal input#inputMilestoneTitle', changeItems.milestone1.title, {reset: true}); + this.click('#milestone_creation_modal .modal-footer .btn-primary'); + this.test.assertExists('#milestone_creation_modal #inputMilestoneDueDate:invalid', 'Should not create a milestone without a date'); + }, function fail() { + this.capture('screenshot/milestoneCreation/waitForMilestoneCreationModal-error.png'); + this.test.assert(false, 'Milestone modal can not be found'); + }); + }); + + /** + * Fill the form and create the milestone + */ + casper.then(function fillAndSubmitMilestoneCreationModal() { + return this.waitForSelector('#milestone_creation_modal.ready', function fillForm() { + this.sendKeys('#milestone_creation_modal input#inputMilestoneTitle', changeItems.milestone1.title, {reset: true}); + this.sendKeys('#milestone_creation_modal input#inputMilestoneDueDate', changeItems.milestone1.date, {reset: true}); + this.waitWhileSelector('#milestone_creation_modal input:invalid', function submit() { + this.click('#milestone_creation_modal .modal-footer .btn-primary'); + this.test.assert(true, 'milestone created'); + }, function fail() { + this.capture('screenshot/milestoneCreation/WaitForInvalidInput.png'); + this.test.assert(false, 'input are still invalid after sending the data'); + }); + }, function fail() { + this.capture('screenshot/milestoneCreation/fillAndSubmitMilestoneCreationModal-error.png'); + this.test.assert(false, 'Milestone modal can not be found'); + }); + }); + + /** + * Wait for modal to close + */ + casper.then(function waitForMilestoneCreationModalToBeClosed() { + return this.waitWhileSelector('#milestone_creation_modal', function modalClosed() { + this.test.assert(true, 'Modal is closed'); + }, function fail() { + this.capture('screenshot/milestoneCreation/waitForMilestoneCreationModalToBeClosed-error.png'); + this.test.assert(false, 'Milestone creation modal not closed'); + }); + }); + + /** + * Verify the milestone is in the list + */ + casper.then(function checkMilestoneIsCreated() { + return this.waitForSelector('#milestone_table tr td.reference', function modalClosed() { + this.test.assertSelectorHasText('#milestone_table tr td.reference', changeItems.milestone1.title); + }, function fail() { + this.capture('screenshot/milestoneCreation/checkMilestoneIsCreated-error.png'); + this.test.assert(false, 'Milestone not in the list'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/change-management/milestone/milestoneDeletion.js b/docdoku-web-front/tests/js/change-management/milestone/milestoneDeletion.js new file mode 100644 index 0000000000..23e661be4a --- /dev/null +++ b/docdoku-web-front/tests/js/change-management/milestone/milestoneDeletion.js @@ -0,0 +1,82 @@ +/*global casper,urls,workspace*/ + +casper.test.begin('Milestone deletion tests suite', 2, function milestoneDeletionTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open change management URL + * */ + + casper.then(function () { + return this.open(urls.changeManagement); + }); + + /** + * Open milestones nav + */ + casper.then(function waitForMilestonesNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/milestones"]', function clickOnMilestoneNavLink() { + this.click('a[href="#' + workspace + '/milestones"]'); + }, function fail() { + this.capture('screenshot/milestoneDeletion/waitForMilestonesNavLink-error.png'); + this.test.assert(false, 'Milestone nav link can not be found'); + }); + }); + + /** + * Click the 'select all' checkbox + */ + casper.then(function waitForSelectAllCheckbox() { + return this.waitForSelector('#milestone_table thead tr:first-child th:first-child input', function clickOnSelectAllCheckbox() { + this.click('#milestone_table thead tr:first-child th:first-child input'); + }, function fail() { + this.capture('screenshot/milestoneDeletion/waitForSelectAllCheckbox-error.png'); + this.test.assert(false, 'Select all checkbox can not be found'); + }); + }); + + /** + * Wait for the delete button to appear + */ + casper.then(function waitForDeleteButton() { + return this.waitForSelector('.actions .delete', function clickOnDeleteButton() { + this.click('.actions .delete'); + this.test.assert(true, 'Delete button available'); + }, function fail() { + this.capture('screenshot/milestoneDeletion/waitForDeleteButton-error.png'); + this.test.assert(false, 'Select all checkbox can not be found'); + }); + }); + + /** + * Wait for the confirmation modal + */ + casper.then(function waitForConfirmationModal() { + return this.waitForSelector('.bootbox', function confirmDeletion() { + this.click('.bootbox .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/milestoneDeletion/waitForConfirmationModal-error.png'); + this.test.assert(false, 'Change issue deletion confirmation modal can not be found'); + }); + }); + + /** + * Assert that there's no more entries in the table + **/ + casper.then(function waitForTableToBeEmpty() { + return this.waitWhileSelector('#milestone_table tbody tr:first-child td:first-child input', function onBaselineTableEmpty() { + this.test.assert(true, 'No more issues in the list'); + }, function fail() { + this.capture('screenshot/milestoneDeletion/waitForTableToBeEmpty-error.png'); + this.test.assert(false, 'Issue table still not empty'); + }); + }); + + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/change-management/order/orderCreation.js b/docdoku-web-front/tests/js/change-management/order/orderCreation.js new file mode 100644 index 0000000000..2f7689ea01 --- /dev/null +++ b/docdoku-web-front/tests/js/change-management/order/orderCreation.js @@ -0,0 +1,97 @@ +/*global casper,urls,workspace,changeItems*/ + +casper.test.begin('Change order creation tests suite', 3, function changeOrderCreationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open change management URL + * */ + + casper.then(function () { + return this.open(urls.changeManagement); + }); + + /** + * Open change orders nav + */ + casper.then(function waitForChangeOrdersNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/orders"]', function clickOnChangeOrderNavLink() { + this.click('a[href="#' + workspace + '/orders"]'); + }, function fail() { + this.capture('screenshot/orderCreation/waitForChangeOrdersNavLink-error.png'); + this.test.assert(false, 'Change order nav link can not be found'); + }); + }); + + /** + * Open order creation modal + */ + casper.then(function openNewChangeOrderModal() { + return this.waitForSelector('.actions .new-order', + function clickOnChangeOrderCreationLink() { + this.click('.actions .new-order'); + }, function fail() { + this.capture('screenshot/orderCreation/openNewChangeOrderModal-error.png'); + this.test.assert(false, 'New order button can not be found'); + } + ); + }); + + + /** + * Try to create an order without a name + */ + casper.then(function waitForChangeOrderCreationModal() { + return this.waitForSelector('#order_creation_modal .modal-footer .btn-primary', function createOrderWithoutName() { + this.click('#order_creation_modal .modal-footer .btn-primary'); + this.test.assertExists('#order_creation_modal #inputOrderName:invalid', 'Should not create an order without a name'); + }, function fail() { + this.capture('screenshot/orderCreation/waitForChangeOrderCreationModal-error.png'); + this.test.assert(false, 'Change order modal can not be found'); + }); + }); + + /** + * Fill the form and create the order + */ + casper.then(function fillAndSubmitChangeOrderCreationModal() { + return this.waitForSelector('#order_creation_modal input#inputOrderName', function fillForm() { + this.sendKeys('#order_creation_modal input#inputOrderName', changeItems.changeOrder1.number, {reset: true}); + this.click('#order_creation_modal .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/orderCreation/fillAndSubmitChangeOrderCreationModal-error.png'); + this.test.assert(false, 'Change order modal can not be found'); + }); + }); + + /** + * Wait for modal to close + */ + casper.then(function waitForChangeOrderCreationModalToBeClosed() { + return this.waitWhileSelector('#order_creation_modal', function modalClosed() { + this.test.assert(true, 'Modal is closed'); + }, function fail() { + this.capture('screenshot/orderCreation/checkForChangeOrderCreation-error.png'); + this.test.assert(false, 'Change order creation modal not closed'); + }); + }); + + /** + * Verify the order is in the list + */ + casper.then(function checkOrderIsCreated() { + return this.waitForSelector('#order_table tr td.reference', function modalClosed() { + this.test.assertSelectorHasText('#order_table tr td.reference', changeItems.changeOrder1.number); + }, function fail() { + this.capture('screenshot/orderCreation/checkForChangeOrderCreation-error.png'); + this.test.assert(false, 'Change order not in the list'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/change-management/order/orderDeletion.js b/docdoku-web-front/tests/js/change-management/order/orderDeletion.js new file mode 100644 index 0000000000..8b7a9f73ba --- /dev/null +++ b/docdoku-web-front/tests/js/change-management/order/orderDeletion.js @@ -0,0 +1,81 @@ +/*global casper,urls,workspace*/ + +casper.test.begin('Change order deletion tests suite', 2, function changeOrderDeletionTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open change management URL + * */ + + casper.then(function () { + return this.open(urls.changeManagement); + }); + + /** + * Open change orders nav + */ + casper.then(function waitForChangeOrdersNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/orders"]', function clickOnChangeOrderNavLink() { + this.click('a[href="#' + workspace + '/orders"]'); + }, function fail() { + this.capture('screenshot/orderDeletion/waitForChangeOrdersNavLink-error.png'); + this.test.assert(false, 'Change order nav link can not be found'); + }); + }); + + /** + * Click the 'select all' checkbox + */ + casper.then(function waitForSelectAllCheckbox() { + return this.waitForSelector('#order_table thead tr:first-child th:first-child input', function clickOnSelectAllCheckbox() { + this.click('#order_table thead tr:first-child th:first-child input'); + }, function fail() { + this.capture('screenshot/orderDeletion/waitForSelectAllCheckbox-error.png'); + this.test.assert(false, 'Select all checkbox can not be found'); + }); + }); + + /** + * Wait for the delete button to appear + */ + casper.then(function waitForDeleteButton() { + return this.waitForSelector('.actions .delete', function clickOnDeleteButton() { + this.click('.actions .delete'); + this.test.assert(true, 'Delete button available'); + }, function fail() { + this.capture('screenshot/orderDeletion/waitForDeleteButton-error.png'); + this.test.assert(false, 'Select all checkbox can not be found'); + }); + }); + + /** + * Wait for the confirmation modal + */ + casper.then(function waitForConfirmationModal() { + this.waitForSelector('.bootbox', function confirmDeletion() { + this.click('.bootbox .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/orderDeletion/waitForConfirmationModal-error.png'); + this.test.assert(false, 'Change order deletion confirmation modal can not be found'); + }); + }); + + /** + * Assert that there's no more entries in the table + **/ + casper.then(function waitForTableToBeEmpty() { + return this.waitWhileSelector('#order_table tbody tr:first-child td:first-child input', function onBaselineTableEmpty() { + this.test.assert(true, 'No more orders in the list'); + }, function fail() { + this.capture('screenshot/orderDeletion/waitForTableToBeEmpty-error.png'); + this.test.assert(false, 'Order table still not empty'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/change-management/request/requestCreation.js b/docdoku-web-front/tests/js/change-management/request/requestCreation.js new file mode 100644 index 0000000000..6f6ea221fc --- /dev/null +++ b/docdoku-web-front/tests/js/change-management/request/requestCreation.js @@ -0,0 +1,95 @@ +/*global casper,urls,workspace,changeItems*/ + +casper.test.begin('Change request creation tests suite', 3, function changeRequestCreationTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open change management URL + * */ + + casper.then(function () { + return this.open(urls.changeManagement); + }); + + /** + * Open change requests nav + */ + casper.then(function waitForChangeRequestsNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/requests"]', function clickOnChangeRequestsNavLink() { + this.click('a[href="#' + workspace + '/requests"]'); + }, function fail() { + this.capture('screenshot/requestCreation/waitForChangeRequestsNavLink-error.png'); + this.test.assert(false, 'Change request nav link can not be found'); + }); + }); + + /** + * Open request creation modal + */ + casper.then(function openNewChangeRequestModal() { + return this.waitForSelector('.actions .new-request', + function clickOnChangeRequestCreationLink() { + this.click('.actions .new-request'); + }, function fail() { + this.capture('screenshot/requestCreation/openNewChangeRequestModal-error.png'); + this.test.assert(false, 'New request button can not be found'); + } + ); + }); + + /** + * Try to create an request without a name + */ + casper.then(function waitForChangeRequestCreationModal() { + return this.waitForSelector('#request_creation_modal .modal-footer .btn-primary', function createRequestWithoutName() { + this.click('#request_creation_modal .modal-footer .btn-primary'); + this.test.assertExists('#request_creation_modal #inputRequestName:invalid', 'Should not create an request without a name'); + }, function fail() { + this.capture('screenshot/requestCreation/waitForChangeRequestCreationModal-error.png'); + this.test.assert(false, 'Change request modal can not be found'); + }); + }); + + /** + * Fill the form and create the request + */ + casper.then(function fillAndSubmitChangeRequestCreationModal() { + return this.waitForSelector('#request_creation_modal input#inputRequestName', function fillForm() { + this.sendKeys('#request_creation_modal input#inputRequestName', changeItems.changeRequest1.number, {reset: true}); + this.click('#request_creation_modal .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/requestCreation/fillAndSubmitChangeRequestCreationModal-error.png'); + this.test.assert(false, 'Change request modal can not be found'); + }); + }); + + /** + * Wait for modal to close + */ + casper.then(function waitForChangeRequestCreationModalToBeClosed() { + return this.waitWhileSelector('#request_creation_modal', function modalClosed() { + this.test.assert(true, 'Change request creation modal is closed'); + }, function fail() { + this.capture('screenshot/requestCreation/checkForChangeRequestCreation-error.png'); + this.test.assert(false, 'Change request creation modal not closed'); + }); + }); + + /** + * Verify the request is in the list + */ + casper.then(function checkRequestIsCreated() { + return this.waitForSelector('#request_table tr td.reference', function modalClosed() { + this.test.assertSelectorHasText('#request_table tr td.reference', changeItems.changeRequest1.number); + }, function fail() { + this.capture('screenshot/requestCreation/checkForChangeRequestCreation-error.png'); + this.test.assert(false, 'Change request not in the list'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/change-management/request/requestDeletion.js b/docdoku-web-front/tests/js/change-management/request/requestDeletion.js new file mode 100644 index 0000000000..a6c7d9c379 --- /dev/null +++ b/docdoku-web-front/tests/js/change-management/request/requestDeletion.js @@ -0,0 +1,81 @@ +/*global casper,urls,workspace*/ + +casper.test.begin('Change request deletion tests suite', 2, function changeRequestDeletionTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open change management URL + * */ + + casper.then(function () { + return this.open(urls.changeManagement); + }); + + /** + * Open change requests nav + */ + casper.then(function waitForChangeRequestsNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/requests"]', function clickOnChangeRequestNavLink() { + this.click('a[href="#' + workspace + '/requests"]'); + }, function fail() { + this.capture('screenshot/requestDeletion/waitForChangeRequestsNavLink-error.png'); + this.test.assert(false, 'Change requests nav link can not be found'); + }); + }); + + /** + * Click the 'select all' checkbox + */ + casper.then(function waitForSelectAllCheckbox() { + return this.waitForSelector('#request_table thead tr:first-child th:first-child input', function clickOnSelectAllCheckbox() { + this.click('#request_table thead tr:first-child th:first-child input'); + }, function fail() { + this.capture('screenshot/requestDeletion/waitForSelectAllCheckbox-error.png'); + this.test.assert(false, 'Select all checkbox can not be found'); + }); + }); + + /** + * Wait for the delete button to appear + */ + casper.then(function waitForDeleteButton() { + return this.waitForSelector('.actions .delete', function clickOnDeleteButton() { + this.click('.actions .delete'); + this.test.assert(true, 'Delete button available'); + }, function fail() { + this.capture('screenshot/requestDeletion/waitForDeleteButton-error.png'); + this.test.assert(false, 'Select all checkbox can not be found'); + }); + }); + + /** + * Wait for the confirmation modal + */ + casper.then(function waitForConfirmationModal() { + return this.waitForSelector('.bootbox', function confirmDeletion() { + this.click('.bootbox .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/requestDeletion/waitForConfirmationModal-error.png'); + this.test.assert(false, 'Change request deletion confirmation modal can not be found'); + }); + }); + + /** + * Assert that there's no more entries in the table + **/ + casper.then(function waitForTableToBeEmpty() { + return this.waitWhileSelector('#request_table tbody tr:first-child td:first-child input', function onBaselineTableEmpty() { + this.test.assert(true, 'No more requests in the list'); + }, function fail() { + this.capture('screenshot/requestDeletion/waitForTableToBeEmpty-error.png'); + this.test.assert(false, 'Request table still not empty'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/change-management/role/roleCreation.js b/docdoku-web-front/tests/js/change-management/role/roleCreation.js new file mode 100644 index 0000000000..a97395d14e --- /dev/null +++ b/docdoku-web-front/tests/js/change-management/role/roleCreation.js @@ -0,0 +1,126 @@ +/*global casper,urls,workspace,roles*/ + +casper.test.begin('Role creation tests suite', 7, function roleCreationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open change management URL + * */ + + casper.then(function () { + return this.open(urls.changeManagement); + }); + + /** + * Open change workflow nav + */ + casper.then(function waitForChangeWorkflowNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/workflows"]', function clickOnChangeWorkflowNavLink() { + this.click('a[href="#' + workspace + '/workflows"]'); + }, function fail() { + this.capture('screenshot/roleCreation/waitForChangeWorkflowNavLink-error.png'); + this.test.assert(false, 'Workflow nav link can not be found'); + }); + }); + + /** + * Wait for role button + */ + casper.then(function waitForRoleButton() { + return this.waitForSelector('.actions .roles', function clickOnRolesButton() { + this.click('.actions .roles'); + this.test.assert(true, 'Role button is displayed'); + }, function fail() { + this.capture('screenshot/roleCreation/waitForRoleButton-error.png'); + this.test.assert(false, 'Role button can not be found'); + }); + }); + + /** + * Wait for role modal + */ + casper.then(function waitForRoleModal() { + return this.waitForSelector('#roles-modal', function roleModalDisplayed() { + this.test.assert(true, 'Role modal is displayed'); + }, function fail() { + this.capture('screenshot/roleCreation/waitForRoleButton-error.png'); + this.test.assert(false, 'Role button can not be found'); + }); + }); + + /** + * Try to create a role without a name + */ + casper.then(function tryToCreateRoleWithoutName() { + this.click('#roles-modal #form-new-role #new-role'); + this.test.assertExists('#roles-modal #form-new-role .role-name:invalid', 'Should not create a role without a name'); + + }); + + /** + * Try to add a role + */ + casper.then(function tryToAddRole() { + this.sendKeys('#roles-modal #form-new-role .role-name', roles.role1.name, {reset: true}); + this.click('#new-role'); + + return this.waitForSelector('#form-roles > div.roles-item > p > b', function roleAdded() { + this.test.assert(true, 'Role added'); + }, function fail() { + this.capture('screenshot/roleCreation/tryToAddRole-error.png'); + this.test.assert(false, 'Role can not be added'); + }); + }); + + /** + * Add user to default assigned users + */ + casper.then(function tryToAddDefaultAssignedUsers() { + this.evaluate(function () { + var selectize = $('select.role-default-assigned-users')[0].selectize; + var _login = Object.keys(selectize.options)[0]; + selectize.addItem(_login); + return true; + }); + + return this.waitForSelector('#form-roles > div > .role-default-assigned-users > div.selectize-input > div', function(){ + this.test.assert(true, 'Default user added'); + }, function fail() { + this.capture('screenshot/roleCreation/tryToCreateRole-error.png'); + this.test.assert(false, 'Modal not closed'); + }); + }); + + /** + * Try to create the added role + */ + casper.then(function tryToCreateRole() { + this.click('#save-roles'); + + return this.waitWhileSelector('#roles-modal', function modalClosed() { + this.test.assert(true, 'Modal closed'); + }, function fail() { + this.capture('screenshot/roleCreation/tryToCreateRole-error.png'); + this.test.assert(false, 'Modal not closed'); + }); + }); + + /** + * Wait to new worflow button enabling + */ + casper.then(function waitForNewWorkflowButtonEnabling() { + return this.waitForSelector('.actions .new:enabled', function clickOnNewWorkflowButton() { + this.test.assert(true, 'New Workflow button is enabled'); + }, function fail() { + this.capture('screenshot/roleCreation/waitForNewButtonEnabling-error.png'); + this.test.assert(false, 'New Workflow button still disabled'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/change-management/workflow/workflowCreation.js b/docdoku-web-front/tests/js/change-management/workflow/workflowCreation.js new file mode 100644 index 0000000000..864ff91db5 --- /dev/null +++ b/docdoku-web-front/tests/js/change-management/workflow/workflowCreation.js @@ -0,0 +1,129 @@ +/*global casper,urls,workspace,workflows*/ + +casper.test.begin('Workflow creation tests suite', 8, function workflowCreationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open change management URL + * */ + + casper.then(function () { + return this.open(urls.changeManagement); + }); + + /** + * Open change workflow nav + */ + casper.then(function waitForChangeWorkflowNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/workflows"]', function clickOnChangeWorkflowNavLink() { + this.click('a[href="#' + workspace + '/workflows"]'); + }, function fail() { + this.capture('screenshot/workflowCreation/waitForChangeWorkflowNavLink-error.png'); + this.test.assert(false, 'Workflow nav link can not be found'); + }); + }); + + /** + * Wait for workflow button + */ + casper.then(function waitForNewWorkflowButton() { + return this.waitForSelector('.actions .new:enabled', function clickOnNewWorkflowButton() { + this.click('.actions .new'); + this.test.assert(true, 'New Workflow button is displayed'); + }, function fail() { + this.capture('screenshot/workflowCreation/waitForNewWorkflowButton-error.png'); + this.test.assert(false, 'New Workflow button can not be found'); + }); + }); + + + /** + * Wait for workflow editor + */ + casper.then(function waitForWorkflowEditor() { + return this.waitForSelector('#workflow-editor', function editorDisplayed() { + this.test.assert(true, 'Editor displayed'); + }, function fail() { + this.capture('screenshot/workflowCreation/waitForWorkflowEditor-error.png'); + this.test.assert(false, 'Editor can not be displayed'); + }); + }); + + /** + * Try to create a workflow without a name + */ + casper.then(function tryToCreateWorkflowWithoutName() { + return this.waitForSelector('.actions #save-workflow', function clickOnSaveWorkflowButton() { + this.click('.actions #save-workflow'); + this.test.assertExists('.actions #workflow-name:invalid', 'Should not create a workflow without a name'); + this.sendKeys('.actions #workflow-name', workflows.workflow1.name, {reset: true}); + this.sendKeys('#workflow-editor #final-state', workflows.workflow1.finalState, {reset: true}); + }, function fail() { + this.capture('screenshot/workflowCreation/tryToCreateWorkflowWithoutName-error.png'); + this.test.assert(false, 'Save workflow button can not be found'); + }); + }); + + /** + * Try to create an activity + */ + casper.then(function createActivity() { + return this.waitForSelector('#workflow-editor #add-activity', function clickOnNewActivityButton() { + this.click('#workflow-editor #add-activity'); + this.test.assertExists('#activity-list .activity-section .activity', 'Should create an activity'); + this.sendKeys('#activity-list .activity-section .activity .activity-topbar .activity-state', workflows.workflow1.activities.activity1.name, {reset: true}); + + }, function fail() { + this.capture('screenshot/workflowCreation/createActivity-error.png'); + this.test.assert(false, 'Add activity button can not be found'); + }); + }); + + /** + * Try to create a task + */ + casper.then(function createTask() { + return this.waitForSelector('#activity-list .activity-section .activity .add-task', function clickOnNewTaskButton() { + this.click('#activity-list .activity-section .activity .add-task'); + this.test.assertExists('#activity-list .activity-section .activity .task-list .task', 'Should create an task'); + this.click('#activity-list .activity-section .activity .task-list .task p.task-name'); + this.test.assertExists('#activity-list .activity-section .activity .task-list .task input.task-name', 'Should show the input of the task'); + this.sendKeys('#activity-list .activity-section .activity .task-list .task input.task-name', workflows.workflow1.activities.activity1.tasks.task1.name, {reset: true}); + }, function fail() { + this.capture('screenshot/workflowCreation/createTask-error.png'); + this.test.assert(false, 'Add task button can not be found'); + }); + }); + + /** + * Save the workflow + */ + casper.then(function saveWorkflow() { + this.click('.actions #save-workflow'); + return this.waitWhileSelector('#workflow-editor', function workflowEditorClosed() { + this.test.assert(true, 'Workflow editor has been closed'); + }, function fail() { + this.capture('screenshot/workflowCreation/saveWorkflow-error.png'); + this.test.assert(false, 'Workflow editor can not be closed'); + }); + }); + + /** + * Check if workflow is now in the list + */ + casper.then(function checkForWorkflowToBeCreated() { + return this.waitForSelector('.workflow-table', function workflowTableDisplayed() { + this.test.assertSelectorHasText('.workflow-table tbody tr:first-child td.reference', workflows.workflow1.name); + }, function fail() { + this.capture('screenshot/workflowCreation/checkForWorkflowToBeCreated-error.png'); + this.test.assert(false, 'Workflow not in the list'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/change-management/workflow/workflowDeletion.js b/docdoku-web-front/tests/js/change-management/workflow/workflowDeletion.js new file mode 100644 index 0000000000..d0cddea736 --- /dev/null +++ b/docdoku-web-front/tests/js/change-management/workflow/workflowDeletion.js @@ -0,0 +1,81 @@ +/*global casper,urls,workspace*/ + +casper.test.begin('Workflow Deletion tests suite', 2, function workflowDeletionTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open change management URL + * */ + + casper.then(function () { + return this.open(urls.changeManagement); + }); + + /** + * Open change workflow nav + */ + casper.then(function waitForChangeWorkflowNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/workflows"]', function clickOnChangeWorkflowNavLink() { + this.click('a[href="#' + workspace + '/workflows"]'); + }, function fail() { + this.capture('screenshot/workflowDeletion/waitForChangeWorkflowNavLink-error.png'); + this.test.assert(false, 'Workflow nav link can not be found'); + }); + }); + + /** + * Select the first workflow by its checkbox + */ + casper.then(function clickOnAllWorkflowsCheckbox() { + return this.waitForSelector('.workflow-table thead tr:first-child th:first-child input', function tableDisplayed() { + this.click('.workflow-table thead tr:first-child th:first-child input'); + }, function fail() { + this.capture('screenshot/workflowDeletion/clickOnAllWorkflowsCheckbox-error.png'); + this.test.assert(false, 'Workflow table can not be found'); + }); + }); + + /** + * Check if the delete button appears, and click it + */ + casper.then(function checkForDeleteButton() { + return this.waitForSelector('.actions .delete', function buttonDisplayed() { + this.test.assert(true, 'Delete button displayed'); + this.click('.actions .delete'); + }, function fail() { + this.capture('screenshot/workflowDeletion/checkForDeleteButton-error.png'); + this.test.assert(false, 'Delete workflow button can not be found'); + }); + }); + + /** + * Check if the delete button appears, and click it + */ + casper.then(function waitForConfirmationModal() { + return this.waitForSelector('.bootbox', function confirmWorkflowDeletion() { + this.click('.bootbox .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/workflowDeletion/waitForConfirmationModal-error.png'); + this.test.assert(false, 'Workflow deletion confirmation modal can not be found'); + }); + }); + + /** + * Check if the workflow has disappeared + */ + casper.then(function checkForWorkflowsDeleted() { + return this.waitWhileSelector('.workflow-table tbody tr:first-child td:first-child input', function workflowListEmpty() { + this.test.assert(true, 'Workflows deleted'); + }, function fail() { + this.capture('screenshot/workflowDeletion/checkForWorkflowDeleted-error.png'); + this.test.assert(false, 'The workflow list is not empty'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/change-management/workflow/workflowDuplication.js b/docdoku-web-front/tests/js/change-management/workflow/workflowDuplication.js new file mode 100644 index 0000000000..0efc2b2e93 --- /dev/null +++ b/docdoku-web-front/tests/js/change-management/workflow/workflowDuplication.js @@ -0,0 +1,138 @@ +/*global casper,urls,workspace,workflows*/ + +casper.test.begin('Workflow duplication tests suite', 6, function workflowDuplicationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open change management URL + * */ + + casper.then(function () { + return this.open(urls.changeManagement); + }); + + /** + * Open change workflow nav + */ + casper.then(function waitForChangeWorkflowNavLink() { + this.waitForSelector('a[href="#' + workspace + '/workflows"]', function clickOnChangeWorkflowNavLink() { + this.click('a[href="#' + workspace + '/workflows"]'); + }, function fail() { + this.capture('screenshot/workflowDuplication/waitForChangeWorkflowNavLink-error.png'); + this.test.assert(false, 'Workflow nav link can not be found'); + }); + }); + + + /** + * Open 1st workflow + */ + + casper.then(function checkWorkflowTable() { + this.waitForSelector('.workflow-table tbody tr:first-child td.reference', function workflowTableDisplayed() { + this.click('.workflow-table tbody tr:first-child td.reference'); + }, function fail() { + this.capture('screenshot/workflowDuplication/checkWorkflowTable-error.png'); + this.test.assert(false, 'Workflow table not displayed'); + }); + }); + + /** + * Wait for workflow editor + * */ + + casper.then(function waitForWorkflowEditor() { + this.waitForSelector('#workflow-editor', function workflowEditorDisplayed() { + this.test.assert(true, 'Workflow editor opened'); + }, function fail() { + this.capture('screenshot/workflowDuplication/waitForWorkflowEditor-error.png'); + this.test.assert(false, 'Workflow editor not displayed'); + }); + }); + + /** + * Wait for duplicate button + * */ + + casper.then(function waitForDuplicateButton() { + this.waitForSelector('#copy-workflow', function duplicateButtonDisplayed() { + this.click('#copy-workflow'); + }, function fail() { + this.capture('screenshot/workflowDuplication/waitForDuplicateButton-error.png'); + this.test.assert(false, 'Workflow duplicate button not displayed'); + }); + }); + + /** + * Wait for the duplication modal + * */ + casper.then(function waitForDuplicationModal() { + this.waitForSelector('#modal-copy-workflow.in', function duplicationModalDisplayed() { + this.test.assert(true, 'Workflow duplication modal displayed'); + }, function fail() { + this.capture('screenshot/workflowDuplication/waitForDuplicationModal-error.png'); + this.test.assert(false, 'Workflow duplicate modal not displayed'); + }); + }); + + /** + * Wait for the text input to be automatically filled + * */ + casper.then(function waitForWorkflowNameToBeFilled() { + this.waitWhileSelector('#workflow-copy-name:invalid', function workflowNameFilled() { + this.test.assertSelectorExist('#workflow-copy-name[value="' + workflows.workflow1.name + '"]', 'Input should contain ' + workflows.workflow1.name); + this.sendKeys('#workflow-copy-name', workflows.workflow2.name, {reset: true}); + this.click('#save-copy-workflow-btn'); + }, function fail() { + this.capture('screenshot/workflowDuplication/waitForWorkflowNameToBeFilled-error.png'); + this.test.assert(false, 'Workflow name not automatically filled'); + }); + }); + + /** + * Wait for the duplication modal to disappear + * */ + + casper.then(function waitForDuplicationModalToDisappear() { + this.waitWhileSelector('#modal-copy-workflow', function duplicationModalHidden() { + this.test.assert(true, 'Duplication modal disappeared'); + }, function fail() { + this.capture('screenshot/workflowDuplication/waitForDuplicationModalToDisappear-error.png'); + this.test.assert(false, 'Workflow duplicate modal did not disappear'); + }); + }); + + /** + * Wait for the workflow editor to disappear + * */ + + casper.then(function waitForWorkflowEditorToDisappear() { + this.waitWhileSelector('#workflow-editor', function workflowEditorHidden() { + this.test.assert(true, 'Workflow editor disappeared'); + }, function fail() { + this.capture('screenshot/workflowDuplication/waitForWorkflowEditorToDisappear-error.png'); + this.test.assert(false, 'Workflow editor did not disappear'); + }); + }); + + /** + * Check that we have two lines in the workflow table + * */ + + casper.then(function checkForNewWorkflowCreated() { + this.waitForSelector('.workflow-table tbody tr', function workflowTableDisplayed() { + this.test.assertElementCount('.workflow-table tbody tr', 2, 'Should have 2 rows in workflow table'); + }, function fail() { + this.capture('screenshot/workflowDuplication/checkForNewWorkflowCreated-error.png'); + this.test.assert(false, 'Workflow table not displayed'); + }); + }); + + + casper.run(function allDone() { + this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/common/attributes.js b/docdoku-web-front/tests/js/common/attributes.js new file mode 100644 index 0000000000..8a262bcc05 --- /dev/null +++ b/docdoku-web-front/tests/js/common/attributes.js @@ -0,0 +1,131 @@ +/*global casper,urls*/ + +casper.test.begin('Document creation with attributes', 2, function documentCreationWithAttributes() { + 'use strict'; + casper.open(''); + + /** + * Open document management URL + * */ + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open modal for new document + */ + casper.then(function () { + var newDocumentButtonSelector = '#document-management-content .new-document'; + return this.waitForSelector(newDocumentButtonSelector, function () { + this.click(newDocumentButtonSelector); + }, function () { + this.capture('screenshot/attributes/clickOnNewDocument-error.png'); + this.test.assert(false, 'newDocumentButton id not clickable'); + }); + + }); + + casper.then(function openModal() { + var attributesTabSelector = '.nav.nav-tabs > li:nth-child(3) > a'; + return this.waitForSelector(attributesTabSelector, function () { + this.click(attributesTabSelector); + + }, function () { + this.capture('screenshot/attributes/clickOnAttributeTab-error.png'); + this.test.assert(false, 'Attribute tab cannot be found'); + }); + }); + /** + * Add Attribute + */ + casper.then(function () { + return this.waitForSelector('.nav.nav-tabs > li:nth-child(3).active', function () { + this.click('.btn.add'); + }, function () { + this.capture('screenshot/attributes/attributeTabBecomeActive-error.png'); + this.test.assert(false, 'Attribute tab not appearing'); + }); + }); + + casper.then(function () { + return this.waitForSelector('.list-item.well', function () { + this.test.assertElementCount('.list-item.well', 1); + this.click('.btn.btn-default.cancel'); + }, function () { + this.capture('screenshot/attributes/addAttribute-error.png'); + this.test.assert(false, 'Attribute not appearing in the list'); + }); + }); + + + /** + * Same test on Part + */ + + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partCreation/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Open the part creation modal + */ + casper.then(function waitForNewPartButton() { + return this.waitForSelector('.actions .new-part', function clickNewPartButton() { + this.click('.actions .new-part'); + }, function fail() { + this.capture('screenshot/partCreation/waitForNewPartButton-error.png'); + this.test.assert(false, 'New part button can not be found'); + }); + }); + + casper.then(function openModal() { + var attributesTabSelector = '.nav.nav-tabs > li:nth-child(3) > a'; + return this.waitForSelector(attributesTabSelector, function () { + this.click(attributesTabSelector); + + }, function () { + this.capture('screenshot/attributes/clickOnAttributeTab-error.png'); + this.test.assert(false, 'Attribute tab cannot be found'); + }); + }); + /** + * Add Attribute + */ + casper.then(function () { + return this.waitForSelector('.nav.nav-tabs > li:nth-child(3).active', function () { + this.click('.btn.add'); + }, function () { + this.capture('screenshot/attributes/attributeTabBecomeActive-error.png'); + this.test.assert(false, 'Attribute tab not appearing'); + }); + }); + + + casper.then(function () { + return this.waitForSelector('.list-item.well', function () { + this.test.assertElementCount('.list-item.well', 1); + this.click('.btn[data-dismiss="modal"]'); + }, function () { + this.capture('screenshot/attributes/addAttribute-error.png'); + this.test.assert(false, 'Attribute not appearing in the list'); + }); + }); + + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/common/partFromTemplate.js b/docdoku-web-front/tests/js/common/partFromTemplate.js new file mode 100644 index 0000000000..f13d8e98cc --- /dev/null +++ b/docdoku-web-front/tests/js/common/partFromTemplate.js @@ -0,0 +1,193 @@ +/*global casper,urls,products,$*/ +casper.test.begin('Part from template creation tests suite', 19, function partCreationTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partCreation/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Open the part creation modal + */ + casper.then(function waitForNewPartButton() { + return this.waitForSelector('.actions .new-part', function clickNewPartButton() { + this.click('.actions .new-part'); + }, function fail() { + this.capture('screenshot/parteaCreation/waitForNewPartButton-error.png'); + this.test.assert(false, 'New part button can not be found'); + }); + }); + + /** + * open new modal + */ + casper.then(function openModal() { + return this.waitForSelector('#part_creation_modal input#inputPartNumber', function onNewPartFormReady() { + this.test.assert(true, 'modal opened'); + }, function fail() { + this.capture('screenshot/partCreation/onNewPartFormReady-error.png'); + this.test.assert(false, 'New part form can not be found'); + }); + }); + + casper.then(function selectTemplate() { + //wait for the third option to be loaded, sometimes the modal is created, but the template + //are still to be injected. + return this.waitForSelector('#inputPartTemplate option:nth-child(3)', function succeed() { + this.test.assert(true, 'The template are loaded in the creation modal'); + this.evaluate(function () { + document.querySelector('#inputPartTemplate').selectedIndex = 1; + $('#inputPartTemplate').change(); + return true; + }); + }, function fail() { + this.test.assert(false, 'Could not load the template in the creation modal'); + }); + }); + /** + * Create a part with its partNumber and its partName and choose a template + */ + casper.then(function fillNewPartModalForm() { + this.sendKeys('#part_creation_modal input#inputPartNumber', products.part2.number, {reset: true}); + this.sendKeys('#part_creation_modal input#inputPartName', products.part2.name, {reset: true}); + }); + + + /** + * Go to attribute template + */ + casper.then(function goToAttributeTab() { + this.click('.nav.nav-tabs > li:nth-child(3) > a'); + return this.waitForSelector('.nav.nav-tabs > li:nth-child(3).active', function () { + this.test.assert(true, 'Attribute tab found'); + }, function () { + this.capture('screenshot/attributes/clickOnAttributeTab-error.png'); + this.test.assert(false, 'Attribute tab cannot be found'); + }); + }); + + casper.then(function countAttributeFromTemplate() { + return this.waitForSelector('#attributes-list .list-item', function () { + this.test.assertElementCount('#attributes-list input.name[disabled=disabled]', 2, + 'The attributes name input should be disabled'); + this.test.assertElementCount('#attributes-list select.type', 0, + 'The select type should not be present'); + this.test.assertElementCount('#attributes-list div.type', 2, + 'A div with the type of the attribute should be present'); + this.test.assertElementCount('#attributes-list .fa.fa-bars.sortable-handler.invisible', 2, + 'there should be no sortable button'); + this.test.assertElementCount('#attributes-list .fa.fa-times.invisible', 2, + 'there should be no delete button'); + }, function fail() { + this.test.assert(false, 'list of attributes from template not present'); + }); + }); + + /** + * Go to main tab + */ + casper.then(function () { + this.click('.nav.nav-tabs > li:nth-child(1) > a'); + return this.waitForSelector('.nav.nav-tabs > li:nth-child(1).active', function () { + this.test.assert(true, 'Main tab found'); + + }, function () { + this.capture('screenshot/attributes/clickOnMainTab-error.png'); + this.test.assert(false, 'Main tab not appearing'); + }); + }); + + /** + * Choose second template + */ + + casper.then(function fillNewPartModalForm() { + return this.waitForSelector('#part_creation_modal input#inputPartNumber', function onNewPartFormReady() { + this.test.assertElementCount('#inputPartTemplate option', 3, 'there should be two template available'); + this.evaluate(function () { + document.querySelector('#inputPartTemplate').selectedIndex = 2; + $('#inputPartTemplate').change(); + return true; + }); + }, function fail() { + this.capture('screenshot/partCreation/onNewPartFormReady-error.png'); + this.test.assert(false, 'New part form can not be found'); + }); + }); + + /** + * Go to attribute template + */ + casper.then(function () { + this.click('.nav.nav-tabs > li:nth-child(3) > a'); + return this.waitForSelector('.nav.nav-tabs > li:nth-child(3).active', function () { + this.test.assert(true, 'Attribute tab found'); + + }, function () { + this.capture('screenshot/attributes/clickOnAttributeTab-error.png'); + this.test.assert(false, 'Attribute tab not appearing'); + }); + }); + + /** + * Assert attributes input are present and rightly displayed. + */ + casper.then(function countAttributeFromTemplate() { + + return this.waitForSelector('#attributes-list .list-item', function () { + this.test.assertElementCount('#attributes-list input.name[disabled=disabled]', 1, + 'The attributes name input should be disabled'); + this.test.assertElementCount('#attributes-list select.type', 1, + 'Only one select type should be present'); + this.test.assertElementCount('#attributes-list div.type', 1, + 'A div with the type of the attribute should be present'); + this.test.assertElementCount('#attributes-list input.value[required]', 1, + 'One input Value should be required (mandatory)'); + this.test.assertElementCount('#attributes-list .fa.fa-bars.sortable-handler.invisible', 0, + 'there should be sortable button'); + this.test.assertElementCount('#attributes-list .fa.fa-times.invisible', 1, + 'there should be one delete button invisible'); + this.test.assertElementCount('#attributes-list .fa.fa-times', 2, + 'there should be two delete button'); + }, function fail() { + this.test.assert(false, 'list of attributes from template not present'); + }); + }); + + /** + * Dismiss and close modal + */ + casper.then(function waitForModalToBeClosed() { + this.click('.btn[data-dismiss="modal"]'); + return this.waitWhileSelector('#part_creation_modal', function onPartModalClosed() { + this.test.assert(true, 'Part modal has been closed'); + }, function fail() { + this.capture('screenshot/partCreation/waitForModalToBeClosed-error.png'); + this.test.assert(false, 'Part modal can not close'); + }); + }); + + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/content-type/contentTypeCheck.js b/docdoku-web-front/tests/js/content-type/contentTypeCheck.js new file mode 100644 index 0000000000..2d06da86ef --- /dev/null +++ b/docdoku-web-front/tests/js/content-type/contentTypeCheck.js @@ -0,0 +1,34 @@ +/*global casper,urls,homeUrl*/ + +casper.test.begin('Content type check tests suite', 6, function contentTypeCheckTestsSuite() { + + 'use strict'; + + var urlsToTest = [ + homeUrl, + urls.workspaceAdministration, + urls.changeManagement, + urls.documentManagement, + urls.productManagement, + urls.productStructure + ]; + + var expectedContentType = 'text/html;charset=utf-8'; + + urlsToTest.map(function(url){ + casper.thenOpen(url, function homePageLoaded() { + var contentType = ''; + this.currentResponse.headers.forEach(function(header){ + if(header.name === 'Content-Type'){ + contentType = header.value; + } + }); + this.test.assertEqual(contentType.replace(/ /g, '').toLowerCase(), expectedContentType, 'Content Type should be "text/html;charset=UTF-8"'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentAddLink.js b/docdoku-web-front/tests/js/document-management/document/documentAddLink.js new file mode 100644 index 0000000000..0d512a39e3 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentAddLink.js @@ -0,0 +1,129 @@ +/*global casper,urls,workspace,documents*/ +casper.test.begin('Document add link tests suite', 3, function documentAddLinkTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentAddLink/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Wait for document to be displayed in list + */ + + casper.then(function waitForDocumentDisplayed() { + return this.waitForSelector('#document-management-content table.dataTable tr[title="'+documents.document1.number+'"] td.reference', function documentIsDisplayed() { + this.click('#document-management-content table.dataTable tr[title="'+documents.document1.number+'"] td.reference'); + }, function fail() { + this.capture('screenshot/documentAddLink/waitForDocumentDisplayed-error.png'); + this.test.assert(false, 'Document can not be found'); + }); + }); + + /** + * Wait for document modal + */ + + casper.then(function waitForDocumentModal() { + var modalTab = '.document-modal .tabs li a[href="#tab-iteration-links"]'; + + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/documentAddLink/waitForDocumentModal-error.png'); + this.test.assert(false, 'Document modal can not be found'); + }); + }); + + /** + * Wait for Links modal tab + */ + casper.then(function waitForDocumentModalLinksTab() { + return this.waitForSelector('.document-modal .linked-items-reference-typehead', function tabOpened() { + this.test.assert(true, 'Links tab opened'); + }, function fail() { + this.capture('screenshot/documentAddLink/waitForDocumentModalLinksTab-error.png'); + this.test.assert(false, 'Document modal Links tab can not be found'); + }); + }); + + /** + * Wait for documents select list + */ + casper.then(function waitForDocumentsSelectList() { + this.sendKeys('.document-modal .linked-items-reference-typehead', documents.document1.documentLink, {reset: true}); + + return this.waitForSelector('#iteration-links > div > ul.typeahead.dropdown-menu > li:first-child > a', function documentsSelectListDisplayed() { + this.click('#iteration-links > div > ul.typeahead.dropdown-menu > li:first-child > a'); + }, function fail() { + this.capture('screenshot/documentAddLink/waitForDocumentsSelectList-error.png'); + this.test.assert(false, 'Documents select list can not be found'); + }); + }); + + /** + * Wait for linked document display + */ + casper.then(function waitForLinkedDocumentDisplay() { + return this.waitForSelector('.document-modal .linked-items-view > ul.linked-items > li:first-child > a.reference', function linkDocumentDisplayed() { + this.test.assert(true, 'Link added'); + }, function fail() { + this.capture('screenshot/documentAddLink/waitForLinkedDocumentDisplay-error.png'); + this.test.assert(false, 'Linked document can not be found and saved'); + }); + }); + + /** + * Set a comment + */ + + casper.then(function openLinkComment(){ + var descriptionButton = '.document-modal .linked-items-view > ul.linked-items > li:first-child > a.edit-linked-item-comment'; + var descriptionInput = '.document-modal .linked-items-view > ul.linked-items > li:first-child .commentInput'; + this.click(descriptionButton); + return this.waitForSelector(descriptionInput,function openDescription(){ + this.sendKeys(descriptionInput,documents.document1.documentLinkComment,{reset:true}); + this.click('.document-modal .linked-items-view > ul.linked-items > li:first-child .validate-comment'); + },function fail(){ + this.capture('screenshot/documentAddLink/addLinkComment-error.png'); + this.test.assert(false, 'Cannot edit document link comment'); + }); + }); + + /** + * Save and wait for the modal to be closed + */ + + casper.then(function savePartIteration(){ + this.click('#save-iteration'); + return this.waitWhileSelector('.document-modal', function modalClosed() { + this.test.assert(true, 'Document modal has been closed'); + }, function fail() { + this.capture('screenshot/documentAddLink/waitForModalToBeClosed-error.png'); + this.test.assert(false, 'Document modal not closed'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentCheckin.js b/docdoku-web-front/tests/js/document-management/document/documentCheckin.js new file mode 100644 index 0000000000..a22de11767 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentCheckin.js @@ -0,0 +1,81 @@ +/*global casper,urls,workspace,documents*/ + +casper.test.begin('Document checkin tests suite', 1, function documentCheckinTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentCheckin/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Select the first document with checkbox + */ + casper.then(function waitForDocumentTable() { + var checkbox = '#document-management-content table.dataTable tbody tr:first-child td:nth-child(2) input'; + return this.waitForSelector(checkbox, function clickOnDocumentCheckbox() { + this.click(checkbox); + }, function fail() { + this.capture('screenshot/documentCheckin/waitForDocumentTable-error.png'); + this.test.assert(false, 'Document can not be found'); + }); + }); + + /** + * Click on checkin button + */ + casper.then(function waitForCheckinButton() { + return this.waitForSelector('.actions .checkin', function clickOnCheckinButton() { + this.click('.actions .checkin'); + }, function fail() { + this.capture('screenshot/documentCheckin/waitForCheckinButton-error.png'); + this.test.assert(false, 'Checkin button can not be found'); + }); + }); + + /** + * Set an iteration note + */ + casper.then(function waitForIterationNotePrompt() { + return this.waitForSelector('#prompt_modal #prompt_input.ready', function fillIterationNote() { + this.sendKeys('#prompt_modal #prompt_input', documents.document1.iterationNote, {reset: true}); + this.click('#prompt_modal .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/documentCheckin/waitForIterationNotePrompt-error.png'); + this.test.assert(false, 'Iteration note modal not found'); + }); + }); + + /** + * Wait for the checkin button to be disabled + */ + casper.then(function waitForCheckinButtonDisabled() { + return this.waitForSelector('.actions .checkin:disabled', function documentIsCheckin() { + this.test.assert(true, 'Document has been checkin'); + }, function fail() { + this.capture('screenshot/documentCheckin/waitForCheckinButtonDisabled-error.png'); + this.test.assert(false, 'Document has not been checkin'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentCheckout.js b/docdoku-web-front/tests/js/document-management/document/documentCheckout.js new file mode 100644 index 0000000000..5670e79053 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentCheckout.js @@ -0,0 +1,80 @@ +/*global casper,urls,$*/ + +casper.test.begin('Document checkout tests suite', 2, function documentCheckoutTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentCheckout/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Select the first document with checkbox + */ + casper.then(function waitForDocumentTable() { + var checkbox = '#document-management-content table.dataTable tbody tr:first-child td:nth-child(2) input'; + return this.waitForSelector(checkbox, function clickOnDocumentCheckbox() { + this.click(checkbox); + }, function fail() { + this.capture('screenshot/documentCheckout/waitForDocumentTable-error.png'); + this.test.assert(false, 'Document can not be found'); + }); + }); + + /** + * Click on checkout button + */ + casper.then(function waitForCheckoutButton() { + return this.waitForSelector('.actions .checkout', function clickOnCheckoutButton() { + this.click('.actions .checkout'); + }, function fail() { + this.capture('screenshot/documentCheckout/waitForCheckoutButton-error.png'); + this.test.assert(false, 'Checkout button can not be found'); + }); + }); + + /** + * Wait for the checkout button to be disabled + */ + casper.then(function waitForCheckoutButtonDisabled() { + return this.waitForSelector('.actions .checkout:disabled', function documentIsCheckout() { + this.test.assert(true, 'Document has been checked out'); + }, function fail() { + this.capture('screenshot/documentCheckout/waitForCheckoutButtonDisabled-error.png'); + this.test.assert(false, 'Document has not been checked out'); + }); + }); + + /** + * Wait for the checked out number to be updated + */ + casper.then(function waitForCheckedOutNumberUpdated() { + return this.waitForSelector('.nav-checkedOut-number-item', function checkCheckedOutNumber() { + this.test.assertSelectorHasText('.nav-checkedOut-number-item', '3', 'Checkout nav number updated'); + }, function fail() { + this.capture('screenshot/documentCheckout/waitForNavUpdateCount.png'); + this.test.assert(false, 'Checkout nav number not updated'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentClickLink.js b/docdoku-web-front/tests/js/document-management/document/documentClickLink.js new file mode 100644 index 0000000000..8619a54c0b --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentClickLink.js @@ -0,0 +1,111 @@ +/*global casper,urls,workspace,documents,defaultUrl*/ +casper.test.begin('Document click link tests suite', 3, function documentClickLinkTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentClickLink/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Wait for document to be displayed in list + */ + + casper.then(function waitForDocumentDisplayed() { + return this.waitForSelector('#document-management-content table.dataTable tr[title="'+documents.document1.number+'"] td.reference', function documentIsDisplayed() { + this.click('#document-management-content table.dataTable tr[title="'+documents.document1.number+'"] td.reference'); + }, function fail() { + this.capture('screenshot/documentClickLink/waitForDocumentDisplayed-error.png'); + this.test.assert(false, 'Document can not be found'); + }); + }); + + /** + * Wait for document modal + */ + + casper.then(function waitForDocumentModal() { + var modalTab = '.document-modal .tabs li a[href="#tab-iteration-links"]'; + + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/documentClickLink/waitForDocumentModal-error.png'); + this.test.assert(false, 'Document modal can not be found'); + }); + }); + + /** + * Wait for Links modal tab + */ + casper.then(function waitForDocumentModalLinksTab() { + return this.waitForSelector('.document-modal .linked-items-reference-typehead', function tabOpened() { + this.test.assert(true, 'Links tab opened'); + }, function fail() { + this.capture('screenshot/documentClickLink/waitForDocumentModalLinksTab-error.png'); + this.test.assert(false, 'Document modal Links tab can not be found'); + }); + }); + + /** + * Wait for linked documents display + */ + casper.then(function waitForLinkDisplay() { + return this.waitForSelector('#iteration-links > .linked-items-view > ul.linked-items > li:first-child', function linkDocumentDisplayed() { + this.click('#iteration-links > .linked-items-view > ul.linked-items > li:first-child > a.reference'); + }, function fail() { + this.capture('screenshot/documentClickLink/waitForLinkDisplay-error.png'); + this.test.assert(false, 'Linked document can not be found'); + }); + }); + + /** + * Wait for document modal closed + */ + casper.then(function waitForDocumentModalClosed() { + var modalTitle = '.document-modal > .modal-header > h3 > a[href="' + contextPath + '/documents/#' + workspace + '/' + documents.document1.number +'/A"]'; + + return this.waitWhileVisible(modalTitle, function documentModalClosed() { + this.test.assert(true, 'Document modal closed'); + this.capture('screenshot/documentClickLink/waitForLinkedDocumentModal-shouldbeclosed.png'); + }, function fail() { + this.capture('screenshot/documentClickLink/waitForDocumentModalClosed-error.png'); + this.test.assert(false, 'Document modal is still displayed'); + }); + }); + /** + * Wait for linked document modal + */ + casper.then(function waitForLinkedDocumentModal() { + var modalTitle = '.document-modal > .modal-header > h3 > a'; + return this.waitForSelector(modalTitle, function linkedDocumentModal() { + this.test.assertSelectorHasText('.document-modal > .modal-header > h3 > a', documents.document1.documentLink, 'Linked document modal opened'); + }, function fail() { + this.capture('screenshot/documentClickLink/waitForLinkedDocumentModal-error.png'); + this.test.assert(false, 'Linked document modal can not be found'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentCreation.js b/docdoku-web-front/tests/js/document-management/document/documentCreation.js new file mode 100644 index 0000000000..179cae34a4 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentCreation.js @@ -0,0 +1,82 @@ +/*global casper,urls,workspace,documents*/ + +casper.test.begin('Document creation tests suite', 2, function documentCreationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentCreation/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Open folder creation modal + */ + + casper.then(function clickOnDocumentCreationLink() { + this.click('.actions .new-document'); + }); + + /** + * Wait for modal + */ + + casper.then(function waitForDocumentCreationModal() { + return this.waitForSelector('.modal.document-modal.new-document', function () { + this.click('.modal.document-modal.new-document .btn.btn-primary'); + this.test.assertExists('.modal.document-modal.new-document input.reference:invalid', 'Should not create document without a reference'); + }, function fail() { + this.capture('screenshot/documentCreation/waitForDocumentCreationModal-error.png'); + this.test.assert(false, 'New document modal can not be found'); + }); + }); + + /** + * Fill the form and create document + */ + + casper.then(function fillAndSubmitDocumentCreationModal() { + return this.waitForSelector('.modal.document-modal.new-document input.reference', function () { + this.sendKeys('.modal.document-modal.new-document input.reference', documents.document1.number); + this.click('.modal.document-modal.new-document .btn.btn-primary'); + }, function fail() { + this.capture('screenshot/documentCreation/fillAndSubmitDocumentCreationModal-error.png'); + this.test.assert(false, 'New document form can not be found'); + }); + }); + + /** + * Check if document has been created + */ + + casper.then(function checkForDocumentCreation() { + return this.waitForSelector('#document-management-content table.dataTable tr[title="'+documents.document1.number+'"] td.reference', function documentHasBeenCreated() { + this.test.assertSelectorHasText('#document-management-content table.dataTable tr[title="'+documents.document1.number+'"] td.reference a', documents.document1.number); + }, function fail() { + this.capture('screenshot/documentCreation/checkForDocumentCreation-error.png'); + this.test.assert(false, 'New document created can not be found'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentCreationFromTemplate.js b/docdoku-web-front/tests/js/document-management/document/documentCreationFromTemplate.js new file mode 100644 index 0000000000..990871ef34 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentCreationFromTemplate.js @@ -0,0 +1,79 @@ +/*global casper,urls,workspace,documents,$*/ + +casper.test.begin('Document creation from template tests suite', 2, function documentCreationFromTemplateTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentCreationFromTemplate/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Open folder creation modal + */ + + casper.then(function clickOnDocumentCreationLink() { + this.click('.actions .new-document'); + }); + + /** + * Wait for modal + */ + + casper.then(function waitForDocumentCreationModal() { + return this.waitForSelector('.modal.document-modal.new-document .template-selector', function () { + + this.evaluate(function () { + document.querySelector('.modal.document-modal.new-document .template-selector').selectedIndex = 1; + $('.modal.document-modal.new-document .template-selector').change(); + $('.modal.document-modal.new-document input.reference').val('000'); + $('.modal.document-modal.new-document input.reference').focus(); + return true; + }); + + this.click('.modal.document-modal.new-document .btn.btn-primary'); + + }, function fail() { + this.capture('screenshot/documentCreationFromTemplate/waitForDocumentCreationModal-error.png'); + this.test.assert(false, 'New document modal can not be found'); + }); + }); + + + /** + * Check if document has been created + */ + + casper.then(function checkForDocumentCreation() { + return this.waitForSelector('#document-management-content table.dataTable tr[title="' + documents.template1.maskGenerated + '"] td.reference', function documentHasBeenCreated() { + this.test.assertSelectorHasText('#document-management-content table.dataTable tr[title="' + documents.template1.maskGenerated + '"] td.reference a', documents.template1.maskGenerated); + this.test.assertSelectorHasText('#document-management-content table.dataTable tr[title="' + documents.template1.maskGenerated + '"] td.type', documents.template1.type); + }, function fail() { + this.capture('screenshot/documentCreationFromTemplate/checkForDocumentCreation-error.png'); + this.test.assert(false, 'New document created can not be found'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentCreationWithWorkflow.js b/docdoku-web-front/tests/js/document-management/document/documentCreationWithWorkflow.js new file mode 100644 index 0000000000..64e3001f2f --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentCreationWithWorkflow.js @@ -0,0 +1,288 @@ +/*global casper,urls,workspace,documents,workflows,$*/ + +casper.test.begin('Document creation with workflow tests suite', 9, function documentCreationWithWorkflowTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Open folder creation modal + */ + + casper.then(function clickOnDocumentCreationLink() { + this.click('.actions .new-document'); + }); + + /** + * Wait for modal + */ + + casper.then(function waitForDocumentCreationModal() { + return this.waitForSelector('.modal.document-modal.new-document', function () { + this.sendKeys('.modal.document-modal.new-document input.reference', documents.documentWithWorkflow.number); + this.click('.modal.document-modal.new-document .nav.nav-tabs > li:nth-child(2) > a'); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/waitForDocumentCreationModal-error.png'); + this.test.assert(false, 'New document modal can not be found'); + }); + }); + + /** + * Wait for workflow selector to be filled + */ + + casper.then(function waitForWorkflowSelector() { + return this.waitForSelector('.modal.document-modal.new-document .workflow-selector option:nth-child(2)', function () { + this.evaluate(function () { + document.querySelector('.modal.document-modal.new-document .workflow-selector').selectedIndex = 1; + $('.modal.document-modal.new-document .workflow-selector').change(); + return true; + }); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/waitForWorkflowSelector-error.png'); + this.test.assert(false, 'Workflow selector not found'); + }); + }); + + /** + * Wait for role item + */ + + casper.then(function waitForRoleItem() { + return this.waitForSelector('.modal.document-modal.new-document .role-mapping .roles-item', function () { + this.test.assertElementCount('.modal.document-modal.new-document .role-mapping .roles-item', 1, 'There should be one role item in role mapping list'); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/waitForRoleItem-error.png'); + this.test.assert(false, 'No role item can not be found'); + }); + }); + + /** + * Select person in charge + */ + casper.then(function selectRole() { + this.evaluate(function () { + document.querySelector('select.role-user-mapped').selectedIndex = 1; + $('.modal.document-modal.new-document .role-user-mapped').change(); + return true; + }); + }); + + /** + * Submit document + */ + casper.then(function submit() { + this.click('.modal.document-modal.new-document .btn.btn-primary'); + }); + + /** + * Check if document has been created + */ + + casper.then(function checkForDocumentCreation() { + return this.waitForSelector('#document-management-content table.dataTable tr[title="' + documents.documentWithWorkflow.number + '"] td.reference', function documentHasBeenCreated() { + this.test.assertSelectorHasText('#document-management-content table.dataTable tr[title="' + documents.documentWithWorkflow.number + '"] td.reference a', documents.documentWithWorkflow.number); + this.test.assertSelectorHasText('#document-management-content table.dataTable tr[title="' + documents.documentWithWorkflow.number + '"] td.life-cycle-state', workflows.workflow1.activities.activity1.name); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/checkForDocumentCreation-error.png'); + this.test.assert(false, 'New document created can not be found'); + }); + }); + + /*** + * Open the document modal + */ + casper.then(function openCreatedDocument() { + this.click('#document-management-content table.dataTable tr[title="' + documents.documentWithWorkflow.number + '"] td.reference'); + var modalTab = '.document-modal .tabs li a[href="#tab-iteration-files"]'; + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/openCreatedDocument-error.png'); + this.test.assert(false, 'Document modal can not be found'); + }); + }); + + + /** + * Wait the modal tab + */ + casper.then(function waitForDocumentModalTab() { + return this.waitForSelector('.document-modal .upload-btn', function tabSelected() { + this.test.assert(true, 'File upload tab opened'); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/waitForDocumentModalTab-error.png'); + this.test.assert(false, 'Document modal tab can not be found'); + }); + }); + + /** + * Choose a file and upload + */ + + casper.then(function setFileAndUpload() { + this.fill('.document-modal .upload-form', { + 'upload': 'res/document-upload.txt' + }, false); + + return this.waitFor(function check() { + return this.evaluate(function () { + return document.querySelectorAll('.document-modal .attachedFiles ul.file-list li').length === 1; + }); + }, function then() { + this.test.assert(true, 'File has been uploaded to document'); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/setFileAndUpload-error.png'); + this.test.assert(false, 'Cannot upload the file'); + }); + + }); + + /** + * Checkin file + */ + casper.then(function openIterationTab() { + var modalTab = '.document-modal .tabs li a[href="#tab-iteration-iteration"]'; + return this.waitForSelector(modalTab, function tabOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/openIterationTab-error.png'); + this.test.assert(false, 'Iteration tab can not be found'); + }); + }); + + /** + * Checkin file + */ + casper.then(function checkinDocument() { + var checkinButton = '#tab-iteration-iteration .action-checkin'; + return this.waitForSelector(checkinButton, function buttonFound() { + this.click(checkinButton); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/checkinDocument-error.png'); + this.test.assert(false, 'Checkin button can not be found'); + }); + }); + + /** + * Checkin file + */ + casper.then(function documentCheckedIn() { + var checkoutButton = '#tab-iteration-iteration .action-checkout'; + return this.waitForSelector(checkoutButton, function buttonFound() { + this.test.assert(true, 'File has been checked in'); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/documentCheckedIn-error.png'); + this.test.assert(false, 'Checkout button can not be found'); + }); + }); + + /** + * Download file + */ + casper.then(function openFileTab() { + var modalTab = '.document-modal .tabs li a[href="#tab-iteration-files"]'; + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/openFileTab-error.png'); + this.test.assert(false, 'Modal tab can not be found'); + }); + }); + + /** + * Download file + */ + casper.then(function downloadDocumentFile() { + var modalTab = '.document-modal .tabs li a[href="#tab-iteration-files"]'; + return this.waitForSelector(modalTab, function modalOpened() { + var fileLink = '#iteration-files > div > ul > li > a[href="/api/files/'+workspace+'/documents/'+documents.documentWithWorkflow.number +'/A/1/document-upload.txt"]'; + this.test.assertSelectorExist(fileLink, 'A link should be present to download the document'); + this.click(fileLink); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/downloadDocumentFile-error.png'); + this.test.assert(false, 'Document file tab can not be found'); + }); + }); + + /** + * Open Lifecycle tab and approve task + */ + casper.then(function openLifecycleTab() { + var modalTab = '.document-modal .tabs li a[href="#tab-iteration-lifecycle"]'; + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/openLifecycleTab-error.png'); + this.test.assert(false, 'Lifecycle tab can not be found'); + }); + }); + + /** + * Approve task + */ + casper.then(function approveTask() { + var activityElement = '#lifecycle-activities-wrapper #lifecycle-activities .activity.in_progress'; + return this.waitForSelector(activityElement, function modalOpened() { + this.test.assertSelectorExist(activityElement, 'An activity should be present and marked in_progress'); + this.click(activityElement+' .approve-task'); + this.click(activityElement+' .closure-comment-form > div.task-buttons > input'); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/approveTask-error.png'); + this.test.assert(false, 'Activity can not be found'); + }); + }); + + /** + * Wait for activity being completed + */ + casper.then(function waitTaskBeingCompleted() { + var activityElement = '#lifecycle-activities-wrapper #lifecycle-activities .activity.complete'; + return this.waitForSelector(activityElement, function taskCompleted() { + this.test.assert(true, 'An activity should be present and marked as completed'); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/waitTaskBeingCompleted-error.png'); + this.test.assert(false, 'Task has not been completed'); + }); + }); + + /** + * Checkout again the document (compliant with next steps) + */ + casper.then(function checkoutDocument() { + this.click('.document-modal .tabs li a[href="#tab-iteration-iteration"]'); + var checkoutButton = '#tab-iteration-iteration .action-checkout'; + return this.waitForSelector(checkoutButton, function buttonFound() { + this.click(checkoutButton); + }, function fail() { + this.capture('screenshot/documentCreationWithWorkflow/checkoutDocument-error.png'); + this.test.assert(false, 'Checkout button can not be found'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentDeletion.js b/docdoku-web-front/tests/js/document-management/document/documentDeletion.js new file mode 100644 index 0000000000..2027400bb5 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentDeletion.js @@ -0,0 +1,85 @@ +/*global casper,urls,workspace,documents*/ + +casper.test.begin('Document deletion tests suite', 1, function documentDeletionTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentDeletion/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Wait for document to be displayed in list + */ + + casper.then(function waitForDocumentDisplayed() { + return this.waitForSelector('#document-management-content table.dataTable tbody tr[title="'+documents.document1.number+'"] td.reference', function documentIsDisplayed() { + this.click('#document-management-content table.dataTable tbody tr[title="'+documents.document1.number+'"] td input[type=checkbox]'); + }, function fail() { + this.capture('screenshot/documentDeletion/waitForDocumentDisplayed-error.png'); + this.test.assert(false, 'Document to delete rows can not be found'); + }); + }); + + /** + * Wait for document suppression button + */ + + casper.then(function waitForDeleteButtonDisplayed() { + return this.waitForSelector('.actions .delete', function deleteButtonIsDisplayed() { + this.click('.actions .delete'); + }, function fail() { + this.capture('screenshot/documentDeletion/waitForDeleteButtonDisplayed-error.png'); + this.test.assert(false, 'Document delete button can not be found'); + }); + }); + + + /** + * Confirm document deletion + */ + + casper.then(function confirmDocumentDeletion() { + return this.waitForSelector('.bootbox', function confirmBoxAppeared() { + this.click('.bootbox .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/documentDeletion/confirmDocumentDeletion-error.png'); + this.test.assert(false, 'Document deletion confirmation modal can not be found'); + }); + }); + + /** + * Wait for document to be removed + */ + + casper.then(function waitForDocumentDeletion() { + return this.waitWhileSelector('#document-management-content table.dataTable tbody tr[title="'+documents.document1.number+'"]', function documentDeleted() { + this.test.assert(true, 'Document has been deleted'); + }, function fail() { + this.capture('screenshot/documentDeletion/waitForDocumentDeletion-error.png'); + this.test.assert(false, 'Document has not been deleted'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentFilesRemove.js b/docdoku-web-front/tests/js/document-management/document/documentFilesRemove.js new file mode 100644 index 0000000000..9bceb0757e --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentFilesRemove.js @@ -0,0 +1,313 @@ +/*global casper,urls,workspace,documents*/ +casper.test.begin('Document Files Remove tests suite', 14, function documentUploadCadTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Wait for document to be displayed in list + */ + + casper.then(function waitForDocumentDisplayed() { + return this.waitForSelector('#document-management-content table.dataTable tr[title="'+documents.document1.number+'"] td.reference', function documentIsDisplayed() { + this.click('#document-management-content table.dataTable tr[title="'+documents.document1.number+'"] td.reference'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/waitForDocumentDisplayed-error.png'); + this.test.assert(false, 'Document can not be found'); + }); + }); + + /** + * Wait for document modal + */ + + casper.then(function waitForDocumentModal() { + var modalTab = '.document-modal .tabs li a[href="#tab-iteration-files"]'; + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/documentFilesRemove/waitForDocumentModal-error.png'); + this.test.assert(false, 'Document modal can not be found'); + }); + }); + + /** + * Wait the modal tab + */ + casper.then(function waitForDocumentModalTab() { + return this.waitForSelector('.document-modal .upload-btn', function tabSelected() { + this.test.assert(true, 'File upload tab opened'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/waitForDocumentModalTab-error.png'); + this.test.assert(false, 'Upload button can not be found'); + }); + }); + /** + * Wait for the files to be displayed + */ + casper.then(function setFileAndUpload() { + + return this.waitFor(function check() { + return this.evaluate(function () { + return document.querySelectorAll('.document-modal .attachedFiles ul.file-list li').length === 1; + }); + }, function then() { + this.test.assert(true, 'File previously uploaded is present'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/setFileAndUpload-error.png'); + this.test.assert(false, 'File previously uploaded is not present'); + }); + }); + + /** + * Set the file to be deleted + */ + casper.then(function removeFile() { + this.click('ul.file-list li.file input.file-check'); + return this.waitForSelector('input.file-check:checked', function then() { + this.test.assert(true,'File is set to be removed'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/setFileToRemove-error.png'); + this.test.assert(false,'File can not be set to be removed'); + }); + }); + + /** + * Save all + */ + casper.then(function saveChanged() { + this.click('#save-iteration'); + return this.waitWhileSelector('div.document-modal', function then () { + this.test.assert(true,'Modal closed'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/closeModal-error.png'); + this.test.assert(false,'Could not close modal'); + }); + }); + + /** + * Wait for document modal + */ + + casper.then(function waitForDocumentModal() { + this.click('#document-management-content table.dataTable tr[title="'+documents.document1.number+'"] td.reference'); + var modalTab = '.document-modal .tabs li a[href="#tab-iteration-files"]'; + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/documentFilesRemove/waitForDocumentModal-error.png'); + this.test.assert(false, 'Document modal can not be found'); + }); + }); + + /** + * Wait the modal tab + */ + casper.then(function waitForDocumentModalTab() { + return this.waitForSelector('.document-modal .upload-btn', function tabSelected() { + this.test.assert(true, 'File upload tab opened'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/waitForDocumentModalTab-error.png'); + this.test.assert(false, 'Upload button can not be found'); + }); + }); + + /** + * Choose a file and upload + */ + casper.then(function setFileAndUpload() { + this.fill('.document-modal .upload-form', { + 'upload': 'res/document-upload.txt' + }, false); + + return this.waitFor(function check() { + return this.evaluate(function () { + return document.querySelectorAll('.document-modal .attachedFiles ul.file-list li').length === 1; + }); + }, function then() { + this.test.assert(true, 'File has been uploaded to document'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/setFileAndUpload-error.png'); + this.test.assert(false, 'Cannot upload the file'); + }); + + }); + + /** + * Choose a second file and upload + */ + casper.then(function setFileAndUpload() { + this.fill('.document-modal .upload-form', { + 'upload': 'res/document-upload2.txt' + }, false); + + return this.waitFor(function check() { + return this.evaluate(function () { + return document.querySelectorAll('.document-modal .attachedFiles ul.file-list li').length === 2; + }); + }, function then() { + this.test.assert(true, 'Second File has been uploaded to document'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/setFileAndUpload-error.png'); + this.test.assert(false, 'Cannot upload the file'); + }); + + }); + + /** + * Save all + */ + casper.then(function saveChanged() { + this.click('#save-iteration'); + }); + + /** + * Wait for the modal to close + */ + casper.then(function waitForClosingModal() { + return this.waitWhileSelector('div.document-modal', function then () { + this.test.assert(true,'Modal closed'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/closeModal-error.png'); + this.test.assert(false,'Could not close modal'); + }); + }); + + /** + * Wait for document modal + */ + + casper.then(function waitForDocumentModal() { + this.click('#document-management-content table.dataTable tr[title="'+documents.document1.number+'"] td.reference'); + var modalTab = '.document-modal .tabs li a[href="#tab-iteration-files"]'; + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/documentFilesRemove/waitForDocumentModal-error.png'); + this.test.assert(false, 'Document modal can not be found'); + }); + }); + + /** + * Wait the modal tab + */ + casper.then(function waitForDocumentModalTab() { + return this.waitForSelector('.document-modal .upload-btn', function tabSelected() { + this.test.assert(true, 'File upload tab opened'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/waitForDocumentModalTab-error.png'); + this.test.assert(false, 'Upload button can not be found'); + }); + }); + + /** + * Assert that the 2 files are present in the modal + */ + casper.then(function assertFilesUploaded() { + return this.waitFor(function check() { + return this.evaluate(function () { + return document.querySelectorAll('.document-modal .attachedFiles ul.file-list li').length === 2; + }); + }, function then() { + this.test.assert(true, 'Files uploaded are present in the modal'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/setFileAndUpload-error.png'); + this.test.assert(false, 'Cannot find uploaded files'); + }); + }); + + /** + * Check All the files to be deleted + */ + casper.then(function checkAll() { + this.click('a.toggle-checkAll'); + return this.waitFor(function checkAll() { + return this.evaluate(function() { + return document.querySelectorAll('input.file-check:checked').length === 2; + }); + }, function then() { + this.test.assert(true, 'All files have been checked'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/checkAllButton-error.png'); + this.test.assert(false, 'All files have not been checked'); + }); + }); + + /** + * Save all + */ + casper.then(function saveChanged() { + this.click('#save-iteration'); + return this.waitWhileSelector('div.document-modal', function then () { + this.test.assert(true,'Modal closed'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/closeModal-error.png'); + }); + }); + + /** + * Wait for document modal + */ + + casper.then(function waitForDocumentModal() { + this.click('#document-management-content table.dataTable tr[title="'+documents.document1.number+'"] td.reference'); + var modalTab = '.document-modal .tabs li a[href="#tab-iteration-files"]'; + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/documentFilesRemove/waitForDocumentModal-error.png'); + this.test.assert(false, 'Document modal can not be found'); + }); + }); + + /** + * Wait the modal tab + */ + casper.then(function waitForDocumentModalTab() { + return this.waitForSelector('.document-modal .upload-btn', function tabSelected() { + this.test.assert(true, 'File upload tab opened'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/waitForDocumentModalTab-error.png'); + this.test.assert(false, 'Upload button can not be found'); + }); + }); + + /** + * Assert that no files are present + */ + casper.then(function assertAllFilesRemoved() { + return this.waitWhileSelector('.document-modal .attachedFiles ul.file-list li', function checkNoFile() { + this.test.assert(true, 'There is no file present'); + }, function fail() { + this.capture('screenshot/documentFilesRemove/assertAllFilesRemoved-error.png'); + this.test.assert(false, 'There should be no file present'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); + diff --git a/docdoku-web-front/tests/js/document-management/document/documentMultipleCheckin.js b/docdoku-web-front/tests/js/document-management/document/documentMultipleCheckin.js new file mode 100644 index 0000000000..1ec4e529c3 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentMultipleCheckin.js @@ -0,0 +1,96 @@ +/*global casper,urls,workspace,documents*/ + +casper.test.begin('Document multiple checkin tests suite', 2, function documentMultipleCheckinTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentMultipleCheckin/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Select all documents with checkbox + */ + casper.then(function waitForDocumentTable() { + var checkbox = '#document-management-content table.dataTable thead tr th input[type="checkbox"]'; + return this.waitForSelector(checkbox, function clickOnDocumentCheckbox() { + this.click(checkbox); + }, function fail() { + this.capture('screenshot/documentMultipleCheckin/waitForDocumentTable-error.png'); + this.test.assert(false, 'Document can not be found'); + }); + }); + + + /** + * Click on checkin button + */ + casper.then(function waitForCheckinButton() { + return this.waitForSelector('.actions .checkin', function clickOnCheckinButton() { + this.click('.actions .checkin'); + }, function fail() { + this.capture('screenshot/documentMultipleCheckin/waitForCheckinButton-error.png'); + this.test.assert(false, 'Checkin button can not be found'); + }); + }); + + /** + * Set an iteration note + */ + casper.then(function waitForIterationNotePrompt() { + return this.waitForSelector('#prompt_modal #prompt_input.ready', function fillIterationNote() { + this.sendKeys('#prompt_modal #prompt_input', documents.document1.iterationNote, {reset: true}); + this.click('#prompt_modal .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/documentMultipleCheckin/waitForIterationNotePrompt-error.png'); + this.test.assert(false, 'Iteration note modal not found'); + }); + }); + + /** + * Wait for the checkin button to be disabled + */ + casper.then(function waitForCheckinButtonDisabled() { + return this.waitForSelector('.actions .checkin:disabled', function documentIsCheckin() { + this.test.assert(true, 'Document has been checkin'); + }, function fail() { + this.capture('screenshot/documentMultipleCheckin/waitForCheckinButtonDisabled-error.png'); + this.test.assert(false, 'Document has not been checkin'); + }); + }); + + /** + * Wait for all button to be checkin + */ + casper.then(function waitForDisplayCheckin() { + return this.waitWhileSelector('tbody > tr > td.reference.doc-ref > a > i.fa.fa-pencil', function () { + this.test.assert(true, 'Document retrieved'); + }, function fail() { + this.capture('screenshot/documentMultipleCheckin/waitForCheckinDocuments-error.png'); + this.test.assert(false, 'Document has not been checkin'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentMultipleCheckout.js b/docdoku-web-front/tests/js/document-management/document/documentMultipleCheckout.js new file mode 100644 index 0000000000..c7bacdbefb --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentMultipleCheckout.js @@ -0,0 +1,84 @@ +/*global casper,urls,workspace,documents*/ + +casper.test.begin('Documents multiple checkout tests suite', 2, function documentMultipleCheckoutTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentMultipleCheckout/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Select all documents with checkbox + */ + casper.then(function waitForDocumentTable() { + var checkbox = '#document-management-content table.dataTable thead tr th input[type="checkbox"]'; + return this.waitForSelector(checkbox, function clickOnDocumentCheckbox() { + this.click(checkbox); + }, function fail() { + this.capture('screenshot/documentMultipleCheckin/waitForDocumentTable-error.png'); + this.test.assert(false, 'Document can not be found'); + }); + }); + + /** + * Click on checkout button + */ + casper.then(function waitForCheckoutButton() { + return this.waitForSelector('.actions .checkout', function clickOnCheckoutButton() { + this.click('.actions .checkout'); + }, function fail() { + this.capture('screenshot/documentMultipleCheckout/waitForCheckoutButton-error.png'); + this.test.assert(false, 'Checkout button can not be found'); + }); + }); + + + /** + * Wait for the checkout button to be disabled + */ + casper.then(function waitForCheckoutButtonDisabled() { + return this.waitForSelector('.actions .checkout:disabled', function documentIsCheckout() { + this.test.assert(true, 'Documents have been checkout'); + }, function fail() { + this.capture('screenshot/documentMultipleCheckout/waitForCheckoutButtonDisabled-error.png'); + this.test.assert(false, 'Documents have not been checkout'); + }); + }); + + /** + * Wait for all button to be checkout + */ + casper.then(function waitForDisplayCheckin() { + return this.waitWhileSelector('tbody > tr > td.reference.doc-ref > a > i.fa.fa-eye', function () { + this.test.assert(true, 'Document retrieved'); + }, function fail() { + this.capture('screenshot/documentMultipleCheckout/waitForCheckinDocuments-error.png'); + this.test.assert(false, 'Document has not been checkin'); + }); + }); + + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentMultipleDeletion.js b/docdoku-web-front/tests/js/document-management/document/documentMultipleDeletion.js new file mode 100644 index 0000000000..ef1e48dee4 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentMultipleDeletion.js @@ -0,0 +1,88 @@ +/*global casper,urls,workspace,documents*/ + +casper.test.begin('Document multiple deletion tests suite', 1, function documentMultipleDeletionTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentMultipleDeletion/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Select all documents with checkbox + */ + casper.then(function waitForDocumentTable() { + var checkbox = '#document-management-content table.dataTable thead tr th input[type="checkbox"]'; + return this.waitForSelector(checkbox, function clickOnDocumentCheckbox() { + this.click(checkbox); + }, function fail() { + this.capture('screenshot/documentMultipleDeletion/waitForDocumentTable-error.png'); + this.test.assert(false, 'Document table can not be found'); + }); + }); + + /** + * Wait for document suppression button + */ + + casper.then(function waitForDeleteButtonDisplayed() { + return this.waitForSelector('.actions .delete', function deleteButtonIsDisplayed() { + this.click('.actions .delete'); + + }, function fail() { + this.capture('screenshot/documentMultipleDeletion/waitForDeleteButtonDisplayed-error.png'); + this.test.assert(false, 'Document delete button can not be found'); + }); + }); + + + /** + * Confirm documents deletion + */ + + casper.then(function confirmDocumentsDeletion() { + return this.waitForSelector('.bootbox', function confirmBoxAppeared() { + this.click('.bootbox .modal-footer .btn-primary'); + + }, function fail() { + this.capture('screenshot/documentMultipleDeletion/confirmDocumentDeletion-error.png'); + this.test.assert(false, 'Document deletion confirmation modal can not be found'); + }); + }); + + /** + * Wait for documents to be removed + */ + + casper.then(function waitForDocumentsDeletion() { + return this.waitWhileSelector('#document-management-content table.dataTable tbody tr td.reference', function documentsDeleted() { + this.test.assert(true, 'Documents have been deleted'); + + }, function fail() { + this.capture('screenshot/documentMultipleDeletion/waitForDocumentDeletion-error.png'); + this.test.assert(false, 'Documents have not been deleted'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentMultipleRelease.js b/docdoku-web-front/tests/js/document-management/document/documentMultipleRelease.js new file mode 100644 index 0000000000..f83ccefdde --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentMultipleRelease.js @@ -0,0 +1,100 @@ +/*global casper,urls,products*/ + +casper.test.begin('Documents multiple release tests suite', 3, function documentMultipleReleaseTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/MultipleDocumentsRelease/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Select 2 checked in documents with checkbox + */ + casper.then(function waitForDocumentTable() { + return this.waitForSelector('#document-management-content table.dataTable tbody tr:nth-child(2) td:nth-child(2) input', function clickOnDocumentCheckbox() { + this.click('#document-management-content table.dataTable tbody tr:nth-child(2) td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/MultipleDocumentsRelease/waitForDocumentTable1-error.png'); + this.test.assert(false, 'Document cannot be found'); + }); + }); + casper.then(function waitForDocumentTable() { + return this.waitForSelector('#document-management-content table.dataTable tbody tr:nth-child(3) td:nth-child(2) input', function clickOnDocumentCheckbox() { + this.click('#document-management-content table.dataTable tbody tr:nth-child(3) td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/MultipleDocumentsRelease/waitForDocumentTable2-error.png'); + this.test.assert(false, 'Document cannot be found'); + }); + }); + + /** + * Check and click on the release document button + */ + casper.then(function waitForReleaseButton() { + return this.waitForSelector('.actions .new-release', function checkVisible() { + this.test.assertVisible('.actions .new-release', 'Release button visible'); + this.click('.actions .new-release'); + }, function fail() { + this.capture('screenshot/MultipleDocumentsRelease/waitForReleaseButton-error.png'); + this.test.assert(false, 'Release button not visible'); + }); + }); + + /** + * Release selection modal + */ + casper.then(function waitForReleaseSelectionPrompt() { + return this.waitForSelector('.bootbox.modal', function confirmReleaseSelection() { + this.click('.bootbox.modal .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/MultipleDocumentsRelease/waitForReleaseSelectionPrompt-error.png'); + this.test.assert(false, 'Release selection modal not found'); + }); + }); + + /** + * Wait for the release button to be disabled + */ + casper.then(function waitForReleaseButtonDisabled() { + return this.waitWhileVisible('.actions .new-release', function checkHidden() { + this.test.assert(true, 'Release button hidden'); + }, function fail() { + this.capture('screenshot/MultipleDocumentsRelease/waitForReleaseButtonDisabled-error.png'); + this.test.assert(false, 'Release button not hidden'); + }); + }); + + /** + * Check document has been released + */ + casper.then(function waitForReleaseIconDisplayed() { + this.waitForSelector('#document-management-content table.dataTable tbody i.fa.fa-check', function documentIsReleased() { + this.test.assertElementCount('#document-management-content table.dataTable tbody i.fa.fa-check', 2, 'Documents have been released'); + }, function fail() { + this.capture('screenshot/MultipleDocumentsRelease/waitForReleaseIconDisplayed-error.png'); + this.test.assert(false, 'Documents have not been released'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentMultipleUndoCheckout.js b/docdoku-web-front/tests/js/document-management/document/documentMultipleUndoCheckout.js new file mode 100644 index 0000000000..caccc03e70 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentMultipleUndoCheckout.js @@ -0,0 +1,108 @@ +/*global casper,urls,workspace,documents*/ + +casper.test.begin('Documents multiple undocheckout tests suite', 1, function documentUndoCheckoutTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/MultipleDocumentUndoCheckout/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Select the first document with checkbox + */ + casper.then(function waitForDocumentTable() { + var checkbox = '#document-management-content table.dataTable tbody tr:first-child td:nth-child(2) input'; + return this.waitForSelector(checkbox, function clickOnDocumentCheckbox() { + this.click(checkbox); + }, function fail() { + this.capture('screenshot/MultipleDocumentUndoCheckout/waitForDocumentTable-error.png'); + this.test.assert(false, 'Document can not be found'); + }); + }); + + /** + * Select the second document with checkbox + */ + casper.then(function waitForDocumentTable() { + var checkbox = '#document-management-content table.dataTable tbody tr:nth-child(2) td:nth-child(2) input'; + return this.waitForSelector(checkbox, function clickOnDocumentCheckbox() { + this.click(checkbox); + }, function fail() { + this.capture('screenshot/MultipleDocumentUndoCheckout/waitForDocumentTable-error.png'); + this.test.assert(false, 'Document can not be found'); + }); + }); + + /** + * Select the 3rd document with checkbox + */ + casper.then(function waitForDocumentTable() { + var checkbox = '#document-management-content table.dataTable tbody tr:nth-child(3) td:nth-child(2) input'; + return this.waitForSelector(checkbox, function clickOnDocumentCheckbox() { + this.click(checkbox); + }, function fail() { + this.capture('screenshot/MultipleDocumentUndoCheckout/waitForDocumentTable-error.png'); + this.test.assert(false, 'Document can not be found'); + }); + }); + + /** + * Click on undocheckout button + */ + casper.then(function waitForUndoCheckoutButton() { + return this.waitForSelector('.actions .undocheckout', function clickOnUndoCheckoutButton() { + this.click('.actions .undocheckout'); + }, function fail() { + this.capture('screenshot/MultipleDocumentUndoCheckout/waitForUndoCheckoutButton-error.png'); + this.test.assert(false, 'UndoCheckout button can not be found'); + }); + }); + + /** + * Wait for confirmation box + */ + casper.then(function waitForConfirmationBox() { + return this.waitForSelector('div.modal-body', function fillIterationNote() { + this.click('.modal-footer a[data-handler="1"]'); + }, function fail() { + this.capture('screenshot/MultipleDocumentUndoCheckout/waitForIterationNotePrompt-error.png'); + this.test.assert(false, 'Iteration note modal not found'); + }); + }); + + /** + * Wait for the undocheckout button to be disabled + */ + casper.then(function waitForUndoCheckoutButtonDisabled() { + return this.waitForSelector('.actions .undocheckout:disabled', function documentIsUndoCheckout() { + this.test.assert(true, 'Documents have been undocheckout'); + }, function fail() { + this.capture('screenshot/MultipleDocumentUndoCheckout/waitForUndoCheckoutButtonDisabled-error.png'); + this.test.assert(false, 'Documents have not been checkout'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentObsolete.js b/docdoku-web-front/tests/js/document-management/document/documentObsolete.js new file mode 100644 index 0000000000..e9b3cc8043 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentObsolete.js @@ -0,0 +1,93 @@ +/*global casper,urls,products*/ + +casper.test.begin('Document obsolete tests suite', 3, function documentObsoleteTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentObsolete/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Select the first document with checkbox + */ + casper.then(function waitForDocumentTable() { + var checkbox = '#document-management-content table.dataTable tbody tr:first-child td:nth-child(2) input'; + return this.waitForSelector(checkbox, function clickOnDocumentCheckbox() { + this.click(checkbox); + }, function fail() { + this.capture('screenshot/documentObsolete/waitForDocumentTable-error.png'); + this.test.assert(false, 'Document can not be found'); + }); + }); + + /** + * Check and click on Mark as obsolete button + */ + casper.then(function waitForMarkAsObsoleteButton() { + return this.waitForSelector('.actions .mark-as-obsolete', function checkVisible() { + this.test.assertVisible('.actions .mark-as-obsolete', 'Mark as obsolete button visible'); + this.click('.actions .mark-as-obsolete'); + }, function fail() { + this.capture('screenshot/documentObsolete/waitForMarkAsObsoleteButton-error.png'); + this.test.assert(false, 'Mark as obsolete button not visible'); + }); + }); + + /** + * Mark as obsolete modal + */ + casper.then(function waitForMarkAsObsoletePrompt() { + return this.waitForSelector('.bootbox.modal', function confirmMarkAsObsolete() { + this.click('.bootbox.modal .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/documentObsolete/waitForMarkAsObsoletePrompt-error.png'); + this.test.assert(false, 'Mark as obsolete modal not found'); + }); + }); + + /** + * Wait for the Mark as obsolete button to be disabled + */ + casper.then(function waitForMarkAsObsoleteButtonDisabled() { + return this.waitForSelector('.actions .mark-as-obsolete', function checkHidden() { + this.test.assertNotVisible('.actions .mark-as-obsolete', 'Mark as obsolete button hidden'); + }, function fail() { + this.capture('screenshot/documentObsolete/waitForMarkAsObsoleteButtonDisabled-error.png'); + this.test.assert(false, 'Mark as obsolete button not hidden'); + }); + }); + + /** + * Check document has been marked as obsolete + */ + casper.then(function waitForObsoleteIconDisplayed() { + this.waitForSelector('#document-management-content table.dataTable tbody i.fa.fa-frown-o', function documentIsObsolete() { + this.test.assertElementCount('#document-management-content table.dataTable tbody i.fa.fa-frown-o', 1, 'Document has been marked as obsolete'); + }, function fail() { + this.capture('screenshot/documentObsolete/waitForObsoleteIconDisplayed-error.png'); + this.test.assert(false, 'Document has not been marked as obsolete'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentRelease.js b/docdoku-web-front/tests/js/document-management/document/documentRelease.js new file mode 100644 index 0000000000..7766222e31 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentRelease.js @@ -0,0 +1,93 @@ +/*global casper,urls,products*/ + +casper.test.begin('Document release tests suite', 3, function documentReleaseTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentRelease/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Select the first document with checkbox + */ + casper.then(function waitForDocumentTable() { + var checkbox = '#document-management-content table.dataTable tbody tr:first-child td:nth-child(2) input'; + return this.waitForSelector(checkbox, function clickOnDocumentCheckbox() { + this.click(checkbox); + }, function fail() { + this.capture('screenshot/documentRelease/waitForDocumentTable-error.png'); + this.test.assert(false, 'Document can not be found'); + }); + }); + + /** + * Check and click on the release document button + */ + casper.then(function waitForReleaseButton() { + return this.waitForSelector('.actions .new-release', function checkVisible() { + this.test.assertVisible('.actions .new-release', 'Release button visible'); + this.click('.actions .new-release'); + }, function fail() { + this.capture('screenshot/documentRelease/waitForReleaseButton-error.png'); + this.test.assert(false, 'Release button not visible'); + }); + }); + + /** + * Release selection modal + */ + casper.then(function waitForReleaseSelectionPrompt() { + return this.waitForSelector('.bootbox.modal', function confirmReleaseSelection() { + this.click('.bootbox.modal .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/documentRelease/waitForReleaseSelectionPrompt-error.png'); + this.test.assert(false, 'Release selection modal not found'); + }); + }); + + /** + * Wait for the release button to be disabled + */ + casper.then(function waitForReleaseButtonDisabled() { + return this.waitWhileVisible('.actions .new-release', function checkHidden() { + this.test.assert(true, 'Release button hidden'); + }, function fail() { + this.capture('screenshot/documentRelease/waitForReleaseButtonDisabled-error.png'); + this.test.assert(false, 'Release button not hidden'); + }); + }); + + /** + * Check document has been released + */ + casper.then(function waitForReleaseIconDisplayed() { + this.waitForSelector('#document-management-content table.dataTable tbody i.fa.fa-check', function documentIsReleased() { + this.test.assertElementCount('#document-management-content table.dataTable tbody i.fa.fa-check', 1, 'Document has been released'); + }, function fail() { + this.capture('screenshot/documentRelease/waitForReleaseIconDisplayed-error.png'); + this.test.assert(false, 'Document has not been released'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentUploadFile.js b/docdoku-web-front/tests/js/document-management/document/documentUploadFile.js new file mode 100644 index 0000000000..5b7f675a0d --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentUploadFile.js @@ -0,0 +1,91 @@ +/*global casper,urls,workspace,documents*/ +casper.test.begin('Document upload file tests suite', 2, function documentUploadCadTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentUpload/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Wait for document to be displayed in list + */ + + casper.then(function waitForDocumentDisplayed() { + return this.waitForSelector('#document-management-content table.dataTable tr[title="'+documents.document1.number+'"] td.reference', function documentIsDisplayed() { + this.click('#document-management-content table.dataTable tr[title="'+documents.document1.number+'"] td.reference'); + }, function fail() { + this.capture('screenshot/documentUpload/waitForDocumentDisplayed-error.png'); + this.test.assert(false, 'Document can not be found'); + }); + }); + + /** + * Wait for document modal + */ + + casper.then(function waitForDocumentModal() { + var modalTab = '.document-modal .tabs li a[href="#tab-iteration-files"]'; + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/documentUpload/waitForDocumentModal-error.png'); + this.test.assert(false, 'Document modal can not be found'); + }); + }); + + /** + * Wait the modal tab + */ + casper.then(function waitForDocumentModalTab() { + return this.waitForSelector('.document-modal .upload-btn', function tabSelected() { + this.test.assert(true, 'File upload tab opened'); + }, function fail() { + this.capture('screenshot/documentUpload/waitForDocumentModalTab-error.png'); + this.test.assert(false, 'Upload button can not be found'); + }); + }); + /** + * Choose a file and upload + */ + casper.then(function setFileAndUpload() { + this.fill('.document-modal .upload-form', { + 'upload': 'res/document-upload.txt' + }, false); + + return this.waitFor(function checkFileHasBeenUploaded () { + return this.evaluate(function () { + return document.querySelectorAll('.document-modal .attachedFiles ul.file-list li').length === 1; + }); + }, function then() { + this.test.assert(true, 'File has been uploaded to document'); + }, function fail() { + this.capture('screenshot/documentUpload/setFileAndUpload-error.png'); + this.test.assert(false, 'Cannot upload the file'); + }); + + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/document-management/document/documentsCreation.js b/docdoku-web-front/tests/js/document-management/document/documentsCreation.js new file mode 100644 index 0000000000..48367c3838 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/document/documentsCreation.js @@ -0,0 +1,144 @@ +/*global casper,urls,workspace,documents*/ + +casper.test.begin('Documents creation tests suite', 4, function documentsCreationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentsCreation/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Open folder creation modal + */ + + casper.then(function clickOnDocumentCreationLink() { + this.click('.actions .new-document'); + }); + + /** + * Wait for modal + */ + + casper.then(function waitForDocumentCreationModal() { + return this.waitForSelector('.modal.document-modal.new-document', function () { + this.click('.modal.document-modal.new-document .btn.btn-primary'); + this.test.assertExists('.modal.document-modal.new-document input.reference:invalid', 'Should not create document without a reference'); + }, function fail() { + this.capture('screenshot/documentsCreation/waitForDocumentCreationModal-error.png'); + this.test.assert(false, 'New document modal can not be found'); + }); + }); + + /** + * Fill the form and create document + */ + + casper.then(function fillAndSubmitDocumentCreationModal() { + return this.waitForSelector('.modal.document-modal.new-document input.reference', function () { + this.sendKeys('.modal.document-modal.new-document input.reference', documents.document2.number); + this.click('.modal.document-modal.new-document .btn.btn-primary'); + }, function fail() { + this.capture('screenshot/documentsCreation/fillAndSubmitDocumentCreationModal-error.png'); + this.test.assert(false, 'New document form can not be found'); + }); + }); + + /** + * Check if document has been created + */ + + casper.then(function checkForDocumentCreation() { + return this.waitForSelector('#document-management-content table.dataTable tr[title="'+documents.document2.number+'"] td.reference a', function documentHasBeenCreated() { + this.test.assertSelectorHasText('#document-management-content table.dataTable tr[title="'+documents.document2.number+'"] td.reference a', documents.document2.number); + }, function fail() { + this.capture('screenshot/documentsCreation/checkForDocumentCreation-error.png'); + this.test.assert(false, 'New document created can not be found'); + }); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/documentsCreation/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Open folder creation modal + */ + + casper.then(function clickOnDocumentCreationLink() { + this.click('.actions .new-document'); + }); + + /** + * Wait for modal + */ + + casper.then(function waitForDocumentCreationModal() { + return this.waitForSelector('.modal.document-modal.new-document', function () { + this.click('.modal.document-modal.new-document .btn.btn-primary'); + this.test.assertExists('.modal.document-modal.new-document input.reference:invalid', 'Should not create document without a reference'); + }, function fail() { + this.capture('screenshot/documentsCreation/waitForDocumentCreationModal-error.png'); + this.test.assert(false, 'New document modal can not be found'); + }); + }); + + /** + * Fill the form and create document + */ + + casper.then(function fillAndSubmitDocumentCreationModal() { + return this.waitForSelector('.modal.document-modal.new-document input.reference', function () { + this.sendKeys('.modal.document-modal.new-document input.reference', documents.document3.number); + this.click('.modal.document-modal.new-document .btn.btn-primary'); + }, function fail() { + this.capture('screenshot/documentsCreation/fillAndSubmitDocumentCreationModal-error.png'); + this.test.assert(false, 'New document form can not be found'); + }); + }); + + /** + * Check if document has been created + */ + + casper.then(function checkForDocumentCreation() { + return this.waitForSelector('#document-management-content table.dataTable tr[title="'+documents.document3.number+'"] td.reference a', function documentHasBeenCreated() { + this.test.assertSelectorHasText('#document-management-content table.dataTable tr[title="'+documents.document3.number+'"] td.reference a', documents.document3.number); + }, function fail() { + this.capture('screenshot/documentsCreation/checkForDocumentCreation-error.png'); + this.test.assert(false, 'New document created can not be found'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/folder/folderCreation.js b/docdoku-web-front/tests/js/document-management/folder/folderCreation.js new file mode 100644 index 0000000000..472b73f86c --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/folder/folderCreation.js @@ -0,0 +1,49 @@ +/*global casper,urls,workspace,documents*/ + +casper.test.begin('Folder creation tests suite', 1, function folderCreationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('#folder-nav > .nav-list-entry > a', function clickFolderNavLink() { + this.click('#folder-nav > .nav-list-entry > a'); + }); + }); + + /** + * Open folder creation modal + */ + casper.then(function clickOnFolderCreationLink() { + this.click('#folder-nav > div.nav-list-entry > div.btn-group > ul.dropdown-menu > li.new-folder > a'); + return this.waitForSelector('#new-folder-form', function openFolderCreationModal() { + this.sendKeys('#new-folder-form input', documents.folder1, {reset: true}); + this.click('button[form=new-folder-form]'); + }); + }); + + /** + * Check if folder has been created + * */ + casper.then(function checkIfFolderHasBeenCreated() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.test.assert(true, 'Folder has been created'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/folder/folderDeletion.js b/docdoku-web-front/tests/js/document-management/folder/folderDeletion.js new file mode 100644 index 0000000000..2954b68279 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/folder/folderDeletion.js @@ -0,0 +1,60 @@ +/*global casper,urls,documents*/ + +casper.test.begin('Folder deletion tests suite', 1, function folderDeletionTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Click on delete folder link + */ + + casper.then(function waitForDeleteFolderLink() { + return this.waitForSelector('#folder-nav .items a[title="' + documents.folder1 + '"] + .btn-group .delete a', function clickFolderDeleteLink() { + this.click('#folder-nav .items a[title="' + documents.folder1 + '"] + .btn-group .delete a'); + }, function fail() { + this.capture('screenshot/folderDeletion/waitForDeleteFolderLink-error.png'); + this.test.assert(false, 'Folder link not found'); + }); + }); + + + /** + * Confirm folder deletion + */ + + casper.then(function confirmFolderDeletion() { + return this.waitForSelector('.bootbox', function confirmBoxAppeared() { + this.click('.bootbox .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/folderDeletion/confirmFolderDeletion-error.png'); + this.test.assert(false, 'Folder deletion confirmation modal can not be found'); + }); + }); + + /** + * Test if folder has been deleted + */ + + casper.then(function waitForFolderDisappear() { + return this.waitWhileSelector('#folder-nav .items a[title=' + documents.folder1 + ']', function folderHasBEenDeleted() { + this.test.assert(true, 'Folder deleted'); + }, function fail() { + this.capture('screenshot/folderDeletion/waitForFolderDisappear-error.png'); + this.test.assert(false, 'Folder still there'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/lifecycle/approveTask.js b/docdoku-web-front/tests/js/document-management/lifecycle/approveTask.js new file mode 100644 index 0000000000..211761cfc2 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/lifecycle/approveTask.js @@ -0,0 +1,48 @@ +/*global casper,urls,workspace,documents*/ + +casper.test.begin('Document approve task tests suite', 7, function documentApproveTaskTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/approveTask/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /*** + * Open the document modal + */ + casper.then(function openCreatedDocument() { + this.click('#document-management-content table.dataTable tr[title="' + documents.documentWithWorkflow.number + '"] td.reference'); + var modalTab = '.document-modal .tabs li a[href="#tab-iteration-files"]'; + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/approveTask/openCreatedDocument-error.png'); + this.test.assert(false, 'Document modal can not be found'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/document-management/lov/lovCreation.js b/docdoku-web-front/tests/js/document-management/lov/lovCreation.js new file mode 100644 index 0000000000..36b5003bfd --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/lov/lovCreation.js @@ -0,0 +1,119 @@ +/*global casper,urls,documents*/ + +casper.test.begin('LOV creation tests suite', 5, function documentLOVCreationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open template nav + */ + + casper.then(function waitForTemplateNavLink() { + return this.waitForSelector('#template-nav > .nav-list-entry > a', function clickTemplateNavLink() { + this.click('#template-nav > .nav-list-entry > a'); + }); + }); + + /** + * Open LOV creation modal + */ + + casper.then(function waitForLOVCreationLink() { + return this.waitForSelector('.actions .list-lov', function clickOnLOVCreationLink() { + this.click('.actions .list-lov'); + }); + }); + + /** + * Wait for lov creation modal and click on add + */ + + casper.then(function waitForLOVCreationModal() { + return this.waitForSelector('.list-lov', function lovItemCreationModalDisplayed() { + this.click('.addLOVButton'); + this.test.assertExists('.lovItem', 'An item should be added to the list'); + }); + }); + + /** + * Try creation of empty item + */ + + casper.then(function tryCreateLOVEmpty() { + this.click('.btn-saveLovs'); + this.test.assertExists('input.lovItemNameInput:invalid', 'Should not create lov without a name'); + }); + + /** + * Fill item name + */ + + casper.then(function addNameToLOVItem() { + this.sendKeys('input.lovItemNameInput', documents.lov.itemName); + }); + + /** + * Try creation of empty possible value + */ + + casper.then(function tryCreateLOVWithEmptyPossibleValue() { + this.click('.btn-saveLovs'); + this.test.assertExists('input.lovItemNameValueNameInput:invalid', 'Should not create lov entry without a name'); + }); + + /** + * Fill item possible value + */ + + casper.then(function addLOVItemPossibleValue() { + this.sendKeys('input.lovItemNameValueNameInput', documents.lov.possibleValueName); + this.sendKeys('input.lovItemNameValueValueInput', documents.lov.possibleValueValue); + }); + + /** + * Try creation + */ + + casper.then(function tryCreateLOV() { + this.click('.btn-saveLovs'); + return this.waitWhileSelector('.modal', function () { + this.test.assert(true, 'modal closed'); + }); + }); + + /** + * Re-open the modal to check + */ + casper.then(function reopenModal() { + return this.waitForSelector('.actions .list-lov', function clickOnLOVCreationLink() { + this.click('.actions .list-lov'); + }); + }); + + /** + * Check the number of item + */ + + casper.then(function waitForLOVCreationModal() { + return this.waitForSelector('.modal.list_lov .list_of_lov .lovItem', function checkLOVPersistence() { + this.test.assertElementCount('.list_of_lov .lovItem', 1, 'One element should be in the list of LOV'); + }, function(){ + this.capture('screenshot/lovCreation/waitForLOVCreationModal-error.png'); + this.test.assert(false,'One element should be in the list of LOV'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/lov/lovDeletion.js b/docdoku-web-front/tests/js/document-management/lov/lovDeletion.js new file mode 100644 index 0000000000..588b42d9e9 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/lov/lovDeletion.js @@ -0,0 +1,86 @@ +/*global casper,urls*/ + +/** + * @author lebeaujulien on 12/03/15. + */ + +casper.test.begin('LOV deletion tests suite', 3, function documentLOVDeletionTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open template nav + */ + + casper.then(function waitForTemplateNavLink() { + return this.waitForSelector('#template-nav > .nav-list-entry > a', function clickTemplateNavLink() { + this.click('#template-nav > .nav-list-entry > a'); + }); + }); + + /** + * Open LOV creation modal + */ + + casper.then(function waitForLOVCreationLink() { + return this.waitForSelector('.actions .list-lov', function clickOnLOVCreationLink() { + this.click('.actions .list-lov'); + }); + }); + + /** + * Check the number of item + */ + + casper.then(function waitForLOVCreationModal() { + return this.waitForSelector('.modal.list_lov .lovItem', function checkLOVPersistence() { + this.test.assertElementCount('.lovItem', 1, 'One element should be in the list of LOV'); + }, function(){ + this.capture('screenshot/lovDeletion/waitForLOVCreationModal-error.png'); + }); + }); + + /** + * Delete the item and save + */ + casper.then(function deleteItem() { + this.click('.deleteLovItem'); + this.click('.btn-saveLovs'); + return this.waitWhileSelector('.modal', function () { + this.test.assert(true, 'modal closed'); + }); + }); + + /** + * Re-open the modal to check + */ + casper.then(function reopenModal() { + this.waitForSelector('.actions .list-lov', function clickOnLOVCreationLink() { + this.click('.actions .list-lov'); + }); + }); + + /** + * Check the number of item + */ + + casper.then(function waitForLOVCreationModal() { + return this.waitForSelector('.modal.list_lov .list_of_lov', function checkDeletion() { + this.test.assertDoesntExist('.lovItem', 'LOV should be deleted'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/lov/lovInTemplateCreation.js b/docdoku-web-front/tests/js/document-management/lov/lovInTemplateCreation.js new file mode 100644 index 0000000000..d328ab5c07 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/lov/lovInTemplateCreation.js @@ -0,0 +1,169 @@ +/*global $,casper,urls,documents*/ +/*jshint -W040*/ +casper.test.begin('LOV creation and use in template', 5, function LOVTemplateCreationTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + casper.then(function () { + return this.open(urls.documentManagement); + }); + + function cantFindElement(selector) { + this.test.assertNotExists(selector, selector + ' should be displayed'); + } + + /** + * Open template nav + */ + casper.then(function () { + var templateMenuItemSelecor = '#template-nav > .nav-list-entry > a'; + return this.waitForSelector(templateMenuItemSelecor, function clickTemplateNavLink() { + this.click(templateMenuItemSelecor); + }, cantFindElement.bind(this, templateMenuItemSelecor)); + }); + + /** + * Open LOV creation modal and create a lov Color + * TODO : Split this 'wait selectors' into unit functions + */ + casper.then(function () { + var actionNewLovButtonSelector = '.actions .list-lov'; + return this.waitForSelector(actionNewLovButtonSelector, function () { + this.click(actionNewLovButtonSelector); + var modalSelector = '.modal.list_lov'; + this.waitForSelector(modalSelector, function () { + this.click('.addLOVButton'); + this.test.assertExists('.lovItem', 'An item should be added to the list'); + this.click('.addLOVValue'); + this.click('.addLOVValue'); + this.sendKeys('input.lovItemNameInput', documents.lov.color.itemName); + var firstItemSelector = '#lovCreationform .lovValues .lovPossibleValue:nth-child(1)'; + this.waitForSelector(firstItemSelector, function () { + this.sendKeys('#lovCreationform .lovValues .lovPossibleValue:nth-child(1) .lovItemNameValueNameInput', documents.lov.color.namePairValueNameRed); + this.sendKeys('#lovCreationform .lovValues .lovPossibleValue:nth-child(1) .lovItemNameValueValueInput', documents.lov.color.namePairValueValueRed); + var secondItemSelector = '#lovCreationform .lovValues .lovPossibleValue:nth-child(2)'; + this.waitForSelector(secondItemSelector, function () { + this.sendKeys('#lovCreationform .lovValues .lovPossibleValue:nth-child(2) .lovItemNameValueNameInput', documents.lov.color.namePairValueNameGreen); + this.sendKeys('#lovCreationform .lovValues .lovPossibleValue:nth-child(2) .lovItemNameValueValueInput', documents.lov.color.namePairValueValueGreen); + var thirdItemSelector = '#lovCreationform .lovValues .lovPossibleValue:nth-child(3)'; + this.waitForSelector(thirdItemSelector, function () { + this.sendKeys('#lovCreationform .lovValues .lovPossibleValue:nth-child(3) .lovItemNameValueNameInput', documents.lov.color.namePairValueNameBlue); + this.sendKeys('#lovCreationform .lovValues .lovPossibleValue:nth-child(3) .lovItemNameValueValueInput', documents.lov.color.namePairValueValueBlue); + this.click('.btn-saveLovs'); + }, cantFindElement.bind(this, thirdItemSelector)); + }, cantFindElement.bind(this, secondItemSelector)); + }, cantFindElement.bind(this, firstItemSelector)); + }, cantFindElement.bind(this, modalSelector)); + }, cantFindElement.bind(this, actionNewLovButtonSelector)); + }); + + /** + * Open the new model template modal and fill ID + * TODO : Split this 'wait selectors' into unit functions + */ + casper.then(function () { + return this.waitWhileSelector('.modal.list_lov', function () { + this.click('.actions .new-template'); + this.waitForSelector('.modal.new-template', function () { + this.waitForSelector('.modal.new-template input.reference', function () { + this.sendKeys('.modal.new-template input.reference', documents.lov.template.number); + }, cantFindElement.bind(this, '.modal.new-template input.reference')); + }, cantFindElement.bind(this, '.modal.new-template')); + }, function () { + this.capture('screenshot/lov/lovCreationSave-error.png'); + this.test.assert(false, 'The save of the lov failed because the modal is still open'); + }); + + }); + /* + * * TODO : Split this 'wait selectors' into unit functions + */ + casper.then(function () { + var templateTabSelector = '.modal.new-template .tabs > ul > li:nth-child(3) > a'; + return this.waitForSelector(templateTabSelector, function () { + this.click(templateTabSelector); + //wait until the tab Attributes is display + var attributViewSelector = '.tab-pane.attributes.attributes-edit.active'; + this.waitForSelector(attributViewSelector, function () { + this.waitForSelector('.add', function () { + this.click('.add'); + var inputSelector = attributViewSelector + ' .list-item input.name'; + this.waitForSelector(inputSelector, function () { + this.sendKeys(inputSelector, documents.lov.template.attributeName); + }, cantFindElement.bind(this, inputSelector)); + }, cantFindElement.bind(this, '.add')); + }, cantFindElement.bind(this, attributViewSelector)); + + }, cantFindElement.bind(this, templateTabSelector)); + }); + + /** + * Select type for the attribut to be the LOV and save + */ + casper.then(function () { + var selector = '.tab-pane.attributes.attributes-edit.active .list-item select.type option[value="' + documents.lov.color.itemName + '"]'; + return this.waitForSelector(selector, function () { + this.evaluate(function (pOptionSelector) { + var option = document.querySelector(pOptionSelector); + $(option).attr('selected', 'selected').change(); + return true; + }, selector); + this.click('.modal.new-template .btn.btn-primary'); + }, cantFindElement.bind(this, selector)); + + }); + + /** + * Check if the template has been created and open the modal + * TODO : Split this 'wait selectors' into unit functions + */ + casper.then(function () { + var modalOfTemplateCreationSelector = '.modal.new-template'; + return this.waitWhileSelector(modalOfTemplateCreationSelector, function () { + var templateReferenceInListSelector = '#document-management-content table.dataTable tr td.reference'; + this.waitForSelector(templateReferenceInListSelector, function templateHasBeenCreated() { + this.test.assertSelectorHasText(templateReferenceInListSelector, documents.lov.template.number); + this.click('.dataTables_wrapper .items > tr:first-child .reference'); + this.waitForSelector(modalOfTemplateCreationSelector, function () { + this.test.assertExist('.modal.new-template input.reference[value="' + documents.lov.template.number + '"]', 'Reference of the template should be' + documents.lov.template.number); + }, cantFindElement.bind(this, modalOfTemplateCreationSelector)); + }, cantFindElement.bind(this, templateReferenceInListSelector)); + }, function () { + this.capture('screenshot/lov/templateWithLOVAttributeSave-error.png'); + this.test.assert(false, 'The save of the template failed because the modal is still open'); + }); + + }); + + /** + * Check the type of the attribute + * TODO : Split this 'wait selectors' into unit functions + */ + casper.then(function () { + var templateAttributTabSelector = '.modal.new-template .tabs > ul > li:nth-child(3) > a'; + return this.waitForSelector(templateAttributTabSelector, function () { + this.click(templateAttributTabSelector); + var attributViewSelector = '.tab-pane.attributes.attributes-edit.active'; + this.waitForSelector(attributViewSelector, function () { + var attributeNameSelector = '.tab-pane.attributes.attributes-edit.active .list-item input.name[value="' + documents.lov.template.attributeName + '"]'; + this.test.assertExist(attributeNameSelector, 'Attribut name should be ' + documents.lov.template.attributeName); + var selectSelector = '.tab-pane.attributes.attributes-edit.active .list-item select.type'; + var expectedValue = documents.lov.color.itemName; + var isValueOk = this.evaluate(function (selector, expectedValueForSelect) { + var selectValue = $(selector).val(); + return selectValue === expectedValueForSelect; + }, selectSelector, expectedValue); + this.test.assertTrue(isValueOk, 'Value of the type of the attribut should be ' + expectedValue); + }, cantFindElement.bind(this, attributViewSelector)); + }, cantFindElement.bind(this, templateAttributTabSelector)); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/document-management/share/expiredSharedDocument.js b/docdoku-web-front/tests/js/document-management/share/expiredSharedDocument.js new file mode 100644 index 0000000000..bcb3c57f42 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/share/expiredSharedDocument.js @@ -0,0 +1,33 @@ +/*global casper,urls*/ + +casper.test.begin('Expired private shared document tests suite', 1, function expiredPrivateSharedDocumentTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.privateDocumentPermalinkExpired); + }); + + /** + * Check we have the expired resource page + */ + + casper.then(function checkForExpiredPage() { + return this.waitForSelector('#not-found-view > h1', function () { + this.test.assert(true, 'Expired entity page displayed'); + }, function fail() { + this.capture('screenshot/expiredSharedDocument/checkForExpiredPage-error.png'); + this.test.assert(false, 'Expired shared entity page not displayed'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/share/privateSharedDocument.js b/docdoku-web-front/tests/js/document-management/share/privateSharedDocument.js new file mode 100644 index 0000000000..9f1f5845a8 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/share/privateSharedDocument.js @@ -0,0 +1,62 @@ +/*global casper,urls,documents*/ + +casper.test.begin('Private shared document tests suite', 3, function privateSharedDocumentTestsSuite() { + + 'use strict'; + + var titleSelector = '#content > .document-revision > div > h3'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.privateDocumentPermalink); + }); + + /** + * We should be prompted for password + */ + casper.then(function checkPasswordIsRequested() { + return this.waitForSelector('#prompt_modal #prompt_input.ready', function passwordRequested() { + this.sendKeys('#prompt_modal #prompt_input', documents.document1.sharedPassword, {reset: true}); + this.click('#submitPrompt'); + this.test.assert(true, 'We are prompted for password'); + }, function fail() { + this.capture('screenshot/privateSharedDocument/checkPasswordIsRequested-error.png'); + this.test.assert(false, 'Password field can not be found'); + }); + }); + + /** + * Check for document title + */ + + casper.then(function checkDocumentTitle() { + return this.waitForSelector(titleSelector, function titleDisplayed() { + this.test.assertSelectorHasText(titleSelector, documents.document1.number + '-A'); + }, function fail() { + this.capture('screenshot/privateSharedDocument/checkDocumentTitle-error.png'); + this.test.assert(false, 'Title can not be found'); + }); + }); + + /** + * Check for document iteration note + */ + casper.then(function checkIterationNote() { + this.click('.nav-tabs a[href="#tab-document-iteration"]'); + return this.waitForSelector(titleSelector, function iterationNoteDisplayed() { + this.test.assertSelectorHasText('#tab-document-iteration > table > tbody > tr:nth-child(2) > td', documents.document1.iterationNote); + }, function fail() { + this.capture('screenshot/privateSharedDocument/checkIterationNote-error.png'); + this.test.assert(false, 'Iteration note can not be found'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/share/publicSharedDocument.js b/docdoku-web-front/tests/js/document-management/share/publicSharedDocument.js new file mode 100644 index 0000000000..ab315546ad --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/share/publicSharedDocument.js @@ -0,0 +1,46 @@ +/*global casper,urls,documents*/ + +casper.test.begin('Public shared document tests suite', 2, function publicSharedDocumentTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentPermalink); + }); + + /** + * Check for document title + */ + + casper.then(function checkDocumentTitle() { + return this.waitForSelector('#content > .document-revision > div > h3', function titleDisplayed() { + this.test.assertSelectorHasText('#content > .document-revision > div > h3', documents.document1.number + '-A'); + }, function fail() { + this.capture('screenshot/publicSharedDocument/checkDocumentTitle-error.png'); + this.test.assert(false, 'Title can not be found'); + }); + }); + + /** + * Check for document iteration note + */ + casper.then(function checkIterationNote() { + this.click('.nav-tabs a[href="#tab-document-iteration"]'); + return this.waitForSelector('#content > .document-revision > div > h3', function iterationNoteDisplayed() { + this.test.assertSelectorHasText('#tab-document-iteration > table > tbody > tr:nth-child(2) > td', documents.document1.iterationNote); + }, function fail() { + this.capture('screenshot/publicSharedDocument/checkIterationNote-error.png'); + this.test.assert(false, 'Iteration note can not be found'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/share/sharedDocumentCreation.js b/docdoku-web-front/tests/js/document-management/share/sharedDocumentCreation.js new file mode 100644 index 0000000000..5fcb579eef --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/share/sharedDocumentCreation.js @@ -0,0 +1,178 @@ +/*global casper,urls,workspace,documents*/ + +casper.test.begin('Shared document creation tests suite', 7, function sharedDocumentCreationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open folder nav + */ + + casper.then(function waitForFolderNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]', function () { + this.click('a[href="#' + workspace + '/folders/' + documents.folder1 + '"]'); + }, function fail() { + this.capture('screenshot/sharedDocumentCreation/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Folder nav link can not be found'); + }); + }); + + /** + * Find the shared document link in the document list + */ + + casper.then(function waitForDocumentList() { + var link = '#document-management-content table.dataTable tbody tr:first-child td.document-master-share i'; + return this.waitForSelector(link, function onLinkFound() { + this.click(link); + }, function fail() { + this.capture('screenshot/sharedDocumentCreation/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Shared document modal can not be found'); + }); + }); + + /** + * Wait for modal + */ + casper.then(function waitForSharedDocumentCreationModal() { + return this.waitForSelector('#share-modal', function modalOpened() { + this.test.assert(true, 'Shared document modal is opened'); + }, function fail() { + this.capture('screenshot/sharedDocumentCreation/waitForSharedDocumentCreationModal-error.png'); + this.test.assert(false, 'Shared document modal can not be found'); + }); + }); + + /** + * Set the document as public + */ + casper.then(function setDocumentAsPublicShared() { + + this.click('#share-modal .public-shared-switch .switch-off input'); + return this.waitForSelector('#share-modal .public-shared-switch .switch-on', function publicSharedCreated() { + this.test.assert(true, 'Document is now public shared'); + }, function fail() { + this.capture('screenshot/sharedDocumentCreation/setDocumentAsPublicShared-error.png'); + this.test.assert(false, 'Shared document cannot be shared as public'); + }); + + }); + + + /** + * Create a private share, with expire date and password + */ + casper.then(function createDocumentPrivateShare() { + + this.sendKeys('#private-share .password', documents.document1.sharedPassword, {reset: true}); + this.sendKeys('#private-share .confirm-password', documents.document1.sharedPassword, {reset: true}); + this.sendKeys('#private-share .expire-date', documents.document1.expireDate, {reset: true}); + + this.click('#private-share #generate-private-share'); + + return this.waitForSelector('#private-share > div > div > a', function onLinkGenerated() { + var url = this.fetchText('#private-share > div > div > a'); + urls.privateDocumentPermalink = url; + this.test.assert(true, 'Private share created : ' + url); + }, function fail() { + this.capture('screenshot/sharedDocumentCreation/createDocumentPrivateShare-error.png'); + this.test.assert(false, 'Shared document cannot be shared as private'); + }); + }); + + + /** + * Close the modal + */ + casper.then(function closeSharedDocumentModal() { + + this.click('#share-modal > div.modal-header > button'); + + return this.waitWhileSelector('#share-modal', function modalClosed() { + this.test.assert(true, 'Shared document modal closed'); + }, function fail() { + this.capture('screenshot/sharedDocumentCreation/closeSharedDocumentModal-error.png'); + this.test.assert(false, 'Shared document modal cannot be closed'); + }); + + }); + + /** + * Reopen the modal to create a second private share, expired one. + */ + casper.then(function waitForDocumentList() { + var link = '#document-management-content table.dataTable tbody tr:first-child td.document-master-share i'; + return this.waitForSelector(link, function onLinkFound() { + this.click(link); + }, function fail() { + this.capture('screenshot/sharedDocumentCreation/waitForDocumentList-error.png'); + this.test.assert(false, 'Shared document modal can not be found'); + }); + }); + + /** + * Wait for modal + */ + casper.then(function waitForSharedDocumentCreationModal() { + return this.waitForSelector('#share-modal', function modalOpened() { + + this.test.assert(true, 'Shared document modal is opened'); + + this.sendKeys('#private-share .password', documents.document1.sharedPassword, {reset: true}); + this.sendKeys('#private-share .confirm-password', documents.document1.sharedPassword, {reset: true}); + this.sendKeys('#private-share .expire-date', documents.document1.expireDate2, {reset: true}); + this.click('#private-share #generate-private-share'); + + }, function fail() { + this.capture('screenshot/sharedDocumentCreation/waitForSharedDocumentCreationModal-error.png'); + this.test.assert(false, 'Shared document modal can not be found'); + }); + }); + + /** + * Save the generated url for test later + */ + casper.then(function createDocumentPrivateShare() { + return this.waitForSelector('#private-share > div > div > a', function onLinkGenerated() { + var url = this.fetchText('#private-share > div > div > a'); + urls.privateDocumentPermalinkExpired = url; + this.test.assert(true, 'Private share created expiring yesterday : ' + url); + }, function fail() { + this.capture('screenshot/sharedDocumentCreation/createDocumentPrivateShare-error.png'); + this.test.assert(false, 'Shared document cannot be shared as private'); + }); + + }); + + /** + * Close the modal + */ + casper.then(function closeSharedDocumentModal() { + + this.click('#share-modal > div.modal-header > button'); + + return this.waitWhileSelector('#share-modal', function modalClosed() { + this.test.assert(true, 'Shared document modal closed'); + }, function fail() { + this.capture('screenshot/sharedDocumentCreation/closeSharedDocumentModal-error.png'); + this.test.assert(false, 'Shared document modal cannot be closed'); + }); + + }); + + + casper.run(function allDone() { + this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/document-management/tag/tagCreation.js b/docdoku-web-front/tests/js/document-management/tag/tagCreation.js new file mode 100644 index 0000000000..98b80dc030 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/tag/tagCreation.js @@ -0,0 +1,149 @@ +/*global casper,urls,documents,$*/ + +casper.test.begin('Document tag creation tests suite', 8, function documentTagCreationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Click on the tag button + * */ + casper.then(function openTagCreationModal() { + return this.waitForSelector('.actions .tags', function buttonDisplayed() { + this.click('.actions .tags'); + }, function fail() { + this.capture('screenshot/documentTagCreation/openTagCreationModal-error.png'); + this.test.assert(false, 'Tag creation button can not be found'); + }); + }); + + + /** + * Wait for the modal to be opened + * */ + casper.then(function waitTagCreationModal() { + return this.waitForSelector('.modal.tag-management .newTag', function modalOpened() { + this.test.assert(true, 'Tag creation modal opened'); + }, function fail() { + this.capture('screenshot/documentTagCreation/waitTagCreationModal-error.png'); + this.test.assert(false, 'Tag creation modal can not be found'); + }); + }); + + + + /** + * Check the tags list is empty + * */ + casper.then(function checkNoTag() { + return this.waitForSelector('.modal.tag-management ul.existing-tags-list', function tagsListDisplayed() { + this.test.assertElementCount('.modal.tag-management ul.existing-tags-list li', 0, 'Should find no tag'); + }, function fail() { + this.capture('screenshot/documentTagCreation/tagsList-error.png'); + this.test.assert(false, 'Tags list contains tags already'); + }); + }); + + + /** + * Try to add a empty tag + * */ + casper.then(function createEmptyTag() { + return this.waitFor(function checkHidden() { + return this.evaluate(function() { + return $('.modal.tag-management .newTag-button').is(':hidden'); + }); + },function success() { + this.test.assert(true, 'add tag button hidden'); + }, function fail() { + this.test.assert(false, 'add tab button not hidden'); + }); + }); + + /** + * Send key to input + */ + casper.then(function sendKey() { + this.sendKeys('.modal.tag-management .newTag', documents.tags.tag1,{reset:true}); + return this.waitFor(function checkVisible() { + return this.evaluate(function() { + return $('.modal.tag-management .newTag-button').is(':visible'); + }); + }, function success() { + this.test.assert(true, 'add tag button visible'); + }, function fail() { + this.test.assert(false, 'add tag button not visible'); + }); + }); + + /** + * Click on the add tag button + * */ + casper.then(function addTag() { + return this.waitForSelector('.modal.tag-management .newTag-button', function buttonIsVisible() { + this.click('.modal.tag-management .newTag-button'); + }, function fail() { + this.capture('screenshot/documentTagCreation/addTagButtonNotVisible-error.png'); + this.test.assert(false, 'Tag add button can not be found'); + }); + }); + + /** + * Try to add a tag + * */ + casper.then(function createTags() { + return this.waitForSelector('.modal.tag-management ul.existing-tags-list li',function tagAdded(){ + this.test.assertElementCount('.modal.tag-management ul.existing-tags-list li', 1, 'Should add a tag'); + },function fail(){ + this.capture('screenshot/documentTagCreation/createTags-error.png'); + this.test.assert(false, 'Cannot add a tag'); + }); + }); + + /** + * Assert the input has been reset + * */ + casper.then(function checkInputReset() { + this.test.assertField('.modal.tag-management .newTag', null, 'Input has been reset'); + }); + + /** + * + * Try to add an other tag + * */ + casper.then(function createAnOtherTags() { + this.sendKeys('.modal.tag-management .newTag', documents.tags.tag2, {reset: true}); + this.click('.modal.tag-management .newTag-button'); + this.test.assertElementCount('.modal.tag-management ul.existing-tags-list li', 2, 'Should add an other tag'); + }); + + /** + * + * Save the tags + * TODO : remove wait(100), use wait selector instead + * */ + casper.then(function saveTags() { + return this.wait(100, function () { + this.click('.modal.tag-management .modal-footer .btn-primary'); + this.waitWhileSelector('.modal.tag-management', function modalClosed() { + this.test.assert(true, 'Tag modal has been closed'); + }, function fail() { + this.capture('screenshot/documentTagCreation/saveTags-error.png'); + this.test.assert(false, 'Tag modal can not be closed'); + }); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/tag/tagDeletion.js b/docdoku-web-front/tests/js/document-management/tag/tagDeletion.js new file mode 100644 index 0000000000..d5b8974522 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/tag/tagDeletion.js @@ -0,0 +1,81 @@ +/*global casper,urls*/ + +casper.test.begin('Document tag deletion tests suite', 5, function documentTagDeletionTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Click on the tag button + * */ + casper.then(function openTagCreationModal() { + return this.waitForSelector('.actions .tags', function buttonDisplayed() { + this.click('.actions .tags'); + }, function fail() { + this.capture('screenshot/documentTagDeletion/openTagCreationModal-error.png'); + this.test.assert(false, 'Tag deletion button can not be found'); + }); + }); + + + /** + * Wait for the modal to be opened + * */ + casper.then(function waitTagCreationModal() { + return this.waitForSelector('.modal.tag-management .newTag', function modalOpened() { + this.test.assert(true, 'Tag deletion modal opened'); + }, function fail() { + this.capture('screenshot/documentTagDeletion/waitTagCreationModal-error.png'); + this.test.assert(false, 'Tag deletion modal can not be found'); + }); + }); + + /** + * Wait for the modal to be opened + * */ + casper.then(function waitTagCreationModal() { + return this.waitForSelector('.modal.tag-management ul.existing-tags-list li', function modalOpened() { + this.test.assert(true, 'Tag modal opened'); + + this.click('.modal.tag-management ul.existing-tags-list li a'); + this.test.assertElementCount('.modal.tag-management ul.existing-tags-list li', 1, 'Should have remove the tag from the list'); + + this.click('.modal.tag-management ul.existing-tags-list li a'); + this.test.assertElementCount('.modal.tag-management ul.existing-tags-list li', 0, 'Should have remove other the tag from the list'); + + }, function fail() { + this.capture('screenshot/documentTagDeletion/waitTagCreationModal-error.png'); + this.test.assert(false, 'Tag modal can not be found'); + }); + }); + + /** + * + * Save the tags + * TODO : remove wait(100) find other solution with wait for selector + * */ + casper.then(function saveTags() { + return this.wait(100, function () { + this.click('.modal.tag-management .modal-footer .btn-primary'); + this.waitWhileSelector('.modal.tag-management', function modalClosed() { + this.test.assert(true, 'Tag modal has been closed'); + }, function fail() { + this.capture('screenshot/documentTagDeletion/saveTags-error.png'); + this.test.assert(false, 'Tag modal can not be closed'); + }); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/tag/tagList.js b/docdoku-web-front/tests/js/document-management/tag/tagList.js new file mode 100644 index 0000000000..8a88ac905d --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/tag/tagList.js @@ -0,0 +1,47 @@ +/*global casper,urls,workspace*/ + +casper.test.begin('Document tag list tests suite', 1, function documentTagListTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open tag nav + */ + + casper.then(function waitForTagNavLink() { + return this.waitForSelector('a[href="#' + workspace + '/tags"]', function () { + this.click('a[href="#' + workspace + '/tags"]'); + }, function fail() { + this.capture('screenshot/tagList/waitForFolderNavLink-error.png'); + this.test.assert(false, 'Tag nav link can not be found'); + }); + }); + + /** + * Count tag length + */ + + casper.then(function countTags() { + return this.waitForSelector('#tag-nav > ul > li.tag', function () { + this.test.assertElementCount('#tag-nav > ul > li.tag', 2, 'Should have 2 tags created'); + }, function fail() { + this.capture('screenshot/tagList/countTags-error.png'); + this.test.assert(false, 'Tags links can not be found'); + }); + }); + + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/template/templateCreation.js b/docdoku-web-front/tests/js/document-management/template/templateCreation.js new file mode 100644 index 0000000000..26c8ba5b86 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/template/templateCreation.js @@ -0,0 +1,77 @@ +/*global casper,urls,documents*/ + +casper.test.begin('Document template creation tests suite', 4, function documentTemplateCreationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open template nav + */ + + casper.then(function waitForTemplateNavLink() { + return this.waitForSelector('#template-nav > .nav-list-entry > a', function clickTemplateNavLink() { + this.click('#template-nav > .nav-list-entry > a'); + }); + }); + + /** + * Open template creation modal + */ + + casper.then(function waitForTemplateCreationLink() { + return this.waitForSelector('.actions .new-template', function clickOnTemplateCreationLink() { + this.click('.actions .new-template'); + }); + }); + + /** + * Wait for template creation modal + */ + + casper.then(function waitForTemplateCreationModal() { + return this.waitForSelector('.modal.new-template', function templateCreationModalDisplayed() { + this.click('.modal.new-template .btn.btn-primary'); + this.test.assertExists('.modal.new-template input.reference:invalid', 'Should not create document template without a reference'); + }); + }); + + /** + * Fill the form and create document template + */ + + casper.then(function fillAndSubmitTemplateCreationModal() { + return this.waitForSelector('.modal.new-template input.reference', function () { + this.sendKeys('.modal.new-template input.reference', documents.template1.number); + this.sendKeys('.modal.new-template input.type', documents.template1.type); + this.sendKeys('.modal.new-template input.mask', documents.template1.mask); + this.click('.modal.new-template input.id-generated'); + this.click('.modal.new-template .btn.btn-primary'); + }); + }); + + /** + * Check if template has been created + * */ + + casper.then(function checkIfTemplateHasBeenCreated() { + return this.waitForSelector('#document-management-content table.dataTable tr td.reference', function templateHasBeenCreated() { + this.test.assertSelectorHasText('#document-management-content table.dataTable tr td.reference', documents.template1.number); + this.test.assertSelectorHasText('#document-management-content table.dataTable tr td.type', documents.template1.type); + this.test.assertSelectorHasText('#document-management-content table.dataTable tr td.mask', documents.template1.mask); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/document-management/template/templateDeletion.js b/docdoku-web-front/tests/js/document-management/template/templateDeletion.js new file mode 100644 index 0000000000..ea7d45d713 --- /dev/null +++ b/docdoku-web-front/tests/js/document-management/template/templateDeletion.js @@ -0,0 +1,87 @@ +/*global casper,urls*/ + +casper.test.begin('Document template deletion tests suite', 1, function documentTemplateDeletionTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open document management URL + * */ + + casper.then(function () { + return this.open(urls.documentManagement); + }); + + /** + * Open template nav + */ + + casper.then(function waitForTemplateNavLink() { + return this.waitForSelector('#template-nav > .nav-list-entry > a', function clickTemplateNavLink() { + this.click('#template-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/templateDeletion/waitForTemplateNavLink-error.png'); + this.test.assert(false, 'Template nav link not found'); + }); + }); + + /** + * Wait for template to be displayed in list + */ + + casper.then(function waitForTemplateDisplayed() { + return this.waitForSelector('#document-management-content table.dataTable tr td.reference', function templateIsDisplayed() { + this.click('#document-management-content table.dataTable tr td:first-child input[type=checkbox]'); + }, function fail() { + this.capture('screenshot/templateDeletion/waitForTemplateDisplayed-error.png'); + this.test.assert(false, 'Template not found'); + }); + }); + + /** + * Wait for template suppression button + */ + + casper.then(function waitForDeleteButtonDisplayed() { + return this.waitForSelector('.actions .delete', function deleteButtonIsDisplayed() { + this.click('.actions .delete'); + }, function fail() { + this.capture('screenshot/templateDeletion/waitForDeleteButtonDisplayed-error.png'); + this.test.assert(false, 'Delete template button not found'); + }); + }); + + + /** + * Confirm template deletion + */ + + casper.then(function confirmTemplateDeletion() { + return this.waitForSelector('.bootbox', function confirmBoxAppeared() { + this.click('.bootbox .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/templateDeletion/confirmTemplateDeletion-error.png'); + this.test.assert(false, 'Template deletion confirmation modal can not be found'); + }); + }); + + + /** + * Wait for template to be removed + */ + + casper.then(function waitForTemplateDeletion() { + return this.waitWhileSelector('#document-management-content table.dataTable tr td.reference', function templateDeleted() { + this.test.assert(true, 'Template deleted'); + }, function fail() { + this.capture('screenshot/templateDeletion/waitForTemplateDeletion-error.png'); + this.test.assert(false, 'Template still there'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/includes/helpers.js b/docdoku-web-front/tests/js/includes/helpers.js new file mode 100644 index 0000000000..02cd6e99d7 --- /dev/null +++ b/docdoku-web-front/tests/js/includes/helpers.js @@ -0,0 +1,14 @@ +/*jshint -W098*/ + +var helpers = { + findReasonInResponseHeaders: function (headers) { + 'use strict'; + var reason = ''; + headers.forEach(function (header) { + if (header.name === 'Reason-Phrase') { + reason = header.value; + } + }); + return reason; + } +}; diff --git a/docdoku-web-front/tests/js/includes/vars.js b/docdoku-web-front/tests/js/includes/vars.js new file mode 100644 index 0000000000..b74d433d05 --- /dev/null +++ b/docdoku-web-front/tests/js/includes/vars.js @@ -0,0 +1,271 @@ +/*global casper*/ + +/*jshint -W098*/ + +var domain = casper.cli.get('domain'); +var port = casper.cli.get('port'); +var workspace = casper.cli.get('workspace'); +var login = casper.cli.get('login'); +var pass = casper.cli.get('pass'); +var contextPath = casper.cli.get('contextPath'); +var protocol = casper.cli.get('protocol'); + +var defaultUrl = protocol + '://' + domain + ':' + port; +var homeUrl = defaultUrl + contextPath; + +function twoDigit(n) { + 'use strict'; + return n > 9 ? n : '0' + n; +} + +var tomorrow = new Date(Date.now() + 86400000); +var yesterday = new Date(Date.now() - 86400000); +var tomorrowValue = tomorrow.getFullYear() + '-' + twoDigit(tomorrow.getMonth() + 1) + '-' + twoDigit(tomorrow.getDate()); +var yesterdayValue = yesterday.getFullYear() + '-' + twoDigit(yesterday.getMonth() + 1) + '-' + twoDigit(yesterday.getDate()); + +var documents = { + template1: { + number: 'CasperJsTestDocumentTemplate', + type: 'CasperJsTestDocumentTemplateType', + mask: 'MASK-###', + maskGenerated:'MASK-000' + }, + folder1: 'CasperJsTestFolder', + document1: { + number: '000-AAA-CasperJsTestDocument', + iterationNote: 'This is the first iteration of this document', + sharedPassword: 'azertyuiop', + expireDate: tomorrowValue, + expireDate2: yesterdayValue, + documentLink: '100-AAA-CasperJsTestDocument', + documentLinkComment: 'Some dummy comment' + }, + document2: { + number: '100-AAA-CasperJsTestDocument', + iterationNote: 'This is the first iteration of this document', + sharedPassword: 'azertyuiop', + expireDate: tomorrowValue, + expireDate2: yesterdayValue + }, + document3: { + number: '200-AAA-CasperJsTestDocument', + iterationNote: 'This is the first iteration of this document', + sharedPassword: 'azertyuiop', + expireDate: tomorrowValue, + expireDate2: yesterdayValue + }, + documentWithWorkflow: { + number: '300-AAA-CasperJsTestDocument', + iterationNote: 'This is a document with a workflow' + }, + tags: { + tag1: 'Foo', + tag2: 'Bar' + }, + lov: { + itemName: 'casperTestItemName', + possibleValueName: 'None', + possibleValueValue: '0', + color: { + itemName: 'Color', + namePairValueNameRed: 'Red', + namePairValueValueRed: 'rouge', + namePairValueNameGreen: 'Green', + namePairValueValueGreen: 'vert', + namePairValueNameBlue: 'Blue', + namePairValueValueBlue: 'bleu' + }, + + template: { + number: 'TemplateWithLOVAttributeColor', + attributeName: 'colorAttributes' + } + + } +}; + +var products = { + template1: { + number: 'CasperJsTestPartTemplate', + type: 'CasperJsTestPartType', + mask: 'FAX_***_##' + }, + template2: { + number: 'CasperJsTestPartTemplate2', + type: 'CasperJsTestPartType2', + attrInstance: 'CasperJsTestAttrInst' + }, + part1: { + number: '000-AAA-CasperJsTestPart', + name: 'CasperJsTestPart', + iterationNote: 'This is the first iteration of this part', + sharedPassword: 'azertyuiop', + expireDate: tomorrowValue, + expireDate2: yesterdayValue, + attributeValue: 'CasperJs', + documentLink: '000-AAA-CasperJsTestDocument' + }, + part2: { + number: '000-AAA-CasperJsTestPart2', + name: 'CasperJsTestPart2', + iterationNote: 'This is the first iteration of this part', + sharedPassword: 'azertyuiop', + expireDate: tomorrowValue, + expireDate2: yesterdayValue, + attributeName1: 'CasperJsTestAttr', + attributeName2: 'CasperJsTestAttr-lock' + }, + product1: { + number: '000-AAA-CasperJsTestProduct', + description: 'CasperJsTestProduct' + }, + assembly: { + parts: { + '100-AAA-CasperJsAssemblyP1': { + tx: 100, + ty: 150, + tz: -140, + rx: 1.57, + ry: 1.57, + rz: 0 + }, + '200-AAA-CasperJsAssemblyP2': { + tx: -100, + ty: -150, + tz: -140, + rx: -1.57, + ry: 1.57, + rz: 1.57 + }, + '300-AAA-CasperJsAssemblyP3': { + tx: -100, + ty: 150, + tz: -140, + rx: 1.57, + ry: 0.95, + rz: -1.57 + }, + '400-AAA-CasperJsAssemblyP4': { + tx: 100, + ty: -150, + tz: 140, + rx: -1.57, + ry: 0.95, + rz: 1.57 + } + } + } +}; + +var productInstances = { + productInstance1: { + serialNumber: 'CasperJsTestSerialNumber', + iterationNote: 'First Iteration Casper', + pathDataValue: 'CasperJsTestData' + } +}; + +var baselines = { + baseline1: { + name: '000-AAA-CasperJsTestBaseline', + description: 'This is a baseline' + }, + baseline2: { + name: '001-AAA-CasperJsTestBaseline', + description: 'This is also a baseline' + } +}; + +var changeItems = { + changeIssue1: { + number: 'CasperJsTestIssue' + }, + changeRequest1: { + number: 'CasperJsTestRequest' + }, + changeOrder1: { + number: 'CasperJsTestOrder' + }, + milestone1: { + title: 'CasperJsTestMilestone', + date: '2015-12-12' + } +}; + +var roles = { + role1: { + name: 'Lecteur' + } +}; + +var p2pLinks = { + type: 'FOO' +}; + +var workflows = { + role1: 'CasperJsRole1', + role2: 'CasperJsRole2', + role3: 'CasperJsRole3', + workflow1: { + name: 'CasperJsTestWorkflow', + finalState: 'CasperJsFinalState', + activities: { + activity1: { + name: 'CasperJsTestActivity', + tasks: { + task1: { + name: 'CasperJsTestTask' + } + } + } + } + }, + workflow2: { + name: 'CasperJsTestWorkflow2' + } +}; + +var urls = { + productManagement: homeUrl + 'product-management/#' + workspace, + productStructure: homeUrl + 'product-structure/#' + workspace + '/' + products.product1.number + '/config-spec/wip/bom', + productStructureInstances: homeUrl + 'api/workspaces/' + workspace + '/products/' + products.product1.number + '/instances?configSpec=wip&path=-1', + productStructureForDeliverable: homeUrl + 'product-structure/#' + workspace + '/' + products.product1.number + '/config-spec/pi-' + productInstances.productInstance1.serialNumber + '/bom', + documentManagement: homeUrl + 'document-management/#' + workspace, + changeManagement: homeUrl + 'change-management/#' + workspace, + workspaceAdministration:homeUrl+'workspace-management/', + + documentPermalink: homeUrl + 'documents/#' + workspace + '/' + documents.document1.number + '/A', + partPermalink: homeUrl + 'parts/#' + workspace + '/' + products.part1.number + '/A', + + // Set on share creation + privateDocumentPermalink: null, + privateDocumentPermalinkExpired: null, + + privatePartPermalink: null, + privatePartPermalinkExpired: null +}; + +var apiUrls = { + userInfo: homeUrl + 'api/workspaces/' + workspace + '/users/me', + deletePart1: homeUrl + 'api/workspaces/' + workspace + '/parts/' + products.part1.number + '-A', + deletePart2: homeUrl + 'api/workspaces/' + workspace + '/parts/' + products.part2.number + '-A', + deletePartTemplate: homeUrl + 'api/workspaces/' + workspace + '/part-templates/' + products.template1.number, + deletePartTemplate2: homeUrl + 'api/workspaces/' + workspace + '/part-templates/' + products.template2.number, + deleteDocumentTemplate: homeUrl + 'api/workspaces/' + workspace + '/document-templates/' + documents.lov.template.number, + deleteLov1: homeUrl + 'api/workspaces/' + workspace + '/lov/' + documents.lov.itemName, + deleteLov2: homeUrl + 'api/workspaces/' + workspace + '/lov/' + documents.lov.color.itemName, + deleteDocument1: homeUrl + 'api/workspaces/' + workspace + '/documents/' + documents.document1.number + '-A', + deleteDocument2: homeUrl + 'api/workspaces/' + workspace + '/documents/' + documents.document2.number + '-A', + deleteDocument3: homeUrl + 'api/workspaces/' + workspace + '/documents/' + documents.document3.number + '-A', + deleteProduct: homeUrl + 'api/workspaces/' + workspace + '/products/' + products.product1.number, + deleteFolder: homeUrl + 'api/workspaces/' + workspace + '/folders/' + workspace + ':' + documents.folder1, + getBaselines: homeUrl + 'api/workspaces/' + workspace + '/products/' + products.product1.number + '/baselines', + deleteProductInstance: homeUrl + 'api/workspaces/' + workspace + '/products/' + products.product1.number + '/product-instances/' + productInstances.productInstance1.serialNumber, + getWorkflows: homeUrl + 'api/workspaces/' + workspace + '/workflow-models', + getRoles: homeUrl + 'api/workspaces/' + workspace + '/roles', + getTags: homeUrl + 'api/workspaces/' + workspace + '/tags', + getDocuments: homeUrl + 'api/workspaces/' + workspace + '/documents', + getParts: homeUrl + 'api/workspaces/' + workspace + '/parts', + milestones: homeUrl + 'api/workspaces/' + workspace + '/changes/milestones', + queries: homeUrl + 'api/workspaces/' + workspace + '/parts/queries' +}; diff --git a/docdoku-web-front/tests/js/pre/clean.js b/docdoku-web-front/tests/js/pre/clean.js new file mode 100644 index 0000000000..9d3d8538ec --- /dev/null +++ b/docdoku-web-front/tests/js/pre/clean.js @@ -0,0 +1,272 @@ +/*global casper,apiUrls,helpers,products,workspace,homeUrl*/ +casper.test.begin('Cleaning potential data', 0, function cleanTestsSuite() { + 'use strict'; + + casper.open(''); + + // Roles + casper.then(function cleanupRoles() { + var that = this; + return this.open(apiUrls.getRoles, {method: 'GET'}).then(function (response) { + if (response.status === 200) { + var roles = JSON.parse(this.getPageContent()); + roles.forEach(function (role) { + that.log('Deleting role ' + role.id, 'info'); + that.open(apiUrls.getRoles + '/' + role.id, {method: 'DELETE'}).then(function () { + that.log('Role ' + role.id + ' deleted', 'info'); + }); + }); + } else { + this.log('Cannot delete test roles, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + // Workflows + casper.then(function cleanupWorkflows() { + var that = this; + return this.open(apiUrls.getWorkflows, {method: 'GET'}).then(function (response) { + if (response.status === 200) { + var workflows = JSON.parse(this.getPageContent()); + workflows.forEach(function (workflow) { + that.log('Deleting workflow ' + workflow.id, 'info'); + that.open(apiUrls.getWorkflows + '/' + workflow.id, {method: 'DELETE'}).then(function (response) { + if (response.status === 200) { + that.log('Workflow ' + workflow.id + ' deleted', 'info'); + } else { + that.log('Cannot delete workflow ' + workflow.id + ', reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + } else { + this.log('Cannot delete test workflow, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + // Tags + casper.then(function cleanupTags() { + var that = this; + return this.open(apiUrls.getTags, {method: 'GET'}).then(function (response) { + if (response.status === 200) { + var tags = JSON.parse(this.getPageContent()); + tags.forEach(function (tag) { + that.log('Deleting tag ' + tag.id, 'info'); + return that.open(apiUrls.getTags + '/' + tag.id, {method: 'DELETE'}).then(function (response) { + if (response.status === 200) { + that.log('Tag ' + tag.id + ' deleted', 'info'); + } else { + that.log('Cannot delete tag ' + tag.id + ', reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + } else { + this.log('Cannot delete test tags, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + // Product instances + casper.then(function cleanupProductInstances() { + return this.open(apiUrls.deleteProductInstance, {method: 'DELETE'}).then(function (response) { + if (response.status === 200) { + this.log('Product instance has been deleted', 'info'); + } else { + this.log('Cannot delete product instance, status ' + response.status + ',reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + + // Baselines + casper.then(function cleanupBaselines() { + var that = this; + return this.open(apiUrls.getBaselines, {method: 'GET'}).then(function (response) { + if (response.status === 200) { + var baselines = JSON.parse(this.getPageContent()); + baselines.forEach(function (baseline) { + that.log('Deleting baseline ' + baseline.id, 'info'); + that.open(apiUrls.getBaselines + '/' + baseline.id, {method: 'DELETE'}).then(function () { + that.log('Baseline ' + baseline.id + ' deleted', 'info'); + }); + }); + + } else { + this.log('Cannot get baselines for product, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + // Products + casper.then(function cleanupProducts() { + return this.open(apiUrls.deleteProduct, {method: 'DELETE'}).then(function (response) { + if (response.status === 200) { + this.log('Test products has been deleted', 'info'); + } else { + this.log('Cannot delete test products, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + // Parts + casper.then(function cleanupPart1() { + return this.open(apiUrls.deletePart1, {method: 'DELETE'}).then(function (response) { + if (response.status === 200) { + this.log('Test part1 has been deleted', 'info'); + } else { + this.log('Cannot delete test part1, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + casper.then(function cleanupPart2() { + return this.open(apiUrls.deletePart2, {method: 'DELETE'}).then(function (response) { + if (response.status === 200) { + this.log('Test part2 has been deleted', 'info'); + } else { + this.log('Cannot delete test part2, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + // Assembly parts + casper.then(function test() { + var partNumbers = Object.keys(products.assembly.parts); + return partNumbers.forEach(function (partNumber) { + return casper.open(homeUrl + 'api/workspaces/' + workspace + '/parts/' + partNumber + '-A', {method: 'DELETE'}).then(function (response) { + if (response.status === 200) { + this.log('Part ' + partNumber + ' deleted', 'info'); + } else { + this.log('Cannot delete part ' + partNumber + ', reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + }); + + // Part templates + casper.then(function cleanupPartTemplates() { + return this.open(apiUrls.deletePartTemplate, {method: 'DELETE'}).then(function (response) { + if (response.status === 200) { + this.log('Test part templates has been deleted', 'info'); + } else { + this.log('Cannot delete test part templates, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + casper.then(function cleanupPartTemplates2() { + return this.open(apiUrls.deletePartTemplate2, {method: 'DELETE'}).then(function (response) { + if (response.status === 200) { + this.log('Test part templates2 has been deleted', 'info'); + } else { + this.log('Cannot delete test part templates2, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + // Part templates + casper.then(function cleanupDocumentTemplates() { + return this.open(apiUrls.deleteDocumentTemplate, {method: 'DELETE'}).then(function (response) { + if (response.status === 200) { + this.log('Test document template has been deleted', 'info'); + } else { + this.log('Cannot delete test document template, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + // Documents + casper.then(function cleanupDocuments() { + var that = this; + that.log('Deleting documents', 'info'); + return this.open(apiUrls.getDocuments, {method: 'GET'}).then(function (response) { + that.log(response); + if (response.status === 200) { + var documents = JSON.parse(this.getPageContent()); + documents.forEach(function (document) { + that.log('Deleting document' + document.id, 'info'); + that.open(apiUrls.getDocuments + '/' + document.id, {method: 'DELETE'}).then(function (response) { + if (response.status === 200) { + that.log('Document ' + document.id + ' deleted', 'info'); + } else { + that.log('Cannot delete test document ' + document.id + ', reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + } else { + this.log('Cannot delete test documents, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + // Folders + casper.then(function cleanupFolders() { + return this.open(apiUrls.deleteFolder, {method: 'DELETE'}).then(function (response) { + if (response.status === 200) { + this.log('Test folders has been deleted', 'info'); + } else { + this.log('Cannot delete test folders, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + // LOV + casper.then(function cleanupLovs() { + return this.open(apiUrls.deleteLov1, {method: 'DELETE'}).then(function (response) { + if (response.status === 200) { + this.log('Lov has been deleted', 'info'); + } else { + this.log('Cannot delete lov, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + casper.then(function cleanupLovs() { + return this.open(apiUrls.deleteLov2, {method: 'DELETE'}).then(function (response) { + if (response.status === 200) { + this.log('Lov has been deleted', 'info'); + } else { + this.log('Cannot delete lov, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + // delete all milestone + casper.then(function cleanupMilestones() { + var that = this; + return this.open(apiUrls.milestones, {method: 'GET'}).then(function (response) { + if (response.status === 200) { + var milestones = JSON.parse(this.getPageContent()); + milestones.forEach(function (milestone) { + that.log('Deleting milestone ' + milestone.id, 'info'); + that.open(apiUrls.milestones + '/' + milestone.id, {method: 'DELETE'}).then(function () { + that.log('milestone ' + milestone.id + ' deleted', 'info'); + }); + }); + } else { + this.log('Cannot delete test milestone, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + // Queries + casper.then(function cleanupQueries() { + var that = this; + return this.open(apiUrls.queries, {method: 'GET'}).then(function (response) { + if (response.status === 200) { + var queries = JSON.parse(this.getPageContent()); + queries.forEach(function (query) { + that.log('Deleting query ' + query.id, 'info'); + that.open(apiUrls.queries + '/' + query.id, {method: 'DELETE'}).then(function () { + that.log('Query ' + query.id + ' deleted', 'info'); + }); + }); + } else { + this.log('Cannot delete test queries, reason : ' + helpers.findReasonInResponseHeaders(response.headers), 'warning'); + } + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/pre/start.js b/docdoku-web-front/tests/js/pre/start.js new file mode 100644 index 0000000000..f8bb174438 --- /dev/null +++ b/docdoku-web-front/tests/js/pre/start.js @@ -0,0 +1,89 @@ +/*global casper,homeUrl*/ + +'use strict'; + +var extend = function (destination, source) { + for (var property in source) { + if (destination[property] && (typeof(destination[property]) === 'object') && + (destination[property].toString() === '[object Object]') && source[property]) { + extend(destination[property], source[property]); + } + else { + destination[property] = source[property]; + } + } + return destination; +}; + +var conf = casper.cli.options; + +// This is the first file in the tests suite : use casper.start() +casper.options.viewportSize = { + width: 1680, + height: 1050 +}; + +// add AJAX waiting logic to onResourceRequested + +casper.options.onResourceRequested = function (casper, requestData) { + if (conf.debugRequests) { + if(requestData.url.match('/api/')){ + console.log(requestData.method + ' ' + requestData.url); + console.log(JSON.stringify(requestData.headers)); + } + } + + if (conf.waitOnRequest) { + // do not wait for fonts + if(requestData.url.indexOf('fonts') === -1 && requestData.url.indexOf('livereload.js') === -1) { + this.log('Waiting for AJAX request: ' + requestData.url, 'info'); + casper.waitForResource(requestData.url, function () { + this.log('AJAX request returned: ' + requestData.url, 'info'); + }, function () { + this.log('AJAX request didn\'t return after wait period: ' + requestData.url, 'warning'); + }, conf.requestTimeOut || 100); + } + } +}; + +if (conf.debugResponses) { + casper.options.onResourceReceived = function (C, response) { + if(response.url.match('/api/')){ + console.log('#'+response.status + ' ' +response.statusText + ' ' + response.url); + console.log(JSON.stringify(response.headers)); + } + }; +} +// Wait actions +casper.options.waitTimeout = 10 * 1000; // 10 sec +// Global test duration +casper.options.timeout = conf.globalTimeout * 60 * 1000; + +casper.start(); + +casper.setFilter('page.confirm', function (msg) { + this.log('Confirm box: ' + msg, 'warning'); + return true; +}); + +casper.on('remote.alert', function (msg) { + this.log('Alert box: ' + msg, 'warning'); + this.capture('screenshot/alert/' + Date.now() + '.png'); + if (conf.debug) { + this.debugHTML(); + } + return true; +}); + +casper.on('remote.message', function remoteMessage(message) { + this.log('[WebConsole] ' + message, 'info'); +}); + +casper.test.begin('DocdokuPLM Tests suite', 1, function docdokuPLMTestsSuite() { + casper.thenOpen(homeUrl, function homePageLoaded() { + this.test.assert(true, 'Tests begins'); + }); + casper.run(function letsGo() { + this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/product-management/assembly/assemblyCheck.js b/docdoku-web-front/tests/js/product-management/assembly/assemblyCheck.js new file mode 100644 index 0000000000..6b9dc9b58c --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/assembly/assemblyCheck.js @@ -0,0 +1,100 @@ +/*global casper,urls,products*/ + +casper.test.begin('Assembly check tests suite', 29, function assemblyCheckTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/assemblyCheck/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Wait the part list + */ + casper.then(function waitForPartList() { + var link = '#part_table tbody tr:first-child td.part_number'; + return this.waitForSelector(link, function clickPartNavLink() { + this.click(link); + }, function fail() { + this.capture('screenshot/assemblyCheck/waitForPartList-error.png'); + this.test.assert(false, 'Part list can not be found'); + }); + }); + + /** + * Wait the modal + */ + casper.then(function waitForPartModal() { + var modalTab = '#part-modal .tabs li a[href="#tab-assembly"]'; + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/assemblyCheck/waitForPartModal-error.png'); + this.test.assert(false, 'Part modal can not be found'); + }); + }); + + /** + * Wait the modal tab + */ + casper.then(function waitForPartModalTab() { + return this.waitForSelector('#part-modal .component', function tabSelected() { + this.test.assert(true, 'Assembly tab opened'); + }, function fail() { + this.capture('screenshot/assemblyCheck/waitForPartModalTab-error.png'); + this.test.assert(false, 'Part modal tab can not be found'); + }); + }); + + /** + * Check all partNumbers coordinates + */ + + /** + * Fill the form + */ + var partNumbers = Object.keys(products.assembly.parts); + var parts = products.assembly.parts; + + partNumbers.forEach(function (partNumber) { + casper.then(function checkUsageLink() { + + var element = '.component input[name="number"][value="' + partNumber + '"]'; + + this.test.assertExists(element, 'Part ' + partNumber + ' is in the assembly'); + + this.test.assertExists(element + ' ~ .cad-instances .cad-instance .coord-group .coord[name="tx"][value="' + parts[partNumber].tx + '"]', 'Tx OK'); + this.test.assertExists(element + ' ~ .cad-instances .cad-instance .coord-group .coord[name="ty"][value="' + parts[partNumber].ty + '"]', 'Ty OK'); + this.test.assertExists(element + ' ~ .cad-instances .cad-instance .coord-group .coord[name="tz"][value="' + parts[partNumber].tz + '"]', 'Tz OK'); + + this.test.assertExists(element + ' ~ .cad-instances .cad-instance .coord-group .coord[name="rx"][value="' + parts[partNumber].rx + '"]', 'Rx OK'); + this.test.assertExists(element + ' ~ .cad-instances .cad-instance .coord-group .coord[name="ry"][value="' + parts[partNumber].ry + '"]', 'Ry OK'); + this.test.assertExists(element + ' ~ .cad-instances .cad-instance .coord-group .coord[name="rz"][value="' + parts[partNumber].rz + '"]', 'Rz OK'); + + }); + }); + + + casper.run(function allDone() { + this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/assembly/assemblyCreation.js b/docdoku-web-front/tests/js/product-management/assembly/assemblyCreation.js new file mode 100644 index 0000000000..d0c292726c --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/assembly/assemblyCreation.js @@ -0,0 +1,164 @@ +/*global casper,urls,products,homeUrl,workspace*/ + +casper.test.begin('Assembly creation tests suite', 14, function assemblyCreationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/assemblyCreation/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Open the first item modal view + */ + casper.then(function waitForPartList() { + var link = '#part_table tbody tr:first-child td.part_number'; + return this.waitForSelector(link, function clickPartNavLink() { + this.test.assertElementCount('#part_table tbody tr', 1, 'There should be only 1 entry in the table'); + this.click(link); + }, function fail() { + this.capture('screenshot/assemblyCreation/waitForPartList-error.png'); + this.test.assert(false, 'Part list can not be found'); + }); + }); + + /** + * Wait the modal + */ + casper.then(function waitForPartModal() { + var modalTab = '#part-modal .tabs li a[href="#tab-assembly"]'; + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/assemblyCreation/waitForPartModal-error.png'); + this.test.assert(false, 'Part modal can not be found'); + }); + }); + + /** + * Wait the modal tab + */ + casper.then(function waitForPartModalTab() { + return this.waitForSelector('#part-modal #tab-assembly .components', function tabSelected() { + this.test.assert(true, 'Assembly tab opened'); + }, function fail() { + this.capture('screenshot/assemblyCreation/waitForPartModalTab-error.png'); + this.test.assert(false, 'Part modal tab can not be found'); + }); + }); + + /** + * Fill the form + */ + var partNumbers = Object.keys(products.assembly.parts); + var parts = products.assembly.parts; + + partNumbers.forEach(function (partNumber) { + casper.then(function createNewParts() { + + this.click('#part-modal #create-part-revision-as-part-usage-link'); + + var element = '#part-modal #tab-assembly .components .component:last-child'; + + // Expand view + this.click(element + ' .toggle-cad-instances'); + + // Fill coordinates + this.sendKeys(element + ' input[name="number"]', partNumber, {reset: true}); + this.sendKeys(element + ' input[name="tx"]', parts[partNumber].tx.toString(), {reset: true}); + this.sendKeys(element + ' input[name="ty"]', parts[partNumber].ty.toString(), {reset: true}); + this.sendKeys(element + ' input[name="tz"]', parts[partNumber].tz.toString(), {reset: true}); + this.sendKeys(element + ' input[name="rx"]', parts[partNumber].rx.toString(), {reset: true}); + this.sendKeys(element + ' input[name="ry"]', parts[partNumber].ry.toString(), {reset: true}); + this.sendKeys(element + ' input[name="rz"]', parts[partNumber].rz.toString(), {reset: true}); + this.test.assert(true, partNumber + ' created'); + + }); + }); + + /** + * Save it + */ + casper.then(function saveParts() { + this.click('#part-modal #save-part'); + }); + + /** + * Wait for modal to be closed + */ + casper.then(function closePartsModal() { + return this.waitWhileSelector('#part-modal', function modalClosed() { + this.test.assert(true, 'Modal has been closed'); + }, function fail() { + this.capture('screenshot/assemblyCreation/waitModalToBeClosed-error.png'); + this.test.assert(false, 'Modal is still not closed'); + }); + }); + + /** + * Check that the list contains now 5 parts + */ + + casper.then(function checkIfPartsInAssemblyAreCreated() { + return this.waitFor(function check() { + return this.evaluate(function () { + return document.querySelectorAll('#part_table tbody tr').length === 5; + }); + }, function then() { + this.test.assert(true, '5 entries in the table'); + }, function fail() { + this.capture('screenshot/assemblyCreation/checkIfPartsInAssemblyAreCreated-error.png'); + this.test.assert(false, 'There are not 5 entries in the table'); + }); + }); + + /** + * Check if assembly / leaf icons are well set + */ + + casper.then(function checkAssemblyIconsSet() { + return this.waitForSelector('#part_table .fa.fa-cube', function check() { + this.test.assertElementCount('#part_table .fa.fa-cube', 4, 'found 4 leaf parts'); + this.test.assertElementCount('#part_table .fa.fa-cubes', 1, 'found 1 assembly part'); + }); + }); + + /** + * Checkin all parts + */ + + partNumbers.forEach(function(partNumber) { + casper.then(function checkinPart() { + // Run xhrs, more convenient here. + return this.open(apiUrls.getParts + '/' + partNumber + '-A/checkin', {method: 'PUT'}).then(function (response) { + this.test.assertEquals(response.status, 200, 'Part ' + partNumber + ' is checked in'); + }, function () { + this.test.assert(false, 'Part ' + partNumber + ' has not been checked in'); + this.capture('screenshot/assemblyCreation/checkinPart-'+partNumber+'-error.png'); + }); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/assembly/bomInspection.js b/docdoku-web-front/tests/js/product-management/assembly/bomInspection.js new file mode 100644 index 0000000000..5bd986ad53 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/assembly/bomInspection.js @@ -0,0 +1,160 @@ +/*global casper,urls,products*/ + +casper.test.begin('Bom inspection tests suite', 13, function bomInspectionTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open product structure URL + * */ + + casper.then(function () { + return this.open(urls.productStructure); + }); + + /** + * Assert the tree is collapsed (1 node) + */ + + casper.then(function waitTree() { + return this.waitForSelector('#product_nav_list_container > .treeview > ul > li', function treeDisplayed() { + this.test.assert(true, 'Tree is displayed'); + this.test.assertSelectorHasText('#product_nav_list_container > .treeview > ul > li > a > label', products.part1.name + ' < ' + products.part1.number + '-A-2 > (1)', 'The first node is correctly named'); + }, function fail() { + this.capture('screenshot/assembly/waitTree-error.png'); + this.test.assert(false, 'Product tree can not be found'); + }); + }); + + /** + * Click on the first node + */ + + casper.then(function clickRootNode() { + this.click('#product_nav_list_container > .treeview > ul > li > a > label'); + }); + + /** + * Enter bom mode + * */ + + casper.then(function openBom() { + return this.waitForSelector('#bom_view_btn', function clickOnBomModeButton() { + this.click('#bom_view_btn'); + this.test.assert(true, 'Bom button found'); + }, function fail() { + this.capture('screenshot/assembly/openBom-error.png'); + this.test.assert(false, 'Bom link can not be found'); + }); + }); + + /** + * Wait for bom table + * */ + + casper.then(function waitForBomTable() { + return this.waitForSelector('#bom_table', function bomDisplayed() { + this.test.assert(true, 'Bom list displayed'); + }, function fail() { + this.capture('screenshot/assembly/waitForBomTable-error.png'); + this.test.assert(false, 'Bom list can not be found'); + }); + }); + + + /** + * Assert rows count is 4 + * + * */ + + casper.then(function countBomTableRows() { + return this.waitForSelector('#bom_table > tbody > tr:nth-child(4)', function rowsAvailable() { + this.test.assert(true, '4 entries in the bom list'); + }, function fail() { + this.capture('screenshot/assembly/countBomTableRows-error.png'); + this.test.assert(false, 'Bom list has not 4 entries in the list'); + }); + }); + + /** + * Expand the root node + */ + casper.then(function openStructureInTree() { + this.click('#product_nav_list_container > .treeview > ul > li > .hitarea'); + return this.waitForSelector('#product_nav_list_container > .treeview > ul > li > ul > li', function childNodesDisplayed() { + this.test.assert(true, 'Child nodes are shown'); + }, function fail() { + this.capture('screenshot/assembly/openStructureInTree-error.png'); + this.test.assert(false, 'Child nodes not shown'); + }); + }); + + /** + * Count child nodes + * */ + casper.then(function countChildNodesInTree() { + this.test.assertElementCount('#product_nav_list_container > .treeview > ul > li > ul > li ', 4, '4 child nodes displayed'); + }); + + /** + * Click on the first child of the root node + */ + + casper.then(function clickRootNode() { + this.click('#product_nav_list_container > .treeview > ul > li > ul > li:first-child > a > label'); + }); + + /** + * Assert rows count is 1 in the bom + * + * */ + casper.then(function countBomTableRows() { + return this.waitForSelector('#bom_table > tbody > tr:only-child', function rowsAvailabled() { + this.test.assert(true, '1 entry in the bom list'); + }, function fail() { + this.capture('screenshot/assembly/countBomTableRows-error.png'); + this.test.assert(false, 'Bom list may have only one child'); + }); + }); + + /** + * Check the root node + */ + + casper.then(function checkRootNode() { + this.test.assertExists('#product_nav_list_container > .treeview > ul > li > .load-3D:not(:checked)', 'Checkbox is unchecked'); + this.click('#product_nav_list_container > .treeview > ul > li > .load-3D'); + this.test.assertExists('#product_nav_list_container > .treeview > ul > li > .load-3D:checked', 'Checkbox is now checked'); + }); + + /** + * Count child nodes checked + * */ + casper.then(function countChildNodesCheckedInTree() { + this.test.assertElementCount('#product_nav_list_container > .treeview > ul > li > ul > li > .load-3D:checked', 4, '4 child nodes checked'); + }); + + /** + * Uncheck the root node + */ + + casper.then(function unCheckRootNode() { + this.click('#product_nav_list_container > .treeview > ul > li > .load-3D'); + this.test.assertExists('#product_nav_list_container > .treeview > ul > li > .load-3D:not(:checked)', 'Checkbox is now unchecked'); + }); + + /** + * Count child nodes checked + * */ + casper.then(function countChildNodesUnCheckedInTree() { + this.test.assertElementCount('#product_nav_list_container > .treeview > ul > li > ul > li > .load-3D:not(:checked)', 4, '4 child nodes are now unchecked'); + }); + + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/assembly/instancesCheck.js b/docdoku-web-front/tests/js/product-management/assembly/instancesCheck.js new file mode 100644 index 0000000000..8fb5815e41 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/assembly/instancesCheck.js @@ -0,0 +1,23 @@ +/*global casper,urls*/ + +casper.test.begin('Instances tests suite', 1, function instancesCheckTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Test instances REST resource, make sure provider is working + */ + + casper.thenOpen(urls.productStructureInstances,function onResponse(response){ + this.test.assertEqual(response.status,200,'Should have a 200 http code for '+urls.productStructureInstances); + }, function fail(){ + this.test.assert(false,'Should have a response for '+urls.productStructureInstances); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/baseline/baselineCreation.js b/docdoku-web-front/tests/js/product-management/baseline/baselineCreation.js new file mode 100644 index 0000000000..cd54ac5f59 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/baseline/baselineCreation.js @@ -0,0 +1,95 @@ +/*global casper,urls,baselines*/ + +casper.test.begin('Baseline creation tests suite', 2, function baselineCreationTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to product nav + */ + casper.then(function waitForProductNavLink() { + return this.waitForSelector('#product-nav > .nav-list-entry > a', function clickProductNavLink() { + this.click('#product-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/baselineCreation/waitForProductNavLink-error.png'); + this.test.assert(false, 'Product nav link can not be found'); + }); + }); + + /** + * Select the first product with checkbox + */ + casper.then(function waitForProductTable() { + return this.waitForSelector('#product_table tbody tr:first-child td:first-child input', function clickOnProductCheckbox() { + this.click('#product_table tbody tr:first-child td:first-child input'); + }, function fail() { + this.capture('screenshot/baselineCreation/waitForProductTable-error.png'); + this.test.assert(false, 'Product can not be found'); + }); + }); + + /** + * Click on baseline creation button + */ + casper.then(function waitForBaselineCreationButton() { + return this.waitForSelector('.actions .new-baseline', function openBaselineCreationModal() { + this.click('.actions .new-baseline'); + }, function fail() { + this.capture('screenshot/baselineCreation/waitForBaselineCreationButton-error.png'); + this.test.assert(false, 'New baseline button can not be found'); + }); + }); + + /** + * Try to create a baseline without a name + */ + casper.then(function waitForBaselineCreationModal() { + return this.waitForSelector('#baseline_creation_modal .modal-footer .btn-primary', function baselineCreationModalOpened() { + this.click('#baseline_creation_modal .modal-footer .btn-primary'); + this.test.assertExists('#baseline_creation_modal #inputBaselineName:invalid', 'Should not create baseline without a name'); + }, function fail() { + this.capture('screenshot/baselineCreation/waitForBaselineCreationModal-error.png'); + this.test.assert(false, 'New baseline modal can not be found'); + }); + }); + + /** + * Try to create the baseline + */ + casper.then(function tryToCreateABaseline() { + return this.waitForSelector('#baseline_creation_modal #inputBaselineName', function fillBaselineCreationForm() { + this.sendKeys('#baseline_creation_modal #inputBaselineName', baselines.baseline1.name, {reset: true}); + this.sendKeys('#baseline_creation_modal #inputBaselineDescription', baselines.baseline1.description, {reset: true}); + this.click('#baseline_creation_modal .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/baselineCreation/tryToCreateABaseline-error.png'); + this.test.assert(false, 'Cannot create the baseline'); + }); + }); + + /** + * Wait for the modal to be closed + */ + casper.then(function waitForModalToBeClosed() { + return this.waitWhileSelector('#baseline_creation_modal', function onBaselineCreationModalClosed() { + this.test.assert(true, 'Baseline creation modal has been closed'); + }, function fail() { + this.capture('screenshot/baselineCreation/waitForModalToBeClosed-error.png'); + this.test.assert(false, 'Baseline creation modal can not close'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/baseline/baselineDeletion.js b/docdoku-web-front/tests/js/product-management/baseline/baselineDeletion.js new file mode 100644 index 0000000000..8deac554b9 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/baseline/baselineDeletion.js @@ -0,0 +1,81 @@ +/*global casper,urls*/ + +casper.test.begin('Baseline deletion tests suite', 2, function baselineDeletionTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to baselines nav + */ + casper.then(function waitForBaselineNavLink() { + return this.waitForSelector('#baselines-nav > .nav-list-entry > a', function clickBaselineNavLink() { + this.click('#baselines-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/baselineDeletion/waitForBaselineNavLink-error.png'); + this.test.assert(false, 'Baseline nav link can not be found'); + }); + }); + + /** + * Click the 'select all' checkbox + */ + casper.then(function waitForSelectAllBaselinesCheckbox() { + return this.waitForSelector('#baseline_table thead tr:first-child th:first-child input', function clickOnSelectAllBaselinesCheckbox() { + this.click('#baseline_table thead tr:first-child th:first-child input'); + }, function fail() { + this.capture('screenshot/baselineDeletion/waitForSelectAllBaselinesCheckbox-error.png'); + this.test.assert(false, 'Select all baselines checkbox can not be found'); + }); + }); + + /** + * Wait for the delete button to appear + */ + casper.then(function waitForDeleteButton() { + return this.waitForSelector('.actions .delete', function clickOnDeleteButton() { + this.click('.actions .delete'); + this.test.assert(true, 'Delete button available'); + }, function fail() { + this.capture('screenshot/baselineDeletion/waitForDeleteButton-error.png'); + this.test.assert(false, 'Select all baselines checkbox can not be found'); + }); + }); + + /** + * Wait for the confirmation modal + */ + casper.then(function waitForConfirmationModal() { + return this.waitForSelector('.bootbox', function confirmBaselinesDeletion() { + this.click('.bootbox .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/baselineDeletion/waitForConfirmationModal-error.png'); + this.test.assert(false, 'Baseline deletion confirmation modal can not be found'); + }); + }); + + /** + * Assert that there's no more entries in the table + **/ + casper.then(function waitForTableToBeEmpty() { + return this.waitWhileSelector('#baseline_table tbody tr:first-child td:first-child input', function onBaselineTableEmpty() { + this.test.assert(true, 'No more baselines in the list'); + }, function fail() { + this.capture('screenshot/baselineDeletion/waitForTableToBeEmpty-error.png'); + this.test.assert(false, 'Baseline table still not empty'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/checkUsedByList.js b/docdoku-web-front/tests/js/product-management/part/checkUsedByList.js new file mode 100644 index 0000000000..d4dac6ab9d --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/checkUsedByList.js @@ -0,0 +1,61 @@ +/*global casper,urls,products,productInstances*/ + +casper.test.begin('Used by tab tests suite', 2, function usedByTabTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }); + }); + + /** + * Wait for part list display + */ + + casper.then(function waitForPartInList() { + return this.waitForSelector('#part_table tbody tr:first-child td.part_number', function openPartModal() { + this.click('#part_table tbody tr:first-child td.part_number span'); + }); + }); + + /** + * Wait for part modal + */ + casper.then(function waitForModalDisplay() { + return this.waitForSelector('#part-modal li a[href="#tab-iteration-used-by"]', function openUsedByTab() { + this.click('#part-modal li a[href="#tab-iteration-used-by"]'); + }); + }); + + /** + * Wait for used by tab + */ + casper.then(function waitForUsedByDisplay() { + return this.waitForSelector('#used-by-group-list-view > div > div > div > div.group-title', function checkValues() { + this.test.assertSelectorHasText('#used-by-group-list-view > div > div > div > div.group-title', '< '+products.product1.number+' >', 'Part must be present in product "< '+products.product1.number+' >"'); + this.test.assertSelectorHasText('#used-by-product-instances > li.used-by-item > div.reference', productInstances.productInstance1.serialNumber, 'Part must be present in product instance "'+productInstances.productInstance1.serialNumber+'"'); + }); + }); + + + casper.run(function () { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partAddLink.js b/docdoku-web-front/tests/js/product-management/part/partAddLink.js new file mode 100644 index 0000000000..3293c74abc --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partAddLink.js @@ -0,0 +1,98 @@ +/*global casper,urls,products*/ +casper.test.begin('Part add link tests suite', 2, function partAddLinkTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partAddLink/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Wait for part list display + */ + + casper.then(function waitForPartInList() { + return this.waitForSelector('#part_table tbody tr:first-child td.part_number', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:first-child td.part_number span'); + }, function fail() { + this.capture('screenshot/partAddLink/waitForPartList-error.png'); + this.test.assert(false, 'Part list can not be found'); + }); + }); + + /** + * Wait for part modal + */ + + casper.then(function waitForPartModal() { + var modalTab = '#part-modal .tabs li a[href="#tab-part-links"]'; + + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/partAddLink/waitForPartModal-error.png'); + this.test.assert(false, 'Part modal can not be found'); + }); + }); + + /** + * Wait for Links modal tab + */ + casper.then(function waitForPartModalLinksTab() { + return this.waitForSelector('#part-modal .linked-items-reference-typehead', function tabOpened() { + this.test.assert(true, 'Links tab opened'); + }, function fail() { + this.capture('screenshot/partAddLink/waitForPartModalLinksTab-error.png'); + this.test.assert(false, 'Part modal Links tab can not be found'); + }); + }); + + /** + * Wait for parts select list + */ + casper.then(function waitForDocumentsSelectList() { + this.sendKeys('#part-modal .linked-items-reference-typehead', products.part1.documentLink, {reset: true}); + + return this.waitForSelector('#iteration-links > .linked-items-view > ul.dropdown-menu > li:first-child', function documentsSelectListDisplayed() { + this.click('#iteration-links > .linked-items-view > ul.dropdown-menu > li:first-child'); + }, function fail() { + this.capture('screenshot/partAddLink/waitForDocumentsSelectList-error.png'); + this.test.assert(false, 'Documents select list can not be found'); + }); + }); + + /** + * Wait for linked document display + */ + casper.then(function waitForLinkedDocumentDisplay() { + return this.waitForSelector('#iteration-links > .linked-items-view > ul.linked-items > li:first-child', function linkDocumentDisplayed() { + this.test.assert(true, 'Link added'); + this.click('#part-modal .btn.btn-primary'); + }, function fail() { + this.capture('screenshot/partAddLink/waitForLinkedDocumentDisplay-error.png'); + this.test.assert(false, 'Linked document can not be found and saved'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partCheckin.js b/docdoku-web-front/tests/js/product-management/part/partCheckin.js new file mode 100644 index 0000000000..cb8e6c24f1 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partCheckin.js @@ -0,0 +1,94 @@ +/*global casper,urls,products*/ + +casper.test.begin('Part checkin tests suite', 3, function partCheckinTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partCheckin/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Select the first part with checkbox + */ + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:first-child td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:first-child td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/partCheckin/waitForPartTable-error.png'); + this.test.assert(false, 'Part can not be found'); + }); + }); + + /** + * Click on checkin button + */ + casper.then(function waitForCheckinButton() { + this.test.assertSelectorHasText('.nav-checkedOut-number-item', 1, 'checkout number at 1 in nav'); + return this.waitForSelector('.actions .checkin:not([disabled])', function clickOnCheckinButton() { + this.click('.actions .checkin'); + }, function fail() { + this.capture('screenshot/partCheckin/waitForCheckinButton-error.png'); + this.test.assert(false, 'Checkin button can not be found'); + }); + }); + + /** + * Set an iteration note + */ + casper.then(function waitForIterationNotePrompt() { + return this.waitForSelector('#prompt_modal #prompt_input.ready', function fillIterationNote() { + this.sendKeys('#prompt_modal #prompt_input', products.part1.iterationNote, {reset: true}); + this.click('#prompt_modal .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/partCheckin/waitForIterationNotePrompt-error.png'); + this.test.assert(false, 'Iteration note modal not found'); + }); + }); + + /** + * Wait for the checkin button to be disabled + */ + casper.then(function waitForCheckinButtonDisabled() { + this.waitForSelector('.actions .checkin:disabled', function partIsCheckin() { + this.test.assert(true, 'Part has been checkin'); + }, function fail() { + this.capture('screenshot/partCheckin/waitForCheckinButtonDisabled-error.png'); + this.test.assert(false, 'Part has not been checked in'); + }); + }); + + /** + * Wait for the badge info button + */ + casper.then(function waitForBadgeDisplayed() { + this.waitForSelector('.badge.nav-checkedOut-number-item.badge-info', function badgeDisplayed() { + this.test.assertSelectorHasText('.nav-checkedOut-number-item', 0, 'Checkout number updated in nav'); + }, function fail() { + this.capture('screenshot/partCheckin/waitForBadgeDisplayed-error.png'); + this.test.assert(false, 'Checkout number not updated in nav'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partCheckout.js b/docdoku-web-front/tests/js/product-management/part/partCheckout.js new file mode 100644 index 0000000000..7de2f6770f --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partCheckout.js @@ -0,0 +1,85 @@ +/*global casper,urls,$*/ + +casper.test.begin('Part checkout tests suite', 3, function partCheckoutTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partCheckout/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Select the first part with checkbox + */ + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:first-child td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:first-child td:nth-child(2) input'); + this.test.assertSelectorHasText('.nav-checkedOut-number-item', 0, 'checkout number at 0 in nav'); + }, function fail() { + this.capture('screenshot/partCheckout/waitForPartTable-error.png'); + this.test.assert(false, 'Part can not be found'); + }); + }); + + /** + * Click on checkout button + */ + casper.then(function waitForCheckoutButton() { + return this.waitForSelector('.actions .checkout', function clickOnCheckoutButton() { + this.click('.actions .checkout'); + }, function fail() { + this.capture('screenshot/partCheckout/waitForCheckoutButton-error.png'); + this.test.assert(false, 'Checkout button can not be found'); + }); + }); + + /** + * Wait for the checkout button to be disabled + */ + casper.then(function waitForCheckoutButtonDisabled() { + return this.waitForSelector('.actions .checkout:disabled', function partIsCheckout() { + this.test.assert(true, 'Part has been checkout'); + }, function fail() { + this.capture('screenshot/partCheckout/waitForCheckoutButtonDisabled-error.png'); + this.test.assert(false, 'Part has not been checkout'); + }); + }); + + /** + * Wait for the checked out number to be updated + */ + casper.then(function waitForCheckedOutNumberUpdated() { + return this.waitFor(function check() { + return this.evaluate(function () { + return $('.nav-checkedOut-number-item').text() === '1'; + }); + }, function then() { + this.test.assert(true, 'Checkout nav number updated'); + }, function fail() { + this.capture('screenshot/partCheckout/waitForNavUpdateCount.png'); + this.test.assert(false, 'Checkout nav number not updated'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partClickLink.js b/docdoku-web-front/tests/js/product-management/part/partClickLink.js new file mode 100644 index 0000000000..4dd91cd45e --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partClickLink.js @@ -0,0 +1,97 @@ +/*global casper,urls,workspace,products,defaultUrl*/ +casper.test.begin('Part click link tests suite', 2, function partClickLinkTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partClickLink/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Wait for part list display + */ + + casper.then(function waitForPartInList() { + return this.waitForSelector('#part_table tbody tr:first-child td.part_number', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:first-child td.part_number span'); + }, function fail() { + this.capture('screenshot/partClickLink/waitForPartList-error.png'); + this.test.assert(false, 'Part list can not be found'); + }); + }); + + /** + * Wait for part modal + */ + + casper.then(function waitForPartModal() { + var modalTab = '#part-modal .tabs li a[href="#tab-part-links"]'; + + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/partClickLink/waitForPartModal-error.png'); + this.test.assert(false, 'Part modal can not be found'); + }); + }); + + /** + * Wait for Links modal tab + */ + casper.then(function waitForPartModalLinksTab() { + return this.waitForSelector('#part-modal .linked-items-reference-typehead', function tabOpened() { + this.test.assert(true, 'Links tab opened'); + }, function fail() { + this.capture('screenshot/partClickLink/waitForPartModalLinksTab-error.png'); + this.test.assert(false, 'Part modal Links tab can not be found'); + }); + }); + + /** + * Wait for linked document display + */ + casper.then(function waitForLinkedDocumentDisplay() { + return this.waitForSelector('#iteration-links > .linked-items-view > ul.linked-items > li:first-child', function linkDocumentDisplayed() { + this.click('#iteration-links > .linked-items-view > ul.linked-items > li:first-child > a.reference'); + }, function fail() { + this.capture('screenshot/partClickLink/waitForLinkedDocumentDisplay-error.png'); + this.test.assert(false, 'Linked document can not be found'); + }); + }); + + /** + * Wait for linked document modal + */ + casper.then(function waitForLinkedDocumentDisplay() { + var modalTitle = '.document-modal > .modal-header > h3 > a[href="' + defaultUrl + '/documents/#' + workspace + '/' + products.part1.documentLink +'/A"]'; + + return this.waitForSelector(modalTitle, function linkedModalOpened() { + this.test.assert(true, 'Linked document modal opened'); + }, function fail() { + this.capture('screenshot/partClickLink/waitForLinkedDocumentModal-error.png'); + this.test.assert(false, 'Linked document modal can not be found'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partCreation.js b/docdoku-web-front/tests/js/product-management/part/partCreation.js new file mode 100644 index 0000000000..310f6134a7 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partCreation.js @@ -0,0 +1,165 @@ +/*global casper,urls,products,$*/ + +casper.test.begin('Part creation tests suite', 8, function partCreationTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partCreation/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Open the part creation modal + */ + casper.then(function waitForNewPartButton() { + return this.waitForSelector('.actions .new-part', function clickNewPartButton() { + this.click('.actions .new-part'); + }, function fail() { + this.capture('screenshot/partCreation/waitForNewPartButton-error.png'); + this.test.assert(false, 'New part button can not be found'); + }); + }); + + /** + * Create a part without a part number + * */ + casper.then(function waitForNewPartModal() { + return this.waitForSelector('#part_creation_modal .btn-primary', function createEmptyPart() { + this.click('#part_creation_modal .btn-primary'); + this.test.assertExists('#part_creation_modal input#inputPartNumber:invalid', 'Should not create part without a part number'); + }, function fail() { + this.capture('screenshot/partCreation/waitForNewPartModal-error.png'); + this.test.assert(false, 'New part modal can not be found'); + }); + }); + + + /** + * wait for the input to be loaded + */ + casper.then(function waitForPartNumberInput() { + return this.waitForSelector('#part_creation_modal input#inputPartNumber', function onNewPartFormReady() { + this.test.assert(true, 'partNumber input loaded'); + + }, function fail() { + this.capture('screenshot/partCreation/onNewPartFormReady-error.png'); + this.test.assert(false, 'New part form can not be found'); + }); + }); + + /** + * Create a part with its partNumber and its partName + */ + casper.then(function fillNewPartModalForm() { + return this.waitForSelector('#inputPartTemplate option:nth-child(3)', function injectTemplate() { + this.test.assertElementCount('#inputPartTemplate option', 3, 'template options are present'); + this.evaluate(function () { + document.querySelector('#inputPartTemplate').selectedIndex = 2; + $('#inputPartTemplate').change(); + return true; + }); + this.sendKeys('#part_creation_modal input#inputPartNumber', products.part1.number, {reset: true}); + this.sendKeys('#part_creation_modal input#inputPartName', products.part1.name, {reset: true}); + }, function fail() { + this.capture('screenshot/partCreation/templatesNotInjected.png'); + this.test.assert(false, 'templates are not injected'); + }); + }); + + + casper.then(function openAttributesTab() { + var attributesTabSelector = '.nav.nav-tabs > li:nth-child(3) > a'; + return this.waitForSelector(attributesTabSelector, function () { + this.click(attributesTabSelector); + }, function fail() { + this.capture('screenshot/partCreation/clickOnAttributeTab-error.png'); + this.test.assert(false, 'Attribute tab cannot be found'); + }); + }); + + /** + * open attribute tab + */ + + casper.then(function assertAttributesTabActive() { + return this.waitForSelector('.nav.nav-tabs > li:nth-child(3).active', function openTab() { + this.test.assert(true, 'attribute tab open'); + }, function fail() { + this.test.assert(false, 'could not set attribute tab to active'); + }); + }); + /** + * send value to input attributes + */ + casper.then(function fillAttributes() { + this.waitForSelector(' #attributes-list .list-item', function openTab() { + this.sendKeys('#attributes-list input[required].value', products.part1.attributeValue); + this.click('#part_creation_modal .btn-primary'); + }, function fail() { + this.capture('screenshot/partCreation/listAttributeNotFound.png'); + this.test.assert(false, 'attributes list not found'); + }); + }); + + /** + * Wait for the part to be created, will appears in the list + */ + casper.then(function waitForPartToBeCreated() { + return this.waitFor(function check() { + return this.evaluate(function () { + return $('#part_table tbody tr:first-child td.part_number span').text() === '000-AAA-CasperJsTestPart'; + }); + }, function partHasBeenCreated() { + this.test.assertSelectorHasText('#part_table tbody tr:first-child td.part_number span', products.part1.number); + this.test.assertSelectorHasText('#part_table tbody tr:first-child td:nth-child(8)', products.part1.name); + }, function fail() { + this.capture('screenshot/partCreation/waitForPartToBeCreated-error.png'); + this.test.assert(false, 'New part created can not be found'); + }); + }); + + casper.then(function waitForCountUpdate() { + //check if the nav button with the number of checkout part has been updated + return this.waitFor(function check() { + return this.evaluate(function () { + return $('.nav-checkedOut-number-item').text() === '1'; + }); + }, function then() { + this.test.assert(true, 'part-nav checkout number has been updated.'); + }, function fail() { + this.capture('screenshot/partCreation/waitForNavUpdateCount.png'); + this.test.assert(false, 'Checkout nav number not updated'); + }); + }); + + casper.then(function waitForModalToBeClosed() { + return this.waitWhileSelector('#part_creation_modal', function onPartModalClosed() { + this.test.assert(true, 'Part modal has been closed'); + }, function fail() { + this.capture('screenshot/partCreation/waitForModalToBeClosed-error.png'); + this.test.assert(false, 'Part modal can not close'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partDeletion.js b/docdoku-web-front/tests/js/product-management/part/partDeletion.js new file mode 100644 index 0000000000..0eb1e4fa86 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partDeletion.js @@ -0,0 +1,99 @@ +/*global casper,urls,$*/ + +casper.test.begin('Part deletion tests suite', 3, function partDeletionTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partDeletion/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Test delete a part + */ + + casper.then(function waitForPartInList() { + return this.waitForSelector('#part_table tbody tr:first-child td.part_number', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:first-child td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/partDeletion/waitForPartInList-error.png'); + this.test.assert(false, 'Part to delete rows can not be found'); + }); + }); + + casper.then(function waitForDeleteButton() { + return this.waitForSelector('.actions .delete', function clickOnDeleteButton() { + this.click('.actions .delete'); + }, function fail() { + this.capture('screenshot/partDeletion/waitForDeleteButton.png'); + this.test.assert(false, 'Delete button is not displayed'); + }); + }); + + casper.then(function waitForDeletionConfirmationModal() { + this.waitForSelector('.bootbox', function confirmPartDeletion() { + this.click('.bootbox .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/partDeletion/waitForDeletionConfirmationModal-error.png'); + this.test.assert(false, 'Part deletion confirmation modal can not be found'); + }); + }); + + casper.then(function waitForDeletionConfirmationModalDisappear() { + this.waitWhileSelector('.bootbox', function confirmPartDeletion() { + this.test.assert(true, 'Deletion confirmation modal has disappeared'); + }, function fail() { + this.capture('screenshot/partDeletion/waitForDeletionConfirmationModalDisappear-error.png'); + this.test.assert(false, 'Part deletion confirmation modal still displayed'); + }); + }); + + casper.then(function waitForPartDisappear() { + return this.waitFor(function check() { + return this.evaluate(function () { + return $('#part_table tbody tr').length === 4; + }); + }, function then() { + this.test.assert(true, 'Part has been deleted'); + }, function fail() { + this.capture('screenshot/partDeletion/waitForPartDisappear-error.png'); + this.test.assert(false, 'Part has not been deleted'); + }); + }); + + casper.then(function waitForNavUpdate() { + return this.waitFor(function check() { + return this.evaluate(function () { + return $('.nav-checkedOut-number-item').text() === '0'; + }); + }, function then() { + this.test.assert(true, 'Checkout nav number updated'); + }, function fail() { + this.capture('screenshot/partDeletion/waitForNavUpdateCount.png'); + this.test.assert(false, 'Checkout nav number not updated'); + }); + }); + + casper.run(function () { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partMultipleDeletion.js b/docdoku-web-front/tests/js/product-management/part/partMultipleDeletion.js new file mode 100644 index 0000000000..2c26001a88 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partMultipleDeletion.js @@ -0,0 +1,88 @@ +/*global casper,urls,$*/ + +casper.test.begin('Part multiple deletion tests suite', 1, function partMultipleDeletionTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partMultipleDeletion/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Select all parts with checkbox + */ + casper.then(function waitForPartTable() { + var checkbox = '#product-management-content table.dataTable thead tr th input[type="checkbox"]'; + return this.waitForSelector(checkbox, function clickOnPartCheckbox() { + this.click(checkbox); + }, function fail() { + this.capture('screenshot/partMultipleDeletion/waitForPartTable-error.png'); + this.test.assert(false, 'Part table can not be found'); + }); + }); + + /** + * Wait for part suppression button + */ + + casper.then(function waitForDeleteButtonDisplayed() { + return this.waitForSelector('.actions .delete', function deleteButtonIsDisplayed() { + this.click('.actions .delete'); + + }, function fail() { + this.capture('screenshot/partMultipleDeletion/waitForDeleteButtonDisplayed-error.png'); + this.test.assert(false, 'Parts delete button can not be found'); + }); + }); + + + /** + * Confirm parts deletion + */ + + casper.then(function confirmPartsDeletion() { + return this.waitForSelector('.bootbox', function confirmBoxAppeared() { + this.click('.bootbox .modal-footer .btn-primary'); + + }, function fail() { + this.capture('screenshot/partMultipleDeletion/confirmPartDeletion-error.png'); + this.test.assert(false, 'Part deletion confirmation modal can not be found'); + }); + }); + + /** + * Wait for parts to be removed + */ + + casper.then(function waitForDocumentsDeletion() { + return this.waitWhileSelector('#product-management-content table.dataTable tbody tr td.reference', function partsDeleted() { + this.test.assert(true, 'Parts have been deleted'); + + }, function fail() { + this.capture('screenshot/partMultipleDeletion/waitForPartDeletion-error.png'); + this.test.assert(false, 'Parts have not been deleted'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partObsolete.js b/docdoku-web-front/tests/js/product-management/part/partObsolete.js new file mode 100644 index 0000000000..7537c7e1e0 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partObsolete.js @@ -0,0 +1,92 @@ +/*global casper,urls,products*/ + +casper.test.begin('Part obsolete tests suite', 3, function partObsoleteTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partObsolete/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Select the first part with checkbox + */ + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:first-child td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:first-child td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/partObsolete/waitForPartTable-error.png'); + this.test.assert(false, 'Part can not be found'); + }); + }); + + /** + * Check and click on Mark as obsolete button + */ + casper.then(function waitForMarkAsObsoleteButton() { + return this.waitForSelector('.actions .mark-as-obsolete', function checkVisible() { + this.test.assertVisible('.actions .mark-as-obsolete', 'Mark as obsolete button visible'); + this.click('.actions .mark-as-obsolete'); + }, function fail() { + this.capture('screenshot/partObsolete/waitForMarkAsObsoleteButton-error.png'); + this.test.assert(false, 'Mark as obsolete button not visible'); + }); + }); + + /** + * Mark as obsolete modal + */ + casper.then(function waitForMarkAsObsoletePrompt() { + return this.waitForSelector('.bootbox.modal', function confirmMarkAsObsolete() { + this.click('.bootbox.modal .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/partObsolete/waitForMarkAsObsoletePrompt-error.png'); + this.test.assert(false, 'Mark as obsolete modal not found'); + }); + }); + + /** + * Wait for the Mark as obsolete button to be disabled + */ + casper.then(function waitForMarkAsObsoleteButtonDisabled() { + return this.waitWhileVisible('.actions .mark-as-obsolete', function checkHidden() { + this.test.assert(true, 'Mark as obsolete button hidden'); + }, function fail() { + this.capture('screenshot/partObsolete/waitForMarkAsObsoleteButtonDisabled-error.png'); + this.test.assert(false, 'Mark as obsolete button not hidden'); + }); + }); + + /** + * Check part has been marked as obsolete + */ + casper.then(function waitForObsoleteIconDisplayed() { + this.waitForSelector('#part_table i.fa.fa-frown-o', function partIsObsolete() { + this.test.assertElementCount('#part_table i.fa.fa-frown-o', 1, 'Part has been marked as obsolete'); + }, function fail() { + this.capture('screenshot/partObsolete/waitForObsoleteIconDisplayed-error.png'); + this.test.assert(false, 'Part has not been marked as obsolete'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partRelease.js b/docdoku-web-front/tests/js/product-management/part/partRelease.js new file mode 100644 index 0000000000..650cf2f81e --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partRelease.js @@ -0,0 +1,92 @@ +/*global casper,urls,products*/ + +casper.test.begin('Part release tests suite', 3, function partReleaseTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partRelease/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Select the first part with checkbox + */ + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:first-child td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:first-child td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/partRelease/waitForPartTable-error.png'); + this.test.assert(false, 'Part can not be found'); + }); + }); + + /** + * Check and click on the release part button + */ + casper.then(function waitForReleaseButton() { + return this.waitForSelector('.actions .new-release', function checkVisible() { + this.test.assertVisible('.actions .new-release', 'Release button visible'); + this.click('.actions .new-release'); + }, function fail() { + this.capture('screenshot/partRelease/waitForReleaseButton-error.png'); + this.test.assert(false, 'Release button not visible'); + }); + }); + + /** + * Release selection modal + */ + casper.then(function waitForReleaseSelectionPrompt() { + return this.waitForSelector('.bootbox.modal', function confirmReleaseSelection() { + this.click('.bootbox.modal .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/partRelease/waitForReleaseSelectionPrompt-error.png'); + this.test.assert(false, 'Release selection modal not found'); + }); + }); + + /** + * Wait for the release button to be disabled + */ + casper.then(function waitForReleaseButtonDisabled() { + return this.waitWhileVisible('.actions .new-release', function checkHidden() { + this.test.assert(true, 'Release button hidden'); + }, function fail() { + this.capture('screenshot/partRelease/waitForReleaseButtonDisabled-error.png'); + this.test.assert(false, 'Release button not hidden'); + }); + }); + + /** + * Check part has been released + */ + casper.then(function waitForReleaseIconDisplayed() { + this.waitForSelector('#part_table i.fa.fa-check', function partIsReleased() { + this.test.assertElementCount('#part_table i.fa.fa-check', 1, 'Part has been released'); + }, function fail() { + this.capture('screenshot/partRelease/waitForReleaseIconDisplayed-error.png'); + this.test.assert(false, 'Part has not been released'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partUploadAttachedFiles.js b/docdoku-web-front/tests/js/product-management/part/partUploadAttachedFiles.js new file mode 100644 index 0000000000..3ac7b0dae0 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partUploadAttachedFiles.js @@ -0,0 +1,101 @@ +/*global casper,urls*/ + +casper.test.begin('Part upload attached file tests suite', 3, function partUploadCadTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partUpload/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Wait the part list + */ + casper.then(function waitForPartList() { + var link = '#part_table tbody tr:first-child td.part_number'; + return this.waitForSelector(link, function clickPartNavLink() { + this.click(link); + }, function fail() { + this.capture('screenshot/partUpload/waitForPartList-error.png'); + this.test.assert(false, 'Part list can not be found'); + }); + }); + + /** + * Wait the modal + */ + casper.then(function waitForPartModal() { + var modalTab = '#part-modal .tabs li a[href="#tab-part-files"]'; + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/partUpload/waitForPartModal-error.png'); + this.test.assert(false, 'Part modal can not be found'); + }); + }); + + /** + * Wait the modal tab + */ + casper.then(function waitForPartModalTab() { + return this.waitForSelector('#part-modal .upload-btn', function tabSelected() { + this.test.assert(true, ' file upload tab opened'); + }, function fail() { + this.capture('screenshot/partUpload/waitForPartModalTab-error.png'); + this.test.assert(false, 'Part modal tab can not be found'); + }); + }); + + /** + * Chose a file and upload + */ + casper.then(function setFileAndUpload() { + this.fill('#iteration-files div:nth-child(2) .upload-form', { + 'upload': 'res/part-upload.obj' + }, false); + + return this.waitFor(function check() { + return this.evaluate(function () { + return document.querySelectorAll('#iteration-files div:nth-child(2) ul.file-list li').length === 1; + }); + }, function then() { + this.test.assert(true, 'File has been uploaded to part'); + }, function fail() { + this.capture('screenshot/partUpload/setFileAndUpload-error.png'); + this.test.assert(false, 'Cannot upload the file'); + }); + }); + + /** + * Check if CAD file icons are well set + */ + + casper.then(function checkCADFileIconsSet() { + return this.waitForSelector('#part_table .fa.fa-paperclip', function check() { + this.test.assertElementCount('#part_table .fa.fa-paperclip', 1, 'found 1 part with CAD file'); + }); + }); + + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partUploadNativeCadFile.js b/docdoku-web-front/tests/js/product-management/part/partUploadNativeCadFile.js new file mode 100644 index 0000000000..18bbdc241d --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partUploadNativeCadFile.js @@ -0,0 +1,101 @@ +/*global casper,urls*/ + +casper.test.begin('Part upload native cad file tests suite', 3, function partUploadCadTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partUpload/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Wait the part list + */ + casper.then(function waitForPartList() { + var link = '#part_table tbody tr:first-child td.part_number'; + return this.waitForSelector(link, function clickPartNavLink() { + this.click(link); + }, function fail() { + this.capture('screenshot/partUpload/waitForPartList-error.png'); + this.test.assert(false, 'Part list can not be found'); + }); + }); + + /** + * Wait the modal + */ + casper.then(function waitForPartModal() { + var modalTab = '#part-modal .tabs li a[href="#tab-part-files"]'; + return this.waitForSelector(modalTab, function modalOpened() { + this.click(modalTab); + }, function fail() { + this.capture('screenshot/partUpload/waitForPartModal-error.png'); + this.test.assert(false, 'Part modal can not be found'); + }); + }); + + /** + * Wait the modal tab + */ + casper.then(function waitForPartModalTab() { + return this.waitForSelector('#part-modal .upload-btn', function tabSelected() { + this.test.assert(true, 'Native cad file upload tab opened'); + }, function fail() { + this.capture('screenshot/partUpload/waitForPartModalTab-error.png'); + this.test.assert(false, 'Part modal tab can not be found'); + }); + }); + + /** + * Chose a file and upload + */ + casper.then(function setFileAndUpload() { + this.fill('#iteration-files div:nth-child(1) .upload-form', { + 'upload': 'res/part-upload.obj' + }, false); + + return this.waitFor(function check() { + return this.evaluate(function () { + return document.querySelectorAll('#part-modal .attachedFiles ul.file-list li').length === 1; + }); + }, function then() { + this.test.assert(true, 'File has been uploaded to part'); + }, function fail() { + this.capture('screenshot/partUpload/setFileAndUpload-error.png'); + this.test.assert(false, 'Cannot upload the file'); + }); + }); + + /** + * Check if CAD file icons are well set + */ + + casper.then(function checkCADFileIconsSet() { + return this.waitForSelector('#part_table .fa.fa-paperclip', function check() { + this.test.assertElementCount('#part_table .fa.fa-paperclip', 1, 'found 1 part with CAD file'); + }); + }); + + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partsMultipleCheckin.js b/docdoku-web-front/tests/js/product-management/part/partsMultipleCheckin.js new file mode 100644 index 0000000000..aee77c95df --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partsMultipleCheckin.js @@ -0,0 +1,148 @@ +/*global casper,urls,products*/ + +casper.test.begin('Part multiple checkin tests suite', 5, function partsMultipleCheckinTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/MultiplePartsCheckin/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Select the checkout parts with checkbox + */ + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:nth-child(2) td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:nth-child(2) td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/MultiplePartsCheckin/waitForPartTable-error.png'); + this.test.assert(false, 'Part cannot be found'); + }); + }); + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:nth-child(3) td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:nth-child(3) td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/MultiplePartsCheckin/waitForPartTable-error.png'); + this.test.assert(false, 'Part cannot be found'); + }); + }); + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:nth-child(5) td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:nth-child(5) td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/MultiplePartsCheckin/waitForPartTable-error.png'); + this.test.assert(false, 'Part cannot be found'); + }); + }); + + /** + * Click on checkin button + */ + casper.then(function waitForCheckinButton() { + return this.waitForSelector('.actions .checkin:not([disabled])', function clickOnCheckinButton() { + var nbPart = this.evaluate(function () { + return document.querySelectorAll('i.fa.fa-pencil').length; + }); + this.test.assertSelectorHasText('.nav-checkedOut-number-item', nbPart, 'Checkout number at ' + nbPart + ' in nav'); + this.click('.actions .checkin'); + }, function fail() { + this.capture('screenshot/MultiplePartsCheckin/waitForCheckinButton-error.png'); + this.test.assert(false, 'Checkin button can not be found'); + }); + }); + + /** + * Send keys for an iteration note + */ + casper.then(function waitForIterationNotePrompt() { + return this.waitForSelector('#prompt_modal #prompt_input.ready', function promptIterationNoteModal() { + this.sendKeys('#prompt_modal #prompt_input', products.part1.iterationNote, {reset: true}); + }, function fail() { + this.capture('screenshot/MultiplePartsCheckin/waitForIterationNotePrompt-error.png'); + this.test.assert(false, 'Iteration note modal not found'); + }); + }); + + /** + * Save an iteration note + */ + casper.then(function addIterationNote() { + return this.waitForSelector('#prompt_modal #prompt_input.ready', function fillIterationNote() { + this.click('#prompt_modal .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/MultiplePartsCheckin/waitForIterationNoteSent-error.png'); + this.test.assert(false, 'Iteration note cannot be sent'); + }); + }); + + /** + * Wait for iteration note modal disappear + */ + casper.then(function waitForIterationNoteHidden() { + return this.waitWhileSelector('#prompt_modal #prompt_input.ready', function hideIterationNoteModal() { + this.test.assert(true, 'Iteration note modal has disappeared'); + }, function fail() { + this.capture('screenshot/MultiplePartsCheckin/waitForIterationNoteGone-error.png'); + this.test.assert(false, 'Iteration note modal still visible'); + }); + }); + + /** + * Wait for the checkin button to be disabled + */ + casper.then(function waitForCheckinButtonDisabled() { + return this.waitForSelector('.actions .checkin[disabled]', function checkinButtonDisabled() { + this.test.assert(true, 'Checkin button is disabled'); + }, function fail() { + this.capture('screenshot/MultiplePartsCheckin/waitForCheckinButtonDisabled-error.png'); + this.test.assert(false, 'Checkin button is not disabled'); + }); + }); + + /** + * Wait for the parts to be all checked in + */ + casper.then(function waitForPartsCheckedIn() { + return this.waitWhileSelector('#part_table i.fa.fa-pencil', function partsAreCheckedIn() { + this.test.assertElementCount('#part_table i.fa.fa-pencil', 0, 'All parts checked in'); + }, function fail() { + this.capture('screenshot/MultiplePartsCheckin/waitForPartsCheckedIn-error.png'); + this.test.assert(false, 'All parts have not been checked in'); + }); + }); + + /** + * Wait for the checked out number to be updated + */ + casper.then(function waitForCheckedOutNumberUpdated() { + return this.waitForSelector('.nav-checkedOut-number-item', function checkedOutNumberIsUpdated() { + this.test.assertSelectorHasText('.nav-checkedOut-number-item', 0, 'Checked out number updated (0 in nav)'); + }, function fail() { + this.capture('screenshot/MultiplePartsCheckin/waitForCheckedOutNumberUpdated-error.png'); + this.test.assert(false, 'Checked out number has not been updated'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partsMultipleCheckout.js b/docdoku-web-front/tests/js/product-management/part/partsMultipleCheckout.js new file mode 100644 index 0000000000..a41ff09f28 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partsMultipleCheckout.js @@ -0,0 +1,93 @@ +/*global casper,urls*/ + +casper.test.begin('Parts multiple checkout tests suite', 3, function partsMultipleCheckoutTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partCheckout/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Select the parts with checkbox + */ + + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:nth-child(2) td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:nth-child(2) td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/partsCheckout/waitForPartTable-error.png'); + this.test.assert(false, 'Part cannot be found'); + }); + }); + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:nth-child(3) td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:nth-child(3) td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/partsCheckout/waitForPartTable-error.png'); + this.test.assert(false, 'Part cannot be found'); + }); + }); + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:nth-child(5) td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:nth-child(5) td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/partsCheckout/waitForPartTable-error.png'); + this.test.assert(false, 'Part cannot be found'); + }); + }); + + + /** + * Click on checkout button + */ + casper.then(function waitForCheckoutButton() { + return this.waitForSelector('.actions .checkout', function clickOnCheckoutButton() { + this.test.assertSelectorHasText('.nav-checkedOut-number-item', 0, 'checkout number at 0 in nav'); + this.click('.actions .checkout'); + }, function fail() { + this.capture('screenshot/partCheckout/waitForCheckoutButton-error.png'); + this.test.assert(false, 'Checkout button can not be found'); + }); + }); + + /** + * Wait for the checkout button to be disabled + */ + casper.then(function waitForCheckoutButtonDisabled() { + return this.waitForSelector('.actions .checkout:disabled', function partIsCheckout() { + this.test.assert(true, 'Parts have been checkout'); + var nbPart = this.evaluate(function () { + return document.querySelectorAll('i.fa.fa-pencil').length; + }); + this.test.assertSelectorHasText('.nav-checkedOut-number-item', nbPart, 'checkout number updated (' + nbPart + ' in nav)'); + + }, function fail() { + this.capture('screenshot/baselineCreation/waitForCheckoutButtonDisabled-error.png'); + this.test.assert(false, 'Parts have not been checkout'); + }); + }); + + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partsMultipleRelease.js b/docdoku-web-front/tests/js/product-management/part/partsMultipleRelease.js new file mode 100644 index 0000000000..9316ca323b --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partsMultipleRelease.js @@ -0,0 +1,112 @@ +/*global casper,urls,products*/ + +casper.test.begin('Parts multiple release tests suite', 3, function partMultipleReleaseTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/MultiplePartsRelease/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Select the checked in parts with checkbox + */ + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:nth-child(2) td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:nth-child(2) td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/MultiplePartsRelease/waitForPartTable-error.png'); + this.test.assert(false, 'Part cannot be found'); + }); + }); + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:nth-child(3) td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:nth-child(3) td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/MultiplePartsRelease/waitForPartTable-error.png'); + this.test.assert(false, 'Part cannot be found'); + }); + }); + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:nth-child(5) td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:nth-child(5) td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/MultiplePartsRelease/waitForPartTable-error.png'); + this.test.assert(false, 'Part cannot be found'); + }); + }); + + /** + * Check and click on the release part button + */ + casper.then(function waitForReleaseButton() { + return this.waitForSelector('.actions .new-release', function checkVisible() { + this.test.assertVisible('.actions .new-release', 'Release button visible'); + this.click('.actions .new-release'); + }, function fail() { + this.capture('screenshot/MultiplePartsRelease/waitForReleaseButton-error.png'); + this.test.assert(false, 'Release button not visible'); + }); + }); + + /** + * Release selection modal + */ + casper.then(function waitForReleaseSelectionPrompt() { + return this.waitForSelector('.bootbox.modal', function confirmReleaseSelection() { + this.click('.bootbox.modal .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/MultiplePartsRelease/waitForReleaseSelectionPrompt-error.png'); + this.test.assert(false, 'Release selection modal not found'); + }); + }); + + /** + * Wait for the release button to be disabled + */ + casper.then(function waitForReleaseButtonDisabled() { + return this.waitWhileVisible('.actions .new-release', function checkHidden() { + this.test.assert(true, 'Release button hidden'); + }, function fail() { + this.capture('screenshot/MultiplePartsRelease/waitForReleaseButtonDisabled-error.png'); + this.test.assert(false, 'Release button not hidden'); + }); + }); + + /** + * Check part has been released + */ + casper.then(function waitForReleaseIconDisplayed() { + return this.waitFor(function check() { + return this.evaluate(function () { + return $('#part_table i.fa.fa-check').length === 3; + }); + }, function then() { + this.test.assert(true, 'Parts have been released'); + }, function fail() { + this.capture('screenshot/MultiplePartsRelease/waitForReleaseIconDisplayed-error.png'); + this.test.assert(false, 'Parts have not been released'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/partsMultipleUndoCheckout.js b/docdoku-web-front/tests/js/product-management/part/partsMultipleUndoCheckout.js new file mode 100644 index 0000000000..a55931adc9 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/partsMultipleUndoCheckout.js @@ -0,0 +1,98 @@ +/*global casper,urls*/ + +casper.test.begin('Parts multiple undo checkout tests suite', 1, function partsMultipleUndoCheckoutTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partUndoCheckout/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Select the parts with checkbox + */ + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:nth-child(2) td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:nth-child(2) td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/partUndoCheckout/waitForPartTable-error.png'); + this.test.assert(false, 'Part cannot be found'); + }); + }); + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:nth-child(3) td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:nth-child(3) td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/partUndoCheckout/waitForPartTable-error.png'); + this.test.assert(false, 'Part cannot be found'); + }); + }); + casper.then(function waitForPartTable() { + return this.waitForSelector('#part_table tbody tr:nth-child(5) td:nth-child(2) input', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:nth-child(5) td:nth-child(2) input'); + }, function fail() { + this.capture('screenshot/partUndoCheckout/waitForPartTable-error.png'); + this.test.assert(false, 'Part cannot be found'); + }); + }); + + + /** + * Click on checkout button + */ + casper.then(function waitForUndoCheckoutButton() { + return this.waitForSelector('.actions .undocheckout', function clickOnUndoCheckoutButton() { + this.click('.actions .undocheckout'); + }, function fail() { + this.capture('screenshot/partUndoCheckout/waitForUndoCheckoutButton-error.png'); + this.test.assert(false, 'undoCheckout button can not be found'); + }); + }); + + /** + * Wait for confirmation box + */ + casper.then(function waitForConfirmationBox() { + return this.waitForSelector('div.modal-body', function fillIterationNote() { + this.click('.modal-footer a[data-handler="1"]'); + }, function fail() { + this.capture('screenshot/MultiplePartsCheckin/waitForIterationNotePrompt-error.png'); + this.test.assert(false, 'Iteration note modal not found'); + }); + }); + + /** + * Wait for the checkout button to be disabled + */ + casper.then(function waitForCheckoutButtonDisabled() { + return this.waitForSelector('.actions .undocheckout:disabled', function partIsCheckout() { + this.test.assert(true, 'Parts have been undocheckout'); + }, function fail() { + this.capture('screenshot/partUndoCheckout/waitForCheckoutButtonDisabled-error.png'); + this.test.assert(false, 'Parts have not been checkout'); + }); + }); + + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/part/showPartDetails.js b/docdoku-web-front/tests/js/product-management/part/showPartDetails.js new file mode 100644 index 0000000000..86ab033538 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/part/showPartDetails.js @@ -0,0 +1,67 @@ +/*global casper,urls,products*/ + +casper.test.begin('Part details tests suite', 3, function partDetailsTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }); + }); + + /** + * Wait for part list display + */ + + casper.then(function waitForPartInList() { + return this.waitForSelector('#part_table tbody tr:first-child td.part_number', function clickOnPartCheckbox() { + this.click('#part_table tbody tr:first-child td.part_number span'); + }); + }); + + /** + * Wait for part modal + */ + casper.then(function waitForModalDisplay() { + return this.waitForSelector('#part-modal', function testPartModal() { + this.test.assertSelectorHasText('#form-part div:first-child div span', products.part1.number); + this.test.assertSelectorHasText('#form-part div:nth-child(2) div span', products.part1.name); + }); + }); + + /** + * Close modal + */ + + casper.then(function waitForCancelButton() { + return this.waitForSelector('#part-modal button.close', function closePartModal() { + this.click('#part-modal button.close'); + }); + }); + + casper.then(function waitForModalToBeClosed() { + return this.waitWhileSelector('#part-modal', function onPartModalClosed() { + this.test.assert(true, 'Part modal has been closed'); + }); + }); + + casper.run(function () { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/pathToPathLink/pathToPathLinkCheck.js b/docdoku-web-front/tests/js/product-management/pathToPathLink/pathToPathLinkCheck.js new file mode 100644 index 0000000000..2a8c87fa4b --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/pathToPathLink/pathToPathLinkCheck.js @@ -0,0 +1,183 @@ +/*global casper,urls,p2pLinks,$*/ + +casper.test.begin('Path to path link check tests suite', 26, function pathToPathLinkCheckTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open product structure URL + * */ + + casper.then(function () { + return this.open(urls.productStructure); + }); + + /** + * Assert the tree is displayed + */ + + casper.then(function waitTree() { + return this.waitForSelector('#product_nav_list_container > .treeview > ul > li', function treeDisplayed() { + this.test.assert(true, 'Tree is displayed'); + }, function fail() { + this.capture('screenshot/pathToPathLinkCheck/waitTree-error.png'); + this.test.assert(false, 'Product tree can not be found'); + }); + }); + + /** + * Expand tree + */ + casper.then(function expandTree() { + return this.waitForSelector('#product_nav_list > ul > li > .hitarea', function expandButtonAvailable() { + this.test.assert(true, 'Expand button is available'); + this.click('#product_nav_list > ul > li > .hitarea'); + }, function fail() { + this.capture('screenshot/pathToPathLinkCheck/expandTree-error.png'); + this.test.assert(false, 'Expand button can not be found'); + }); + }); + + /** + * Click on two checkboxes parts + */ + casper.then(function selectParts() { + return this.waitForSelector('#product_nav_list > ul > li > ul > li > .selectable-part-checkbox', function selectParts() { + this.click('#product_nav_list > ul > li > ul > li:first-child > .selectable-part-checkbox'); + this.click('#product_nav_list > ul > li > ul > li:nth-child(2) > .selectable-part-checkbox'); + }, function fail() { + this.capture('screenshot/pathToPathLinkCheck/selectParts-error.png'); + this.test.assert(false, 'Select checkboxes can not be found'); + }); + }); + + /** + * Click on typed link creation button and check if created link is present + */ + casper.then(function openCreationModal() { + this.click('#path_to_path_link_btn'); + return this.waitForSelector('.modal.path-to-path-link-modal #path-to-path-links > .well', function modalIsDisplayed() { + this.test.assertElementCount('#path-to-path-links > .well', 1, 'One path to path link should be present'); + }, function fail() { + this.capture('screenshot/pathToPathLinkCheck/openCreationModal-error.png'); + this.test.assert(false, 'No path to path link found'); + }); + }); + + /** + * Assert that we can add some new links in wip mode, then close modal + */ + casper.then(function verifyWeCanAddPathToPathLink() { + return this.waitForSelector('.modal.path-to-path-link-modal .btn.add-path-to-path-link-btn', function verifyWeCanAddPathToPathLink() { + this.test.assert(true, 'We should be able to add new links'); + this.click('.modal.path-to-path-link-modal .modal-footer button.cancel-button'); + }); + }); + + /** + * Wait for the modal to be closed + */ + casper.then(function waitModalToBeClosed() { + return this.waitWhileSelector('.modal.path-to-path-link-modal', function waitModalToBeClosed() { + this.test.assert(true, 'Modal should be closed'); + }); + }); + + function runTreeTests() { + + /** + * Wait for tree to be ready + */ + casper.then(function treeReRendered() { + this.waitForSelector('#product_nav_list > ul > li > a > label', function treeReRendered() { + this.test.assert(true, 'Tree should be ready to be tested'); + }); + }); + + /** + * Check that we can change structure mode + */ + casper.then(function checkIfStructureCanBeChangedToType() { + this.waitForSelector('#path_to_path_link_selector_list', function checkIfStructureCanBeChangedToType() { + this.test.assert(true, 'Select link type should be present'); + this.test.assertSelectorHasText('#path_to_path_link_selector_list option:nth-child(2)', p2pLinks.type, 'Type selector should have "' + p2pLinks.type + '" present'); + + this.evaluate(function () { + document.querySelector('#path_to_path_link_selector_list').selectedIndex = 1; + $('#path_to_path_link_selector_list').change(); + return true; + }); + + }); + }); + + /** + * Wait for tree to be re-render, then check first node name + */ + casper.then(function treeReRendered() { + this.waitForSelector('#product_nav_list > ul > li > a > label', function treeReRendered() { + this.test.assertSelectorHasText('#product_nav_list > ul > li > a > label', p2pLinks.type, 'First Node of tree should be named "' + p2pLinks.type + '"'); + }, function fail() { + this.capture('screenshot/pathToPathLinkCheck/TreeNotRenderedError.png'); + this.test.assert(false, 'Could not load tree'); + }); + }); + + /** + * Expand all nodes, then check for node names + */ + casper.then(function expandFirstNode() { + this.click('#product_nav_list > ul > li > .hitarea'); + }); + + casper.then(function expandSecondNode() { + this.waitForSelector('#product_nav_list > ul > li > ul > li > .hitarea', function expandFirstNode() { + this.click('#product_nav_list > ul > li > ul > li > .hitarea'); + }); + }); + + casper.then(function checkExpandedNodes() { + this.waitForSelector('#product_nav_list > ul > li > ul > li > ul > li > .hitarea', function checkExpandedNodes() { + this.test.assert(true, 'Nodes should be expanded'); + this.test.assertSelectorHasText('#product_nav_list > ul > li > ul > li > a > label', ' < 100-AAA-CasperJsAssemblyP1-A-2 > (1) ', 'Second node should be named " < 100-AAA-CasperJsAssemblyP1-A-2 > (1) "'); + this.test.assertSelectorHasText('#product_nav_list > ul > li > ul > li > ul > li > a > label', ' < 200-AAA-CasperJsAssemblyP2-A-2 > (1) ', 'Second node should be named " < 200-AAA-CasperJsAssemblyP2-A-2 > (1) "'); + }); + }); + + } + + runTreeTests(); + + /** + * Run the same tests for baselines + */ + casper.then(function switchToBaselineMode() { + this.evaluate(function () { + document.querySelector('#config_spec_type_selector_list').selectedIndex = 1; + $('#config_spec_type_selector_list').change(); + return true; + }); + }); + + runTreeTests(); + + /** + * Run the same tests for productInstances + */ + casper.then(function switchToProductInstancesMode() { + this.evaluate(function () { + document.querySelector('#config_spec_type_selector_list').selectedIndex = 2; + $('#config_spec_type_selector_list').change(); + return true; + }); + }); + + runTreeTests(); + + casper.run(function allDone() { + this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/pathToPathLink/pathToPathLinkCreation.js b/docdoku-web-front/tests/js/product-management/pathToPathLink/pathToPathLinkCreation.js new file mode 100644 index 0000000000..d138a4f38a --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/pathToPathLink/pathToPathLinkCreation.js @@ -0,0 +1,100 @@ +/*global casper,urls,p2pLinks,$*/ + +casper.test.begin('Path to path link creation tests suite', 8, function pathToPathLinkCreationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open product structure URL + * */ + + casper.then(function () { + return this.open(urls.productStructure); + }); + + /** + * Assert the tree is displayed + */ + + casper.then(function waitTree() { + return this.waitForSelector('#product_nav_list_container > .treeview > ul > li', function treeDisplayed() { + this.test.assert(true, 'Tree is displayed'); + }, function fail() { + this.capture('screenshot/pathToPathLinkCreation/waitTree-error.png'); + this.test.assert(false, 'Product tree can not be found'); + }); + }); + + /** + * Expand tree + */ + casper.then(function expandTree() { + return this.waitForSelector('#product_nav_list > ul > li > .hitarea', function expandButtonAvailable() { + this.test.assert(true, 'Expand button is available'); + this.click('#product_nav_list > ul > li > .hitarea'); + }, function fail() { + this.capture('screenshot/pathToPathLinkCreation/expandTree-error.png'); + this.test.assert(false, 'Expand button can not be found'); + }); + }); + + /** + * Click on two checkboxes parts + */ + casper.then(function selectParts() { + return this.waitForSelector('#product_nav_list > ul > li > ul > li > .selectable-part-checkbox', function selectParts() { + this.click('#product_nav_list > ul > li > ul > li:first-child > .selectable-part-checkbox'); + + var isHidden = this.evaluate(function () { + return $('#path_to_path_link_btn:hidden').length > 0; + }); + + this.test.assert(isHidden, 'Path to path link creation button should not be visible'); + + this.click('#product_nav_list > ul > li > ul > li:nth-child(2) > .selectable-part-checkbox'); + + this.test.assertElementCount('#product_nav_list > ul > li > ul > li > .selectable-part-checkbox:checked', 2, 'Two checkbox should be selected'); + + var isVisible = this.evaluate(function () { + return $('#path_to_path_link_btn:visible').length > 0; + }); + + this.test.assert(isVisible, 'Path to path link creation button should not be visible'); + + }, function fail() { + this.capture('screenshot/pathToPathLinkCreation/selectParts-error.png'); + this.test.assert(false, 'Select checkboxes can not be found'); + }); + }); + + /** + * Click on typed link creation button + */ + casper.then(function openCreationModal() { + this.click('#path_to_path_link_btn'); + return this.waitForSelector('.modal.path-to-path-link-modal .btn.add-path-to-path-link-btn', function modalIsDisplayed() { + this.test.assert(true, 'Path to path link modal is displayed'); + this.click('.modal.path-to-path-link-modal .btn.add-path-to-path-link-btn'); + this.test.assertElementCount('#path-to-path-links > .well', 1, 'One path to path link has been added'); + this.sendKeys('#path-to-path-links > .well:first-child .add-type-input', p2pLinks.type, {reset: true}); + this.click('.modal.path-to-path-link-modal div.modal-footer > button.save-button'); + }); + }); + + /** + * Wait for modal to disappear + */ + casper.then(function waitForLinkToBeCreated() { + return this.waitWhileSelector('.modal.path-to-path-link-modal', function waitForLinkToBeCreated() { + this.test.assert(true, 'Typed link modal has disappear'); + }); + }); + + + casper.run(function allDone() { + this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/product-instance/productInstanceCreation.js b/docdoku-web-front/tests/js/product-management/product-instance/productInstanceCreation.js new file mode 100644 index 0000000000..18b3d3745c --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/product-instance/productInstanceCreation.js @@ -0,0 +1,113 @@ +/*global casper,urls,productInstances*/ + +casper.test.begin('Product instance creation tests suite', 5, function productInstanceCreationTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to product instances nav + */ + casper.then(function waitForProductInstanceNavLink() { + return this.waitForSelector('#product-instances-nav > .nav-list-entry > a', function clickProductInstanceNavLink() { + this.click('#product-instances-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/productInstanceCreation/waitForProductInstanceNavLink-error.png'); + this.test.assert(false, 'Product instance nav link can not be found'); + }); + }); + + /** + * Find the new product instance button + */ + casper.then(function waitForNewProductInstanceButton() { + return this.waitForSelector('.actions .new-product-instance', function clickOnNewProductInstanceButton() { + this.click('.actions .new-product-instance'); + }, function fail() { + this.capture('screenshot/productInstanceCreation/waitForNewProductInstanceButton-error.png'); + this.test.assert(false, 'New product instance button can not be found'); + }); + }); + + /** + * Wait for the modal to be opened + */ + casper.then(function waitForNewProductInstanceModal() { + return this.waitForSelector('#product_instance_creation_modal', function modalOpened() { + this.test.assert(true, 'Product instance creation modal opened'); + }, function fail() { + this.capture('screenshot/productInstanceCreation/waitForNewProductInstanceModal-error.png'); + this.test.assert(false, 'Product instance creation modal can not be found'); + }); + }); + + /** + * Wait for the selects to be loaded + */ + casper.then(function waitForData() { + return this.waitForSelector('#product_instance_creation_modal #inputBaseline > option', function dataReady() { + this.test.assert(true, 'Product instance ready to create'); + }, function fail() { + this.capture('screenshot/productInstanceCreation/waitForData-error.png'); + this.test.assert(false, 'No data available to create the product instance'); + }); + }); + + /** + * Try to create the product instance without a serial number + */ + casper.then(function tryToCreateProductInstanceWithoutSerialNumber() { + this.click('#product_instance_creation_modal .modal-footer .btn.btn-primary'); + this.test.assertExists('#product_instance_creation_modal #inputSerialNumber:invalid', 'Should not create a product instance without the name'); + }); + + /** + * Try to create the product instance + */ + casper.then(function tryToCreateProductInstance() { + return this.waitForSelector('#product_instance_creation_modal', function fillForm() { + this.sendKeys('#product_instance_creation_modal #inputSerialNumber', productInstances.productInstance1.serialNumber, {reset: true}); + this.click('#product_instance_creation_modal .modal-footer .btn.btn-primary'); + }, function fail() { + this.capture('screenshot/productInstanceCreation/tryToCreateProductInstance-error.png'); + this.test.assert(false, 'Product instance modal can not be found'); + }); + }); + + /** + * Wait for the modal to be closed + */ + casper.then(function waitForProductInstanceModalToBeClosed() { + return this.waitWhileSelector('#product_instance_creation_modal', function onModalClosed() { + this.test.assert(true, 'Product instance creation modal closed'); + }, function fail() { + this.capture('screenshot/productInstanceCreation/waitForProductInstanceModalToBeClosed-error.png'); + this.test.assert(false, 'Product instance creation modal not closed'); + }); + }); + + /** + * Wait for the line in the table + */ + casper.then(function waitForProductInstanceToBeCreated() { + return this.waitForSelector('#product_instances_table > tbody > tr > td.reference', function onProductInstanceCreated() { + this.test.assert(true, 'Product instance created'); + }, function fail() { + this.capture('screenshot/productInstanceCreation/waitForProductInstanceToBeCreated-error.png'); + this.test.assert(false, 'Product instance not in the list'); + }); + }); + + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/product-management/product-instance/productInstanceData.js b/docdoku-web-front/tests/js/product-management/product-instance/productInstanceData.js new file mode 100644 index 0000000000..0b2d7c09eb --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/product-instance/productInstanceData.js @@ -0,0 +1,239 @@ +/*global casper,urls,productInstances,$*/ + +casper.test.begin('Product instance data path tests suite', 22, function productInstanceDataPathTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productStructureForDeliverable); + }); + + + /** + * Wait for Serial Number selected + */ + casper.then(function waitForSerialNumber() { + return this.waitForSelector('#config_spec_type_selector_list', function loadConfiguration() { + return this.evaluate(function () { + var selectedIndex = document.querySelector('#config_spec_type_selector_list').selectedIndex; + return selectedIndex === 2; + }, function then() { + this.test.assert(true, 'Serial number config selected'); + }, function fail() { + this.capture('screenshot/product-instance/SerialNumberConfigNotYetSelected.png'); + this.test.assert(false, 'Serial number config is not selected'); + }); + }); + }); + + /** + * Click on the checkbox from the bom + */ + casper.then(function waitForBOM() { + return this.waitForSelector('.selectable-part-checkbox', function loadDataButton() { + this.click('.selectable-part-checkbox'); + this.test.assert(true, 'click on product-instance checkbox'); + }, function fail() { + this.capture('screenshot/product-instance/FailToLoadDeliverable.png'); + this.test.assert(false, 'could not load deliverable'); + }); + }); + + /** + * Wait for the Deliverable Data button + */ + casper.then(function waitForDeliverableButton() { + return this.waitUntilVisible('#path_data_btn', function clickOnDeliverableButton() { + this.test.assert(true, 'deliverable data button present'); + this.click('#path_data_btn'); + }, function fail() { + this.capture('screenshot/product-instance/NoDeliverableButton.png'); + this.test.assert(false, 'deliverable data button not present'); + }); + + }); + + /** + * Wait for the Deliverable Data modal + */ + casper.then(function openDataModal() { + return this.waitForSelector('.product-instance-data-modal', function waitForModal() { + this.test.assert(true, 'modal opened'); + }, function fail() { + this.capture('screenshot/product-instance/ModalNotFound.png'); + this.test.assert(false, 'could not open modal'); + }); + }); + + /** + * Count the part attributes present in the modal + */ + casper.then(function countPartAttribute() { + return this.waitForSelector('#partAttributes .list-item', function countAttributes() { + this.test.assertElementCount('#partAttributes .list-item', 2, '2 parts attributes present'); + }, function fail() { + this.capture('screenshot/product-instance/CouldNotLoadPartAttributes.png'); + this.test.assert(false, 'could not load the part attributes'); + }); + + }); + + casper.then(function countPathDataAttr() { + return this.waitForSelector('#pathDataAttributes', function countDataPath() { + this.test.assertElementCount('#pathDataAttributes .list-item', 1, '1 data path attr present'); + }, function fail() { + this.test.assert(false, 'could not load the data path attributes'); + }); + }); + + /** + * Count tab present in the modal + */ + casper.then(function countTabPresent() { + this.test.assertElementCount('ul.nav.nav-tabs li', 2, '2 tabs present in the modal'); + }); + + /** + * Add iteration note and save + */ + casper.then(function addIterationNote() { + this.sendKeys('.description-input', productInstances.productInstance1.iterationNote); + this.click('.save-button'); + this.test.assertExists('#pathDataAttributes input.value:invalid', + 'should not create iteration without the mandatory attribute'); + this.sendKeys('#pathDataAttributes input.value', productInstances.productInstance1.pathDataValue); + this.click('.save-button'); + }); + + /** + * close the modal + */ + casper.then(function reopenModal() { + return this.waitWhileSelector('.product-instance-data-modal', function waitCloseModal() { + this.test.assert(true, 'modal closed'); + }, function fail() { + this.capture('screenshot/product-instance/ModalNotClosing.png'); + this.test.assert(false, 'could not close the modal'); + }); + }); + + /** + * Check for icon in PS + */ + casper.then(function waitForPathDataIcon() { + return this.waitForSelector('#product_nav_list_container > .treeview > ul > li > i.fa-asterisk', function iconShown() { + this.test.assert(true, 'Should refresh the treeview and show the path data icon'); + }, function fail() { + this.capture('screenshot/product-instance/ModalNotClosing.png'); + this.test.assert(false, 'could not close the modal'); + }); + }); + + /** + * click on the checkbox from the bom + */ + casper.then(function waitForBOM() { + return this.waitForSelector('.selectable-part-checkbox', function loadDataButton() { + this.click('.selectable-part-checkbox'); + this.test.assert(true, 'click on product-instance checkbox'); + }, function fail() { + this.capture('screenshot/product-instance/FailToLoadDeliverable.png'); + this.test.assert(false, 'could not load deliverable'); + }); + }); + + /** + * re-open the modal + */ + casper.then(function reopenModal() { + return this.waitForSelector('#path_data_btn', function openModal() { + this.click('#path_data_btn'); + this.test.assert(true, 'deliverable data button present'); + }, function fail() { + this.capture('screenshot/product-instance/NoDeliverableButton.png'); + this.test.assert(false, 'deliverable data button not present'); + }); + }); + + casper.then(function waitForModal() { + return this.waitForSelector('.product-instance-data-modal', function modalOpened() { + this.test.assert(true, 'deliverable data modal opened'); + }, function fail() { + this.capture('screenshot/product-instance/NoDeliverableModal.png'); + this.test.assert(false, 'could not open deliverable data modal'); + }); + }); + + /** + * Now there should be 4 tab present and an iteration note. + */ + casper.then(function countTab() { + return this.waitForSelector('#tab-attributes', function waitForModal() { + this.test.assertElementCount('ul.nav.nav-tabs li', 4, '2 tabs present in the modal'); + this.test.assertExists('.product-instance-data-modal div.path-description'); + this.test.assertExists('input.description-input[value="' + productInstances.productInstance1.iterationNote + '"]'); + + }, function fail() { + this.capture('screenshot/product-instance/DeliverableDataModal-notFound.png'); + this.test.assert(false, 'deliverable data modal not found'); + }); + + }); + + /** + * Test if the iteration note is present + */ + casper.then(function assertIterationNote() { + //Wait for the input value to be injected, can take some time. + return this.waitForSelector('#pathDataAttributes input.value[value="' + productInstances.productInstance1.pathDataValue + '"]', function found() { + this.test.assert(true, 'the input value is given to the view'); + }, function fail() { + this.test.assert(false, 'the previously given value is not printed in the input'); + }); + }); + + + /** + * Go to the tab attributes + */ + casper.then(function waitForModal() { + return this.waitForSelector('.product-instance-data-modal #tab-attributes', function waitModal() { + this.click('ul.nav.nav-tabs li:nth-child(2) a'); + }, function fail() { + this.test.assert(false, 'could not open modal'); + }); + }); + + /** + * Test the attributes + */ + casper.then(function testDataAttributes() { + return this.waitForSelector('ul.nav.nav-tabs li:nth-child(2).active', function () { + this.test.assertElementCount('#partAttributes input.name[disabled]', 2, + 'the two part attributes name should be disabled in partAttributes'); + this.test.assertDoesntExist('#partAttributes input.value', + 'There should be no input for value in partAttributes'); + this.test.assertElementCount('#partAttributes div.controls.type', 2, + 'the type should be disabled for partAttributes'); + this.test.assertElementCount('#pathDataAttributes input.name[disabled]', 1, + 'the two part attributes name should be disabled for the pathDataAttributes'); + this.test.assertElementCount('#pathDataAttributes div.controls.type', 1, + 'the type should be disabled for pathDataAttributes'); + this.test.assertElementCount('#pathDataAttributes input.value[required]', 1, + 'the path data input for value should be required'); + this.click('.cancel-button'); + }, function fail() { + this.capture('screenshot/product-instance/DeliverableTabAttributes-NotFound.png'); + this.test.assert(false, 'could not load the attribute tab'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/product-management/product-instance/productInstanceDeletion.js b/docdoku-web-front/tests/js/product-management/product-instance/productInstanceDeletion.js new file mode 100644 index 0000000000..89f2309cb2 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/product-instance/productInstanceDeletion.js @@ -0,0 +1,81 @@ +/*global casper,urls*/ + +casper.test.begin('Product instance deletion tests suite', 2, function productInstanceDeletionTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to product instances nav + */ + casper.then(function waitForProductInstanceNavLink() { + return this.waitForSelector('#product-instances-nav > .nav-list-entry > a', function clickProductInstanceNavLink() { + this.click('#product-instances-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/productInstanceDeletion/waitForProductInstanceNavLink-error.png'); + this.test.assert(false, 'Product instance nav link can not be found'); + }); + }); + + /** + * Click the 'select all' checkbox + */ + casper.then(function waitForSelectAllCheckbox() { + return this.waitForSelector('#product_instances_table thead tr:first-child th:first-child input', function clickOnSelectAllCheckbox() { + this.click('#product_instances_table thead tr:first-child th:first-child input'); + }, function fail() { + this.capture('screenshot/productInstanceDeletion/waitForSelectAllCheckbox-error.png'); + this.test.assert(false, 'Select all checkbox can not be found'); + }); + }); + + /** + * Wait for the delete button to appear + */ + casper.then(function waitForDeleteButton() { + return this.waitForSelector('.actions .delete', function clickOnDeleteButton() { + this.click('.actions .delete'); + this.test.assert(true, 'Delete button available'); + }, function fail() { + this.capture('screenshot/productInstanceDeletion/waitForDeleteButton-error.png'); + this.test.assert(false, 'Select all checkbox can not be found'); + }); + }); + + /** + * Wait for the confirmation modal + */ + casper.then(function waitForConfirmationModal() { + return this.waitForSelector('.bootbox', function confirmDeletion() { + this.click('.bootbox .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/productInstanceDeletion/waitForConfirmationModal-error.png'); + this.test.assert(false, 'Product instance deletion confirmation modal can not be found'); + }); + }); + + /** + * Assert that there's no more entries in the table + **/ + casper.then(function waitForTableToBeEmpty() { + return this.waitWhileSelector('#product_instances_table tbody tr:first-child td:first-child input', function onBaselineTableEmpty() { + this.test.assert(true, 'No more product instances in the list'); + }, function fail() { + this.capture('screenshot/productInstanceDeletion/waitForTableToBeEmpty-error.png'); + this.test.assert(false, 'Product instance table still not empty'); + }); + }); + + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/product-management/product/productCreation.js b/docdoku-web-front/tests/js/product-management/product/productCreation.js new file mode 100644 index 0000000000..b6f8affffa --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/product/productCreation.js @@ -0,0 +1,90 @@ +/*global casper,urls,products*/ + +casper.test.begin('Product creation tests suite', 3, function productCreationTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to product nav + */ + casper.then(function waitForProductNavLink() { + return this.waitForSelector('#product-nav > .nav-list-entry > a', function clickProductNavLink() { + this.click('#product-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/productCreation/waitForProductNavLink-error.png'); + this.test.assert(false, 'Product nav link can not be found'); + }); + }); + + /** + * Open the product creation modal + */ + casper.then(function waitForProductCreationButton() { + return this.waitForSelector('.actions .new-product', function openProductCreationModal() { + this.click('.actions .new-product'); + }, function fail() { + this.capture('screenshot/productCreation/waitForProductCreationButton-error.png'); + this.test.assert(false, 'New product button can not be found'); + }); + }); + + /** + * Wait for the modal to be displayed & Create empty product should fail + */ + casper.then(function waitForModalToBeDisplayed() { + return this.waitForSelector('#product_creation_modal', function createEmptyProduct() { + this.click('#product_creation_modal .btn-primary'); + this.test.assertExists('#product_creation_modal #inputProductId:invalid', 'Should not create product without a product id'); + }, function fail() { + this.capture('screenshot/productCreation/waitForNewProductModal-error.png'); + this.test.assert(false, 'New part modal can not be found'); + }); + }); + + /** + * Create product should + * */ + casper.then(function fillProductCreationForm() { + return this.waitForSelector('#product_creation_modal #inputDescription', function onNewPartFormReady() { + this.sendKeys('#product_creation_modal #inputProductId', products.product1.number, {reset: true}); + this.sendKeys('#product_creation_modal #inputDescription', products.product1.description, {reset: true}); + this.sendKeys('#product_creation_modal #inputPart', products.part1.number, {reset: true}); + + this.evaluate(function (number, name) { + document.querySelector('input#inputPartNumber').setAttribute('value', number); + document.querySelector('input#inputPartName').setAttribute('value', name); + }, { + number: products.part1.number, + name: products.part1.name + }); + + }, function fail() { + this.capture('screenshot/productCreation/onNewProductFormReady-error.png'); + this.test.assert(false, 'New product form can not be found'); + }); + }); + + casper.then(function submitProductCreationForm() { + this.click('#product_creation_modal .btn-primary'); + return this.waitForSelector('#product_table .product_id', function productHasBeenCreated() { + this.test.assertSelectorHasText('#product_table tbody tr:first-child td.product_id', products.product1.number); + this.test.assertSelectorHasText('#product_table tbody tr:first-child td:nth-child(4)', products.part1.number); + }, function fail() { + this.capture('screenshot/productCreation/waitForProductToBeCreated-error.png'); + this.test.assert(false, 'New product created can not be found'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/product-management/product/productDeletion.js b/docdoku-web-front/tests/js/product-management/product/productDeletion.js new file mode 100644 index 0000000000..f23f1eb366 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/product/productDeletion.js @@ -0,0 +1,66 @@ +/*global casper,urls*/ + +casper.test.begin('Product deletion tests suite', 1, function productDeletionTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to product nav + */ + + casper.then(function waitForProductNavLink() { + return this.waitForSelector('#product-nav > .nav-list-entry > a', function clickProductNavLink() { + this.click('#product-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/productDeletion/waitForProductNavLink-error.png'); + this.test.assert(false, 'Product nav link can not be found'); + }); + }); + + /** + * Test delete a product + */ + + casper.then(function waitForProductInList() { + return this.waitForSelector('#product_table tbody tr:first-child td.product_id', function clickOnProductCheckbox() { + this.click('#product_table tbody tr:first-child td:first-child input'); + }, function fail() { + this.capture('screenshot/productDeletion/waitForProductInList-error.png'); + this.test.assert(false, 'Product to delete rows can not be found'); + }); + }); + + casper.then(function clickOnDeleteProductButton() { + this.click('.actions .delete'); + // Confirm deletion + return this.waitForSelector('.bootbox', function confirmProductDeletion() { + this.click('.bootbox .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/productDeletion/waitForDeletionConfirmationModal-error.png'); + this.test.assert(false, 'Product deletion confirmation modal can not be found'); + }); + }); + + casper.then(function waitForProductDisappear() { + return this.waitWhileSelector('#product_table tbody tr:first-child td.product_id', function productHasBeenDeleted() { + casper.test.assert(true, 'Product has been deleted'); + }, function fail() { + this.capture('screenshot/productDeletion/waitForProductDiseapear-error.png'); + this.test.assert(false, 'Product has not been deleted'); + }); + }); + + casper.run(function () { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/queryBuilder/queryBuilderSearch.js b/docdoku-web-front/tests/js/product-management/queryBuilder/queryBuilderSearch.js new file mode 100644 index 0000000000..5de319343e --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/queryBuilder/queryBuilderSearch.js @@ -0,0 +1,136 @@ +/*global casper,urls,products,$*/ + +casper.test.begin('Query builder search tests suite', 5, function queryBuilderSearchTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement + '/parts'); + }); + + /** + * Wait for query builder button + * */ + casper.then(function waitForQueryBuilderDisplayButton() { + return this.waitForSelector('#part-search-form .display-query-builder-button', function clickOnQueryBuilderDisplayButton() { + this.click('#part-search-form .display-query-builder-button'); + }, function fail() { + this.capture('screenshot/queryBuilderSearch/waitForQueryBuilderDisplayButton-error.png'); + this.test.assert(false, 'Query builder display button can not be found'); + }); + }); + + /** + * Wait for query builder display + * */ + casper.then(function waitForQueryBuilderDisplay() { + return this.waitForSelector('#query-builder-view', function queryBuilderDisplayed() { + this.test.assert(true, 'Query builder view displayed'); + }, function fail() { + this.capture('screenshot/queryBuilderSearch/waitForQueryBuilderDisplay-error.png'); + this.test.assert(false, 'Query builder view can not be found'); + }); + }); + + /** + * Fill "select" field + * */ + casper.then(function fillSelectField() { + return this.evaluate(function () { + $('.query-select-block .selectize-input').click(); + setTimeout(function () { + $('div.query-select-block div.selectize-dropdown div[data-value="pm.number"]').click(); + $('div.query-select-block div.selectize-dropdown div[data-value="pm.name"]').click(); + }, 0); + return true; + }); + }); + + /** + * Fill condition and + * */ + + casper.then(function fillSelectField() { + this.evaluate(function () { + $('#where_rule_0 > div.rule-filter-container > select').val('attr-TEXT.CasperJsTestAttr-lock'); + $('#where_rule_0 > div.rule-filter-container > select').change(); + return true; + }); + this.sendKeys('#where_rule_0 > div.rule-value-container > input', products.part1.attributeValue); + }); + + + /** + * Run query + * */ + + casper.then(function fillSelectField() { + this.click('#query-builder-view > div.query-actions-block >.search-button'); + }); + + /** + * Wait for result table + * */ + + casper.then(function waitForResultTable() { + return this.waitForSelector('#query-table table', function resultTableDisplayed() { + this.test.assertElementCount('#query-table table tbody tr', 1, 'We should have one result matching our query'); + this.test.assertSelectorHasText('#query-table table tbody tr td span', products.part1.number, 'We should have ' + products.part1.number + ' in the first cell'); + }, function fail() { + this.capture('screenshot/queryBuilderSearch/waitForQueryBuilderDisplay-error.png'); + this.test.assert(false, 'Query builder view can not be found'); + }); + }); + + + /** + * Save query + * */ + + casper.then(function saveQuery() { + this.click('#query-builder-view > div.query-actions-block > .save-button'); + return this.waitForSelector('#prompt_modal.in', function promptDisplayed() { + this.sendKeys('#prompt_modal #prompt_input','myQuery'); + this.click('#prompt_modal #submitPrompt'); + }, function fail() { + this.capture('screenshot/queryBuilderSearch/saveQuery-error.png'); + this.test.assert(false, 'Query builder prompt for save not displayed'); + }); + }); + + /** + * Wait for modal to close + * */ + + casper.then(function saveQuery() { + return this.waitWhileSelector('#prompt_modal', function promptModalClosed() { + this.test.assert(true,'Prompt modal has been closed'); + }, function fail() { + this.capture('screenshot/queryBuilderSearch/saveQuery-error.png'); + this.test.assert(false, 'Query builder prompt for save not closed'); + }); + }); + + /** + * Check in the list if query has been saved + * */ + + casper.then(function onModalClosed() { + return this.waitForSelector('#query-builder-view > div.query-builder-body > div:nth-child(2) > select > option:nth-child(2)', function querySaved() { + this.test.assertSelectorHasText('#query-builder-view > div.query-builder-body > div:nth-child(2) > select > option:nth-child(2)','myQuery', 'Query has been saved'); + }, function fail() { + this.capture('screenshot/queryBuilderSearch/onModalClosed-error.png'); + this.test.assert(false, 'Query builder query not saved'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/share/expiredSharedPart.js b/docdoku-web-front/tests/js/product-management/share/expiredSharedPart.js new file mode 100644 index 0000000000..4570393868 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/share/expiredSharedPart.js @@ -0,0 +1,33 @@ +/*global casper,urls*/ + +casper.test.begin('Expired private shared part tests suite', 1, function expiredPrivateSharedPartTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open part management URL + * */ + + casper.then(function () { + return this.open(urls.privatePartPermalinkExpired); + }); + + /** + * Check we have the expired resource page + */ + + casper.then(function checkForExpiredPage() { + return this.waitForSelector('#not-found-view > h1', function () { + this.test.assert(true, 'Expired entity page displayed'); + }, function fail() { + this.capture('screenshot/expiredSharedPart/checkForExpiredPage-error.png'); + this.test.assert(false, 'Expired shared entity page not displayed'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/product-management/share/privateSharedPart.js b/docdoku-web-front/tests/js/product-management/share/privateSharedPart.js new file mode 100644 index 0000000000..7d0cfc1f3c --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/share/privateSharedPart.js @@ -0,0 +1,62 @@ +/*global casper,urls,products*/ + +casper.test.begin('Private shared part tests suite', 3, function privateSharedPartTestsSuite() { + + 'use strict'; + + var titleSelector = '#content > .part-revision > div > h3'; + + casper.open(''); + + /** + * Open part management URL + * */ + + casper.then(function () { + return this.open(urls.privatePartPermalink); + }); + + /** + * We should be prompted for password + */ + casper.then(function checkPasswordIsRequested() { + return this.waitForSelector('#prompt_modal #prompt_input.ready', function passwordRequested() { + this.sendKeys('#prompt_modal #prompt_input', products.part1.sharedPassword, {reset: true}); + this.click('#submitPrompt'); + this.test.assert(true, 'We are prompted for password'); + }, function fail() { + this.capture('screenshot/privateSharedPart/checkPasswordIsRequested-error.png'); + this.test.assert(false, 'Password field can not be found'); + }); + }); + + /** + * Check for part title + */ + + casper.then(function checkPartTitle() { + return this.waitForSelector(titleSelector, function titleDisplayed() { + this.test.assertSelectorHasText(titleSelector, products.part1.number + '-A'); + }, function fail() { + this.capture('screenshot/privateSharedPart/checkPartTitle-error.png'); + this.test.assert(false, 'Title can not be found'); + }); + }); + + /** + * Check for part iteration note + */ + casper.then(function checkIterationNote() { + this.click('.nav-tabs a[href="#tab-part-iteration"]'); + return this.waitForSelector(titleSelector, function iterationNoteDisplayed() { + this.test.assertSelectorHasText('#tab-part-iteration > table > tbody > tr:nth-child(2) > td', products.part1.iterationNote); + }, function fail() { + this.capture('screenshot/privateSharedPart/checkIterationNote-error.png'); + this.test.assert(false, 'Iteration note can not be found'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/product-management/share/publicSharedPart.js b/docdoku-web-front/tests/js/product-management/share/publicSharedPart.js new file mode 100644 index 0000000000..6eff56cd8b --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/share/publicSharedPart.js @@ -0,0 +1,48 @@ +/*global casper,urls,products*/ + +casper.test.begin('Public shared part tests suite', 2, function publicSharedPartTestsSuite() { + + 'use strict'; + + var titleSelector = '#content > .part-revision > div > h3'; + + casper.open(''); + + /** + * Open part management URL + * */ + + casper.then(function () { + return this.open(urls.partPermalink); + }); + + /** + * Check for part title + */ + + casper.then(function checkPartTitle() { + return this.waitForSelector(titleSelector, function titleDisplayed() { + this.test.assertSelectorHasText(titleSelector, products.part1.number + '-A'); + }, function fail() { + this.capture('screenshot/publicSharedPart/checkPartTitle-error.png'); + this.test.assert(false, 'Title can not be found'); + }); + }); + + /** + * Check for part iteration note + */ + casper.then(function checkIterationNote() { + this.click('.nav-tabs a[href="#tab-part-iteration"]'); + return this.waitForSelector(titleSelector, function iterationNoteDisplayed() { + this.test.assertSelectorHasText('#tab-part-iteration > table > tbody > tr:nth-child(2) > td', products.part1.iterationNote); + }, function fail() { + this.capture('screenshot/publicSharedPart/checkIterationNote-error.png'); + this.test.assert(false, 'Iteration note can not be found'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/product-management/share/sharedPartCreation.js b/docdoku-web-front/tests/js/product-management/share/sharedPartCreation.js new file mode 100644 index 0000000000..4ea4bbf4c3 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/share/sharedPartCreation.js @@ -0,0 +1,175 @@ +/*global casper,urls,products*/ + +casper.test.begin('Shared part creation tests suite', 7, function sharedPartCreationTestsSuite() { + + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part nav + */ + casper.then(function waitForPartNavLink() { + return this.waitForSelector('#part-nav > .nav-list-entry > a', function clickPartNavLink() { + this.click('#part-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/sharedPartCreation/waitForPartNavLink-error.png'); + this.test.assert(false, 'Part nav link can not be found'); + }); + }); + + /** + * Find the shared part link in the part list + */ + + casper.then(function waitForPartList() { + var link = '#part_table tbody tr:first-child td.part-revision-share i'; + return this.waitForSelector(link, function onLinkFound() { + this.click(link); + }, function fail() { + this.capture('screenshot/sharedPartCreation/waitForPartList-error.png'); + this.test.assert(false, 'Shared part modal can not be found'); + }); + }); + + /** + * Wait for modal + */ + casper.then(function waitForSharedPartCreationModal() { + return this.waitForSelector('#share-modal', function modalOpened() { + this.test.assert(true, 'Shared part modal is opened'); + }, function fail() { + this.capture('screenshot/sharedPartCreation/waitForSharedPartCreationModal-error.png'); + this.test.assert(false, 'Shared part modal can not be found'); + }); + }); + + /** + * Set the part as public + */ + casper.then(function setPartAsPublicShared() { + this.click('#share-modal .public-shared-switch .switch-off input'); + return this.waitForSelector('#share-modal .public-shared-switch .switch-on', function publicSharedCreated() { + this.test.assert(true, 'Part is now public shared'); + }, function fail() { + this.capture('screenshot/sharedPartCreation/setPartAsPublicShared-error.png'); + this.test.assert(false, 'Shared part cannot be shared as public'); + }); + + }); + + + /** + * Create a private share, with expire date and password + */ + casper.then(function createPartPrivateShare() { + this.sendKeys('#private-share .password', products.part1.sharedPassword, {reset: true}); + this.sendKeys('#private-share .confirm-password', products.part1.sharedPassword, {reset: true}); + this.sendKeys('#private-share .expire-date', products.part1.expireDate, {reset: true}); + this.click('#private-share #generate-private-share'); + + return this.waitForSelector('#private-share > div > div > a', function onLinkGenerated() { + var url = this.fetchText('#private-share > div > div > a'); + urls.privatePartPermalink = url; + this.test.assert(true, 'Private share created expiring tomorrow : ' + url); + + }, function fail() { + this.capture('screenshot/sharedPartCreation/createPartPrivateShare-error.png'); + this.test.assert(false, 'Shared part cannot be shared as private'); + }); + }); + + + /** + * Close the modal + */ + casper.then(function closeSharedPartModal() { + + this.click('#share-modal > div.modal-header > button'); + + return this.waitWhileSelector('#share-modal', function modalClosed() { + this.test.assert(true, 'Shared part modal closed'); + }, function fail() { + this.capture('screenshot/sharedPartCreation/closeSharedPartModal-error.png'); + this.test.assert(false, 'Shared part modal cannot be closed'); + }); + + }); + + /** + * Reopen the modal to create a second private share, expired one. + */ + casper.then(function waitForPartList() { + var link = '#part_table tbody tr:first-child td.part-revision-share i'; + return this.waitForSelector(link, function onLinkFound() { + this.click(link); + }, function fail() { + this.capture('screenshot/sharedPartCreation/waitForPartList-error.png'); + this.test.assert(false, 'Shared part modal can not be found'); + }); + }); + + /** + * Wait for modal + */ + casper.then(function waitForSharedPartCreationModal() { + return this.waitForSelector('#share-modal', function modalOpened() { + + this.test.assert(true, 'Shared part modal is opened'); + + this.sendKeys('#private-share .password', products.part1.sharedPassword, {reset: true}); + this.sendKeys('#private-share .confirm-password', products.part1.sharedPassword, {reset: true}); + this.sendKeys('#private-share .expire-date', products.part1.expireDate2, {reset: true}); + this.click('#private-share #generate-private-share'); + + }, function fail() { + this.capture('screenshot/sharedPartCreation/waitForSharedPartCreationModal-error.png'); + this.test.assert(false, 'Shared part modal can not be found'); + }); + }); + + /** + * Save the generated url for test later + */ + casper.then(function createPartPrivateShare() { + return this.waitForSelector('#private-share > div > div > a', function onLinkGenerated() { + var url = this.fetchText('#private-share > div > div > a'); + urls.privatePartPermalinkExpired = url; + this.test.assert(true, 'Private share created expiring yesterday : ' + url); + }, function fail() { + this.capture('screenshot/sharedPartCreation/createPartPrivateShare-error.png'); + this.test.assert(false, 'Shared part cannot be shared as private'); + }); + + }); + + /** + * Close the modal + */ + casper.then(function closeSharedPartModal() { + + this.click('#share-modal > div.modal-header > button'); + + return this.waitWhileSelector('#share-modal', function modalClosed() { + this.test.assert(true, 'Shared part modal closed'); + }, function fail() { + this.capture('screenshot/sharedPartCreation/closeSharedPartModal-error.png'); + this.test.assert(false, 'Shared part modal cannot be closed'); + }); + + }); + + + casper.run(function allDone() { + return this.test.done(); + }); + +}); diff --git a/docdoku-web-front/tests/js/product-management/template/partTemplateCreation.js b/docdoku-web-front/tests/js/product-management/template/partTemplateCreation.js new file mode 100644 index 0000000000..63354520dc --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/template/partTemplateCreation.js @@ -0,0 +1,96 @@ +/*global casper,urls,products*/ + +casper.test.begin('Part template creation tests suite', 5, function partTemplateCreationTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part template nav + */ + casper.then(function waitForPartTemplateNavLink() { + return this.waitForSelector('#part-template-nav > .nav-list-entry > a', function clickPartTemplateNavLink() { + this.click('#part-template-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partTemplateCreation/waitForPartTemplateNavLink-error.png'); + this.test.assert(false, 'Part template nav link can not be found'); + }); + }); + + /** + * Wait for the creation button + */ + casper.then(function waitForPartTemplateCreationButton() { + return this.waitForSelector('.actions .new-template', function clickPartTemplateCreationButton() { + this.click('.actions .new-template'); + }, function fail() { + this.capture('screenshot/partTemplateCreation/waitForPartTemplateCreationButton-error.png'); + this.test.assert(false, 'Part template creation button can not be found'); + }); + }); + + /** + * Wait for the creation modal + */ + casper.then(function waitForPartTemplateCreationModal() { + return this.waitForSelector('#part_template_creation_modal', function modalOpened() { + + this.click('#part_template_creation_modal .modal-footer .btn-primary'); + this.test.assertExists('#part_template_creation_modal #part-template-reference:invalid', 'Should not create template without a reference'); + + }, function fail() { + this.capture('screenshot/partTemplateCreation/waitForPartTemplateCreationModal-error.png'); + this.test.assert(false, 'Part template creation modal not found'); + }); + }); + + + /** + * Fill and sumbit the form + */ + casper.then(function fillAndSubmitTheForm() { + this.sendKeys('#part_template_creation_modal #part-template-reference', products.template1.number, {reset: true}); + this.sendKeys('#part_template_creation_modal #part-template-type', products.template1.type, {reset: true}); + this.sendKeys('#part_template_creation_modal #part-template-mask', products.template1.mask, {reset: true}); + this.click('#part_template_creation_modal .modal-footer .btn-primary'); + }); + + + /** + * Wait for the modal to close + */ + casper.then(function waitForModalToClose() { + return this.waitWhileSelector('#part_template_creation_modal', function modalClosed() { + this.test.assert(true, 'Part template creation modal closed'); + }, function fail() { + this.capture('screenshot/partTemplateCreation/waitForModalToClose-error.png'); + this.test.assert(false, 'Part template creation modal not closed'); + }); + }); + + /** + * Check if template has been created in the list + */ + casper.then(function checkForTemplateCreated() { + return this.waitForSelector('#part_template_table .reference', function tableDisplayed() { + this.test.assertSelectorHasText('#part_template_table tbody tr:first-child td.reference', products.template1.number); + this.test.assertSelectorHasText('#part_template_table tbody tr:first-child td:nth-child(3)', products.template1.type); + this.test.assertSelectorHasText('#part_template_table tbody tr:first-child td:nth-child(4)', products.template1.mask); + }, function fail() { + this.capture('screenshot/partTemplateCreation/checkFortemplateCreated-error.png'); + this.test.assert(false, 'New part template can not be found'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/product-management/template/partTemplateDeletion.js b/docdoku-web-front/tests/js/product-management/template/partTemplateDeletion.js new file mode 100644 index 0000000000..97745e1515 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/template/partTemplateDeletion.js @@ -0,0 +1,81 @@ +/*global casper,urls*/ + +casper.test.begin('Part template deletion tests suite', 2, function partTemplateDeletionTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + /** + * Go to part template nav + */ + casper.then(function waitForPartTemplateNavLink() { + return this.waitForSelector('#part-template-nav > .nav-list-entry > a', function clickPartTemplateNavLink() { + this.click('#part-template-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partTemplateDeletion/waitForPartTemplateNavLink-error.png'); + this.test.assert(false, 'Part template nav link can not be found'); + }); + }); + + + /** + * Click the 'select all' checkbox + */ + casper.then(function waitForSelectAllCheckbox() { + return this.waitForSelector('#part_template_table thead tr:first-child th:first-child input', function clickOnSelectAllCheckbox() { + this.click('#part_template_table thead tr:first-child th:first-child input'); + }, function fail() { + this.capture('screenshot/partTemplateDeletion/waitForSelectAllCheckbox-error.png'); + this.test.assert(false, 'Select all checkbox can not be found'); + }); + }); + + /** + * Wait for the delete button to appear + */ + casper.then(function waitForDeleteButton() { + return this.waitForSelector('.actions .delete', function clickOnDeleteButton() { + this.click('.actions .delete'); + this.test.assert(true, 'Delete button available'); + }, function fail() { + this.capture('screenshot/partTemplateDeletion/waitForDeleteButton-error.png'); + this.test.assert(false, 'Select all checkbox can not be found'); + }); + }); + + /** + * Wait for the confirmation modal + */ + casper.then(function waitForConfirmationModal() { + return this.waitForSelector('.bootbox', function confirmDeletion() { + this.click('.bootbox .modal-footer .btn-primary'); + }, function fail() { + this.capture('screenshot/partTemplateDeletion/waitForConfirmationModal-error.png'); + this.test.assert(false, 'Part template deletion confirmation modal can not be found'); + }); + }); + + /** + * Assert that there's no more entries in the table + **/ + casper.then(function waitForTableToBeEmpty() { + return this.waitWhileSelector('#part_template_table tbody tr:first-child td:first-child input', function onBaselineTableEmpty() { + this.test.assert(true, 'No more part templates in the list'); + }, function fail() { + this.capture('screenshot/partTemplateDeletion/waitForTableToBeEmpty-error.png'); + this.test.assert(false, 'Part template table still not empty'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/js/product-management/template/templateWithAttribute.js b/docdoku-web-front/tests/js/product-management/template/templateWithAttribute.js new file mode 100644 index 0000000000..24b98d7130 --- /dev/null +++ b/docdoku-web-front/tests/js/product-management/template/templateWithAttribute.js @@ -0,0 +1,284 @@ +/*global casper,urls,products*/ + +casper.test.begin('Part template attributes tests suite', 14, function partTemplateCreationTestsSuite() { + 'use strict'; + + casper.open(''); + + /** + * Open product management URL + * */ + + casper.then(function () { + return this.open(urls.productManagement); + }); + + casper.then(function waitForPartTemplateNavLink() { + return this.waitForSelector('#part-template-nav > .nav-list-entry > a', function clickPartTemplateNavLink() { + this.click('#part-template-nav > .nav-list-entry > a'); + }, function fail() { + this.capture('screenshot/partTemplateCreation/waitForPartTemplateNavLink-error.png'); + this.test.assert(false, 'Part template nav link can not be found'); + }); + }); + + /** + * Edit the first template + */ + casper.then(function waitForPartTemplateContent() { + //should select the one created + return this.waitForSelector('#part_template_table tbody tr:first-child td.reference', function clickPartTemplateCreationButton() { + this.click('#part_template_table tbody tr:first-child td.reference'); + }, function fail() { + this.capture('screenshot/partTemplateCreation/waitForPartTemplateCreationButton-error.png'); + this.test.assert(false, 'Part template creation button can not be found'); + }); + }); + + /** + * open attribute tab + */ + casper.then(function () { + var attributesTabSelector = '.nav.nav-tabs > li:nth-child(3) > a'; + return this.waitForSelector(attributesTabSelector, function openTab() { + this.click(attributesTabSelector); + }, function fail() { + this.capture('screenshot/attributes/clickOnAttributeTab-error.png'); + this.test.assert(false, 'Attribute tab cannot be found'); + }); + }); + + /** + * wait for tab to be active + */ + casper.then(function assertTabActive() { + return this.waitForSelector('.nav.nav-tabs > li:nth-child(3).active', function () { + this.test.assert(true, 'attributes tab active'); + }, function () { + this.capture('screenshot/attributes/attributeTabBecomeActive-error.png'); + this.test.assert(false, 'Attribute tab not appearing'); + }); + }); + + /** + * add new attributes + */ + casper.then(function addNewAttributes() { + var addAttributeButtonSelector = '#attributes-list .btn.add'; + return this.waitForSelector(addAttributeButtonSelector, function () { + this.click(addAttributeButtonSelector); + }, function () { + this.capture('screenshot/attributes/attributeTabBecomeActive-error.png'); + this.test.assert(false, 'Add attribute button not found'); + }); + }); + + /** + * Test the lock/freeze/mandatory checkbox present + */ + casper.then(function testLockAttributePresent() { + return this.waitForSelector('.lock', function () { + this.test.assert(true, 'lock checkbox present'); + this.test.assertExists('.attribute-locked', 'attribute-locked present'); + this.test.assertExists('.attribute-mandatory', 'attribute-mandatory present'); + }, function fail() { + this.capture('screenshot/attributes/all-lock-not-found.png'); + this.test.assert(false, 'the lock all checkbox should exist'); + }); + }); + + /** + * Clicking on freeze (.lock) should remove the attribute-locked checkbox + */ + casper.then(function testAttrLockRemoved() { + this.click('.lock'); + return this.waitWhileSelector('.attribute-locked', function removingAttrLock() { + this.test.assert(true, 'attribute-locked removed'); + this.test.assertExists('.attribute-mandatory', 'attribute-mandatory present'); + }, function fail() { + this.capture('screenshot/attributes/AttributeLockNotRemoved.png'); + this.test.assert(false, 'attribute-locked should have been removed'); + }); + + }); + + /** + * when freeze is not checked, the attribute-locked checkbox should appear + */ + casper.then(function testAttrLockPresent() { + this.click('.lock'); + return this.waitForSelector('.attribute-locked', function () { + this.test.assert(true, 'attribute-locked exist'); + }, function fail() { + this.test.assert(false, 'attribute-locked should exist'); + }); + }); + + /** + * Creation of a template with all attributes lock + */ + casper.then(function () { + this.click('.lock'); + this.click('#attributes-list .btn.add'); + return this.waitForSelector('.list-item.well:nth-child(2)', function () { + this.test.assertElementCount('.list-item.well', 2); + this.sendKeys('#attributes-list .list-item:nth-child(1) input.name', products.part2.attributeName1, {reset: true}); + this.sendKeys('#attributes-list .list-item:nth-child(2) input.name', products.part2.attributeName1, {reset: true}); + this.click('#part_template_creation_modal .btn.btn-primary'); + }, function () { + this.capture('screenshot/attributes/addAttribute-error.png'); + this.test.assert(false, 'Attribute not appearing in the list'); + }); + + }); + + casper.then(function closeModal() { + return this.waitWhileSelector('#part_template_creation_modal', function () { + this.test.assert(true, 'modal closed'); + }, function fail() { + this.capture('screenshot/attributes/closeModal-error.png'); + this.test.assert(false, 'could not close the modal'); + }); + }); + + /** + * Check if the template has been created and open the edit modal + */ + casper.then(function openEditModal() { + return this.waitForSelector('#part_template_table tbody tr:first-child td.reference', function clickPartTemplateCreationButton() { + this.click('#part_template_table tbody tr:first-child td.reference'); + + }, function fail() { + this.capture('screenshot/partTemplateCreation/waitForPartTemplateCreationButton-error.png'); + this.test.assert(false, 'Part template creation button can not be found'); + }); + }); + + /** + * open attribute tab + */ + casper.then(function openAttrTab() { + var attributesTabSelector = '.nav.nav-tabs > li:nth-child(3) > a'; + return this.waitForSelector(attributesTabSelector, function openTab() { + this.click(attributesTabSelector); + }, function fail() { + this.capture('screenshot/attributes/clickOnAttributeTab-error.png'); + this.test.assert(false, 'Attribute tab cannot be found'); + }); + }); + + + casper.then(function () { + return this.waitForSelector('#attributes-list .list-item.well:nth-child(2)', function () { + this.test.assertElementCount('#attributes-list .list-item.well', 2); + }, function fail() { + this.capture('screenshot/partTemplateCreation/waitForAttrTab-error.png'); + this.test.assert(false, 'could not open modal for edition'); + }); + }); + + /** + * close modal + */ + casper.then(function () { + this.click('.btn[data-dismiss="modal"]'); + return this.waitWhileSelector('#part_template_creation_modal', function () { + this.test.assert(true, 'modal closed'); + }, + function fai() { + this.capture('screenshot/attributes/CloseModal-error.png'); + this.test.assert(false, 'could not close modal'); + }); + }); + + /** + * Wait for the creation button + */ + casper.then(function waitForPartTemplateCreationButton() { + return this.waitForSelector('.actions .new-template', function clickPartTemplateCreationButton() { + this.click('.actions .new-template'); + }, function fail() { + this.capture('screenshot/partTemplateCreation/waitForPartTemplateCreationButton-error.png'); + this.test.assert(false, 'Part template creation button can not be found'); + }); + }); + + + /** + * Fill template form + */ + casper.then(function fillTheForm() { + return this.waitForSelector('#part_template_creation_modal', function modalOpened() { + this.sendKeys('#part_template_creation_modal #part-template-reference', products.template2.number, {reset: true}); + this.sendKeys('#part_template_creation_modal #part-template-type', products.template2.type, {reset: true}); + }, function fail() { + this.capture('screenshot/partTemplateCreation/waitForPartTemplateCreationModal-error.png'); + this.test.assert(false, 'Part template creation modal not found'); + }); + + + }); + + /** + * Go to the attributes tab + */ + casper.then(function goToAttributesTab() { + this.click('.nav.nav-tabs > li:nth-child(3) > a'); + return this.waitForSelector('.nav.nav-tabs > li:nth-child(3).active', function () { + this.test.assert(true, 'Attribute Tab active'); + }, function fail() { + this.capture('screenshot/attributes/attributeTabBecomeActive-error.png'); + this.test.assert(false, 'Attribute tab not appearing'); + }); + }); + + casper.then(function addAttributes() { + var addAttributeButtonSelector = '#attributes-list .btn.add'; + var addInstanceAttributeButtonSelector = '#attribute-product-instance-list .btn.add'; + return this.waitForSelector(addAttributeButtonSelector, function () { + this.click(addAttributeButtonSelector); + this.click(addAttributeButtonSelector); + //add an instance attribute + this.click(addInstanceAttributeButtonSelector); + }, function () { + this.capture('screenshot/attributes/addButtonNotAppearing.png'); + this.test.assert(false, 'Add attribute button not found'); + }); + }); + + /** + * fill the attribute of the template + */ + casper.then(function fillTemplateAttributes() { + + return this.waitForSelector('#attribute-product-instance-list .list-item.well', function () { + this.test.assertElementCount('.list-item.well', 3, '3 elements should have been created, 2 attributes and' + + ' 1 instance attribute'); + this.sendKeys('#attributes-list .list-item:nth-child(1) input.name', products.part2.attributeName1, {reset: true}); + this.sendKeys('#attributes-list .list-item:nth-child(2) input.name', products.part2.attributeName2, {reset: true}); + this.sendKeys('#attribute-product-instance-list .list-item input.name', products.template2.attrInstance, {reset: true}); + this.click('#attributes-list .list-item:nth-child(2) .checkbox.attribute-locked'); + this.click('#attributes-list .list-item:nth-child(2) .checkbox.attribute-mandatory'); + this.click('#attribute-product-instance-list .list-item.well .checkbox.attribute-locked'); + this.click('#attribute-product-instance-list .list-item.well .checkbox.attribute-mandatory'); + this.click('#part_template_creation_modal .btn.btn-primary'); + }, function () { + this.capture('screenshot/attributes/addAttribute-error.png'); + this.test.assert(false, 'Attribute not appearing in the list'); + }); + + }); + + + casper.then(function saveTemplate() { + return this.waitWhileSelector('#part_template_creation_modal', function () { + this.test.assert(true, 'modal closed'); + }, function fail() { + this.test.assert(false, 'could not close the modal'); + }); + }); + + casper.run(function allDone() { + return this.test.done(); + }); +}); diff --git a/docdoku-web-front/tests/res/document-upload.txt b/docdoku-web-front/tests/res/document-upload.txt new file mode 100644 index 0000000000..0f3c53cf31 --- /dev/null +++ b/docdoku-web-front/tests/res/document-upload.txt @@ -0,0 +1,3537 @@ +# 2652 vertices, 884 faces +v 260.312 40.203 -440.008 +v 505.639 40.2029 -412.719 +v 558.965 -9.41835 -404.09 +v 558.965 -9.41835 -404.09 +v 252.222 -54.205 -402.864 +v 260.312 40.203 -440.008 +v 21.7976 40.203 -426.108 +v 260.312 40.203 -440.008 +v 252.222 -54.205 -402.864 +v 252.222 -54.205 -402.864 +v 32.5665 -78.854 -387.298 +v 21.7976 40.203 -426.108 +v -107.623 -99.0134 -353.534 +v -359.273 -19.0616 -211.385 +v -378.533 40.2031 -238.981 +v -378.533 40.2031 -238.981 +v -233.929 40.203 -337.011 +v -107.623 -99.0134 -353.534 +v -627.402 40.2031 -27.0373 +v -378.533 40.2031 -238.981 +v -359.273 -19.0616 -211.385 +v -359.273 -19.0616 -211.385 +v -598.079 -17.5054 13.3313 +v -627.402 40.2031 -27.0373 +v -605.1 40.2031 174.866 +v -635.946 -19.0616 145.914 +v -547.166 40.2031 372.164 +v -99.0235 40.203 155.844 +v 22.0084 -55.5322 181.514 +v -25.3967 40.203 194.286 +v 246.48 40.203 170.173 +v -25.3967 40.203 194.286 +v 22.0084 -55.5322 181.514 +v 22.0084 -55.5322 181.514 +v 246.315 -79.6185 147.057 +v 246.48 40.203 170.173 +v 568.185 40.2029 165.976 +v 246.48 40.203 170.173 +v 246.315 -79.6185 147.057 +v 246.315 -79.6185 147.057 +v 542.48 -44.913 151.06 +v 568.185 40.2029 165.976 +v 762.912 40.2029 67.4993 +v 568.185 40.2029 165.976 +v 542.48 -44.913 151.06 +v 542.48 -44.913 151.06 +v 758.412 -62.8404 43.888 +v 762.912 40.2029 67.4993 +v 841.425 40.2029 35.9378 +v 762.912 40.2029 67.4993 +v 758.412 -62.8404 43.888 +v 758.412 -62.8404 43.888 +v 789.328 -72.2831 -20.678 +v 841.425 40.2029 35.9378 +v 912.514 40.2028 48.5509 +v 841.425 40.2029 35.9378 +v 789.328 -72.2831 -20.678 +v 789.328 -72.2831 -20.678 +v 918.453 -95.2934 -14.2217 +v 912.514 40.2028 48.5509 +v 252.222 -54.205 -402.864 +v 558.965 -9.41835 -404.09 +v 268.451 -169.261 -290.85 +v 32.5665 -78.854 -387.298 +v 252.222 -54.205 -402.864 +v 268.451 -169.261 -290.85 +v -598.079 -17.5054 13.3313 +v -359.273 -19.0616 -211.385 +v -321.215 -77.6915 -143.919 +v -321.215 -77.6915 -143.919 +v -537.312 -100.256 32.2996 +v -598.079 -17.5054 13.3313 +v -359.273 -19.0616 -211.385 +v -107.623 -99.0134 -353.534 +v -321.215 -77.6915 -143.919 +v 260.646 -223.493 -153.714 +v 268.451 -169.261 -290.85 +v 596.877 -226.42 -202.43 +v 596.877 -226.42 -202.43 +v 380.051 -251.531 -66.4672 +v 260.646 -223.493 -153.714 +v 39.9728 -156.039 -209.082 +v 32.5665 -78.854 -387.298 +v 268.451 -169.261 -290.85 +v 268.451 -169.261 -290.85 +v 260.646 -223.493 -153.714 +v 39.9728 -156.039 -209.082 +v -107.623 -99.0134 -353.534 +v 32.5665 -78.854 -387.298 +v 39.9728 -156.039 -209.082 +v 39.9728 -156.039 -209.082 +v -121.936 -142.274 -170.547 +v -107.623 -99.0134 -353.534 +v -537.312 -100.256 32.2996 +v -321.215 -77.6915 -143.919 +v -391.04 -139.908 41.853 +v -321.215 -77.6915 -143.919 +v -107.623 -99.0134 -353.534 +v -121.936 -142.274 -170.547 +v 39.9728 -156.039 -209.082 +v 260.646 -223.493 -153.714 +v 242.389 -203.746 -21.5963 +v 242.389 -203.746 -21.5963 +v 22.0431 -156.814 26.5261 +v 39.9728 -156.039 -209.082 +v -121.936 -142.274 -170.547 +v 39.9728 -156.039 -209.082 +v 22.0431 -156.814 26.5261 +v 22.0431 -156.814 26.5261 +v -77.3162 -125.878 32.3855 +v -121.936 -142.274 -170.547 +v -391.04 -139.908 41.853 +v -321.215 -77.6915 -143.919 +v -213.771 -97.4809 92.1041 +v -213.771 -97.4809 92.1041 +v -298.191 -103.042 127.509 +v -391.04 -139.908 41.853 +v -321.215 -77.6915 -143.919 +v -121.936 -142.274 -170.547 +v -77.3162 -125.878 32.3855 +v -77.3162 -125.878 32.3855 +v -213.771 -97.4809 92.1041 +v -321.215 -77.6915 -143.919 +v 242.389 -203.746 -21.5963 +v 380.051 -251.531 -66.4672 +v 494.4 -118.67 153.3 +v 494.4 -118.67 153.3 +v 257.239 -161.894 50.0724 +v 242.389 -203.746 -21.5963 +v 22.0431 -156.814 26.5261 +v 242.389 -203.746 -21.5963 +v 257.239 -161.894 50.0724 +v 257.239 -161.894 50.0724 +v 34.0969 -108.118 134.952 +v 22.0431 -156.814 26.5261 +v -77.3162 -125.878 32.3855 +v 22.0431 -156.814 26.5261 +v 34.0969 -108.118 134.952 +v 34.0969 -108.118 134.952 +v -50.7188 -67.4952 123.835 +v -77.3162 -125.878 32.3855 +v -298.191 -103.042 127.509 +v -213.771 -97.4809 92.1041 +v -257.987 -30.6083 139.94 +v -257.987 -30.6083 139.94 +v -314.968 -77.0878 227.647 +v -298.191 -103.042 127.509 +v -213.771 -97.4809 92.1041 +v -77.3162 -125.878 32.3855 +v -50.7188 -67.4952 123.835 +v -50.7188 -67.4952 123.835 +v -257.987 -30.6083 139.94 +v -213.771 -97.4809 92.1041 +v 246.315 -79.6185 147.057 +v 257.239 -161.894 50.0724 +v 494.4 -118.67 153.3 +v 494.4 -118.67 153.3 +v 542.48 -44.913 151.06 +v 246.315 -79.6185 147.057 +v 34.0969 -108.118 134.952 +v 257.239 -161.894 50.0724 +v 246.315 -79.6185 147.057 +v 246.315 -79.6185 147.057 +v 22.0084 -55.5322 181.514 +v 34.0969 -108.118 134.952 +v -50.7188 -67.4952 123.835 +v 34.0969 -108.118 134.952 +v 22.0084 -55.5322 181.514 +v 22.0084 -55.5322 181.514 +v -99.0235 40.203 155.844 +v -50.7188 -67.4952 123.835 +v -314.968 -77.0878 227.647 +v -257.987 -30.6083 139.94 +v -260.462 40.203 162.299 +v -260.462 40.203 162.299 +v -284.581 40.203 304.684 +v -314.968 -77.0878 227.647 +v -257.987 -30.6083 139.94 +v -50.7188 -67.4952 123.835 +v -99.0235 40.203 155.844 +v -99.0235 40.203 155.844 +v -260.462 40.203 162.299 +v -257.987 -30.6083 139.94 +v 558.965 -9.41835 -404.09 +v 676.056 -10.6067 -403.457 +v 705.303 -138.614 -221.823 +v 729.558 -244.167 -136.921 +v 846.834 -109.506 -138.578 +v 789.328 -72.2831 -20.678 +v -635.946 -19.0616 145.914 +v -598.079 -17.5054 13.3313 +v -537.312 -100.256 32.2996 +v -537.312 -100.256 32.2996 +v -615.112 -86.8028 174.866 +v -635.946 -19.0616 145.914 +v -547.166 40.2031 372.164 +v -635.946 -19.0616 145.914 +v -615.112 -86.8028 174.866 +v -615.112 -86.8028 174.866 +v -550.064 -44.9519 307.245 +v -547.166 40.2031 372.164 +v -284.581 40.203 304.684 +v -547.166 40.2031 372.164 +v -550.064 -44.9519 307.245 +v -550.064 -44.9519 307.245 +v -314.968 -77.0878 227.647 +v -284.581 40.203 304.684 +v -537.312 -100.256 32.2996 +v -391.04 -139.908 41.853 +v -281.314 -243.634 -48.6181 +v -281.314 -243.634 -48.6181 +v -410.356 -316.705 -17.6217 +v -537.312 -100.256 32.2996 +v -391.04 -139.908 41.853 +v -298.191 -103.042 127.509 +v -169.529 -145.953 141.817 +v -169.529 -145.953 141.817 +v -281.314 -243.634 -48.6181 +v -391.04 -139.908 41.853 +v -298.191 -103.042 127.509 +v -314.968 -77.0878 227.647 +v -255.603 -138.915 227.647 +v -255.603 -138.915 227.647 +v -169.529 -145.953 141.817 +v -298.191 -103.042 127.509 +v -615.112 -86.8028 174.866 +v -537.312 -100.256 32.2996 +v -410.356 -316.705 -17.6217 +v -410.356 -316.705 -17.6217 +v -465.797 -326.302 178.963 +v -615.112 -86.8028 174.866 +v -550.064 -44.9519 307.245 +v -615.112 -86.8028 174.866 +v -465.797 -326.302 178.963 +v -465.797 -326.302 178.963 +v -440.269 -242.311 338.364 +v -550.064 -44.9519 307.245 +v -314.968 -77.0878 227.647 +v -550.064 -44.9519 307.245 +v -440.269 -242.311 338.364 +v -440.269 -242.311 338.364 +v -255.603 -138.915 227.647 +v -314.968 -77.0878 227.647 +v -410.356 -316.705 -17.6217 +v -281.314 -243.634 -48.6181 +v 14.2079 -335.227 -45.2649 +v 14.2079 -335.227 -45.2649 +v -24.9719 -444.533 -3.8576 +v -410.356 -316.705 -17.6217 +v -281.314 -243.634 -48.6181 +v -169.529 -145.953 141.817 +v 32.0551 -247.885 -11.6046 +v 32.0551 -247.885 -11.6046 +v 14.2079 -335.227 -45.2649 +v -281.314 -243.634 -48.6181 +v -169.529 -145.953 141.817 +v -255.603 -138.915 227.647 +v -15.0917 -242.667 60.2354 +v -15.0917 -242.667 60.2354 +v 32.0551 -247.885 -11.6046 +v -169.529 -145.953 141.817 +v -465.797 -326.302 178.963 +v -410.356 -316.705 -17.6217 +v -24.9719 -444.533 -3.8576 +v -24.9719 -444.533 -3.8576 +v -103.65 -447.284 112.409 +v -465.797 -326.302 178.963 +v -440.269 -242.311 338.364 +v -465.797 -326.302 178.963 +v -103.65 -447.284 112.409 +v -103.65 -447.284 112.409 +v -132.463 -330.935 170.696 +v -440.269 -242.311 338.364 +v -255.603 -138.915 227.647 +v -440.269 -242.311 338.364 +v -132.463 -330.935 170.696 +v -132.463 -330.935 170.696 +v -15.0917 -242.667 60.2354 +v -255.603 -138.915 227.647 +v -24.9719 -444.533 -3.8576 +v 14.2079 -335.227 -45.2649 +v 154.274 -422.936 15.1391 +v 154.274 -422.936 15.1391 +v 65.7941 -437.565 48.1499 +v -24.9719 -444.533 -3.8576 +v 14.2079 -335.227 -45.2649 +v 32.0551 -247.885 -11.6046 +v 160.1 -339.077 42.0847 +v 160.1 -339.077 42.0847 +v 154.274 -422.936 15.1391 +v 14.2079 -335.227 -45.2649 +v 32.0551 -247.885 -11.6046 +v -15.0917 -242.667 60.2354 +v 65.6433 -310.298 103.599 +v 65.6433 -310.298 103.599 +v 160.1 -339.077 42.0847 +v 32.0551 -247.885 -11.6046 +v -103.65 -447.284 112.409 +v -24.9719 -444.533 -3.8576 +v 65.7941 -437.565 48.1499 +v 65.7941 -437.565 48.1499 +v -52.7382 -378.505 120.825 +v -103.65 -447.284 112.409 +v -132.463 -330.935 170.696 +v -103.65 -447.284 112.409 +v -52.7382 -378.505 120.825 +v -52.7382 -378.505 120.825 +v -86.6906 -345.821 159.574 +v -132.463 -330.935 170.696 +v -15.0917 -242.667 60.2354 +v -132.463 -330.935 170.696 +v -86.6906 -345.821 159.574 +v -86.6906 -345.821 159.574 +v 65.6433 -310.298 103.599 +v -15.0917 -242.667 60.2354 +v 65.7941 -437.565 48.1499 +v 154.274 -422.936 15.1391 +v 143.503 -427.832 162.674 +v 143.503 -427.832 162.674 +v 76.3055 -449.154 158.966 +v 65.7941 -437.565 48.1499 +v 154.274 -422.936 15.1391 +v 160.1 -339.077 42.0847 +v 140.897 -341.135 179.078 +v 140.897 -341.135 179.078 +v 143.503 -427.832 162.674 +v 154.274 -422.936 15.1391 +v 160.1 -339.077 42.0847 +v 65.6433 -310.298 103.599 +v 61.6315 -308.948 188.674 +v 61.6315 -308.948 188.674 +v 140.897 -341.135 179.078 +v 160.1 -339.077 42.0847 +v -52.7382 -378.505 120.825 +v 65.7941 -437.565 48.1499 +v 76.3055 -449.154 158.966 +v 76.3055 -449.154 158.966 +v -56.9874 -407.19 172.668 +v -52.7382 -378.505 120.825 +v -86.6906 -345.821 159.574 +v -52.7382 -378.505 120.825 +v -56.9874 -407.19 172.668 +v -56.9874 -407.19 172.668 +v -101.405 -340.868 187.717 +v -86.6906 -345.821 159.574 +v 65.6433 -310.298 103.599 +v -86.6906 -345.821 159.574 +v -101.405 -340.868 187.717 +v -101.405 -340.868 187.717 +v 61.6315 -308.948 188.674 +v 65.6433 -310.298 103.599 +v 76.3055 -449.154 158.966 +v 143.503 -427.832 162.674 +v -57.088 -363.103 388.882 +v -57.088 -363.103 388.882 +v -86.187 -376.695 363.046 +v 76.3055 -449.154 158.966 +v 143.503 -427.832 162.674 +v 140.897 -341.135 179.078 +v -61.2255 -234.579 389.398 +v -61.2255 -234.579 389.398 +v -57.088 -363.103 388.882 +v 143.503 -427.832 162.674 +v 140.897 -341.135 179.078 +v 61.6315 -308.948 188.674 +v -121.906 -216.383 347.524 +v -121.906 -216.383 347.524 +v -61.2255 -234.579 389.398 +v 140.897 -341.135 179.078 +v -56.9874 -407.19 172.668 +v 76.3055 -449.154 158.966 +v -86.187 -376.695 363.046 +v -86.187 -376.695 363.046 +v -171.947 -353.317 301.741 +v -56.9874 -407.19 172.668 +v -101.405 -340.868 187.717 +v -56.9874 -407.19 172.668 +v -171.947 -353.317 301.741 +v -171.947 -353.317 301.741 +v -194.818 -237.297 287.062 +v -101.405 -340.868 187.717 +v 61.6315 -308.948 188.674 +v -101.405 -340.868 187.717 +v -194.818 -237.297 287.062 +v -194.818 -237.297 287.062 +v -121.906 -216.383 347.524 +v 61.6315 -308.948 188.674 +v -86.187 -376.695 363.046 +v -57.088 -363.103 388.882 +v -102.277 -349.579 417.599 +v -102.277 -349.579 417.599 +v -120.57 -363.511 397.079 +v -86.187 -376.695 363.046 +v -57.088 -363.103 388.882 +v -61.2255 -234.579 389.398 +v -101.46 -226.311 419.247 +v -101.46 -226.311 419.247 +v -102.277 -349.579 417.599 +v -57.088 -363.103 388.882 +v -61.2255 -234.579 389.398 +v -121.906 -216.383 347.524 +v -150.824 -214.018 384.223 +v -150.824 -214.018 384.223 +v -101.46 -226.311 419.247 +v -61.2255 -234.579 389.398 +v -171.947 -353.317 301.741 +v -86.187 -376.695 363.046 +v -120.57 -363.511 397.079 +v -120.57 -363.511 397.079 +v -209.07 -348.909 327.758 +v -171.947 -353.317 301.741 +v -194.818 -237.297 287.062 +v -171.947 -353.317 301.741 +v -209.07 -348.909 327.758 +v -209.07 -348.909 327.758 +v -220.374 -237.75 317.345 +v -194.818 -237.297 287.062 +v -121.906 -216.383 347.524 +v -194.818 -237.297 287.062 +v -220.374 -237.75 317.345 +v -220.374 -237.75 317.345 +v -150.824 -214.018 384.223 +v -121.906 -216.383 347.524 +v -120.57 -363.511 397.079 +v -102.277 -349.579 417.599 +v -216.691 -299.362 512.471 +v -216.691 -299.362 512.471 +v -279.207 -318.598 477.521 +v -120.57 -363.511 397.079 +v -102.277 -349.579 417.599 +v -101.46 -226.311 419.247 +v -220.533 -242.736 524.241 +v -220.533 -242.736 524.241 +v -216.691 -299.362 512.471 +v -102.277 -349.579 417.599 +v -101.46 -226.311 419.247 +v -150.824 -214.018 384.223 +v -257.957 -155.418 505.428 +v -257.957 -155.418 505.428 +v -220.533 -242.736 524.241 +v -101.46 -226.311 419.247 +v -209.07 -348.909 327.758 +v -120.57 -363.511 397.079 +v -279.207 -318.598 477.521 +v -279.207 -318.598 477.521 +v -372.739 -259.441 407.375 +v -209.07 -348.909 327.758 +v -220.374 -237.75 317.345 +v -209.07 -348.909 327.758 +v -372.739 -259.441 407.375 +v -372.739 -259.441 407.375 +v -394.838 -179.247 404.628 +v -220.374 -237.75 317.345 +v -150.824 -214.018 384.223 +v -220.374 -237.75 317.345 +v -394.838 -179.247 404.628 +v -394.838 -179.247 404.628 +v -257.957 -155.418 505.428 +v -150.824 -214.018 384.223 +v -279.207 -318.598 477.521 +v -216.691 -299.362 512.471 +v -236.316 -297.066 568.835 +v -236.316 -297.066 568.835 +v -317.133 -381.502 577.263 +v -279.207 -318.598 477.521 +v -216.691 -299.362 512.471 +v -220.533 -242.736 524.241 +v -240.158 -240.44 580.604 +v -240.158 -240.44 580.604 +v -236.316 -297.066 568.835 +v -216.691 -299.362 512.471 +v -220.533 -242.736 524.241 +v -257.957 -155.418 505.428 +v -231.113 -142.03 578.573 +v -231.113 -142.03 578.573 +v -240.158 -240.44 580.604 +v -220.533 -242.736 524.241 +v -372.739 -259.441 407.375 +v -279.207 -318.598 477.521 +v -317.133 -381.502 577.263 +v -317.133 -381.502 577.263 +v -521.043 -278.287 466.938 +v -372.739 -259.441 407.375 +v -394.838 -179.247 404.628 +v -372.739 -259.441 407.375 +v -521.043 -278.287 466.938 +v -521.043 -278.287 466.938 +v -543.141 -146.696 464.192 +v -394.838 -179.247 404.628 +v -257.957 -155.418 505.428 +v -394.838 -179.247 404.628 +v -543.141 -146.696 464.192 +v -543.141 -146.696 464.192 +v -231.113 -142.03 578.573 +v -257.957 -155.418 505.428 +v -317.133 -381.502 577.263 +v -236.316 -297.066 568.835 +v -143.793 -387.554 589.922 +v -143.793 -387.554 589.922 +v -180.616 -450.221 583.426 +v -317.133 -381.502 577.263 +v -236.316 -297.066 568.835 +v -240.158 -240.44 580.604 +v -123.358 -345.738 615.393 +v -123.358 -345.738 615.393 +v -143.793 -387.554 589.922 +v -236.316 -297.066 568.835 +v -240.158 -240.44 580.604 +v -231.113 -142.03 578.573 +v -97.4354 -286.208 662.435 +v -97.4354 -286.208 662.435 +v -123.358 -345.738 615.393 +v -240.158 -240.44 580.604 +v -521.043 -278.287 466.938 +v -317.133 -381.502 577.263 +v -384.975 -438.572 704.341 +v -384.975 -438.572 704.341 +v -493.77 -277.971 624.724 +v -521.043 -278.287 466.938 +v -543.141 -146.696 464.192 +v -521.043 -278.287 466.938 +v -493.77 -277.971 624.724 +v -493.77 -277.971 624.724 +v -513.328 -198.074 614.68 +v -543.141 -146.696 464.192 +v -231.113 -142.03 578.573 +v -543.141 -146.696 464.192 +v -513.328 -198.074 614.68 +v -513.328 -198.074 614.68 +v -308.634 -169.787 722.901 +v -231.113 -142.03 578.573 +v -317.133 -381.502 577.263 +v -180.616 -450.221 583.426 +v -207.244 -454.329 671.558 +v -207.244 -454.329 671.558 +v -384.975 -438.572 704.341 +v -317.133 -381.502 577.263 +v -97.4354 -286.208 662.435 +v -231.113 -142.03 578.573 +v -308.634 -169.787 722.901 +v -308.634 -169.787 722.901 +v -117.005 -283.655 756.407 +v -97.4354 -286.208 662.435 +v -493.77 -277.971 624.724 +v -384.975 -438.572 704.341 +v -351.878 -267.828 729.182 +v -351.878 -267.828 729.182 +v -470.519 -230.036 648.255 +v -493.77 -277.971 624.724 +v -513.328 -198.074 614.68 +v -493.77 -277.971 624.724 +v -470.519 -230.036 648.255 +v -308.634 -169.787 722.901 +v -513.328 -198.074 614.68 +v -470.519 -230.036 648.255 +v -470.519 -230.036 648.255 +v -351.878 -267.828 729.182 +v -308.634 -169.787 722.901 +v -384.975 -438.572 704.341 +v -207.244 -454.329 671.558 +v -158.686 -363.501 717.111 +v -158.686 -363.501 717.111 +v -351.878 -267.828 729.182 +v -384.975 -438.572 704.341 +v -117.005 -283.655 756.407 +v -308.634 -169.787 722.901 +v -351.878 -267.828 729.182 +v -351.878 -267.828 729.182 +v -158.686 -363.501 717.111 +v -117.005 -283.655 756.407 +v -180.616 -450.221 583.426 +v -143.793 -387.554 589.922 +v -66.3276 -504.127 711.51 +v -123.358 -345.738 615.393 +v -97.4354 -286.208 662.435 +v -9.81772 -392.599 748.063 +v -207.244 -454.329 671.558 +v -180.616 -450.221 583.426 +v -66.3276 -504.127 711.51 +v -97.4354 -286.208 662.435 +v -117.005 -283.655 756.407 +v -9.81772 -392.599 748.063 +v -158.686 -363.501 717.111 +v -207.244 -454.329 671.558 +v -66.3276 -504.127 711.51 +v -117.005 -283.655 756.407 +v -158.686 -363.501 717.111 +v -9.81772 -392.599 748.063 +v -143.793 -387.554 589.922 +v -123.358 -345.738 615.393 +v -158.686 -363.501 717.111 +v -123.358 -345.738 615.393 +v -9.81772 -392.599 748.063 +v -158.686 -363.501 717.111 +v -66.3276 -504.127 711.51 +v -143.793 -387.554 589.922 +v -158.686 -363.501 717.111 +v 676.056 -10.6067 -403.457 +v 679.933 40.2029 -441.705 +v 824.669 40.2029 -424.477 +v 824.669 40.2029 -424.477 +v 843.785 -71.3395 -368.243 +v 676.056 -10.6067 -403.457 +v 843.785 -71.3395 -368.243 +v 824.669 40.2029 -424.477 +v 878.609 40.2028 -424.149 +v 878.609 40.2028 -424.149 +v 885.246 -63.9092 -351.891 +v 843.785 -71.3395 -368.243 +v 885.246 -63.9092 -351.891 +v 878.609 40.2028 -424.149 +v 909.031 40.2028 -425.978 +v 909.031 40.2028 -425.978 +v 922.953 -39.6783 -375.782 +v 885.246 -63.9092 -351.891 +v 922.953 -39.6783 -375.782 +v 909.031 40.2028 -425.978 +v 968.589 40.2028 -404.236 +v 968.589 40.2028 -404.236 +v 960.668 2.14378 -383.161 +v 922.953 -39.6783 -375.782 +v 960.668 2.14378 -383.161 +v 968.589 40.2028 -404.236 +v 1061.35 40.2028 -356.762 +v 1061.35 40.2028 -356.762 +v 1053.7 -18.2369 -346.758 +v 960.668 2.14378 -383.161 +v 1053.7 -18.2369 -346.758 +v 1061.35 40.2028 -356.762 +v 1119.22 40.2028 -284.353 +v 1119.22 40.2028 -284.353 +v 1117.05 -14.6657 -272.841 +v 1053.7 -18.2369 -346.758 +v 1117.05 -14.6657 -272.841 +v 1119.22 40.2028 -284.353 +v 1152.01 40.2028 -217.427 +v 1152.01 40.2028 -217.427 +v 1130.63 -9.82445 -219.113 +v 1117.05 -14.6657 -272.841 +v 1130.63 -9.82445 -219.113 +v 1152.01 40.2028 -217.427 +v 1154.32 40.2028 -99.7808 +v 1154.32 40.2028 -99.7808 +v 1120.75 -32.3903 -128.277 +v 1130.63 -9.82445 -219.113 +v 1120.75 -32.3903 -128.277 +v 1154.32 40.2028 -99.7808 +v 1150.35 40.2028 -66.9116 +v 1150.35 40.2028 -66.9116 +v 1077.52 -88.1132 -75.2012 +v 1120.75 -32.3903 -128.277 +v 972.648 -63.5674 25.3677 +v 984.155 40.2028 76.1361 +v 948.344 40.2028 68.9338 +v 972.648 -63.5674 25.3677 +v 948.344 40.2028 68.9338 +v 912.514 40.2028 48.5509 +v 912.514 40.2028 48.5509 +v 918.453 -95.2934 -14.2217 +v 972.648 -63.5674 25.3677 +v 758.412 -62.8404 43.888 +v 542.48 -44.913 151.06 +v 682.576 -133.836 164.696 +v 682.576 -133.836 164.696 +v 766.139 -190.462 -12.7916 +v 758.412 -62.8404 43.888 +v 789.328 -72.2831 -20.678 +v 758.412 -62.8404 43.888 +v 766.139 -190.462 -12.7916 +v 766.139 -190.462 -12.7916 +v 729.558 -244.167 -136.921 +v 789.328 -72.2831 -20.678 +v 380.051 -251.531 -66.4672 +v 545.802 -338.023 -93.8417 +v 415.539 -273.108 12.9001 +v 380.051 -251.531 -66.4672 +v 415.539 -273.108 12.9001 +v 494.4 -118.67 153.3 +v 542.48 -44.913 151.06 +v 494.4 -118.67 153.3 +v 682.576 -133.836 164.696 +v 705.303 -138.614 -221.823 +v 846.834 -109.506 -138.578 +v 729.558 -244.167 -136.921 +v 596.877 -226.42 -202.43 +v 729.558 -244.167 -136.921 +v 690.121 -324.877 -50.8344 +v 690.121 -324.877 -50.8344 +v 545.802 -338.023 -93.8417 +v 596.877 -226.42 -202.43 +v 729.558 -244.167 -136.921 +v 766.139 -190.462 -12.7916 +v 764.322 -249.3 -4.0724 +v 729.558 -244.167 -136.921 +v 764.322 -249.3 -4.0724 +v 690.121 -324.877 -50.8344 +v 545.802 -338.023 -93.8417 +v 690.121 -324.877 -50.8344 +v 643.124 -384.905 35.5904 +v 643.124 -384.905 35.5904 +v 518.621 -389.245 -11.2156 +v 545.802 -338.023 -93.8417 +v 764.322 -249.3 -4.0724 +v 766.139 -190.462 -12.7916 +v 682.576 -133.836 164.696 +v 682.576 -133.836 164.696 +v 715.597 -287.239 118.766 +v 764.322 -249.3 -4.0724 +v 545.802 -338.023 -93.8417 +v 518.621 -389.245 -11.2156 +v 415.539 -273.108 12.9001 +v 690.121 -324.877 -50.8344 +v 764.322 -249.3 -4.0724 +v 715.597 -287.239 118.766 +v 715.597 -287.239 118.766 +v 643.124 -384.905 35.5904 +v 690.121 -324.877 -50.8344 +v 494.4 -118.67 153.3 +v 415.539 -273.108 12.9001 +v 400.659 -292.368 56.5926 +v 400.659 -292.368 56.5926 +v 495.985 -186.11 152.255 +v 494.4 -118.67 153.3 +v 682.576 -133.836 164.696 +v 494.4 -118.67 153.3 +v 495.985 -186.11 152.255 +v 495.985 -186.11 152.255 +v 623.367 -177.233 203.09 +v 682.576 -133.836 164.696 +v 518.621 -389.245 -11.2156 +v 643.124 -384.905 35.5904 +v 601.471 -400.28 109.919 +v 601.471 -400.28 109.919 +v 488.245 -391.496 48.2971 +v 518.621 -389.245 -11.2156 +v 715.597 -287.239 118.766 +v 682.576 -133.836 164.696 +v 623.367 -177.233 203.09 +v 623.367 -177.233 203.09 +v 651.012 -315.403 204.493 +v 715.597 -287.239 118.766 +v 415.539 -273.108 12.9001 +v 518.621 -389.245 -11.2156 +v 488.245 -391.496 48.2971 +v 488.245 -391.496 48.2971 +v 400.659 -292.368 56.5926 +v 415.539 -273.108 12.9001 +v 643.124 -384.905 35.5904 +v 715.597 -287.239 118.766 +v 651.012 -315.403 204.493 +v 651.012 -315.403 204.493 +v 601.471 -400.28 109.919 +v 643.124 -384.905 35.5904 +v 495.985 -186.11 152.255 +v 400.659 -292.368 56.5926 +v 376.155 -367.745 167.649 +v 376.155 -367.745 167.649 +v 449.267 -227.751 241.792 +v 495.985 -186.11 152.255 +v 623.367 -177.233 203.09 +v 495.985 -186.11 152.255 +v 449.267 -227.751 241.792 +v 449.267 -227.751 241.792 +v 595.313 -259.375 310.175 +v 623.367 -177.233 203.09 +v 488.245 -391.496 48.2971 +v 601.471 -400.28 109.919 +v 507.031 -409.177 228.749 +v 507.031 -409.177 228.749 +v 438.002 -434.285 183.939 +v 488.245 -391.496 48.2971 +v 651.012 -315.403 204.493 +v 623.367 -177.233 203.09 +v 595.313 -259.375 310.175 +v 595.313 -259.375 310.175 +v 588.146 -375.276 278.435 +v 651.012 -315.403 204.493 +v 400.659 -292.368 56.5926 +v 488.245 -391.496 48.2971 +v 438.002 -434.285 183.939 +v 438.002 -434.285 183.939 +v 376.155 -367.745 167.649 +v 400.659 -292.368 56.5926 +v 601.471 -400.28 109.919 +v 651.012 -315.403 204.493 +v 588.146 -375.276 278.435 +v 588.146 -375.276 278.435 +v 507.031 -409.177 228.749 +v 601.471 -400.28 109.919 +v 449.267 -227.751 241.792 +v 376.155 -367.745 167.649 +v 280.893 -407.647 219.652 +v 280.893 -407.647 219.652 +v 319.396 -255.764 310.744 +v 449.267 -227.751 241.792 +v 449.267 -227.751 241.792 +v 319.396 -255.764 310.744 +v 467.024 -290.574 349.519 +v 467.024 -290.574 349.519 +v 595.313 -259.375 310.175 +v 449.267 -227.751 241.792 +v 438.002 -434.285 183.939 +v 507.031 -409.177 228.749 +v 332.802 -479.854 294.654 +v 332.802 -479.854 294.654 +v 289.487 -450.472 226.311 +v 438.002 -434.285 183.939 +v 588.146 -375.276 278.435 +v 595.313 -259.375 310.175 +v 467.024 -290.574 349.519 +v 467.024 -290.574 349.519 +v 467.468 -451.105 347.786 +v 588.146 -375.276 278.435 +v 376.155 -367.745 167.649 +v 438.002 -434.285 183.939 +v 289.487 -450.472 226.311 +v 289.487 -450.472 226.311 +v 280.893 -407.647 219.652 +v 376.155 -367.745 167.649 +v 507.031 -409.177 228.749 +v 588.146 -375.276 278.435 +v 467.468 -451.105 347.786 +v 467.468 -451.105 347.786 +v 332.802 -479.854 294.654 +v 507.031 -409.177 228.749 +v 280.893 -407.647 219.652 +v 209.876 -408.128 275.697 +v 319.396 -255.764 310.744 +v 289.487 -450.472 226.311 +v 332.802 -479.854 294.654 +v 252.694 -465.87 283.647 +v 280.893 -407.647 219.652 +v 289.487 -450.472 226.311 +v 252.694 -465.87 283.647 +v 252.694 -465.87 283.647 +v 209.876 -408.128 275.697 +v 280.893 -407.647 219.652 +v 209.876 -408.128 275.697 +v 215.546 -402.806 359.667 +v 319.396 -255.764 310.744 +v 252.694 -465.87 283.647 +v 332.802 -479.854 294.654 +v 257.069 -452.122 363.088 +v 209.876 -408.128 275.697 +v 252.694 -465.87 283.647 +v 257.069 -452.122 363.088 +v 257.069 -452.122 363.088 +v 215.546 -402.806 359.667 +v 209.876 -408.128 275.697 +v 319.396 -255.764 310.744 +v 215.546 -402.806 359.667 +v 279.28 -306.382 455.978 +v 279.28 -306.382 455.978 +v 337.988 -252.446 457.825 +v 319.396 -255.764 310.744 +v 467.024 -290.574 349.519 +v 319.396 -255.764 310.744 +v 337.988 -252.446 457.825 +v 337.988 -252.446 457.825 +v 441.552 -285.479 435.883 +v 467.024 -290.574 349.519 +v 257.069 -452.122 363.088 +v 332.802 -479.854 294.654 +v 352.053 -402.807 456.275 +v 352.053 -402.807 456.275 +v 265.056 -381.514 466.161 +v 257.069 -452.122 363.088 +v 467.468 -451.105 347.786 +v 467.024 -290.574 349.519 +v 441.552 -285.479 435.883 +v 441.552 -285.479 435.883 +v 441.455 -368.508 445.446 +v 467.468 -451.105 347.786 +v 215.546 -402.806 359.667 +v 257.069 -452.122 363.088 +v 265.056 -381.514 466.161 +v 265.056 -381.514 466.161 +v 279.28 -306.382 455.978 +v 215.546 -402.806 359.667 +v 332.802 -479.854 294.654 +v 467.468 -451.105 347.786 +v 441.455 -368.508 445.446 +v 441.455 -368.508 445.446 +v 352.053 -402.807 456.275 +v 332.802 -479.854 294.654 +v 337.988 -252.446 457.825 +v 279.28 -306.382 455.978 +v 272.997 -296.869 559.478 +v 272.997 -296.869 559.478 +v 383.441 -229.992 553.769 +v 337.988 -252.446 457.825 +v 441.552 -285.479 435.883 +v 337.988 -252.446 457.825 +v 383.441 -229.992 553.769 +v 383.441 -229.992 553.769 +v 466.393 -250.784 551.819 +v 441.552 -285.479 435.883 +v 265.056 -381.514 466.161 +v 352.053 -402.807 456.275 +v 390.756 -398.519 555.537 +v 390.756 -398.519 555.537 +v 264.043 -387.196 561.999 +v 265.056 -381.514 466.161 +v 441.455 -368.508 445.446 +v 441.552 -285.479 435.883 +v 466.393 -250.784 551.819 +v 466.393 -250.784 551.819 +v 464.616 -343.033 556.584 +v 441.455 -368.508 445.446 +v 279.28 -306.382 455.978 +v 265.056 -381.514 466.161 +v 264.043 -387.196 561.999 +v 264.043 -387.196 561.999 +v 272.997 -296.869 559.478 +v 279.28 -306.382 455.978 +v 352.053 -402.807 456.275 +v 441.455 -368.508 445.446 +v 464.616 -343.033 556.584 +v 464.616 -343.033 556.584 +v 390.756 -398.519 555.537 +v 352.053 -402.807 456.275 +v 383.441 -229.992 553.769 +v 272.997 -296.869 559.478 +v 271.471 -297.372 655.645 +v 271.471 -297.372 655.645 +v 377.032 -230.241 640.682 +v 383.441 -229.992 553.769 +v 466.393 -250.784 551.819 +v 383.441 -229.992 553.769 +v 377.032 -230.241 640.682 +v 377.032 -230.241 640.682 +v 460.045 -254.409 626.719 +v 466.393 -250.784 551.819 +v 264.043 -387.196 561.999 +v 390.756 -398.519 555.537 +v 418.604 -400.968 630.348 +v 418.604 -400.968 630.348 +v 293.651 -378.092 667.416 +v 264.043 -387.196 561.999 +v 464.616 -343.033 556.584 +v 466.393 -250.784 551.819 +v 460.045 -254.409 626.719 +v 460.045 -254.409 626.719 +v 484.703 -326.296 631.39 +v 464.616 -343.033 556.584 +v 272.997 -296.869 559.478 +v 264.043 -387.196 561.999 +v 293.651 -378.092 667.416 +v 293.651 -378.092 667.416 +v 271.471 -297.372 655.645 +v 272.997 -296.869 559.478 +v 390.756 -398.519 555.537 +v 464.616 -343.033 556.584 +v 484.703 -326.296 631.39 +v 484.703 -326.296 631.39 +v 418.604 -400.968 630.348 +v 390.756 -398.519 555.537 +v 377.032 -230.241 640.682 +v 271.471 -297.372 655.645 +v 304.327 -301.295 790.592 +v 304.327 -301.295 790.592 +v 340.152 -202.604 825.111 +v 377.032 -230.241 640.682 +v 460.045 -254.409 626.719 +v 377.032 -230.241 640.682 +v 340.152 -202.604 825.111 +v 340.152 -202.604 825.111 +v 451.236 -235.762 688.596 +v 460.045 -254.409 626.719 +v 293.651 -378.092 667.416 +v 418.604 -400.968 630.348 +v 433.476 -415.899 693.155 +v 433.476 -415.899 693.155 +v 317.335 -377.895 787.405 +v 293.651 -378.092 667.416 +v 484.703 -326.296 631.39 +v 460.045 -254.409 626.719 +v 451.236 -235.762 688.596 +v 451.236 -235.762 688.596 +v 490.612 -314.281 692.953 +v 484.703 -326.296 631.39 +v 271.471 -297.372 655.645 +v 293.651 -378.092 667.416 +v 317.335 -377.895 787.405 +v 317.335 -377.895 787.405 +v 304.327 -301.295 790.592 +v 271.471 -297.372 655.645 +v 418.604 -400.968 630.348 +v 484.703 -326.296 631.39 +v 490.612 -314.281 692.953 +v 490.612 -314.281 692.953 +v 433.476 -415.899 693.155 +v 418.604 -400.968 630.348 +v 340.152 -202.604 825.111 +v 304.327 -301.295 790.592 +v 580.055 -298.385 836.628 +v 580.055 -298.385 836.628 +v 499.471 -177.331 820.408 +v 340.152 -202.604 825.111 +v 451.236 -235.762 688.596 +v 340.152 -202.604 825.111 +v 499.471 -177.331 820.408 +v 499.471 -177.331 820.408 +v 527.033 -193.537 790.548 +v 451.236 -235.762 688.596 +v 317.335 -377.895 787.405 +v 433.476 -415.899 693.155 +v 507.467 -413.427 768.062 +v 507.467 -413.427 768.062 +v 486.543 -438.858 861.734 +v 317.335 -377.895 787.405 +v 490.612 -314.281 692.953 +v 451.236 -235.762 688.596 +v 527.033 -193.537 790.548 +v 527.033 -193.537 790.548 +v 577.257 -297.345 761.531 +v 490.612 -314.281 692.953 +v 304.327 -301.295 790.592 +v 317.335 -377.895 787.405 +v 486.543 -438.858 861.734 +v 486.543 -438.858 861.734 +v 580.055 -298.385 836.628 +v 304.327 -301.295 790.592 +v 433.476 -415.899 693.155 +v 490.612 -314.281 692.953 +v 577.257 -297.345 761.531 +v 577.257 -297.345 761.531 +v 507.467 -413.427 768.062 +v 433.476 -415.899 693.155 +v 486.543 -438.858 861.734 +v 507.467 -413.427 768.062 +v 644.719 -436.229 823.452 +v 644.719 -436.229 823.452 +v 626.437 -452.827 888.782 +v 486.543 -438.858 861.734 +v 580.055 -298.385 836.628 +v 486.543 -438.858 861.734 +v 626.437 -452.827 888.782 +v 626.437 -452.827 888.782 +v 627.235 -359.336 885.962 +v 580.055 -298.385 836.628 +v 507.467 -413.427 768.062 +v 577.257 -297.345 761.531 +v 645.052 -359.418 822.685 +v 645.052 -359.418 822.685 +v 644.719 -436.229 823.452 +v 507.467 -413.427 768.062 +v 499.471 -177.331 820.408 +v 580.055 -298.385 836.628 +v 625.983 -264.872 889.066 +v 625.983 -264.872 889.066 +v 626.526 -181.518 888.681 +v 499.471 -177.331 820.408 +v 527.033 -193.537 790.548 +v 499.471 -177.331 820.408 +v 626.526 -181.518 888.681 +v 626.526 -181.518 888.681 +v 645.299 -197.725 822.477 +v 527.033 -193.537 790.548 +v 577.257 -297.345 761.531 +v 527.033 -193.537 790.548 +v 645.299 -197.725 822.477 +v 645.299 -197.725 822.477 +v 645.229 -252.173 822.897 +v 577.257 -297.345 761.531 +v 645.052 -359.418 822.685 +v 577.257 -297.345 761.531 +v 580.055 -298.385 836.628 +v 580.055 -298.385 836.628 +v 627.235 -359.336 885.962 +v 645.052 -359.418 822.685 +v 577.257 -297.345 761.531 +v 645.229 -252.173 822.897 +v 625.983 -264.872 889.066 +v 625.983 -264.872 889.066 +v 580.055 -298.385 836.628 +v 577.257 -297.345 761.531 +v 626.437 -452.827 888.782 +v 644.719 -436.229 823.452 +v 695.541 -379.792 913.328 +v 627.235 -359.336 885.962 +v 626.437 -452.827 888.782 +v 695.541 -379.792 913.328 +v 644.719 -436.229 823.452 +v 645.052 -359.418 822.685 +v 695.541 -379.792 913.328 +v 626.526 -181.518 888.681 +v 625.983 -264.872 889.066 +v 697.205 -181.253 900.122 +v 645.299 -197.725 822.477 +v 626.526 -181.518 888.681 +v 697.205 -181.253 900.122 +v 645.229 -252.173 822.897 +v 645.299 -197.725 822.477 +v 697.205 -181.253 900.122 +v 645.052 -359.418 822.685 +v 627.235 -359.336 885.962 +v 695.541 -379.792 913.328 +v 625.983 -264.872 889.066 +v 645.229 -252.173 822.897 +v 697.205 -181.253 900.122 +v 960.668 2.14378 -383.161 +v 1053.7 -18.2369 -346.758 +v 1039.45 -77.8383 -303.194 +v 1039.45 -77.8383 -303.194 +v 922.953 -39.6783 -375.782 +v 960.668 2.14378 -383.161 +v 1053.7 -18.2369 -346.758 +v 1117.05 -14.6657 -272.841 +v 1101.1 -49.6417 -253.686 +v 1101.1 -49.6417 -253.686 +v 1039.45 -77.8383 -303.194 +v 1053.7 -18.2369 -346.758 +v 922.953 -39.6783 -375.782 +v 1039.45 -77.8383 -303.194 +v 1015.08 -111.831 -235.109 +v 1015.08 -111.831 -235.109 +v 885.246 -63.9092 -351.891 +v 922.953 -39.6783 -375.782 +v 1039.45 -77.8383 -303.194 +v 1101.1 -49.6417 -253.686 +v 1102.84 -71.0802 -161.378 +v 1102.84 -71.0802 -161.378 +v 1015.08 -111.831 -235.109 +v 1039.45 -77.8383 -303.194 +v 885.246 -63.9092 -351.891 +v 1015.08 -111.831 -235.109 +v 965.997 -129.088 -179.582 +v 965.997 -129.088 -179.582 +v 843.785 -71.3395 -368.243 +v 885.246 -63.9092 -351.891 +v 1015.08 -111.831 -235.109 +v 1102.84 -71.0802 -161.378 +v 1077.52 -88.1132 -75.2012 +v 1077.52 -88.1132 -75.2012 +v 965.997 -129.088 -179.582 +v 1015.08 -111.831 -235.109 +v 843.785 -71.3395 -368.243 +v 965.997 -129.088 -179.582 +v 846.834 -109.506 -138.578 +v 846.834 -109.506 -138.578 +v 676.056 -10.6067 -403.457 +v 843.785 -71.3395 -368.243 +v 965.997 -129.088 -179.582 +v 1077.52 -88.1132 -75.2012 +v 987.83 -96.8683 -34.0016 +v 965.997 -129.088 -179.582 +v 987.83 -96.8683 -34.0016 +v 918.453 -95.2934 -14.2217 +v 965.997 -129.088 -179.582 +v 918.453 -95.2934 -14.2217 +v 846.834 -109.506 -138.578 +v 1101.1 -49.6417 -253.686 +v 1117.05 -14.6657 -272.841 +v 1130.63 -9.82445 -219.113 +v 987.83 -96.8683 -34.0016 +v 1077.52 -88.1132 -75.2012 +v 1056.88 -53.0906 2.82201 +v 1102.84 -71.0802 -161.378 +v 1101.1 -49.6417 -253.686 +v 1130.63 -9.82445 -219.113 +v 1130.63 -9.82445 -219.113 +v 1120.75 -32.3903 -128.277 +v 1102.84 -71.0802 -161.378 +v 1077.52 -88.1132 -75.2012 +v 1102.84 -71.0802 -161.378 +v 1120.75 -32.3903 -128.277 +v 1150.35 40.2028 -66.9116 +v 1132.43 40.2028 -24.6494 +v 1089.27 -39.0682 -37.2052 +v 997.81 -45.2141 37.0952 +v 1056.88 -53.0906 2.82201 +v 1100.85 40.2028 23.3858 +v 1100.85 40.2028 23.3858 +v 1031.13 40.2028 65.4153 +v 997.81 -45.2141 37.0952 +v 997.81 -45.2141 37.0952 +v 984.155 40.2028 76.1361 +v 972.648 -63.5674 25.3677 +v 1089.27 -39.0682 -37.2052 +v 1077.52 -88.1132 -75.2012 +v 1150.35 40.2028 -66.9116 +v 1089.27 -39.0682 -37.2052 +v 1056.88 -53.0906 2.82201 +v 1077.52 -88.1132 -75.2012 +v 972.648 -63.5674 25.3677 +v 918.453 -95.2934 -14.2217 +v 987.83 -96.8683 -34.0016 +v 972.648 -63.5674 25.3677 +v 987.83 -96.8683 -34.0016 +v 1056.88 -53.0906 2.82201 +v 972.648 -63.5674 25.3677 +v 1056.88 -53.0906 2.82201 +v 997.81 -45.2141 37.0952 +v 1089.27 -39.0682 -37.2052 +v 1132.43 40.2028 -24.6494 +v 1122.15 40.2028 2.15071 +v 1089.27 -39.0682 -37.2052 +v 1122.15 40.2028 2.15071 +v 1100.85 40.2028 23.3858 +v 1100.85 40.2028 23.3858 +v 1056.88 -53.0906 2.82201 +v 1089.27 -39.0682 -37.2052 +v 997.81 -45.2141 37.0952 +v 1031.13 40.2028 65.4153 +v 984.155 40.2028 76.1361 +v 558.965 -9.41835 -404.09 +v 596.877 -226.42 -202.43 +v 268.451 -169.261 -290.85 +v 558.965 -9.41835 -404.09 +v 705.303 -138.614 -221.823 +v 596.877 -226.42 -202.43 +v 596.877 -226.42 -202.43 +v 545.802 -338.023 -93.8417 +v 380.051 -251.531 -66.4672 +v 380.051 -251.531 -66.4672 +v 242.389 -203.746 -21.5963 +v 260.646 -223.493 -153.714 +v 705.303 -138.614 -221.823 +v 729.558 -244.167 -136.921 +v 596.877 -226.42 -202.43 +v -627.402 40.2031 -27.0373 +v -598.079 -17.5054 13.3313 +v -974.541 40.2032 116.414 +v -598.079 -17.5054 13.3313 +v -635.946 -19.0616 145.914 +v -966.748 -6.42429 188.349 +v -973.5 40.2032 204.705 +v -966.748 -6.42429 188.349 +v -635.946 -19.0616 145.914 +v -635.946 -19.0616 145.914 +v -605.1 40.2031 174.866 +v -973.5 40.2032 204.705 +v -598.079 -17.5054 13.3313 +v -966.748 -6.42429 188.349 +v -974.541 40.2032 116.414 +v -1356.56 13.1094 175.492 +v -1360.94 40.2032 134.989 +v -974.541 40.2032 116.414 +v -974.541 40.2032 116.414 +v -966.748 -6.42429 188.349 +v -1356.56 13.1094 175.492 +v -1360.36 40.2032 184.7 +v -1356.56 13.1094 175.492 +v -966.748 -6.42429 188.349 +v -966.748 -6.42429 188.349 +v -973.5 40.2032 204.705 +v -1360.36 40.2032 184.7 +v -1360.94 40.2032 134.989 +v -1356.56 13.1094 175.492 +v -1746.98 40.2033 71.4633 +v -1356.56 13.1094 175.492 +v -1360.36 40.2032 184.7 +v -1746.98 40.2033 71.4633 +v 846.834 -109.506 -138.578 +v 918.453 -95.2934 -14.2217 +v 789.328 -72.2831 -20.678 +v 676.056 -10.6067 -403.457 +v 846.834 -109.506 -138.578 +v 705.303 -138.614 -221.823 +v 558.965 -9.41835 -404.09 +v 505.639 40.2029 -412.719 +v 442.767 40.2029 -586.685 +v 676.056 -10.6067 -403.457 +v 558.965 -9.41835 -404.09 +v 482.298 5.71433 -625.285 +v 676.056 -10.6067 -403.457 +v 482.298 5.71433 -625.285 +v 486.264 40.2029 -638.201 +v 486.264 40.2029 -638.201 +v 679.933 40.2029 -441.705 +v 676.056 -10.6067 -403.457 +v 482.298 5.71433 -625.285 +v 558.965 -9.41835 -404.09 +v 442.767 40.2029 -586.685 +v 442.767 40.2029 -586.685 +v 286.808 40.203 -755.55 +v 309.065 20.6301 -777.283 +v 309.065 20.6301 -777.283 +v 482.298 5.71433 -625.285 +v 442.767 40.2029 -586.685 +v 482.298 5.71433 -625.285 +v 309.065 20.6301 -777.283 +v 311.298 40.203 -784.555 +v 311.298 40.203 -784.555 +v 486.264 40.2029 -638.201 +v 482.298 5.71433 -625.285 +v 309.065 20.6301 -777.283 +v 286.808 40.203 -755.55 +v 3.60059 40.203 -892.627 +v 311.298 40.203 -784.555 +v 309.065 20.6301 -777.283 +v 3.60059 40.203 -892.627 +v -107.623 -99.0134 -353.534 +v -233.929 40.203 -337.011 +v -312.712 40.203 -483.512 +v 32.5665 -78.854 -387.298 +v -107.623 -99.0134 -353.534 +v -261.253 5.71445 -577.012 +v 32.5665 -78.854 -387.298 +v -261.253 5.71445 -577.012 +v -260.883 40.203 -590.518 +v -260.883 40.203 -590.518 +v 21.7976 40.203 -426.108 +v 32.5665 -78.854 -387.298 +v -261.253 5.71445 -577.012 +v -107.623 -99.0134 -353.534 +v -312.712 40.203 -483.512 +v -312.712 40.203 -483.512 +v -484.444 40.2031 -650.296 +v -468.805 20.6302 -677.187 +v -468.805 20.6302 -677.187 +v -261.253 5.71445 -577.012 +v -312.712 40.203 -483.512 +v -261.253 5.71445 -577.012 +v -468.805 20.6302 -677.187 +v -468.596 40.2031 -684.792 +v -468.596 40.2031 -684.792 +v -260.883 40.203 -590.518 +v -261.253 5.71445 -577.012 +v -468.805 20.6302 -677.187 +v -484.444 40.2031 -650.296 +v -793.984 40.2031 -706.704 +v -468.596 40.2031 -684.792 +v -468.805 20.6302 -677.187 +v -793.984 40.2031 -706.704 +v 260.312 40.203 -440.008 +v 252.222 134.611 -402.864 +v 558.966 89.8242 -404.09 +v 558.966 89.8242 -404.09 +v 505.639 40.2029 -412.719 +v 260.312 40.203 -440.008 +v 21.7976 40.203 -426.108 +v 32.5666 159.26 -387.298 +v 252.222 134.611 -402.864 +v 252.222 134.611 -402.864 +v 260.312 40.203 -440.008 +v 21.7976 40.203 -426.108 +v -107.623 179.419 -353.534 +v -233.929 40.203 -337.011 +v -378.533 40.2031 -238.981 +v -378.533 40.2031 -238.981 +v -359.273 99.4677 -211.385 +v -107.623 179.419 -353.534 +v -627.402 40.2031 -27.0373 +v -598.079 97.9116 13.3312 +v -359.273 99.4677 -211.385 +v -359.273 99.4677 -211.385 +v -378.533 40.2031 -238.981 +v -627.402 40.2031 -27.0373 +v -605.1 40.2031 174.866 +v -547.166 40.2031 372.164 +v -635.946 99.4678 145.914 +v -99.0235 40.203 155.844 +v -25.3967 40.203 194.286 +v 22.0084 135.938 181.514 +v 246.48 40.203 170.173 +v 246.315 160.024 147.057 +v 22.0084 135.938 181.514 +v 22.0084 135.938 181.514 +v -25.3967 40.203 194.286 +v 246.48 40.203 170.173 +v 568.185 40.2029 165.976 +v 542.48 125.319 151.06 +v 246.315 160.024 147.057 +v 246.315 160.024 147.057 +v 246.48 40.203 170.173 +v 568.185 40.2029 165.976 +v 762.912 40.2029 67.4993 +v 758.412 143.246 43.888 +v 542.48 125.319 151.06 +v 542.48 125.319 151.06 +v 568.185 40.2029 165.976 +v 762.912 40.2029 67.4993 +v 841.425 40.2029 35.9378 +v 789.328 152.689 -20.678 +v 758.412 143.246 43.888 +v 758.412 143.246 43.888 +v 762.912 40.2029 67.4993 +v 841.425 40.2029 35.9378 +v 912.514 40.2028 48.5509 +v 918.453 175.699 -14.2217 +v 789.328 152.689 -20.678 +v 789.328 152.689 -20.678 +v 841.425 40.2029 35.9378 +v 912.514 40.2028 48.5509 +v 252.222 134.611 -402.864 +v 268.451 249.667 -290.85 +v 558.966 89.8242 -404.09 +v 32.5666 159.26 -387.298 +v 268.451 249.667 -290.85 +v 252.222 134.611 -402.864 +v -598.079 97.9116 13.3312 +v -537.312 180.662 32.2996 +v -321.215 158.098 -143.919 +v -321.215 158.098 -143.919 +v -359.273 99.4677 -211.385 +v -598.079 97.9116 13.3312 +v -359.273 99.4677 -211.385 +v -321.215 158.098 -143.919 +v -107.623 179.419 -353.534 +v 260.646 303.899 -153.714 +v 380.051 331.937 -66.4672 +v 596.877 306.826 -202.43 +v 596.877 306.826 -202.43 +v 268.451 249.667 -290.85 +v 260.646 303.899 -153.714 +v 39.9729 236.445 -209.082 +v 260.646 303.899 -153.714 +v 268.451 249.667 -290.85 +v 268.451 249.667 -290.85 +v 32.5666 159.26 -387.298 +v 39.9729 236.445 -209.082 +v -107.623 179.419 -353.534 +v -121.935 222.68 -170.547 +v 39.9729 236.445 -209.082 +v 39.9729 236.445 -209.082 +v 32.5666 159.26 -387.298 +v -107.623 179.419 -353.534 +v -537.312 180.662 32.2996 +v -391.04 220.314 41.853 +v -321.215 158.098 -143.919 +v -321.215 158.098 -143.919 +v -121.935 222.68 -170.547 +v -107.623 179.419 -353.534 +v 39.9729 236.445 -209.082 +v 22.0431 237.22 26.5261 +v 242.389 284.152 -21.5963 +v 242.389 284.152 -21.5963 +v 260.646 303.899 -153.714 +v 39.9729 236.445 -209.082 +v -121.935 222.68 -170.547 +v -77.3161 206.284 32.3854 +v 22.0431 237.22 26.5261 +v 22.0431 237.22 26.5261 +v 39.9729 236.445 -209.082 +v -121.935 222.68 -170.547 +v -391.04 220.314 41.853 +v -298.191 183.448 127.509 +v -213.771 177.887 92.1041 +v -213.771 177.887 92.1041 +v -321.215 158.098 -143.919 +v -391.04 220.314 41.853 +v -321.215 158.098 -143.919 +v -213.771 177.887 92.1041 +v -77.3161 206.284 32.3854 +v -77.3161 206.284 32.3854 +v -121.935 222.68 -170.547 +v -321.215 158.098 -143.919 +v 242.389 284.152 -21.5963 +v 257.24 242.3 50.0724 +v 494.4 199.075 153.3 +v 494.4 199.075 153.3 +v 380.051 331.937 -66.4672 +v 242.389 284.152 -21.5963 +v 22.0431 237.22 26.5261 +v 34.0969 188.524 134.952 +v 257.24 242.3 50.0724 +v 257.24 242.3 50.0724 +v 242.389 284.152 -21.5963 +v 22.0431 237.22 26.5261 +v -77.3161 206.284 32.3854 +v -50.7188 147.901 123.835 +v 34.0969 188.524 134.952 +v 34.0969 188.524 134.952 +v 22.0431 237.22 26.5261 +v -77.3161 206.284 32.3854 +v -298.191 183.448 127.509 +v -314.968 157.494 227.647 +v -257.987 111.014 139.94 +v -257.987 111.014 139.94 +v -213.771 177.887 92.1041 +v -298.191 183.448 127.509 +v -213.771 177.887 92.1041 +v -257.987 111.014 139.94 +v -50.7188 147.901 123.835 +v -50.7188 147.901 123.835 +v -77.3161 206.284 32.3854 +v -213.771 177.887 92.1041 +v 246.315 160.024 147.057 +v 542.48 125.319 151.06 +v 494.4 199.075 153.3 +v 494.4 199.075 153.3 +v 257.24 242.3 50.0724 +v 246.315 160.024 147.057 +v 34.0969 188.524 134.952 +v 22.0084 135.938 181.514 +v 246.315 160.024 147.057 +v 246.315 160.024 147.057 +v 257.24 242.3 50.0724 +v 34.0969 188.524 134.952 +v -50.7188 147.901 123.835 +v -99.0235 40.203 155.844 +v 22.0084 135.938 181.514 +v 22.0084 135.938 181.514 +v 34.0969 188.524 134.952 +v -50.7188 147.901 123.835 +v -314.968 157.494 227.647 +v -284.581 40.203 304.684 +v -260.462 40.203 162.299 +v -260.462 40.203 162.299 +v -257.987 111.014 139.94 +v -314.968 157.494 227.647 +v -257.987 111.014 139.94 +v -260.462 40.203 162.299 +v -99.0235 40.203 155.844 +v -99.0235 40.203 155.844 +v -50.7188 147.901 123.835 +v -257.987 111.014 139.94 +v 558.966 89.8242 -404.09 +v 705.303 219.02 -221.823 +v 676.056 91.0124 -403.457 +v 729.558 324.573 -136.921 +v 789.328 152.689 -20.678 +v 846.835 189.911 -138.578 +v -635.946 99.4678 145.914 +v -615.112 167.209 174.866 +v -537.312 180.662 32.2996 +v -537.312 180.662 32.2996 +v -598.079 97.9116 13.3312 +v -635.946 99.4678 145.914 +v -547.166 40.2031 372.164 +v -550.064 125.358 307.245 +v -615.112 167.209 174.866 +v -615.112 167.209 174.866 +v -635.946 99.4678 145.914 +v -547.166 40.2031 372.164 +v -284.581 40.203 304.684 +v -314.968 157.494 227.647 +v -550.064 125.358 307.245 +v -550.064 125.358 307.245 +v -547.166 40.2031 372.164 +v -284.581 40.203 304.684 +v -537.312 180.662 32.2996 +v -410.355 397.111 -17.6217 +v -281.314 324.041 -48.6181 +v -281.314 324.041 -48.6181 +v -391.04 220.314 41.853 +v -537.312 180.662 32.2996 +v -391.04 220.314 41.853 +v -281.314 324.041 -48.6181 +v -169.529 226.359 141.817 +v -169.529 226.359 141.817 +v -298.191 183.448 127.509 +v -391.04 220.314 41.853 +v -298.191 183.448 127.509 +v -169.529 226.359 141.817 +v -255.603 219.321 227.647 +v -255.603 219.321 227.647 +v -314.968 157.494 227.647 +v -298.191 183.448 127.509 +v -615.112 167.209 174.866 +v -465.797 406.708 178.963 +v -410.355 397.111 -17.6217 +v -410.355 397.111 -17.6217 +v -537.312 180.662 32.2996 +v -615.112 167.209 174.866 +v -550.064 125.358 307.245 +v -440.269 322.717 338.364 +v -465.797 406.708 178.963 +v -465.797 406.708 178.963 +v -615.112 167.209 174.866 +v -550.064 125.358 307.245 +v -314.968 157.494 227.647 +v -255.603 219.321 227.647 +v -440.269 322.717 338.364 +v -440.269 322.717 338.364 +v -550.064 125.358 307.245 +v -314.968 157.494 227.647 +v -410.355 397.111 -17.6217 +v -24.9718 524.939 -3.85764 +v 14.208 415.633 -45.2649 +v 14.208 415.633 -45.2649 +v -281.314 324.041 -48.6181 +v -410.355 397.111 -17.6217 +v -281.314 324.041 -48.6181 +v 14.208 415.633 -45.2649 +v 32.0552 328.291 -11.6047 +v 32.0552 328.291 -11.6047 +v -169.529 226.359 141.817 +v -281.314 324.041 -48.6181 +v -169.529 226.359 141.817 +v 32.0552 328.291 -11.6047 +v -15.0917 323.073 60.2354 +v -15.0917 323.073 60.2354 +v -255.603 219.321 227.647 +v -169.529 226.359 141.817 +v -465.797 406.708 178.963 +v -103.65 527.69 112.409 +v -24.9718 524.939 -3.85764 +v -24.9718 524.939 -3.85764 +v -410.355 397.111 -17.6217 +v -465.797 406.708 178.963 +v -440.269 322.717 338.364 +v -132.463 411.341 170.696 +v -103.65 527.69 112.409 +v -103.65 527.69 112.409 +v -465.797 406.708 178.963 +v -440.269 322.717 338.364 +v -255.603 219.321 227.647 +v -15.0917 323.073 60.2354 +v -132.463 411.341 170.696 +v -132.463 411.341 170.696 +v -440.269 322.717 338.364 +v -255.603 219.321 227.647 +v -24.9718 524.939 -3.85764 +v 65.7942 517.971 48.1499 +v 154.274 503.342 15.1391 +v 154.274 503.342 15.1391 +v 14.208 415.633 -45.2649 +v -24.9718 524.939 -3.85764 +v 14.208 415.633 -45.2649 +v 154.274 503.342 15.1391 +v 160.1 419.483 42.0846 +v 160.1 419.483 42.0846 +v 32.0552 328.291 -11.6047 +v 14.208 415.633 -45.2649 +v 32.0552 328.291 -11.6047 +v 160.1 419.483 42.0846 +v 65.6434 390.704 103.599 +v 65.6434 390.704 103.599 +v -15.0917 323.073 60.2354 +v 32.0552 328.291 -11.6047 +v -103.65 527.69 112.409 +v -52.7381 458.911 120.825 +v 65.7942 517.971 48.1499 +v 65.7942 517.971 48.1499 +v -24.9718 524.939 -3.85764 +v -103.65 527.69 112.409 +v -132.463 411.341 170.696 +v -86.6904 426.227 159.574 +v -52.7381 458.911 120.825 +v -52.7381 458.911 120.825 +v -103.65 527.69 112.409 +v -132.463 411.341 170.696 +v -15.0917 323.073 60.2354 +v 65.6434 390.704 103.599 +v -86.6904 426.227 159.574 +v -86.6904 426.227 159.574 +v -132.463 411.341 170.696 +v -15.0917 323.073 60.2354 +v 65.7942 517.971 48.1499 +v 76.3056 529.56 158.966 +v 143.503 508.238 162.674 +v 143.503 508.238 162.674 +v 154.274 503.342 15.1391 +v 65.7942 517.971 48.1499 +v 154.274 503.342 15.1391 +v 143.503 508.238 162.674 +v 140.897 421.541 179.078 +v 140.897 421.541 179.078 +v 160.1 419.483 42.0846 +v 154.274 503.342 15.1391 +v 160.1 419.483 42.0846 +v 140.897 421.541 179.078 +v 61.6316 389.354 188.674 +v 61.6316 389.354 188.674 +v 65.6434 390.704 103.599 +v 160.1 419.483 42.0846 +v -52.7381 458.911 120.825 +v -56.9872 487.596 172.668 +v 76.3056 529.56 158.966 +v 76.3056 529.56 158.966 +v 65.7942 517.971 48.1499 +v -52.7381 458.911 120.825 +v -86.6904 426.227 159.574 +v -101.405 421.274 187.717 +v -56.9872 487.596 172.668 +v -56.9872 487.596 172.668 +v -52.7381 458.911 120.825 +v -86.6904 426.227 159.574 +v 65.6434 390.704 103.599 +v 61.6316 389.354 188.674 +v -101.405 421.274 187.717 +v -101.405 421.274 187.717 +v -86.6904 426.227 159.574 +v 65.6434 390.704 103.599 +v 76.3056 529.56 158.966 +v -86.1869 457.101 363.046 +v -57.0879 443.509 388.882 +v -57.0879 443.509 388.882 +v 143.503 508.238 162.674 +v 76.3056 529.56 158.966 +v 143.503 508.238 162.674 +v -57.0879 443.509 388.882 +v -61.2254 314.985 389.398 +v -61.2254 314.985 389.398 +v 140.897 421.541 179.078 +v 143.503 508.238 162.674 +v 140.897 421.541 179.078 +v -61.2254 314.985 389.398 +v -121.906 296.789 347.524 +v -121.906 296.789 347.524 +v 61.6316 389.354 188.674 +v 140.897 421.541 179.078 +v -56.9872 487.596 172.668 +v -171.947 433.723 301.741 +v -86.1869 457.101 363.046 +v -86.1869 457.101 363.046 +v 76.3056 529.56 158.966 +v -56.9872 487.596 172.668 +v -101.405 421.274 187.717 +v -194.817 317.703 287.062 +v -171.947 433.723 301.741 +v -171.947 433.723 301.741 +v -56.9872 487.596 172.668 +v -101.405 421.274 187.717 +v 61.6316 389.354 188.674 +v -121.906 296.789 347.524 +v -194.817 317.703 287.062 +v -194.817 317.703 287.062 +v -101.405 421.274 187.717 +v 61.6316 389.354 188.674 +v -86.1869 457.101 363.046 +v -120.57 443.918 397.079 +v -102.276 429.985 417.599 +v -102.276 429.985 417.599 +v -57.0879 443.509 388.882 +v -86.1869 457.101 363.046 +v -57.0879 443.509 388.882 +v -102.276 429.985 417.599 +v -101.46 306.717 419.247 +v -101.46 306.717 419.247 +v -61.2254 314.985 389.398 +v -57.0879 443.509 388.882 +v -61.2254 314.985 389.398 +v -101.46 306.717 419.247 +v -150.824 294.424 384.223 +v -150.824 294.424 384.223 +v -121.906 296.789 347.524 +v -61.2254 314.985 389.398 +v -171.947 433.723 301.741 +v -209.07 429.315 327.758 +v -120.57 443.918 397.079 +v -120.57 443.918 397.079 +v -86.1869 457.101 363.046 +v -171.947 433.723 301.741 +v -194.817 317.703 287.062 +v -220.374 318.156 317.345 +v -209.07 429.315 327.758 +v -209.07 429.315 327.758 +v -171.947 433.723 301.741 +v -194.817 317.703 287.062 +v -121.906 296.789 347.524 +v -150.824 294.424 384.223 +v -220.374 318.156 317.345 +v -220.374 318.156 317.345 +v -194.817 317.703 287.062 +v -121.906 296.789 347.524 +v -120.57 443.918 397.079 +v -279.206 399.004 477.521 +v -216.691 379.768 512.471 +v -216.691 379.768 512.471 +v -102.276 429.985 417.599 +v -120.57 443.918 397.079 +v -102.276 429.985 417.599 +v -216.691 379.768 512.471 +v -220.533 323.142 524.241 +v -220.533 323.142 524.241 +v -101.46 306.717 419.247 +v -102.276 429.985 417.599 +v -101.46 306.717 419.247 +v -220.533 323.142 524.241 +v -257.957 235.824 505.428 +v -257.957 235.824 505.428 +v -150.824 294.424 384.223 +v -101.46 306.717 419.247 +v -209.07 429.315 327.758 +v -372.739 339.847 407.375 +v -279.206 399.004 477.521 +v -279.206 399.004 477.521 +v -120.57 443.918 397.079 +v -209.07 429.315 327.758 +v -220.374 318.156 317.345 +v -394.838 259.653 404.628 +v -372.739 339.847 407.375 +v -372.739 339.847 407.375 +v -209.07 429.315 327.758 +v -220.374 318.156 317.345 +v -150.824 294.424 384.223 +v -257.957 235.824 505.428 +v -394.838 259.653 404.628 +v -394.838 259.653 404.628 +v -220.374 318.156 317.345 +v -150.824 294.424 384.223 +v -279.206 399.004 477.521 +v -317.133 461.908 577.263 +v -236.316 377.472 568.835 +v -236.316 377.472 568.835 +v -216.691 379.768 512.471 +v -279.206 399.004 477.521 +v -216.691 379.768 512.471 +v -236.316 377.472 568.835 +v -240.158 320.846 580.604 +v -240.158 320.846 580.604 +v -220.533 323.142 524.241 +v -216.691 379.768 512.471 +v -220.533 323.142 524.241 +v -240.158 320.846 580.604 +v -231.113 222.436 578.573 +v -231.113 222.436 578.573 +v -257.957 235.824 505.428 +v -220.533 323.142 524.241 +v -372.739 339.847 407.375 +v -521.042 358.693 466.938 +v -317.133 461.908 577.263 +v -317.133 461.908 577.263 +v -279.206 399.004 477.521 +v -372.739 339.847 407.375 +v -394.838 259.653 404.628 +v -543.141 227.102 464.192 +v -521.042 358.693 466.938 +v -521.042 358.693 466.938 +v -372.739 339.847 407.375 +v -394.838 259.653 404.628 +v -257.957 235.824 505.428 +v -231.113 222.436 578.573 +v -543.141 227.102 464.192 +v -543.141 227.102 464.192 +v -394.838 259.653 404.628 +v -257.957 235.824 505.428 +v -317.133 461.908 577.263 +v -180.616 530.627 583.426 +v -143.793 467.96 589.922 +v -143.793 467.96 589.922 +v -236.316 377.472 568.835 +v -317.133 461.908 577.263 +v -236.316 377.472 568.835 +v -143.793 467.96 589.922 +v -123.358 426.144 615.393 +v -123.358 426.144 615.393 +v -240.158 320.846 580.604 +v -236.316 377.472 568.835 +v -240.158 320.846 580.604 +v -123.358 426.144 615.393 +v -97.4353 366.614 662.435 +v -97.4353 366.614 662.435 +v -231.113 222.436 578.573 +v -240.158 320.846 580.604 +v -521.042 358.693 466.938 +v -493.77 358.377 624.724 +v -384.975 518.979 704.341 +v -384.975 518.979 704.341 +v -317.133 461.908 577.263 +v -521.042 358.693 466.938 +v -543.141 227.102 464.192 +v -513.328 278.481 614.68 +v -493.77 358.377 624.724 +v -493.77 358.377 624.724 +v -521.042 358.693 466.938 +v -543.141 227.102 464.192 +v -231.113 222.436 578.573 +v -308.634 250.194 722.901 +v -513.328 278.481 614.68 +v -513.328 278.481 614.68 +v -543.141 227.102 464.192 +v -231.113 222.436 578.573 +v -317.133 461.908 577.263 +v -384.975 518.979 704.341 +v -207.244 534.735 671.558 +v -207.244 534.735 671.558 +v -180.616 530.627 583.426 +v -317.133 461.908 577.263 +v -97.4353 366.614 662.435 +v -117.005 364.061 756.407 +v -308.634 250.194 722.901 +v -308.634 250.194 722.901 +v -231.113 222.436 578.573 +v -97.4353 366.614 662.435 +v -493.77 358.377 624.724 +v -470.518 310.442 648.255 +v -351.878 348.234 729.182 +v -351.878 348.234 729.182 +v -384.975 518.979 704.341 +v -493.77 358.377 624.724 +v -513.328 278.481 614.68 +v -470.518 310.442 648.255 +v -493.77 358.377 624.724 +v -308.634 250.194 722.901 +v -351.878 348.234 729.182 +v -470.518 310.442 648.255 +v -470.518 310.442 648.255 +v -513.328 278.481 614.68 +v -308.634 250.194 722.901 +v -384.975 518.979 704.341 +v -351.878 348.234 729.182 +v -158.686 443.907 717.111 +v -158.686 443.907 717.111 +v -207.244 534.735 671.558 +v -384.975 518.979 704.341 +v -117.005 364.061 756.407 +v -158.686 443.907 717.111 +v -351.878 348.234 729.182 +v -351.878 348.234 729.182 +v -308.634 250.194 722.901 +v -117.005 364.061 756.407 +v -180.616 530.627 583.426 +v -66.3274 584.533 711.51 +v -143.793 467.96 589.922 +v -123.358 426.144 615.393 +v -9.81756 473.005 748.063 +v -97.4353 366.614 662.435 +v -207.244 534.735 671.558 +v -66.3274 584.533 711.51 +v -180.616 530.627 583.426 +v -97.4353 366.614 662.435 +v -9.81756 473.005 748.063 +v -117.005 364.061 756.407 +v -158.686 443.907 717.111 +v -66.3274 584.533 711.51 +v -207.244 534.735 671.558 +v -117.005 364.061 756.407 +v -9.81756 473.005 748.063 +v -158.686 443.907 717.111 +v -143.793 467.96 589.922 +v -158.686 443.907 717.111 +v -123.358 426.144 615.393 +v -123.358 426.144 615.393 +v -158.686 443.907 717.111 +v -9.81756 473.005 748.063 +v -66.3274 584.533 711.51 +v -158.686 443.907 717.111 +v -143.793 467.96 589.922 +v 676.056 91.0124 -403.457 +v 843.785 151.745 -368.243 +v 824.669 40.2029 -424.477 +v 824.669 40.2029 -424.477 +v 679.933 40.2029 -441.705 +v 676.056 91.0124 -403.457 +v 843.785 151.745 -368.243 +v 885.246 144.315 -351.891 +v 878.609 40.2028 -424.149 +v 878.609 40.2028 -424.149 +v 824.669 40.2029 -424.477 +v 843.785 151.745 -368.243 +v 885.246 144.315 -351.891 +v 922.953 120.084 -375.782 +v 909.031 40.2028 -425.978 +v 909.031 40.2028 -425.978 +v 878.609 40.2028 -424.149 +v 885.246 144.315 -351.891 +v 922.953 120.084 -375.782 +v 960.668 78.2619 -383.161 +v 968.589 40.2028 -404.236 +v 968.589 40.2028 -404.236 +v 909.031 40.2028 -425.978 +v 922.953 120.084 -375.782 +v 960.668 78.2619 -383.161 +v 1053.7 98.6425 -346.758 +v 1061.35 40.2028 -356.762 +v 1061.35 40.2028 -356.762 +v 968.589 40.2028 -404.236 +v 960.668 78.2619 -383.161 +v 1053.7 98.6425 -346.758 +v 1117.05 95.0714 -272.841 +v 1119.22 40.2028 -284.353 +v 1119.22 40.2028 -284.353 +v 1061.35 40.2028 -356.762 +v 1053.7 98.6425 -346.758 +v 1117.05 95.0714 -272.841 +v 1130.63 90.2301 -219.113 +v 1152.01 40.2028 -217.427 +v 1152.01 40.2028 -217.427 +v 1119.22 40.2028 -284.353 +v 1117.05 95.0714 -272.841 +v 1130.63 90.2301 -219.113 +v 1120.75 112.796 -128.277 +v 1154.32 40.2028 -99.7808 +v 1154.32 40.2028 -99.7808 +v 1152.01 40.2028 -217.427 +v 1130.63 90.2301 -219.113 +v 1120.75 112.796 -128.277 +v 1077.52 168.519 -75.2012 +v 1150.35 40.2028 -66.9116 +v 1150.35 40.2028 -66.9116 +v 1154.32 40.2028 -99.7808 +v 1120.75 112.796 -128.277 +v 972.648 143.973 25.3677 +v 948.344 40.2028 68.9338 +v 984.155 40.2028 76.1361 +v 972.648 143.973 25.3677 +v 918.453 175.699 -14.2217 +v 912.514 40.2028 48.5509 +v 912.514 40.2028 48.5509 +v 948.344 40.2028 68.9338 +v 972.648 143.973 25.3677 +v 758.412 143.246 43.888 +v 766.139 270.868 -12.7916 +v 682.576 214.241 164.696 +v 682.576 214.241 164.696 +v 542.48 125.319 151.06 +v 758.412 143.246 43.888 +v 789.328 152.689 -20.678 +v 729.558 324.573 -136.921 +v 766.139 270.868 -12.7916 +v 766.139 270.868 -12.7916 +v 758.412 143.246 43.888 +v 789.328 152.689 -20.678 +v 380.051 331.937 -66.4672 +v 415.539 353.513 12.9001 +v 545.802 418.428 -93.8417 +v 380.051 331.937 -66.4672 +v 494.4 199.075 153.3 +v 415.539 353.513 12.9001 +v 542.48 125.319 151.06 +v 682.576 214.241 164.696 +v 494.4 199.075 153.3 +v 705.303 219.02 -221.823 +v 729.558 324.573 -136.921 +v 846.835 189.911 -138.578 +v 596.877 306.826 -202.43 +v 545.802 418.428 -93.8417 +v 690.121 405.282 -50.8344 +v 690.121 405.282 -50.8344 +v 729.558 324.573 -136.921 +v 596.877 306.826 -202.43 +v 729.558 324.573 -136.921 +v 764.322 329.706 -4.0724 +v 766.139 270.868 -12.7916 +v 729.558 324.573 -136.921 +v 690.121 405.282 -50.8344 +v 764.322 329.706 -4.0724 +v 545.802 418.428 -93.8417 +v 518.621 469.651 -11.2156 +v 643.124 465.31 35.5904 +v 643.124 465.31 35.5904 +v 690.121 405.282 -50.8344 +v 545.802 418.428 -93.8417 +v 764.322 329.706 -4.0724 +v 715.597 367.645 118.766 +v 682.576 214.241 164.696 +v 682.576 214.241 164.696 +v 766.139 270.868 -12.7916 +v 764.322 329.706 -4.0724 +v 545.802 418.428 -93.8417 +v 415.539 353.513 12.9001 +v 518.621 469.651 -11.2156 +v 690.121 405.282 -50.8344 +v 643.124 465.31 35.5904 +v 715.597 367.645 118.766 +v 715.597 367.645 118.766 +v 764.322 329.706 -4.0724 +v 690.121 405.282 -50.8344 +v 494.4 199.075 153.3 +v 495.985 266.516 152.255 +v 400.659 372.773 56.5926 +v 400.659 372.773 56.5926 +v 415.539 353.513 12.9001 +v 494.4 199.075 153.3 +v 682.576 214.241 164.696 +v 623.367 257.639 203.09 +v 495.985 266.516 152.255 +v 495.985 266.516 152.255 +v 494.4 199.075 153.3 +v 682.576 214.241 164.696 +v 518.621 469.651 -11.2156 +v 488.245 471.902 48.2971 +v 601.471 480.686 109.919 +v 601.471 480.686 109.919 +v 643.124 465.31 35.5904 +v 518.621 469.651 -11.2156 +v 715.597 367.645 118.766 +v 651.012 395.809 204.493 +v 623.367 257.639 203.09 +v 623.367 257.639 203.09 +v 682.576 214.241 164.696 +v 715.597 367.645 118.766 +v 415.539 353.513 12.9001 +v 400.659 372.773 56.5926 +v 488.245 471.902 48.2971 +v 488.245 471.902 48.2971 +v 518.621 469.651 -11.2156 +v 415.539 353.513 12.9001 +v 643.124 465.31 35.5904 +v 601.471 480.686 109.919 +v 651.012 395.809 204.493 +v 651.012 395.809 204.493 +v 715.597 367.645 118.766 +v 643.124 465.31 35.5904 +v 495.985 266.516 152.255 +v 449.267 308.157 241.792 +v 376.155 448.151 167.649 +v 376.155 448.151 167.649 +v 400.659 372.773 56.5926 +v 495.985 266.516 152.255 +v 623.367 257.639 203.09 +v 595.313 339.781 310.175 +v 449.267 308.157 241.792 +v 449.267 308.157 241.792 +v 495.985 266.516 152.255 +v 623.367 257.639 203.09 +v 488.245 471.902 48.2971 +v 438.002 514.69 183.939 +v 507.031 489.582 228.749 +v 507.031 489.582 228.749 +v 601.471 480.686 109.919 +v 488.245 471.902 48.2971 +v 651.012 395.809 204.493 +v 588.147 455.681 278.435 +v 595.313 339.781 310.175 +v 595.313 339.781 310.175 +v 623.367 257.639 203.09 +v 651.012 395.809 204.493 +v 400.659 372.773 56.5926 +v 376.155 448.151 167.649 +v 438.002 514.69 183.939 +v 438.002 514.69 183.939 +v 488.245 471.902 48.2971 +v 400.659 372.773 56.5926 +v 601.471 480.686 109.919 +v 507.031 489.582 228.749 +v 588.147 455.681 278.435 +v 588.147 455.681 278.435 +v 651.012 395.809 204.493 +v 601.471 480.686 109.919 +v 449.267 308.157 241.792 +v 319.396 336.17 310.744 +v 280.893 488.053 219.652 +v 280.893 488.053 219.652 +v 376.155 448.151 167.649 +v 449.267 308.157 241.792 +v 449.267 308.157 241.792 +v 595.313 339.781 310.175 +v 467.024 370.98 349.519 +v 467.024 370.98 349.519 +v 319.396 336.17 310.744 +v 449.267 308.157 241.792 +v 438.002 514.69 183.939 +v 289.487 530.878 226.311 +v 332.802 560.26 294.654 +v 332.802 560.26 294.654 +v 507.031 489.582 228.749 +v 438.002 514.69 183.939 +v 588.147 455.681 278.435 +v 467.468 531.511 347.786 +v 467.024 370.98 349.519 +v 467.024 370.98 349.519 +v 595.313 339.781 310.175 +v 588.147 455.681 278.435 +v 376.155 448.151 167.649 +v 280.893 488.053 219.652 +v 289.487 530.878 226.311 +v 289.487 530.878 226.311 +v 438.002 514.69 183.939 +v 376.155 448.151 167.649 +v 507.031 489.582 228.749 +v 332.802 560.26 294.654 +v 467.468 531.511 347.786 +v 467.468 531.511 347.786 +v 588.147 455.681 278.435 +v 507.031 489.582 228.749 +v 280.893 488.053 219.652 +v 319.396 336.17 310.744 +v 209.876 488.534 275.697 +v 289.487 530.878 226.311 +v 252.695 546.276 283.647 +v 332.802 560.26 294.654 +v 280.893 488.053 219.652 +v 209.876 488.534 275.697 +v 252.695 546.276 283.647 +v 252.695 546.276 283.647 +v 289.487 530.878 226.311 +v 280.893 488.053 219.652 +v 209.876 488.534 275.697 +v 319.396 336.17 310.744 +v 215.546 483.212 359.667 +v 252.695 546.276 283.647 +v 257.069 532.528 363.088 +v 332.802 560.26 294.654 +v 209.876 488.534 275.697 +v 215.546 483.212 359.667 +v 257.069 532.528 363.088 +v 257.069 532.528 363.088 +v 252.695 546.276 283.647 +v 209.876 488.534 275.697 +v 319.396 336.17 310.744 +v 337.988 332.852 457.825 +v 279.28 386.788 455.978 +v 279.28 386.788 455.978 +v 215.546 483.212 359.667 +v 319.396 336.17 310.744 +v 467.024 370.98 349.519 +v 441.553 365.885 435.883 +v 337.988 332.852 457.825 +v 337.988 332.852 457.825 +v 319.396 336.17 310.744 +v 467.024 370.98 349.519 +v 257.069 532.528 363.088 +v 265.056 461.92 466.161 +v 352.053 483.212 456.275 +v 352.053 483.212 456.275 +v 332.802 560.26 294.654 +v 257.069 532.528 363.088 +v 467.468 531.511 347.786 +v 441.455 448.914 445.446 +v 441.553 365.885 435.883 +v 441.553 365.885 435.883 +v 467.024 370.98 349.519 +v 467.468 531.511 347.786 +v 215.546 483.212 359.667 +v 279.28 386.788 455.978 +v 265.056 461.92 466.161 +v 265.056 461.92 466.161 +v 257.069 532.528 363.088 +v 215.546 483.212 359.667 +v 332.802 560.26 294.654 +v 352.053 483.212 456.275 +v 441.455 448.914 445.446 +v 441.455 448.914 445.446 +v 467.468 531.511 347.786 +v 332.802 560.26 294.654 +v 337.988 332.852 457.825 +v 383.441 310.398 553.769 +v 272.997 377.275 559.478 +v 272.997 377.275 559.478 +v 279.28 386.788 455.978 +v 337.988 332.852 457.825 +v 441.553 365.885 435.883 +v 466.393 331.19 551.819 +v 383.441 310.398 553.769 +v 383.441 310.398 553.769 +v 337.988 332.852 457.825 +v 441.553 365.885 435.883 +v 265.056 461.92 466.161 +v 264.043 467.602 561.999 +v 390.756 478.924 555.537 +v 390.756 478.924 555.537 +v 352.053 483.212 456.275 +v 265.056 461.92 466.161 +v 441.455 448.914 445.446 +v 464.616 423.439 556.584 +v 466.393 331.19 551.819 +v 466.393 331.19 551.819 +v 441.553 365.885 435.883 +v 441.455 448.914 445.446 +v 279.28 386.788 455.978 +v 272.997 377.275 559.478 +v 264.043 467.602 561.999 +v 264.043 467.602 561.999 +v 265.056 461.92 466.161 +v 279.28 386.788 455.978 +v 352.053 483.212 456.275 +v 390.756 478.924 555.537 +v 464.616 423.439 556.584 +v 464.616 423.439 556.584 +v 441.455 448.914 445.446 +v 352.053 483.212 456.275 +v 383.441 310.398 553.769 +v 377.032 310.647 640.682 +v 271.471 377.778 655.645 +v 271.471 377.778 655.645 +v 272.997 377.275 559.478 +v 383.441 310.398 553.769 +v 466.393 331.19 551.819 +v 460.045 334.815 626.719 +v 377.032 310.647 640.682 +v 377.032 310.647 640.682 +v 383.441 310.398 553.769 +v 466.393 331.19 551.819 +v 264.043 467.602 561.999 +v 293.651 458.498 667.416 +v 418.604 481.374 630.348 +v 418.604 481.374 630.348 +v 390.756 478.924 555.537 +v 264.043 467.602 561.999 +v 464.616 423.439 556.584 +v 484.703 406.702 631.39 +v 460.045 334.815 626.719 +v 460.045 334.815 626.719 +v 466.393 331.19 551.819 +v 464.616 423.439 556.584 +v 272.997 377.275 559.478 +v 271.471 377.778 655.645 +v 293.651 458.498 667.416 +v 293.651 458.498 667.416 +v 264.043 467.602 561.999 +v 272.997 377.275 559.478 +v 390.756 478.924 555.537 +v 418.604 481.374 630.348 +v 484.703 406.702 631.39 +v 484.703 406.702 631.39 +v 464.616 423.439 556.584 +v 390.756 478.924 555.537 +v 377.032 310.647 640.682 +v 340.152 283.009 825.111 +v 304.327 381.701 790.592 +v 304.327 381.701 790.592 +v 271.471 377.778 655.645 +v 377.032 310.647 640.682 +v 460.045 334.815 626.719 +v 451.236 316.167 688.596 +v 340.152 283.009 825.111 +v 340.152 283.009 825.111 +v 377.032 310.647 640.682 +v 460.045 334.815 626.719 +v 293.651 458.498 667.416 +v 317.335 458.301 787.405 +v 433.476 496.305 693.155 +v 433.476 496.305 693.155 +v 418.604 481.374 630.348 +v 293.651 458.498 667.416 +v 484.703 406.702 631.39 +v 490.612 394.687 692.953 +v 451.236 316.167 688.596 +v 451.236 316.167 688.596 +v 460.045 334.815 626.719 +v 484.703 406.702 631.39 +v 271.471 377.778 655.645 +v 304.327 381.701 790.592 +v 317.335 458.301 787.405 +v 317.335 458.301 787.405 +v 293.651 458.498 667.416 +v 271.471 377.778 655.645 +v 418.604 481.374 630.348 +v 433.476 496.305 693.155 +v 490.612 394.687 692.953 +v 490.612 394.687 692.953 +v 484.703 406.702 631.39 +v 418.604 481.374 630.348 +v 340.152 283.009 825.111 +v 499.471 257.736 820.408 +v 580.055 378.791 836.628 +v 580.055 378.791 836.628 +v 304.327 381.701 790.592 +v 340.152 283.009 825.111 +v 451.236 316.167 688.596 +v 527.033 273.943 790.548 +v 499.471 257.736 820.408 +v 499.471 257.736 820.408 +v 340.152 283.009 825.111 +v 451.236 316.167 688.596 +v 317.335 458.301 787.405 +v 486.543 519.264 861.734 +v 507.467 493.833 768.062 +v 507.467 493.833 768.062 +v 433.476 496.305 693.155 +v 317.335 458.301 787.405 +v 490.612 394.687 692.953 +v 577.257 377.751 761.531 +v 527.033 273.943 790.548 +v 527.033 273.943 790.548 +v 451.236 316.167 688.596 +v 490.612 394.687 692.953 +v 304.327 381.701 790.592 +v 580.055 378.791 836.628 +v 486.543 519.264 861.734 +v 486.543 519.264 861.734 +v 317.335 458.301 787.405 +v 304.327 381.701 790.592 +v 433.476 496.305 693.155 +v 507.467 493.833 768.062 +v 577.257 377.751 761.531 +v 577.257 377.751 761.531 +v 490.612 394.687 692.953 +v 433.476 496.305 693.155 +v 486.543 519.264 861.734 +v 626.437 533.232 888.782 +v 644.719 516.635 823.452 +v 644.719 516.635 823.452 +v 507.467 493.833 768.062 +v 486.543 519.264 861.734 +v 580.055 378.791 836.628 +v 627.235 439.741 885.962 +v 626.437 533.232 888.782 +v 626.437 533.232 888.782 +v 486.543 519.264 861.734 +v 580.055 378.791 836.628 +v 507.467 493.833 768.062 +v 644.719 516.635 823.452 +v 645.052 439.824 822.685 +v 645.052 439.824 822.685 +v 577.257 377.751 761.531 +v 507.467 493.833 768.062 +v 499.471 257.736 820.408 +v 626.526 261.924 888.681 +v 625.983 345.278 889.066 +v 625.983 345.278 889.066 +v 580.055 378.791 836.628 +v 499.471 257.736 820.408 +v 527.033 273.943 790.548 +v 645.299 278.13 822.477 +v 626.526 261.924 888.681 +v 626.526 261.924 888.681 +v 499.471 257.736 820.408 +v 527.033 273.943 790.548 +v 577.257 377.751 761.531 +v 645.229 332.579 822.897 +v 645.299 278.13 822.477 +v 645.299 278.13 822.477 +v 527.033 273.943 790.548 +v 577.257 377.751 761.531 +v 645.052 439.824 822.685 +v 627.235 439.741 885.962 +v 580.055 378.791 836.628 +v 580.055 378.791 836.628 +v 577.257 377.751 761.531 +v 645.052 439.824 822.685 +v 577.257 377.751 761.531 +v 580.055 378.791 836.628 +v 625.983 345.278 889.066 +v 625.983 345.278 889.066 +v 645.229 332.579 822.897 +v 577.257 377.751 761.531 +v 626.437 533.232 888.782 +v 695.541 460.197 913.328 +v 644.719 516.635 823.452 +v 627.235 439.741 885.962 +v 695.541 460.197 913.328 +v 626.437 533.232 888.782 +v 644.719 516.635 823.452 +v 695.541 460.197 913.328 +v 645.052 439.824 822.685 +v 626.526 261.924 888.681 +v 697.205 261.658 900.122 +v 625.983 345.278 889.066 +v 645.299 278.13 822.477 +v 697.205 261.658 900.122 +v 626.526 261.924 888.681 +v 645.229 332.579 822.897 +v 697.205 261.658 900.122 +v 645.299 278.13 822.477 +v 645.052 439.824 822.685 +v 695.541 460.197 913.328 +v 627.235 439.741 885.962 +v 625.983 345.278 889.066 +v 697.205 261.658 900.122 +v 645.229 332.579 822.897 +v 960.668 78.2619 -383.161 +v 922.953 120.084 -375.782 +v 1039.45 158.244 -303.194 +v 1039.45 158.244 -303.194 +v 1053.7 98.6425 -346.758 +v 960.668 78.2619 -383.161 +v 1053.7 98.6425 -346.758 +v 1039.45 158.244 -303.194 +v 1101.1 130.047 -253.686 +v 1101.1 130.047 -253.686 +v 1117.05 95.0714 -272.841 +v 1053.7 98.6425 -346.758 +v 922.953 120.084 -375.782 +v 885.246 144.315 -351.891 +v 1015.08 192.237 -235.109 +v 1015.08 192.237 -235.109 +v 1039.45 158.244 -303.194 +v 922.953 120.084 -375.782 +v 1039.45 158.244 -303.194 +v 1015.08 192.237 -235.109 +v 1102.84 151.486 -161.378 +v 1102.84 151.486 -161.378 +v 1101.1 130.047 -253.686 +v 1039.45 158.244 -303.194 +v 885.246 144.315 -351.891 +v 843.785 151.745 -368.243 +v 965.997 209.493 -179.582 +v 965.997 209.493 -179.582 +v 1015.08 192.237 -235.109 +v 885.246 144.315 -351.891 +v 1015.08 192.237 -235.109 +v 965.997 209.493 -179.582 +v 1077.52 168.519 -75.2012 +v 1077.52 168.519 -75.2012 +v 1102.84 151.486 -161.378 +v 1015.08 192.237 -235.109 +v 843.785 151.745 -368.243 +v 676.056 91.0124 -403.457 +v 846.835 189.911 -138.578 +v 846.835 189.911 -138.578 +v 965.997 209.493 -179.582 +v 843.785 151.745 -368.243 +v 965.997 209.493 -179.582 +v 846.835 189.911 -138.578 +v 918.453 175.699 -14.2217 +v 965.997 209.493 -179.582 +v 918.453 175.699 -14.2217 +v 987.83 177.274 -34.0016 +v 965.997 209.493 -179.582 +v 987.83 177.274 -34.0016 +v 1077.52 168.519 -75.2012 +v 1101.1 130.047 -253.686 +v 1130.63 90.2301 -219.113 +v 1117.05 95.0714 -272.841 +v 987.83 177.274 -34.0016 +v 1056.88 133.496 2.82201 +v 1077.52 168.519 -75.2012 +v 1102.84 151.486 -161.378 +v 1120.75 112.796 -128.277 +v 1130.63 90.2301 -219.113 +v 1130.63 90.2301 -219.113 +v 1101.1 130.047 -253.686 +v 1102.84 151.486 -161.378 +v 1077.52 168.519 -75.2012 +v 1120.75 112.796 -128.277 +v 1102.84 151.486 -161.378 +v 1150.35 40.2028 -66.9116 +v 1089.27 119.474 -37.2052 +v 1132.43 40.2028 -24.6494 +v 997.81 125.62 37.0952 +v 1031.13 40.2028 65.4153 +v 1100.85 40.2028 23.3858 +v 1100.85 40.2028 23.3858 +v 1056.88 133.496 2.82201 +v 997.81 125.62 37.0952 +v 997.81 125.62 37.0952 +v 972.648 143.973 25.3677 +v 984.155 40.2028 76.1361 +v 1089.27 119.474 -37.2052 +v 1150.35 40.2028 -66.9116 +v 1077.52 168.519 -75.2012 +v 1089.27 119.474 -37.2052 +v 1077.52 168.519 -75.2012 +v 1056.88 133.496 2.82201 +v 972.648 143.973 25.3677 +v 997.81 125.62 37.0952 +v 1056.88 133.496 2.82201 +v 972.648 143.973 25.3677 +v 1056.88 133.496 2.82201 +v 987.83 177.274 -34.0016 +v 972.648 143.973 25.3677 +v 987.83 177.274 -34.0016 +v 918.453 175.699 -14.2217 +v 1089.27 119.474 -37.2052 +v 1122.15 40.2028 2.15071 +v 1132.43 40.2028 -24.6494 +v 1089.27 119.474 -37.2052 +v 1056.88 133.496 2.82201 +v 1100.85 40.2028 23.3858 +v 1100.85 40.2028 23.3858 +v 1122.15 40.2028 2.15071 +v 1089.27 119.474 -37.2052 +v 997.81 125.62 37.0952 +v 984.155 40.2028 76.1361 +v 1031.13 40.2028 65.4153 +v 558.966 89.8242 -404.09 +v 268.451 249.667 -290.85 +v 596.877 306.826 -202.43 +v 558.966 89.8242 -404.09 +v 596.877 306.826 -202.43 +v 705.303 219.02 -221.823 +v 596.877 306.826 -202.43 +v 380.051 331.937 -66.4672 +v 545.802 418.428 -93.8417 +v 380.051 331.937 -66.4672 +v 260.646 303.899 -153.714 +v 242.389 284.152 -21.5963 +v 705.303 219.02 -221.823 +v 596.877 306.826 -202.43 +v 729.558 324.573 -136.921 +v -627.402 40.2031 -27.0373 +v -974.541 40.2032 116.414 +v -598.079 97.9116 13.3312 +v -598.079 97.9116 13.3312 +v -966.748 86.8306 188.349 +v -635.946 99.4678 145.914 +v -973.5 40.2032 204.705 +v -605.1 40.2031 174.866 +v -635.946 99.4678 145.914 +v -635.946 99.4678 145.914 +v -966.748 86.8306 188.349 +v -973.5 40.2032 204.705 +v -598.079 97.9116 13.3312 +v -974.541 40.2032 116.414 +v -966.748 86.8306 188.349 +v -1356.56 67.297 175.492 +v -966.748 86.8306 188.349 +v -974.541 40.2032 116.414 +v -974.541 40.2032 116.414 +v -1360.94 40.2032 134.989 +v -1356.56 67.297 175.492 +v -1360.36 40.2032 184.7 +v -973.5 40.2032 204.705 +v -966.748 86.8306 188.349 +v -966.748 86.8306 188.349 +v -1356.56 67.297 175.492 +v -1360.36 40.2032 184.7 +v -1360.94 40.2032 134.989 +v -1746.98 40.2033 71.4633 +v -1356.56 67.297 175.492 +v -1356.56 67.297 175.492 +v -1746.98 40.2033 71.4633 +v -1360.36 40.2032 184.7 +v 846.835 189.911 -138.578 +v 789.328 152.689 -20.678 +v 918.453 175.699 -14.2217 +v 676.056 91.0124 -403.457 +v 705.303 219.02 -221.823 +v 846.835 189.911 -138.578 +v 558.966 89.8242 -404.09 +v 442.767 40.2029 -586.685 +v 505.639 40.2029 -412.719 +v 676.056 91.0124 -403.457 +v 482.298 74.6915 -625.285 +v 558.966 89.8242 -404.09 +v 676.056 91.0124 -403.457 +v 679.933 40.2029 -441.705 +v 486.264 40.2029 -638.201 +v 486.264 40.2029 -638.201 +v 482.298 74.6915 -625.285 +v 676.056 91.0124 -403.457 +v 482.298 74.6915 -625.285 +v 442.767 40.2029 -586.685 +v 558.966 89.8242 -404.09 +v 442.767 40.2029 -586.685 +v 482.298 74.6915 -625.285 +v 309.065 59.7757 -777.283 +v 309.065 59.7757 -777.283 +v 286.808 40.203 -755.55 +v 442.767 40.2029 -586.685 +v 482.298 74.6915 -625.285 +v 486.264 40.2029 -638.201 +v 311.298 40.203 -784.555 +v 311.298 40.203 -784.555 +v 309.065 59.7757 -777.283 +v 482.298 74.6915 -625.285 +v 309.065 59.7757 -777.283 +v 3.60059 40.203 -892.627 +v 286.808 40.203 -755.55 +v 311.298 40.203 -784.555 +v 3.60059 40.203 -892.627 +v 309.065 59.7757 -777.283 +v -107.623 179.419 -353.534 +v -312.712 40.203 -483.512 +v -233.929 40.203 -337.011 +v 32.5666 159.26 -387.298 +v -261.253 74.6916 -577.012 +v -107.623 179.419 -353.534 +v 32.5666 159.26 -387.298 +v 21.7976 40.203 -426.108 +v -260.883 40.203 -590.518 +v -260.883 40.203 -590.518 +v -261.253 74.6916 -577.012 +v 32.5666 159.26 -387.298 +v -261.253 74.6916 -577.012 +v -312.712 40.203 -483.512 +v -107.623 179.419 -353.534 +v -312.712 40.203 -483.512 +v -261.253 74.6916 -577.012 +v -468.805 59.7759 -677.187 +v -468.805 59.7759 -677.187 +v -484.444 40.2031 -650.296 +v -312.712 40.203 -483.512 +v -261.253 74.6916 -577.012 +v -260.883 40.203 -590.518 +v -468.596 40.2031 -684.792 +v -468.596 40.2031 -684.792 +v -468.805 59.7759 -677.187 +v -261.253 74.6916 -577.012 +v -468.805 59.7759 -677.187 +v -793.984 40.2031 -706.704 +v -484.444 40.2031 -650.296 +v -468.596 40.2031 -684.792 +v -793.984 40.2031 -706.704 +v -468.805 59.7759 -677.187 +f 1 2 3 +f 4 5 6 +f 7 8 9 +f 10 11 12 +f 13 14 15 +f 16 17 18 +f 19 20 21 +f 22 23 24 +f 25 26 27 +f 28 29 30 +f 31 32 33 +f 34 35 36 +f 37 38 39 +f 40 41 42 +f 43 44 45 +f 46 47 48 +f 49 50 51 +f 52 53 54 +f 55 56 57 +f 58 59 60 +f 61 62 63 +f 64 65 66 +f 67 68 69 +f 70 71 72 +f 73 74 75 +f 76 77 78 +f 79 80 81 +f 82 83 84 +f 85 86 87 +f 88 89 90 +f 91 92 93 +f 94 95 96 +f 97 98 99 +f 100 101 102 +f 103 104 105 +f 106 107 108 +f 109 110 111 +f 112 113 114 +f 115 116 117 +f 118 119 120 +f 121 122 123 +f 124 125 126 +f 127 128 129 +f 130 131 132 +f 133 134 135 +f 136 137 138 +f 139 140 141 +f 142 143 144 +f 145 146 147 +f 148 149 150 +f 151 152 153 +f 154 155 156 +f 157 158 159 +f 160 161 162 +f 163 164 165 +f 166 167 168 +f 169 170 171 +f 172 173 174 +f 175 176 177 +f 178 179 180 +f 181 182 183 +f 184 185 186 +f 187 188 189 +f 190 191 192 +f 193 194 195 +f 196 197 198 +f 199 200 201 +f 202 203 204 +f 205 206 207 +f 208 209 210 +f 211 212 213 +f 214 215 216 +f 217 218 219 +f 220 221 222 +f 223 224 225 +f 226 227 228 +f 229 230 231 +f 232 233 234 +f 235 236 237 +f 238 239 240 +f 241 242 243 +f 244 245 246 +f 247 248 249 +f 250 251 252 +f 253 254 255 +f 256 257 258 +f 259 260 261 +f 262 263 264 +f 265 266 267 +f 268 269 270 +f 271 272 273 +f 274 275 276 +f 277 278 279 +f 280 281 282 +f 283 284 285 +f 286 287 288 +f 289 290 291 +f 292 293 294 +f 295 296 297 +f 298 299 300 +f 301 302 303 +f 304 305 306 +f 307 308 309 +f 310 311 312 +f 313 314 315 +f 316 317 318 +f 319 320 321 +f 322 323 324 +f 325 326 327 +f 328 329 330 +f 331 332 333 +f 334 335 336 +f 337 338 339 +f 340 341 342 +f 343 344 345 +f 346 347 348 +f 349 350 351 +f 352 353 354 +f 355 356 357 +f 358 359 360 +f 361 362 363 +f 364 365 366 +f 367 368 369 +f 370 371 372 +f 373 374 375 +f 376 377 378 +f 379 380 381 +f 382 383 384 +f 385 386 387 +f 388 389 390 +f 391 392 393 +f 394 395 396 +f 397 398 399 +f 400 401 402 +f 403 404 405 +f 406 407 408 +f 409 410 411 +f 412 413 414 +f 415 416 417 +f 418 419 420 +f 421 422 423 +f 424 425 426 +f 427 428 429 +f 430 431 432 +f 433 434 435 +f 436 437 438 +f 439 440 441 +f 442 443 444 +f 445 446 447 +f 448 449 450 +f 451 452 453 +f 454 455 456 +f 457 458 459 +f 460 461 462 +f 463 464 465 +f 466 467 468 +f 469 470 471 +f 472 473 474 +f 475 476 477 +f 478 479 480 +f 481 482 483 +f 484 485 486 +f 487 488 489 +f 490 491 492 +f 493 494 495 +f 496 497 498 +f 499 500 501 +f 502 503 504 +f 505 506 507 +f 508 509 510 +f 511 512 513 +f 514 515 516 +f 517 518 519 +f 520 521 522 +f 523 524 525 +f 526 527 528 +f 529 530 531 +f 532 533 534 +f 535 536 537 +f 538 539 540 +f 541 542 543 +f 544 545 546 +f 547 548 549 +f 550 551 552 +f 553 554 555 +f 556 557 558 +f 559 560 561 +f 562 563 564 +f 565 566 567 +f 568 569 570 +f 571 572 573 +f 574 575 576 +f 577 578 579 +f 580 581 582 +f 583 584 585 +f 586 587 588 +f 589 590 591 +f 592 593 594 +f 595 596 597 +f 598 599 600 +f 601 602 603 +f 604 605 606 +f 607 608 609 +f 610 611 612 +f 613 614 615 +f 616 617 618 +f 619 620 621 +f 622 623 624 +f 625 626 627 +f 628 629 630 +f 631 632 633 +f 634 635 636 +f 637 638 639 +f 640 641 642 +f 643 644 645 +f 646 647 648 +f 649 650 651 +f 652 653 654 +f 655 656 657 +f 658 659 660 +f 661 662 663 +f 664 665 666 +f 667 668 669 +f 670 671 672 +f 673 674 675 +f 676 677 678 +f 679 680 681 +f 682 683 684 +f 685 686 687 +f 688 689 690 +f 691 692 693 +f 694 695 696 +f 697 698 699 +f 700 701 702 +f 703 704 705 +f 706 707 708 +f 709 710 711 +f 712 713 714 +f 715 716 717 +f 718 719 720 +f 721 722 723 +f 724 725 726 +f 727 728 729 +f 730 731 732 +f 733 734 735 +f 736 737 738 +f 739 740 741 +f 742 743 744 +f 745 746 747 +f 748 749 750 +f 751 752 753 +f 754 755 756 +f 757 758 759 +f 760 761 762 +f 763 764 765 +f 766 767 768 +f 769 770 771 +f 772 773 774 +f 775 776 777 +f 778 779 780 +f 781 782 783 +f 784 785 786 +f 787 788 789 +f 790 791 792 +f 793 794 795 +f 796 797 798 +f 799 800 801 +f 802 803 804 +f 805 806 807 +f 808 809 810 +f 811 812 813 +f 814 815 816 +f 817 818 819 +f 820 821 822 +f 823 824 825 +f 826 827 828 +f 829 830 831 +f 832 833 834 +f 835 836 837 +f 838 839 840 +f 841 842 843 +f 844 845 846 +f 847 848 849 +f 850 851 852 +f 853 854 855 +f 856 857 858 +f 859 860 861 +f 862 863 864 +f 865 866 867 +f 868 869 870 +f 871 872 873 +f 874 875 876 +f 877 878 879 +f 880 881 882 +f 883 884 885 +f 886 887 888 +f 889 890 891 +f 892 893 894 +f 895 896 897 +f 898 899 900 +f 901 902 903 +f 904 905 906 +f 907 908 909 +f 910 911 912 +f 913 914 915 +f 916 917 918 +f 919 920 921 +f 922 923 924 +f 925 926 927 +f 928 929 930 +f 931 932 933 +f 934 935 936 +f 937 938 939 +f 940 941 942 +f 943 944 945 +f 946 947 948 +f 949 950 951 +f 952 953 954 +f 955 956 957 +f 958 959 960 +f 961 962 963 +f 964 965 966 +f 967 968 969 +f 970 971 972 +f 973 974 975 +f 976 977 978 +f 979 980 981 +f 982 983 984 +f 985 986 987 +f 988 989 990 +f 991 992 993 +f 994 995 996 +f 997 998 999 +f 1000 1001 1002 +f 1003 1004 1005 +f 1006 1007 1008 +f 1009 1010 1011 +f 1012 1013 1014 +f 1015 1016 1017 +f 1018 1019 1020 +f 1021 1022 1023 +f 1024 1025 1026 +f 1027 1028 1029 +f 1030 1031 1032 +f 1033 1034 1035 +f 1036 1037 1038 +f 1039 1040 1041 +f 1042 1043 1044 +f 1045 1046 1047 +f 1048 1049 1050 +f 1051 1052 1053 +f 1054 1055 1056 +f 1057 1058 1059 +f 1060 1061 1062 +f 1063 1064 1065 +f 1066 1067 1068 +f 1069 1070 1071 +f 1072 1073 1074 +f 1075 1076 1077 +f 1078 1079 1080 +f 1081 1082 1083 +f 1084 1085 1086 +f 1087 1088 1089 +f 1090 1091 1092 +f 1093 1094 1095 +f 1096 1097 1098 +f 1099 1100 1101 +f 1102 1103 1104 +f 1105 1106 1107 +f 1108 1109 1110 +f 1111 1112 1113 +f 1114 1115 1116 +f 1117 1118 1119 +f 1120 1121 1122 +f 1123 1124 1125 +f 1126 1127 1128 +f 1129 1130 1131 +f 1132 1133 1134 +f 1135 1136 1137 +f 1138 1139 1140 +f 1141 1142 1143 +f 1144 1145 1146 +f 1147 1148 1149 +f 1150 1151 1152 +f 1153 1154 1155 +f 1156 1157 1158 +f 1159 1160 1161 +f 1162 1163 1164 +f 1165 1166 1167 +f 1168 1169 1170 +f 1171 1172 1173 +f 1174 1175 1176 +f 1177 1178 1179 +f 1180 1181 1182 +f 1183 1184 1185 +f 1186 1187 1188 +f 1189 1190 1191 +f 1192 1193 1194 +f 1195 1196 1197 +f 1198 1199 1200 +f 1201 1202 1203 +f 1204 1205 1206 +f 1207 1208 1209 +f 1210 1211 1212 +f 1213 1214 1215 +f 1216 1217 1218 +f 1219 1220 1221 +f 1222 1223 1224 +f 1225 1226 1227 +f 1228 1229 1230 +f 1231 1232 1233 +f 1234 1235 1236 +f 1237 1238 1239 +f 1240 1241 1242 +f 1243 1244 1245 +f 1246 1247 1248 +f 1249 1250 1251 +f 1252 1253 1254 +f 1255 1256 1257 +f 1258 1259 1260 +f 1261 1262 1263 +f 1264 1265 1266 +f 1267 1268 1269 +f 1270 1271 1272 +f 1273 1274 1275 +f 1276 1277 1278 +f 1279 1280 1281 +f 1282 1283 1284 +f 1285 1286 1287 +f 1288 1289 1290 +f 1291 1292 1293 +f 1294 1295 1296 +f 1297 1298 1299 +f 1300 1301 1302 +f 1303 1304 1305 +f 1306 1307 1308 +f 1309 1310 1311 +f 1312 1313 1314 +f 1315 1316 1317 +f 1318 1319 1320 +f 1321 1322 1323 +f 1324 1325 1326 +f 1327 1328 1329 +f 1330 1331 1332 +f 1333 1334 1335 +f 1336 1337 1338 +f 1339 1340 1341 +f 1342 1343 1344 +f 1345 1346 1347 +f 1348 1349 1350 +f 1351 1352 1353 +f 1354 1355 1356 +f 1357 1358 1359 +f 1360 1361 1362 +f 1363 1364 1365 +f 1366 1367 1368 +f 1369 1370 1371 +f 1372 1373 1374 +f 1375 1376 1377 +f 1378 1379 1380 +f 1381 1382 1383 +f 1384 1385 1386 +f 1387 1388 1389 +f 1390 1391 1392 +f 1393 1394 1395 +f 1396 1397 1398 +f 1399 1400 1401 +f 1402 1403 1404 +f 1405 1406 1407 +f 1408 1409 1410 +f 1411 1412 1413 +f 1414 1415 1416 +f 1417 1418 1419 +f 1420 1421 1422 +f 1423 1424 1425 +f 1426 1427 1428 +f 1429 1430 1431 +f 1432 1433 1434 +f 1435 1436 1437 +f 1438 1439 1440 +f 1441 1442 1443 +f 1444 1445 1446 +f 1447 1448 1449 +f 1450 1451 1452 +f 1453 1454 1455 +f 1456 1457 1458 +f 1459 1460 1461 +f 1462 1463 1464 +f 1465 1466 1467 +f 1468 1469 1470 +f 1471 1472 1473 +f 1474 1475 1476 +f 1477 1478 1479 +f 1480 1481 1482 +f 1483 1484 1485 +f 1486 1487 1488 +f 1489 1490 1491 +f 1492 1493 1494 +f 1495 1496 1497 +f 1498 1499 1500 +f 1501 1502 1503 +f 1504 1505 1506 +f 1507 1508 1509 +f 1510 1511 1512 +f 1513 1514 1515 +f 1516 1517 1518 +f 1519 1520 1521 +f 1522 1523 1524 +f 1525 1526 1527 +f 1528 1529 1530 +f 1531 1532 1533 +f 1534 1535 1536 +f 1537 1538 1539 +f 1540 1541 1542 +f 1543 1544 1545 +f 1546 1547 1548 +f 1549 1550 1551 +f 1552 1553 1554 +f 1555 1556 1557 +f 1558 1559 1560 +f 1561 1562 1563 +f 1564 1565 1566 +f 1567 1568 1569 +f 1570 1571 1572 +f 1573 1574 1575 +f 1576 1577 1578 +f 1579 1580 1581 +f 1582 1583 1584 +f 1585 1586 1587 +f 1588 1589 1590 +f 1591 1592 1593 +f 1594 1595 1596 +f 1597 1598 1599 +f 1600 1601 1602 +f 1603 1604 1605 +f 1606 1607 1608 +f 1609 1610 1611 +f 1612 1613 1614 +f 1615 1616 1617 +f 1618 1619 1620 +f 1621 1622 1623 +f 1624 1625 1626 +f 1627 1628 1629 +f 1630 1631 1632 +f 1633 1634 1635 +f 1636 1637 1638 +f 1639 1640 1641 +f 1642 1643 1644 +f 1645 1646 1647 +f 1648 1649 1650 +f 1651 1652 1653 +f 1654 1655 1656 +f 1657 1658 1659 +f 1660 1661 1662 +f 1663 1664 1665 +f 1666 1667 1668 +f 1669 1670 1671 +f 1672 1673 1674 +f 1675 1676 1677 +f 1678 1679 1680 +f 1681 1682 1683 +f 1684 1685 1686 +f 1687 1688 1689 +f 1690 1691 1692 +f 1693 1694 1695 +f 1696 1697 1698 +f 1699 1700 1701 +f 1702 1703 1704 +f 1705 1706 1707 +f 1708 1709 1710 +f 1711 1712 1713 +f 1714 1715 1716 +f 1717 1718 1719 +f 1720 1721 1722 +f 1723 1724 1725 +f 1726 1727 1728 +f 1729 1730 1731 +f 1732 1733 1734 +f 1735 1736 1737 +f 1738 1739 1740 +f 1741 1742 1743 +f 1744 1745 1746 +f 1747 1748 1749 +f 1750 1751 1752 +f 1753 1754 1755 +f 1756 1757 1758 +f 1759 1760 1761 +f 1762 1763 1764 +f 1765 1766 1767 +f 1768 1769 1770 +f 1771 1772 1773 +f 1774 1775 1776 +f 1777 1778 1779 +f 1780 1781 1782 +f 1783 1784 1785 +f 1786 1787 1788 +f 1789 1790 1791 +f 1792 1793 1794 +f 1795 1796 1797 +f 1798 1799 1800 +f 1801 1802 1803 +f 1804 1805 1806 +f 1807 1808 1809 +f 1810 1811 1812 +f 1813 1814 1815 +f 1816 1817 1818 +f 1819 1820 1821 +f 1822 1823 1824 +f 1825 1826 1827 +f 1828 1829 1830 +f 1831 1832 1833 +f 1834 1835 1836 +f 1837 1838 1839 +f 1840 1841 1842 +f 1843 1844 1845 +f 1846 1847 1848 +f 1849 1850 1851 +f 1852 1853 1854 +f 1855 1856 1857 +f 1858 1859 1860 +f 1861 1862 1863 +f 1864 1865 1866 +f 1867 1868 1869 +f 1870 1871 1872 +f 1873 1874 1875 +f 1876 1877 1878 +f 1879 1880 1881 +f 1882 1883 1884 +f 1885 1886 1887 +f 1888 1889 1890 +f 1891 1892 1893 +f 1894 1895 1896 +f 1897 1898 1899 +f 1900 1901 1902 +f 1903 1904 1905 +f 1906 1907 1908 +f 1909 1910 1911 +f 1912 1913 1914 +f 1915 1916 1917 +f 1918 1919 1920 +f 1921 1922 1923 +f 1924 1925 1926 +f 1927 1928 1929 +f 1930 1931 1932 +f 1933 1934 1935 +f 1936 1937 1938 +f 1939 1940 1941 +f 1942 1943 1944 +f 1945 1946 1947 +f 1948 1949 1950 +f 1951 1952 1953 +f 1954 1955 1956 +f 1957 1958 1959 +f 1960 1961 1962 +f 1963 1964 1965 +f 1966 1967 1968 +f 1969 1970 1971 +f 1972 1973 1974 +f 1975 1976 1977 +f 1978 1979 1980 +f 1981 1982 1983 +f 1984 1985 1986 +f 1987 1988 1989 +f 1990 1991 1992 +f 1993 1994 1995 +f 1996 1997 1998 +f 1999 2000 2001 +f 2002 2003 2004 +f 2005 2006 2007 +f 2008 2009 2010 +f 2011 2012 2013 +f 2014 2015 2016 +f 2017 2018 2019 +f 2020 2021 2022 +f 2023 2024 2025 +f 2026 2027 2028 +f 2029 2030 2031 +f 2032 2033 2034 +f 2035 2036 2037 +f 2038 2039 2040 +f 2041 2042 2043 +f 2044 2045 2046 +f 2047 2048 2049 +f 2050 2051 2052 +f 2053 2054 2055 +f 2056 2057 2058 +f 2059 2060 2061 +f 2062 2063 2064 +f 2065 2066 2067 +f 2068 2069 2070 +f 2071 2072 2073 +f 2074 2075 2076 +f 2077 2078 2079 +f 2080 2081 2082 +f 2083 2084 2085 +f 2086 2087 2088 +f 2089 2090 2091 +f 2092 2093 2094 +f 2095 2096 2097 +f 2098 2099 2100 +f 2101 2102 2103 +f 2104 2105 2106 +f 2107 2108 2109 +f 2110 2111 2112 +f 2113 2114 2115 +f 2116 2117 2118 +f 2119 2120 2121 +f 2122 2123 2124 +f 2125 2126 2127 +f 2128 2129 2130 +f 2131 2132 2133 +f 2134 2135 2136 +f 2137 2138 2139 +f 2140 2141 2142 +f 2143 2144 2145 +f 2146 2147 2148 +f 2149 2150 2151 +f 2152 2153 2154 +f 2155 2156 2157 +f 2158 2159 2160 +f 2161 2162 2163 +f 2164 2165 2166 +f 2167 2168 2169 +f 2170 2171 2172 +f 2173 2174 2175 +f 2176 2177 2178 +f 2179 2180 2181 +f 2182 2183 2184 +f 2185 2186 2187 +f 2188 2189 2190 +f 2191 2192 2193 +f 2194 2195 2196 +f 2197 2198 2199 +f 2200 2201 2202 +f 2203 2204 2205 +f 2206 2207 2208 +f 2209 2210 2211 +f 2212 2213 2214 +f 2215 2216 2217 +f 2218 2219 2220 +f 2221 2222 2223 +f 2224 2225 2226 +f 2227 2228 2229 +f 2230 2231 2232 +f 2233 2234 2235 +f 2236 2237 2238 +f 2239 2240 2241 +f 2242 2243 2244 +f 2245 2246 2247 +f 2248 2249 2250 +f 2251 2252 2253 +f 2254 2255 2256 +f 2257 2258 2259 +f 2260 2261 2262 +f 2263 2264 2265 +f 2266 2267 2268 +f 2269 2270 2271 +f 2272 2273 2274 +f 2275 2276 2277 +f 2278 2279 2280 +f 2281 2282 2283 +f 2284 2285 2286 +f 2287 2288 2289 +f 2290 2291 2292 +f 2293 2294 2295 +f 2296 2297 2298 +f 2299 2300 2301 +f 2302 2303 2304 +f 2305 2306 2307 +f 2308 2309 2310 +f 2311 2312 2313 +f 2314 2315 2316 +f 2317 2318 2319 +f 2320 2321 2322 +f 2323 2324 2325 +f 2326 2327 2328 +f 2329 2330 2331 +f 2332 2333 2334 +f 2335 2336 2337 +f 2338 2339 2340 +f 2341 2342 2343 +f 2344 2345 2346 +f 2347 2348 2349 +f 2350 2351 2352 +f 2353 2354 2355 +f 2356 2357 2358 +f 2359 2360 2361 +f 2362 2363 2364 +f 2365 2366 2367 +f 2368 2369 2370 +f 2371 2372 2373 +f 2374 2375 2376 +f 2377 2378 2379 +f 2380 2381 2382 +f 2383 2384 2385 +f 2386 2387 2388 +f 2389 2390 2391 +f 2392 2393 2394 +f 2395 2396 2397 +f 2398 2399 2400 +f 2401 2402 2403 +f 2404 2405 2406 +f 2407 2408 2409 +f 2410 2411 2412 +f 2413 2414 2415 +f 2416 2417 2418 +f 2419 2420 2421 +f 2422 2423 2424 +f 2425 2426 2427 +f 2428 2429 2430 +f 2431 2432 2433 +f 2434 2435 2436 +f 2437 2438 2439 +f 2440 2441 2442 +f 2443 2444 2445 +f 2446 2447 2448 +f 2449 2450 2451 +f 2452 2453 2454 +f 2455 2456 2457 +f 2458 2459 2460 +f 2461 2462 2463 +f 2464 2465 2466 +f 2467 2468 2469 +f 2470 2471 2472 +f 2473 2474 2475 +f 2476 2477 2478 +f 2479 2480 2481 +f 2482 2483 2484 +f 2485 2486 2487 +f 2488 2489 2490 +f 2491 2492 2493 +f 2494 2495 2496 +f 2497 2498 2499 +f 2500 2501 2502 +f 2503 2504 2505 +f 2506 2507 2508 +f 2509 2510 2511 +f 2512 2513 2514 +f 2515 2516 2517 +f 2518 2519 2520 +f 2521 2522 2523 +f 2524 2525 2526 +f 2527 2528 2529 +f 2530 2531 2532 +f 2533 2534 2535 +f 2536 2537 2538 +f 2539 2540 2541 +f 2542 2543 2544 +f 2545 2546 2547 +f 2548 2549 2550 +f 2551 2552 2553 +f 2554 2555 2556 +f 2557 2558 2559 +f 2560 2561 2562 +f 2563 2564 2565 +f 2566 2567 2568 +f 2569 2570 2571 +f 2572 2573 2574 +f 2575 2576 2577 +f 2578 2579 2580 +f 2581 2582 2583 +f 2584 2585 2586 +f 2587 2588 2589 +f 2590 2591 2592 +f 2593 2594 2595 +f 2596 2597 2598 +f 2599 2600 2601 +f 2602 2603 2604 +f 2605 2606 2607 +f 2608 2609 2610 +f 2611 2612 2613 +f 2614 2615 2616 +f 2617 2618 2619 +f 2620 2621 2622 +f 2623 2624 2625 +f 2626 2627 2628 +f 2629 2630 2631 +f 2632 2633 2634 +f 2635 2636 2637 +f 2638 2639 2640 +f 2641 2642 2643 +f 2644 2645 2646 +f 2647 2648 2649 +f 2650 2651 2652 diff --git a/docdoku-web-front/tests/res/document-upload2.txt b/docdoku-web-front/tests/res/document-upload2.txt new file mode 100644 index 0000000000..0f3c53cf31 --- /dev/null +++ b/docdoku-web-front/tests/res/document-upload2.txt @@ -0,0 +1,3537 @@ +# 2652 vertices, 884 faces +v 260.312 40.203 -440.008 +v 505.639 40.2029 -412.719 +v 558.965 -9.41835 -404.09 +v 558.965 -9.41835 -404.09 +v 252.222 -54.205 -402.864 +v 260.312 40.203 -440.008 +v 21.7976 40.203 -426.108 +v 260.312 40.203 -440.008 +v 252.222 -54.205 -402.864 +v 252.222 -54.205 -402.864 +v 32.5665 -78.854 -387.298 +v 21.7976 40.203 -426.108 +v -107.623 -99.0134 -353.534 +v -359.273 -19.0616 -211.385 +v -378.533 40.2031 -238.981 +v -378.533 40.2031 -238.981 +v -233.929 40.203 -337.011 +v -107.623 -99.0134 -353.534 +v -627.402 40.2031 -27.0373 +v -378.533 40.2031 -238.981 +v -359.273 -19.0616 -211.385 +v -359.273 -19.0616 -211.385 +v -598.079 -17.5054 13.3313 +v -627.402 40.2031 -27.0373 +v -605.1 40.2031 174.866 +v -635.946 -19.0616 145.914 +v -547.166 40.2031 372.164 +v -99.0235 40.203 155.844 +v 22.0084 -55.5322 181.514 +v -25.3967 40.203 194.286 +v 246.48 40.203 170.173 +v -25.3967 40.203 194.286 +v 22.0084 -55.5322 181.514 +v 22.0084 -55.5322 181.514 +v 246.315 -79.6185 147.057 +v 246.48 40.203 170.173 +v 568.185 40.2029 165.976 +v 246.48 40.203 170.173 +v 246.315 -79.6185 147.057 +v 246.315 -79.6185 147.057 +v 542.48 -44.913 151.06 +v 568.185 40.2029 165.976 +v 762.912 40.2029 67.4993 +v 568.185 40.2029 165.976 +v 542.48 -44.913 151.06 +v 542.48 -44.913 151.06 +v 758.412 -62.8404 43.888 +v 762.912 40.2029 67.4993 +v 841.425 40.2029 35.9378 +v 762.912 40.2029 67.4993 +v 758.412 -62.8404 43.888 +v 758.412 -62.8404 43.888 +v 789.328 -72.2831 -20.678 +v 841.425 40.2029 35.9378 +v 912.514 40.2028 48.5509 +v 841.425 40.2029 35.9378 +v 789.328 -72.2831 -20.678 +v 789.328 -72.2831 -20.678 +v 918.453 -95.2934 -14.2217 +v 912.514 40.2028 48.5509 +v 252.222 -54.205 -402.864 +v 558.965 -9.41835 -404.09 +v 268.451 -169.261 -290.85 +v 32.5665 -78.854 -387.298 +v 252.222 -54.205 -402.864 +v 268.451 -169.261 -290.85 +v -598.079 -17.5054 13.3313 +v -359.273 -19.0616 -211.385 +v -321.215 -77.6915 -143.919 +v -321.215 -77.6915 -143.919 +v -537.312 -100.256 32.2996 +v -598.079 -17.5054 13.3313 +v -359.273 -19.0616 -211.385 +v -107.623 -99.0134 -353.534 +v -321.215 -77.6915 -143.919 +v 260.646 -223.493 -153.714 +v 268.451 -169.261 -290.85 +v 596.877 -226.42 -202.43 +v 596.877 -226.42 -202.43 +v 380.051 -251.531 -66.4672 +v 260.646 -223.493 -153.714 +v 39.9728 -156.039 -209.082 +v 32.5665 -78.854 -387.298 +v 268.451 -169.261 -290.85 +v 268.451 -169.261 -290.85 +v 260.646 -223.493 -153.714 +v 39.9728 -156.039 -209.082 +v -107.623 -99.0134 -353.534 +v 32.5665 -78.854 -387.298 +v 39.9728 -156.039 -209.082 +v 39.9728 -156.039 -209.082 +v -121.936 -142.274 -170.547 +v -107.623 -99.0134 -353.534 +v -537.312 -100.256 32.2996 +v -321.215 -77.6915 -143.919 +v -391.04 -139.908 41.853 +v -321.215 -77.6915 -143.919 +v -107.623 -99.0134 -353.534 +v -121.936 -142.274 -170.547 +v 39.9728 -156.039 -209.082 +v 260.646 -223.493 -153.714 +v 242.389 -203.746 -21.5963 +v 242.389 -203.746 -21.5963 +v 22.0431 -156.814 26.5261 +v 39.9728 -156.039 -209.082 +v -121.936 -142.274 -170.547 +v 39.9728 -156.039 -209.082 +v 22.0431 -156.814 26.5261 +v 22.0431 -156.814 26.5261 +v -77.3162 -125.878 32.3855 +v -121.936 -142.274 -170.547 +v -391.04 -139.908 41.853 +v -321.215 -77.6915 -143.919 +v -213.771 -97.4809 92.1041 +v -213.771 -97.4809 92.1041 +v -298.191 -103.042 127.509 +v -391.04 -139.908 41.853 +v -321.215 -77.6915 -143.919 +v -121.936 -142.274 -170.547 +v -77.3162 -125.878 32.3855 +v -77.3162 -125.878 32.3855 +v -213.771 -97.4809 92.1041 +v -321.215 -77.6915 -143.919 +v 242.389 -203.746 -21.5963 +v 380.051 -251.531 -66.4672 +v 494.4 -118.67 153.3 +v 494.4 -118.67 153.3 +v 257.239 -161.894 50.0724 +v 242.389 -203.746 -21.5963 +v 22.0431 -156.814 26.5261 +v 242.389 -203.746 -21.5963 +v 257.239 -161.894 50.0724 +v 257.239 -161.894 50.0724 +v 34.0969 -108.118 134.952 +v 22.0431 -156.814 26.5261 +v -77.3162 -125.878 32.3855 +v 22.0431 -156.814 26.5261 +v 34.0969 -108.118 134.952 +v 34.0969 -108.118 134.952 +v -50.7188 -67.4952 123.835 +v -77.3162 -125.878 32.3855 +v -298.191 -103.042 127.509 +v -213.771 -97.4809 92.1041 +v -257.987 -30.6083 139.94 +v -257.987 -30.6083 139.94 +v -314.968 -77.0878 227.647 +v -298.191 -103.042 127.509 +v -213.771 -97.4809 92.1041 +v -77.3162 -125.878 32.3855 +v -50.7188 -67.4952 123.835 +v -50.7188 -67.4952 123.835 +v -257.987 -30.6083 139.94 +v -213.771 -97.4809 92.1041 +v 246.315 -79.6185 147.057 +v 257.239 -161.894 50.0724 +v 494.4 -118.67 153.3 +v 494.4 -118.67 153.3 +v 542.48 -44.913 151.06 +v 246.315 -79.6185 147.057 +v 34.0969 -108.118 134.952 +v 257.239 -161.894 50.0724 +v 246.315 -79.6185 147.057 +v 246.315 -79.6185 147.057 +v 22.0084 -55.5322 181.514 +v 34.0969 -108.118 134.952 +v -50.7188 -67.4952 123.835 +v 34.0969 -108.118 134.952 +v 22.0084 -55.5322 181.514 +v 22.0084 -55.5322 181.514 +v -99.0235 40.203 155.844 +v -50.7188 -67.4952 123.835 +v -314.968 -77.0878 227.647 +v -257.987 -30.6083 139.94 +v -260.462 40.203 162.299 +v -260.462 40.203 162.299 +v -284.581 40.203 304.684 +v -314.968 -77.0878 227.647 +v -257.987 -30.6083 139.94 +v -50.7188 -67.4952 123.835 +v -99.0235 40.203 155.844 +v -99.0235 40.203 155.844 +v -260.462 40.203 162.299 +v -257.987 -30.6083 139.94 +v 558.965 -9.41835 -404.09 +v 676.056 -10.6067 -403.457 +v 705.303 -138.614 -221.823 +v 729.558 -244.167 -136.921 +v 846.834 -109.506 -138.578 +v 789.328 -72.2831 -20.678 +v -635.946 -19.0616 145.914 +v -598.079 -17.5054 13.3313 +v -537.312 -100.256 32.2996 +v -537.312 -100.256 32.2996 +v -615.112 -86.8028 174.866 +v -635.946 -19.0616 145.914 +v -547.166 40.2031 372.164 +v -635.946 -19.0616 145.914 +v -615.112 -86.8028 174.866 +v -615.112 -86.8028 174.866 +v -550.064 -44.9519 307.245 +v -547.166 40.2031 372.164 +v -284.581 40.203 304.684 +v -547.166 40.2031 372.164 +v -550.064 -44.9519 307.245 +v -550.064 -44.9519 307.245 +v -314.968 -77.0878 227.647 +v -284.581 40.203 304.684 +v -537.312 -100.256 32.2996 +v -391.04 -139.908 41.853 +v -281.314 -243.634 -48.6181 +v -281.314 -243.634 -48.6181 +v -410.356 -316.705 -17.6217 +v -537.312 -100.256 32.2996 +v -391.04 -139.908 41.853 +v -298.191 -103.042 127.509 +v -169.529 -145.953 141.817 +v -169.529 -145.953 141.817 +v -281.314 -243.634 -48.6181 +v -391.04 -139.908 41.853 +v -298.191 -103.042 127.509 +v -314.968 -77.0878 227.647 +v -255.603 -138.915 227.647 +v -255.603 -138.915 227.647 +v -169.529 -145.953 141.817 +v -298.191 -103.042 127.509 +v -615.112 -86.8028 174.866 +v -537.312 -100.256 32.2996 +v -410.356 -316.705 -17.6217 +v -410.356 -316.705 -17.6217 +v -465.797 -326.302 178.963 +v -615.112 -86.8028 174.866 +v -550.064 -44.9519 307.245 +v -615.112 -86.8028 174.866 +v -465.797 -326.302 178.963 +v -465.797 -326.302 178.963 +v -440.269 -242.311 338.364 +v -550.064 -44.9519 307.245 +v -314.968 -77.0878 227.647 +v -550.064 -44.9519 307.245 +v -440.269 -242.311 338.364 +v -440.269 -242.311 338.364 +v -255.603 -138.915 227.647 +v -314.968 -77.0878 227.647 +v -410.356 -316.705 -17.6217 +v -281.314 -243.634 -48.6181 +v 14.2079 -335.227 -45.2649 +v 14.2079 -335.227 -45.2649 +v -24.9719 -444.533 -3.8576 +v -410.356 -316.705 -17.6217 +v -281.314 -243.634 -48.6181 +v -169.529 -145.953 141.817 +v 32.0551 -247.885 -11.6046 +v 32.0551 -247.885 -11.6046 +v 14.2079 -335.227 -45.2649 +v -281.314 -243.634 -48.6181 +v -169.529 -145.953 141.817 +v -255.603 -138.915 227.647 +v -15.0917 -242.667 60.2354 +v -15.0917 -242.667 60.2354 +v 32.0551 -247.885 -11.6046 +v -169.529 -145.953 141.817 +v -465.797 -326.302 178.963 +v -410.356 -316.705 -17.6217 +v -24.9719 -444.533 -3.8576 +v -24.9719 -444.533 -3.8576 +v -103.65 -447.284 112.409 +v -465.797 -326.302 178.963 +v -440.269 -242.311 338.364 +v -465.797 -326.302 178.963 +v -103.65 -447.284 112.409 +v -103.65 -447.284 112.409 +v -132.463 -330.935 170.696 +v -440.269 -242.311 338.364 +v -255.603 -138.915 227.647 +v -440.269 -242.311 338.364 +v -132.463 -330.935 170.696 +v -132.463 -330.935 170.696 +v -15.0917 -242.667 60.2354 +v -255.603 -138.915 227.647 +v -24.9719 -444.533 -3.8576 +v 14.2079 -335.227 -45.2649 +v 154.274 -422.936 15.1391 +v 154.274 -422.936 15.1391 +v 65.7941 -437.565 48.1499 +v -24.9719 -444.533 -3.8576 +v 14.2079 -335.227 -45.2649 +v 32.0551 -247.885 -11.6046 +v 160.1 -339.077 42.0847 +v 160.1 -339.077 42.0847 +v 154.274 -422.936 15.1391 +v 14.2079 -335.227 -45.2649 +v 32.0551 -247.885 -11.6046 +v -15.0917 -242.667 60.2354 +v 65.6433 -310.298 103.599 +v 65.6433 -310.298 103.599 +v 160.1 -339.077 42.0847 +v 32.0551 -247.885 -11.6046 +v -103.65 -447.284 112.409 +v -24.9719 -444.533 -3.8576 +v 65.7941 -437.565 48.1499 +v 65.7941 -437.565 48.1499 +v -52.7382 -378.505 120.825 +v -103.65 -447.284 112.409 +v -132.463 -330.935 170.696 +v -103.65 -447.284 112.409 +v -52.7382 -378.505 120.825 +v -52.7382 -378.505 120.825 +v -86.6906 -345.821 159.574 +v -132.463 -330.935 170.696 +v -15.0917 -242.667 60.2354 +v -132.463 -330.935 170.696 +v -86.6906 -345.821 159.574 +v -86.6906 -345.821 159.574 +v 65.6433 -310.298 103.599 +v -15.0917 -242.667 60.2354 +v 65.7941 -437.565 48.1499 +v 154.274 -422.936 15.1391 +v 143.503 -427.832 162.674 +v 143.503 -427.832 162.674 +v 76.3055 -449.154 158.966 +v 65.7941 -437.565 48.1499 +v 154.274 -422.936 15.1391 +v 160.1 -339.077 42.0847 +v 140.897 -341.135 179.078 +v 140.897 -341.135 179.078 +v 143.503 -427.832 162.674 +v 154.274 -422.936 15.1391 +v 160.1 -339.077 42.0847 +v 65.6433 -310.298 103.599 +v 61.6315 -308.948 188.674 +v 61.6315 -308.948 188.674 +v 140.897 -341.135 179.078 +v 160.1 -339.077 42.0847 +v -52.7382 -378.505 120.825 +v 65.7941 -437.565 48.1499 +v 76.3055 -449.154 158.966 +v 76.3055 -449.154 158.966 +v -56.9874 -407.19 172.668 +v -52.7382 -378.505 120.825 +v -86.6906 -345.821 159.574 +v -52.7382 -378.505 120.825 +v -56.9874 -407.19 172.668 +v -56.9874 -407.19 172.668 +v -101.405 -340.868 187.717 +v -86.6906 -345.821 159.574 +v 65.6433 -310.298 103.599 +v -86.6906 -345.821 159.574 +v -101.405 -340.868 187.717 +v -101.405 -340.868 187.717 +v 61.6315 -308.948 188.674 +v 65.6433 -310.298 103.599 +v 76.3055 -449.154 158.966 +v 143.503 -427.832 162.674 +v -57.088 -363.103 388.882 +v -57.088 -363.103 388.882 +v -86.187 -376.695 363.046 +v 76.3055 -449.154 158.966 +v 143.503 -427.832 162.674 +v 140.897 -341.135 179.078 +v -61.2255 -234.579 389.398 +v -61.2255 -234.579 389.398 +v -57.088 -363.103 388.882 +v 143.503 -427.832 162.674 +v 140.897 -341.135 179.078 +v 61.6315 -308.948 188.674 +v -121.906 -216.383 347.524 +v -121.906 -216.383 347.524 +v -61.2255 -234.579 389.398 +v 140.897 -341.135 179.078 +v -56.9874 -407.19 172.668 +v 76.3055 -449.154 158.966 +v -86.187 -376.695 363.046 +v -86.187 -376.695 363.046 +v -171.947 -353.317 301.741 +v -56.9874 -407.19 172.668 +v -101.405 -340.868 187.717 +v -56.9874 -407.19 172.668 +v -171.947 -353.317 301.741 +v -171.947 -353.317 301.741 +v -194.818 -237.297 287.062 +v -101.405 -340.868 187.717 +v 61.6315 -308.948 188.674 +v -101.405 -340.868 187.717 +v -194.818 -237.297 287.062 +v -194.818 -237.297 287.062 +v -121.906 -216.383 347.524 +v 61.6315 -308.948 188.674 +v -86.187 -376.695 363.046 +v -57.088 -363.103 388.882 +v -102.277 -349.579 417.599 +v -102.277 -349.579 417.599 +v -120.57 -363.511 397.079 +v -86.187 -376.695 363.046 +v -57.088 -363.103 388.882 +v -61.2255 -234.579 389.398 +v -101.46 -226.311 419.247 +v -101.46 -226.311 419.247 +v -102.277 -349.579 417.599 +v -57.088 -363.103 388.882 +v -61.2255 -234.579 389.398 +v -121.906 -216.383 347.524 +v -150.824 -214.018 384.223 +v -150.824 -214.018 384.223 +v -101.46 -226.311 419.247 +v -61.2255 -234.579 389.398 +v -171.947 -353.317 301.741 +v -86.187 -376.695 363.046 +v -120.57 -363.511 397.079 +v -120.57 -363.511 397.079 +v -209.07 -348.909 327.758 +v -171.947 -353.317 301.741 +v -194.818 -237.297 287.062 +v -171.947 -353.317 301.741 +v -209.07 -348.909 327.758 +v -209.07 -348.909 327.758 +v -220.374 -237.75 317.345 +v -194.818 -237.297 287.062 +v -121.906 -216.383 347.524 +v -194.818 -237.297 287.062 +v -220.374 -237.75 317.345 +v -220.374 -237.75 317.345 +v -150.824 -214.018 384.223 +v -121.906 -216.383 347.524 +v -120.57 -363.511 397.079 +v -102.277 -349.579 417.599 +v -216.691 -299.362 512.471 +v -216.691 -299.362 512.471 +v -279.207 -318.598 477.521 +v -120.57 -363.511 397.079 +v -102.277 -349.579 417.599 +v -101.46 -226.311 419.247 +v -220.533 -242.736 524.241 +v -220.533 -242.736 524.241 +v -216.691 -299.362 512.471 +v -102.277 -349.579 417.599 +v -101.46 -226.311 419.247 +v -150.824 -214.018 384.223 +v -257.957 -155.418 505.428 +v -257.957 -155.418 505.428 +v -220.533 -242.736 524.241 +v -101.46 -226.311 419.247 +v -209.07 -348.909 327.758 +v -120.57 -363.511 397.079 +v -279.207 -318.598 477.521 +v -279.207 -318.598 477.521 +v -372.739 -259.441 407.375 +v -209.07 -348.909 327.758 +v -220.374 -237.75 317.345 +v -209.07 -348.909 327.758 +v -372.739 -259.441 407.375 +v -372.739 -259.441 407.375 +v -394.838 -179.247 404.628 +v -220.374 -237.75 317.345 +v -150.824 -214.018 384.223 +v -220.374 -237.75 317.345 +v -394.838 -179.247 404.628 +v -394.838 -179.247 404.628 +v -257.957 -155.418 505.428 +v -150.824 -214.018 384.223 +v -279.207 -318.598 477.521 +v -216.691 -299.362 512.471 +v -236.316 -297.066 568.835 +v -236.316 -297.066 568.835 +v -317.133 -381.502 577.263 +v -279.207 -318.598 477.521 +v -216.691 -299.362 512.471 +v -220.533 -242.736 524.241 +v -240.158 -240.44 580.604 +v -240.158 -240.44 580.604 +v -236.316 -297.066 568.835 +v -216.691 -299.362 512.471 +v -220.533 -242.736 524.241 +v -257.957 -155.418 505.428 +v -231.113 -142.03 578.573 +v -231.113 -142.03 578.573 +v -240.158 -240.44 580.604 +v -220.533 -242.736 524.241 +v -372.739 -259.441 407.375 +v -279.207 -318.598 477.521 +v -317.133 -381.502 577.263 +v -317.133 -381.502 577.263 +v -521.043 -278.287 466.938 +v -372.739 -259.441 407.375 +v -394.838 -179.247 404.628 +v -372.739 -259.441 407.375 +v -521.043 -278.287 466.938 +v -521.043 -278.287 466.938 +v -543.141 -146.696 464.192 +v -394.838 -179.247 404.628 +v -257.957 -155.418 505.428 +v -394.838 -179.247 404.628 +v -543.141 -146.696 464.192 +v -543.141 -146.696 464.192 +v -231.113 -142.03 578.573 +v -257.957 -155.418 505.428 +v -317.133 -381.502 577.263 +v -236.316 -297.066 568.835 +v -143.793 -387.554 589.922 +v -143.793 -387.554 589.922 +v -180.616 -450.221 583.426 +v -317.133 -381.502 577.263 +v -236.316 -297.066 568.835 +v -240.158 -240.44 580.604 +v -123.358 -345.738 615.393 +v -123.358 -345.738 615.393 +v -143.793 -387.554 589.922 +v -236.316 -297.066 568.835 +v -240.158 -240.44 580.604 +v -231.113 -142.03 578.573 +v -97.4354 -286.208 662.435 +v -97.4354 -286.208 662.435 +v -123.358 -345.738 615.393 +v -240.158 -240.44 580.604 +v -521.043 -278.287 466.938 +v -317.133 -381.502 577.263 +v -384.975 -438.572 704.341 +v -384.975 -438.572 704.341 +v -493.77 -277.971 624.724 +v -521.043 -278.287 466.938 +v -543.141 -146.696 464.192 +v -521.043 -278.287 466.938 +v -493.77 -277.971 624.724 +v -493.77 -277.971 624.724 +v -513.328 -198.074 614.68 +v -543.141 -146.696 464.192 +v -231.113 -142.03 578.573 +v -543.141 -146.696 464.192 +v -513.328 -198.074 614.68 +v -513.328 -198.074 614.68 +v -308.634 -169.787 722.901 +v -231.113 -142.03 578.573 +v -317.133 -381.502 577.263 +v -180.616 -450.221 583.426 +v -207.244 -454.329 671.558 +v -207.244 -454.329 671.558 +v -384.975 -438.572 704.341 +v -317.133 -381.502 577.263 +v -97.4354 -286.208 662.435 +v -231.113 -142.03 578.573 +v -308.634 -169.787 722.901 +v -308.634 -169.787 722.901 +v -117.005 -283.655 756.407 +v -97.4354 -286.208 662.435 +v -493.77 -277.971 624.724 +v -384.975 -438.572 704.341 +v -351.878 -267.828 729.182 +v -351.878 -267.828 729.182 +v -470.519 -230.036 648.255 +v -493.77 -277.971 624.724 +v -513.328 -198.074 614.68 +v -493.77 -277.971 624.724 +v -470.519 -230.036 648.255 +v -308.634 -169.787 722.901 +v -513.328 -198.074 614.68 +v -470.519 -230.036 648.255 +v -470.519 -230.036 648.255 +v -351.878 -267.828 729.182 +v -308.634 -169.787 722.901 +v -384.975 -438.572 704.341 +v -207.244 -454.329 671.558 +v -158.686 -363.501 717.111 +v -158.686 -363.501 717.111 +v -351.878 -267.828 729.182 +v -384.975 -438.572 704.341 +v -117.005 -283.655 756.407 +v -308.634 -169.787 722.901 +v -351.878 -267.828 729.182 +v -351.878 -267.828 729.182 +v -158.686 -363.501 717.111 +v -117.005 -283.655 756.407 +v -180.616 -450.221 583.426 +v -143.793 -387.554 589.922 +v -66.3276 -504.127 711.51 +v -123.358 -345.738 615.393 +v -97.4354 -286.208 662.435 +v -9.81772 -392.599 748.063 +v -207.244 -454.329 671.558 +v -180.616 -450.221 583.426 +v -66.3276 -504.127 711.51 +v -97.4354 -286.208 662.435 +v -117.005 -283.655 756.407 +v -9.81772 -392.599 748.063 +v -158.686 -363.501 717.111 +v -207.244 -454.329 671.558 +v -66.3276 -504.127 711.51 +v -117.005 -283.655 756.407 +v -158.686 -363.501 717.111 +v -9.81772 -392.599 748.063 +v -143.793 -387.554 589.922 +v -123.358 -345.738 615.393 +v -158.686 -363.501 717.111 +v -123.358 -345.738 615.393 +v -9.81772 -392.599 748.063 +v -158.686 -363.501 717.111 +v -66.3276 -504.127 711.51 +v -143.793 -387.554 589.922 +v -158.686 -363.501 717.111 +v 676.056 -10.6067 -403.457 +v 679.933 40.2029 -441.705 +v 824.669 40.2029 -424.477 +v 824.669 40.2029 -424.477 +v 843.785 -71.3395 -368.243 +v 676.056 -10.6067 -403.457 +v 843.785 -71.3395 -368.243 +v 824.669 40.2029 -424.477 +v 878.609 40.2028 -424.149 +v 878.609 40.2028 -424.149 +v 885.246 -63.9092 -351.891 +v 843.785 -71.3395 -368.243 +v 885.246 -63.9092 -351.891 +v 878.609 40.2028 -424.149 +v 909.031 40.2028 -425.978 +v 909.031 40.2028 -425.978 +v 922.953 -39.6783 -375.782 +v 885.246 -63.9092 -351.891 +v 922.953 -39.6783 -375.782 +v 909.031 40.2028 -425.978 +v 968.589 40.2028 -404.236 +v 968.589 40.2028 -404.236 +v 960.668 2.14378 -383.161 +v 922.953 -39.6783 -375.782 +v 960.668 2.14378 -383.161 +v 968.589 40.2028 -404.236 +v 1061.35 40.2028 -356.762 +v 1061.35 40.2028 -356.762 +v 1053.7 -18.2369 -346.758 +v 960.668 2.14378 -383.161 +v 1053.7 -18.2369 -346.758 +v 1061.35 40.2028 -356.762 +v 1119.22 40.2028 -284.353 +v 1119.22 40.2028 -284.353 +v 1117.05 -14.6657 -272.841 +v 1053.7 -18.2369 -346.758 +v 1117.05 -14.6657 -272.841 +v 1119.22 40.2028 -284.353 +v 1152.01 40.2028 -217.427 +v 1152.01 40.2028 -217.427 +v 1130.63 -9.82445 -219.113 +v 1117.05 -14.6657 -272.841 +v 1130.63 -9.82445 -219.113 +v 1152.01 40.2028 -217.427 +v 1154.32 40.2028 -99.7808 +v 1154.32 40.2028 -99.7808 +v 1120.75 -32.3903 -128.277 +v 1130.63 -9.82445 -219.113 +v 1120.75 -32.3903 -128.277 +v 1154.32 40.2028 -99.7808 +v 1150.35 40.2028 -66.9116 +v 1150.35 40.2028 -66.9116 +v 1077.52 -88.1132 -75.2012 +v 1120.75 -32.3903 -128.277 +v 972.648 -63.5674 25.3677 +v 984.155 40.2028 76.1361 +v 948.344 40.2028 68.9338 +v 972.648 -63.5674 25.3677 +v 948.344 40.2028 68.9338 +v 912.514 40.2028 48.5509 +v 912.514 40.2028 48.5509 +v 918.453 -95.2934 -14.2217 +v 972.648 -63.5674 25.3677 +v 758.412 -62.8404 43.888 +v 542.48 -44.913 151.06 +v 682.576 -133.836 164.696 +v 682.576 -133.836 164.696 +v 766.139 -190.462 -12.7916 +v 758.412 -62.8404 43.888 +v 789.328 -72.2831 -20.678 +v 758.412 -62.8404 43.888 +v 766.139 -190.462 -12.7916 +v 766.139 -190.462 -12.7916 +v 729.558 -244.167 -136.921 +v 789.328 -72.2831 -20.678 +v 380.051 -251.531 -66.4672 +v 545.802 -338.023 -93.8417 +v 415.539 -273.108 12.9001 +v 380.051 -251.531 -66.4672 +v 415.539 -273.108 12.9001 +v 494.4 -118.67 153.3 +v 542.48 -44.913 151.06 +v 494.4 -118.67 153.3 +v 682.576 -133.836 164.696 +v 705.303 -138.614 -221.823 +v 846.834 -109.506 -138.578 +v 729.558 -244.167 -136.921 +v 596.877 -226.42 -202.43 +v 729.558 -244.167 -136.921 +v 690.121 -324.877 -50.8344 +v 690.121 -324.877 -50.8344 +v 545.802 -338.023 -93.8417 +v 596.877 -226.42 -202.43 +v 729.558 -244.167 -136.921 +v 766.139 -190.462 -12.7916 +v 764.322 -249.3 -4.0724 +v 729.558 -244.167 -136.921 +v 764.322 -249.3 -4.0724 +v 690.121 -324.877 -50.8344 +v 545.802 -338.023 -93.8417 +v 690.121 -324.877 -50.8344 +v 643.124 -384.905 35.5904 +v 643.124 -384.905 35.5904 +v 518.621 -389.245 -11.2156 +v 545.802 -338.023 -93.8417 +v 764.322 -249.3 -4.0724 +v 766.139 -190.462 -12.7916 +v 682.576 -133.836 164.696 +v 682.576 -133.836 164.696 +v 715.597 -287.239 118.766 +v 764.322 -249.3 -4.0724 +v 545.802 -338.023 -93.8417 +v 518.621 -389.245 -11.2156 +v 415.539 -273.108 12.9001 +v 690.121 -324.877 -50.8344 +v 764.322 -249.3 -4.0724 +v 715.597 -287.239 118.766 +v 715.597 -287.239 118.766 +v 643.124 -384.905 35.5904 +v 690.121 -324.877 -50.8344 +v 494.4 -118.67 153.3 +v 415.539 -273.108 12.9001 +v 400.659 -292.368 56.5926 +v 400.659 -292.368 56.5926 +v 495.985 -186.11 152.255 +v 494.4 -118.67 153.3 +v 682.576 -133.836 164.696 +v 494.4 -118.67 153.3 +v 495.985 -186.11 152.255 +v 495.985 -186.11 152.255 +v 623.367 -177.233 203.09 +v 682.576 -133.836 164.696 +v 518.621 -389.245 -11.2156 +v 643.124 -384.905 35.5904 +v 601.471 -400.28 109.919 +v 601.471 -400.28 109.919 +v 488.245 -391.496 48.2971 +v 518.621 -389.245 -11.2156 +v 715.597 -287.239 118.766 +v 682.576 -133.836 164.696 +v 623.367 -177.233 203.09 +v 623.367 -177.233 203.09 +v 651.012 -315.403 204.493 +v 715.597 -287.239 118.766 +v 415.539 -273.108 12.9001 +v 518.621 -389.245 -11.2156 +v 488.245 -391.496 48.2971 +v 488.245 -391.496 48.2971 +v 400.659 -292.368 56.5926 +v 415.539 -273.108 12.9001 +v 643.124 -384.905 35.5904 +v 715.597 -287.239 118.766 +v 651.012 -315.403 204.493 +v 651.012 -315.403 204.493 +v 601.471 -400.28 109.919 +v 643.124 -384.905 35.5904 +v 495.985 -186.11 152.255 +v 400.659 -292.368 56.5926 +v 376.155 -367.745 167.649 +v 376.155 -367.745 167.649 +v 449.267 -227.751 241.792 +v 495.985 -186.11 152.255 +v 623.367 -177.233 203.09 +v 495.985 -186.11 152.255 +v 449.267 -227.751 241.792 +v 449.267 -227.751 241.792 +v 595.313 -259.375 310.175 +v 623.367 -177.233 203.09 +v 488.245 -391.496 48.2971 +v 601.471 -400.28 109.919 +v 507.031 -409.177 228.749 +v 507.031 -409.177 228.749 +v 438.002 -434.285 183.939 +v 488.245 -391.496 48.2971 +v 651.012 -315.403 204.493 +v 623.367 -177.233 203.09 +v 595.313 -259.375 310.175 +v 595.313 -259.375 310.175 +v 588.146 -375.276 278.435 +v 651.012 -315.403 204.493 +v 400.659 -292.368 56.5926 +v 488.245 -391.496 48.2971 +v 438.002 -434.285 183.939 +v 438.002 -434.285 183.939 +v 376.155 -367.745 167.649 +v 400.659 -292.368 56.5926 +v 601.471 -400.28 109.919 +v 651.012 -315.403 204.493 +v 588.146 -375.276 278.435 +v 588.146 -375.276 278.435 +v 507.031 -409.177 228.749 +v 601.471 -400.28 109.919 +v 449.267 -227.751 241.792 +v 376.155 -367.745 167.649 +v 280.893 -407.647 219.652 +v 280.893 -407.647 219.652 +v 319.396 -255.764 310.744 +v 449.267 -227.751 241.792 +v 449.267 -227.751 241.792 +v 319.396 -255.764 310.744 +v 467.024 -290.574 349.519 +v 467.024 -290.574 349.519 +v 595.313 -259.375 310.175 +v 449.267 -227.751 241.792 +v 438.002 -434.285 183.939 +v 507.031 -409.177 228.749 +v 332.802 -479.854 294.654 +v 332.802 -479.854 294.654 +v 289.487 -450.472 226.311 +v 438.002 -434.285 183.939 +v 588.146 -375.276 278.435 +v 595.313 -259.375 310.175 +v 467.024 -290.574 349.519 +v 467.024 -290.574 349.519 +v 467.468 -451.105 347.786 +v 588.146 -375.276 278.435 +v 376.155 -367.745 167.649 +v 438.002 -434.285 183.939 +v 289.487 -450.472 226.311 +v 289.487 -450.472 226.311 +v 280.893 -407.647 219.652 +v 376.155 -367.745 167.649 +v 507.031 -409.177 228.749 +v 588.146 -375.276 278.435 +v 467.468 -451.105 347.786 +v 467.468 -451.105 347.786 +v 332.802 -479.854 294.654 +v 507.031 -409.177 228.749 +v 280.893 -407.647 219.652 +v 209.876 -408.128 275.697 +v 319.396 -255.764 310.744 +v 289.487 -450.472 226.311 +v 332.802 -479.854 294.654 +v 252.694 -465.87 283.647 +v 280.893 -407.647 219.652 +v 289.487 -450.472 226.311 +v 252.694 -465.87 283.647 +v 252.694 -465.87 283.647 +v 209.876 -408.128 275.697 +v 280.893 -407.647 219.652 +v 209.876 -408.128 275.697 +v 215.546 -402.806 359.667 +v 319.396 -255.764 310.744 +v 252.694 -465.87 283.647 +v 332.802 -479.854 294.654 +v 257.069 -452.122 363.088 +v 209.876 -408.128 275.697 +v 252.694 -465.87 283.647 +v 257.069 -452.122 363.088 +v 257.069 -452.122 363.088 +v 215.546 -402.806 359.667 +v 209.876 -408.128 275.697 +v 319.396 -255.764 310.744 +v 215.546 -402.806 359.667 +v 279.28 -306.382 455.978 +v 279.28 -306.382 455.978 +v 337.988 -252.446 457.825 +v 319.396 -255.764 310.744 +v 467.024 -290.574 349.519 +v 319.396 -255.764 310.744 +v 337.988 -252.446 457.825 +v 337.988 -252.446 457.825 +v 441.552 -285.479 435.883 +v 467.024 -290.574 349.519 +v 257.069 -452.122 363.088 +v 332.802 -479.854 294.654 +v 352.053 -402.807 456.275 +v 352.053 -402.807 456.275 +v 265.056 -381.514 466.161 +v 257.069 -452.122 363.088 +v 467.468 -451.105 347.786 +v 467.024 -290.574 349.519 +v 441.552 -285.479 435.883 +v 441.552 -285.479 435.883 +v 441.455 -368.508 445.446 +v 467.468 -451.105 347.786 +v 215.546 -402.806 359.667 +v 257.069 -452.122 363.088 +v 265.056 -381.514 466.161 +v 265.056 -381.514 466.161 +v 279.28 -306.382 455.978 +v 215.546 -402.806 359.667 +v 332.802 -479.854 294.654 +v 467.468 -451.105 347.786 +v 441.455 -368.508 445.446 +v 441.455 -368.508 445.446 +v 352.053 -402.807 456.275 +v 332.802 -479.854 294.654 +v 337.988 -252.446 457.825 +v 279.28 -306.382 455.978 +v 272.997 -296.869 559.478 +v 272.997 -296.869 559.478 +v 383.441 -229.992 553.769 +v 337.988 -252.446 457.825 +v 441.552 -285.479 435.883 +v 337.988 -252.446 457.825 +v 383.441 -229.992 553.769 +v 383.441 -229.992 553.769 +v 466.393 -250.784 551.819 +v 441.552 -285.479 435.883 +v 265.056 -381.514 466.161 +v 352.053 -402.807 456.275 +v 390.756 -398.519 555.537 +v 390.756 -398.519 555.537 +v 264.043 -387.196 561.999 +v 265.056 -381.514 466.161 +v 441.455 -368.508 445.446 +v 441.552 -285.479 435.883 +v 466.393 -250.784 551.819 +v 466.393 -250.784 551.819 +v 464.616 -343.033 556.584 +v 441.455 -368.508 445.446 +v 279.28 -306.382 455.978 +v 265.056 -381.514 466.161 +v 264.043 -387.196 561.999 +v 264.043 -387.196 561.999 +v 272.997 -296.869 559.478 +v 279.28 -306.382 455.978 +v 352.053 -402.807 456.275 +v 441.455 -368.508 445.446 +v 464.616 -343.033 556.584 +v 464.616 -343.033 556.584 +v 390.756 -398.519 555.537 +v 352.053 -402.807 456.275 +v 383.441 -229.992 553.769 +v 272.997 -296.869 559.478 +v 271.471 -297.372 655.645 +v 271.471 -297.372 655.645 +v 377.032 -230.241 640.682 +v 383.441 -229.992 553.769 +v 466.393 -250.784 551.819 +v 383.441 -229.992 553.769 +v 377.032 -230.241 640.682 +v 377.032 -230.241 640.682 +v 460.045 -254.409 626.719 +v 466.393 -250.784 551.819 +v 264.043 -387.196 561.999 +v 390.756 -398.519 555.537 +v 418.604 -400.968 630.348 +v 418.604 -400.968 630.348 +v 293.651 -378.092 667.416 +v 264.043 -387.196 561.999 +v 464.616 -343.033 556.584 +v 466.393 -250.784 551.819 +v 460.045 -254.409 626.719 +v 460.045 -254.409 626.719 +v 484.703 -326.296 631.39 +v 464.616 -343.033 556.584 +v 272.997 -296.869 559.478 +v 264.043 -387.196 561.999 +v 293.651 -378.092 667.416 +v 293.651 -378.092 667.416 +v 271.471 -297.372 655.645 +v 272.997 -296.869 559.478 +v 390.756 -398.519 555.537 +v 464.616 -343.033 556.584 +v 484.703 -326.296 631.39 +v 484.703 -326.296 631.39 +v 418.604 -400.968 630.348 +v 390.756 -398.519 555.537 +v 377.032 -230.241 640.682 +v 271.471 -297.372 655.645 +v 304.327 -301.295 790.592 +v 304.327 -301.295 790.592 +v 340.152 -202.604 825.111 +v 377.032 -230.241 640.682 +v 460.045 -254.409 626.719 +v 377.032 -230.241 640.682 +v 340.152 -202.604 825.111 +v 340.152 -202.604 825.111 +v 451.236 -235.762 688.596 +v 460.045 -254.409 626.719 +v 293.651 -378.092 667.416 +v 418.604 -400.968 630.348 +v 433.476 -415.899 693.155 +v 433.476 -415.899 693.155 +v 317.335 -377.895 787.405 +v 293.651 -378.092 667.416 +v 484.703 -326.296 631.39 +v 460.045 -254.409 626.719 +v 451.236 -235.762 688.596 +v 451.236 -235.762 688.596 +v 490.612 -314.281 692.953 +v 484.703 -326.296 631.39 +v 271.471 -297.372 655.645 +v 293.651 -378.092 667.416 +v 317.335 -377.895 787.405 +v 317.335 -377.895 787.405 +v 304.327 -301.295 790.592 +v 271.471 -297.372 655.645 +v 418.604 -400.968 630.348 +v 484.703 -326.296 631.39 +v 490.612 -314.281 692.953 +v 490.612 -314.281 692.953 +v 433.476 -415.899 693.155 +v 418.604 -400.968 630.348 +v 340.152 -202.604 825.111 +v 304.327 -301.295 790.592 +v 580.055 -298.385 836.628 +v 580.055 -298.385 836.628 +v 499.471 -177.331 820.408 +v 340.152 -202.604 825.111 +v 451.236 -235.762 688.596 +v 340.152 -202.604 825.111 +v 499.471 -177.331 820.408 +v 499.471 -177.331 820.408 +v 527.033 -193.537 790.548 +v 451.236 -235.762 688.596 +v 317.335 -377.895 787.405 +v 433.476 -415.899 693.155 +v 507.467 -413.427 768.062 +v 507.467 -413.427 768.062 +v 486.543 -438.858 861.734 +v 317.335 -377.895 787.405 +v 490.612 -314.281 692.953 +v 451.236 -235.762 688.596 +v 527.033 -193.537 790.548 +v 527.033 -193.537 790.548 +v 577.257 -297.345 761.531 +v 490.612 -314.281 692.953 +v 304.327 -301.295 790.592 +v 317.335 -377.895 787.405 +v 486.543 -438.858 861.734 +v 486.543 -438.858 861.734 +v 580.055 -298.385 836.628 +v 304.327 -301.295 790.592 +v 433.476 -415.899 693.155 +v 490.612 -314.281 692.953 +v 577.257 -297.345 761.531 +v 577.257 -297.345 761.531 +v 507.467 -413.427 768.062 +v 433.476 -415.899 693.155 +v 486.543 -438.858 861.734 +v 507.467 -413.427 768.062 +v 644.719 -436.229 823.452 +v 644.719 -436.229 823.452 +v 626.437 -452.827 888.782 +v 486.543 -438.858 861.734 +v 580.055 -298.385 836.628 +v 486.543 -438.858 861.734 +v 626.437 -452.827 888.782 +v 626.437 -452.827 888.782 +v 627.235 -359.336 885.962 +v 580.055 -298.385 836.628 +v 507.467 -413.427 768.062 +v 577.257 -297.345 761.531 +v 645.052 -359.418 822.685 +v 645.052 -359.418 822.685 +v 644.719 -436.229 823.452 +v 507.467 -413.427 768.062 +v 499.471 -177.331 820.408 +v 580.055 -298.385 836.628 +v 625.983 -264.872 889.066 +v 625.983 -264.872 889.066 +v 626.526 -181.518 888.681 +v 499.471 -177.331 820.408 +v 527.033 -193.537 790.548 +v 499.471 -177.331 820.408 +v 626.526 -181.518 888.681 +v 626.526 -181.518 888.681 +v 645.299 -197.725 822.477 +v 527.033 -193.537 790.548 +v 577.257 -297.345 761.531 +v 527.033 -193.537 790.548 +v 645.299 -197.725 822.477 +v 645.299 -197.725 822.477 +v 645.229 -252.173 822.897 +v 577.257 -297.345 761.531 +v 645.052 -359.418 822.685 +v 577.257 -297.345 761.531 +v 580.055 -298.385 836.628 +v 580.055 -298.385 836.628 +v 627.235 -359.336 885.962 +v 645.052 -359.418 822.685 +v 577.257 -297.345 761.531 +v 645.229 -252.173 822.897 +v 625.983 -264.872 889.066 +v 625.983 -264.872 889.066 +v 580.055 -298.385 836.628 +v 577.257 -297.345 761.531 +v 626.437 -452.827 888.782 +v 644.719 -436.229 823.452 +v 695.541 -379.792 913.328 +v 627.235 -359.336 885.962 +v 626.437 -452.827 888.782 +v 695.541 -379.792 913.328 +v 644.719 -436.229 823.452 +v 645.052 -359.418 822.685 +v 695.541 -379.792 913.328 +v 626.526 -181.518 888.681 +v 625.983 -264.872 889.066 +v 697.205 -181.253 900.122 +v 645.299 -197.725 822.477 +v 626.526 -181.518 888.681 +v 697.205 -181.253 900.122 +v 645.229 -252.173 822.897 +v 645.299 -197.725 822.477 +v 697.205 -181.253 900.122 +v 645.052 -359.418 822.685 +v 627.235 -359.336 885.962 +v 695.541 -379.792 913.328 +v 625.983 -264.872 889.066 +v 645.229 -252.173 822.897 +v 697.205 -181.253 900.122 +v 960.668 2.14378 -383.161 +v 1053.7 -18.2369 -346.758 +v 1039.45 -77.8383 -303.194 +v 1039.45 -77.8383 -303.194 +v 922.953 -39.6783 -375.782 +v 960.668 2.14378 -383.161 +v 1053.7 -18.2369 -346.758 +v 1117.05 -14.6657 -272.841 +v 1101.1 -49.6417 -253.686 +v 1101.1 -49.6417 -253.686 +v 1039.45 -77.8383 -303.194 +v 1053.7 -18.2369 -346.758 +v 922.953 -39.6783 -375.782 +v 1039.45 -77.8383 -303.194 +v 1015.08 -111.831 -235.109 +v 1015.08 -111.831 -235.109 +v 885.246 -63.9092 -351.891 +v 922.953 -39.6783 -375.782 +v 1039.45 -77.8383 -303.194 +v 1101.1 -49.6417 -253.686 +v 1102.84 -71.0802 -161.378 +v 1102.84 -71.0802 -161.378 +v 1015.08 -111.831 -235.109 +v 1039.45 -77.8383 -303.194 +v 885.246 -63.9092 -351.891 +v 1015.08 -111.831 -235.109 +v 965.997 -129.088 -179.582 +v 965.997 -129.088 -179.582 +v 843.785 -71.3395 -368.243 +v 885.246 -63.9092 -351.891 +v 1015.08 -111.831 -235.109 +v 1102.84 -71.0802 -161.378 +v 1077.52 -88.1132 -75.2012 +v 1077.52 -88.1132 -75.2012 +v 965.997 -129.088 -179.582 +v 1015.08 -111.831 -235.109 +v 843.785 -71.3395 -368.243 +v 965.997 -129.088 -179.582 +v 846.834 -109.506 -138.578 +v 846.834 -109.506 -138.578 +v 676.056 -10.6067 -403.457 +v 843.785 -71.3395 -368.243 +v 965.997 -129.088 -179.582 +v 1077.52 -88.1132 -75.2012 +v 987.83 -96.8683 -34.0016 +v 965.997 -129.088 -179.582 +v 987.83 -96.8683 -34.0016 +v 918.453 -95.2934 -14.2217 +v 965.997 -129.088 -179.582 +v 918.453 -95.2934 -14.2217 +v 846.834 -109.506 -138.578 +v 1101.1 -49.6417 -253.686 +v 1117.05 -14.6657 -272.841 +v 1130.63 -9.82445 -219.113 +v 987.83 -96.8683 -34.0016 +v 1077.52 -88.1132 -75.2012 +v 1056.88 -53.0906 2.82201 +v 1102.84 -71.0802 -161.378 +v 1101.1 -49.6417 -253.686 +v 1130.63 -9.82445 -219.113 +v 1130.63 -9.82445 -219.113 +v 1120.75 -32.3903 -128.277 +v 1102.84 -71.0802 -161.378 +v 1077.52 -88.1132 -75.2012 +v 1102.84 -71.0802 -161.378 +v 1120.75 -32.3903 -128.277 +v 1150.35 40.2028 -66.9116 +v 1132.43 40.2028 -24.6494 +v 1089.27 -39.0682 -37.2052 +v 997.81 -45.2141 37.0952 +v 1056.88 -53.0906 2.82201 +v 1100.85 40.2028 23.3858 +v 1100.85 40.2028 23.3858 +v 1031.13 40.2028 65.4153 +v 997.81 -45.2141 37.0952 +v 997.81 -45.2141 37.0952 +v 984.155 40.2028 76.1361 +v 972.648 -63.5674 25.3677 +v 1089.27 -39.0682 -37.2052 +v 1077.52 -88.1132 -75.2012 +v 1150.35 40.2028 -66.9116 +v 1089.27 -39.0682 -37.2052 +v 1056.88 -53.0906 2.82201 +v 1077.52 -88.1132 -75.2012 +v 972.648 -63.5674 25.3677 +v 918.453 -95.2934 -14.2217 +v 987.83 -96.8683 -34.0016 +v 972.648 -63.5674 25.3677 +v 987.83 -96.8683 -34.0016 +v 1056.88 -53.0906 2.82201 +v 972.648 -63.5674 25.3677 +v 1056.88 -53.0906 2.82201 +v 997.81 -45.2141 37.0952 +v 1089.27 -39.0682 -37.2052 +v 1132.43 40.2028 -24.6494 +v 1122.15 40.2028 2.15071 +v 1089.27 -39.0682 -37.2052 +v 1122.15 40.2028 2.15071 +v 1100.85 40.2028 23.3858 +v 1100.85 40.2028 23.3858 +v 1056.88 -53.0906 2.82201 +v 1089.27 -39.0682 -37.2052 +v 997.81 -45.2141 37.0952 +v 1031.13 40.2028 65.4153 +v 984.155 40.2028 76.1361 +v 558.965 -9.41835 -404.09 +v 596.877 -226.42 -202.43 +v 268.451 -169.261 -290.85 +v 558.965 -9.41835 -404.09 +v 705.303 -138.614 -221.823 +v 596.877 -226.42 -202.43 +v 596.877 -226.42 -202.43 +v 545.802 -338.023 -93.8417 +v 380.051 -251.531 -66.4672 +v 380.051 -251.531 -66.4672 +v 242.389 -203.746 -21.5963 +v 260.646 -223.493 -153.714 +v 705.303 -138.614 -221.823 +v 729.558 -244.167 -136.921 +v 596.877 -226.42 -202.43 +v -627.402 40.2031 -27.0373 +v -598.079 -17.5054 13.3313 +v -974.541 40.2032 116.414 +v -598.079 -17.5054 13.3313 +v -635.946 -19.0616 145.914 +v -966.748 -6.42429 188.349 +v -973.5 40.2032 204.705 +v -966.748 -6.42429 188.349 +v -635.946 -19.0616 145.914 +v -635.946 -19.0616 145.914 +v -605.1 40.2031 174.866 +v -973.5 40.2032 204.705 +v -598.079 -17.5054 13.3313 +v -966.748 -6.42429 188.349 +v -974.541 40.2032 116.414 +v -1356.56 13.1094 175.492 +v -1360.94 40.2032 134.989 +v -974.541 40.2032 116.414 +v -974.541 40.2032 116.414 +v -966.748 -6.42429 188.349 +v -1356.56 13.1094 175.492 +v -1360.36 40.2032 184.7 +v -1356.56 13.1094 175.492 +v -966.748 -6.42429 188.349 +v -966.748 -6.42429 188.349 +v -973.5 40.2032 204.705 +v -1360.36 40.2032 184.7 +v -1360.94 40.2032 134.989 +v -1356.56 13.1094 175.492 +v -1746.98 40.2033 71.4633 +v -1356.56 13.1094 175.492 +v -1360.36 40.2032 184.7 +v -1746.98 40.2033 71.4633 +v 846.834 -109.506 -138.578 +v 918.453 -95.2934 -14.2217 +v 789.328 -72.2831 -20.678 +v 676.056 -10.6067 -403.457 +v 846.834 -109.506 -138.578 +v 705.303 -138.614 -221.823 +v 558.965 -9.41835 -404.09 +v 505.639 40.2029 -412.719 +v 442.767 40.2029 -586.685 +v 676.056 -10.6067 -403.457 +v 558.965 -9.41835 -404.09 +v 482.298 5.71433 -625.285 +v 676.056 -10.6067 -403.457 +v 482.298 5.71433 -625.285 +v 486.264 40.2029 -638.201 +v 486.264 40.2029 -638.201 +v 679.933 40.2029 -441.705 +v 676.056 -10.6067 -403.457 +v 482.298 5.71433 -625.285 +v 558.965 -9.41835 -404.09 +v 442.767 40.2029 -586.685 +v 442.767 40.2029 -586.685 +v 286.808 40.203 -755.55 +v 309.065 20.6301 -777.283 +v 309.065 20.6301 -777.283 +v 482.298 5.71433 -625.285 +v 442.767 40.2029 -586.685 +v 482.298 5.71433 -625.285 +v 309.065 20.6301 -777.283 +v 311.298 40.203 -784.555 +v 311.298 40.203 -784.555 +v 486.264 40.2029 -638.201 +v 482.298 5.71433 -625.285 +v 309.065 20.6301 -777.283 +v 286.808 40.203 -755.55 +v 3.60059 40.203 -892.627 +v 311.298 40.203 -784.555 +v 309.065 20.6301 -777.283 +v 3.60059 40.203 -892.627 +v -107.623 -99.0134 -353.534 +v -233.929 40.203 -337.011 +v -312.712 40.203 -483.512 +v 32.5665 -78.854 -387.298 +v -107.623 -99.0134 -353.534 +v -261.253 5.71445 -577.012 +v 32.5665 -78.854 -387.298 +v -261.253 5.71445 -577.012 +v -260.883 40.203 -590.518 +v -260.883 40.203 -590.518 +v 21.7976 40.203 -426.108 +v 32.5665 -78.854 -387.298 +v -261.253 5.71445 -577.012 +v -107.623 -99.0134 -353.534 +v -312.712 40.203 -483.512 +v -312.712 40.203 -483.512 +v -484.444 40.2031 -650.296 +v -468.805 20.6302 -677.187 +v -468.805 20.6302 -677.187 +v -261.253 5.71445 -577.012 +v -312.712 40.203 -483.512 +v -261.253 5.71445 -577.012 +v -468.805 20.6302 -677.187 +v -468.596 40.2031 -684.792 +v -468.596 40.2031 -684.792 +v -260.883 40.203 -590.518 +v -261.253 5.71445 -577.012 +v -468.805 20.6302 -677.187 +v -484.444 40.2031 -650.296 +v -793.984 40.2031 -706.704 +v -468.596 40.2031 -684.792 +v -468.805 20.6302 -677.187 +v -793.984 40.2031 -706.704 +v 260.312 40.203 -440.008 +v 252.222 134.611 -402.864 +v 558.966 89.8242 -404.09 +v 558.966 89.8242 -404.09 +v 505.639 40.2029 -412.719 +v 260.312 40.203 -440.008 +v 21.7976 40.203 -426.108 +v 32.5666 159.26 -387.298 +v 252.222 134.611 -402.864 +v 252.222 134.611 -402.864 +v 260.312 40.203 -440.008 +v 21.7976 40.203 -426.108 +v -107.623 179.419 -353.534 +v -233.929 40.203 -337.011 +v -378.533 40.2031 -238.981 +v -378.533 40.2031 -238.981 +v -359.273 99.4677 -211.385 +v -107.623 179.419 -353.534 +v -627.402 40.2031 -27.0373 +v -598.079 97.9116 13.3312 +v -359.273 99.4677 -211.385 +v -359.273 99.4677 -211.385 +v -378.533 40.2031 -238.981 +v -627.402 40.2031 -27.0373 +v -605.1 40.2031 174.866 +v -547.166 40.2031 372.164 +v -635.946 99.4678 145.914 +v -99.0235 40.203 155.844 +v -25.3967 40.203 194.286 +v 22.0084 135.938 181.514 +v 246.48 40.203 170.173 +v 246.315 160.024 147.057 +v 22.0084 135.938 181.514 +v 22.0084 135.938 181.514 +v -25.3967 40.203 194.286 +v 246.48 40.203 170.173 +v 568.185 40.2029 165.976 +v 542.48 125.319 151.06 +v 246.315 160.024 147.057 +v 246.315 160.024 147.057 +v 246.48 40.203 170.173 +v 568.185 40.2029 165.976 +v 762.912 40.2029 67.4993 +v 758.412 143.246 43.888 +v 542.48 125.319 151.06 +v 542.48 125.319 151.06 +v 568.185 40.2029 165.976 +v 762.912 40.2029 67.4993 +v 841.425 40.2029 35.9378 +v 789.328 152.689 -20.678 +v 758.412 143.246 43.888 +v 758.412 143.246 43.888 +v 762.912 40.2029 67.4993 +v 841.425 40.2029 35.9378 +v 912.514 40.2028 48.5509 +v 918.453 175.699 -14.2217 +v 789.328 152.689 -20.678 +v 789.328 152.689 -20.678 +v 841.425 40.2029 35.9378 +v 912.514 40.2028 48.5509 +v 252.222 134.611 -402.864 +v 268.451 249.667 -290.85 +v 558.966 89.8242 -404.09 +v 32.5666 159.26 -387.298 +v 268.451 249.667 -290.85 +v 252.222 134.611 -402.864 +v -598.079 97.9116 13.3312 +v -537.312 180.662 32.2996 +v -321.215 158.098 -143.919 +v -321.215 158.098 -143.919 +v -359.273 99.4677 -211.385 +v -598.079 97.9116 13.3312 +v -359.273 99.4677 -211.385 +v -321.215 158.098 -143.919 +v -107.623 179.419 -353.534 +v 260.646 303.899 -153.714 +v 380.051 331.937 -66.4672 +v 596.877 306.826 -202.43 +v 596.877 306.826 -202.43 +v 268.451 249.667 -290.85 +v 260.646 303.899 -153.714 +v 39.9729 236.445 -209.082 +v 260.646 303.899 -153.714 +v 268.451 249.667 -290.85 +v 268.451 249.667 -290.85 +v 32.5666 159.26 -387.298 +v 39.9729 236.445 -209.082 +v -107.623 179.419 -353.534 +v -121.935 222.68 -170.547 +v 39.9729 236.445 -209.082 +v 39.9729 236.445 -209.082 +v 32.5666 159.26 -387.298 +v -107.623 179.419 -353.534 +v -537.312 180.662 32.2996 +v -391.04 220.314 41.853 +v -321.215 158.098 -143.919 +v -321.215 158.098 -143.919 +v -121.935 222.68 -170.547 +v -107.623 179.419 -353.534 +v 39.9729 236.445 -209.082 +v 22.0431 237.22 26.5261 +v 242.389 284.152 -21.5963 +v 242.389 284.152 -21.5963 +v 260.646 303.899 -153.714 +v 39.9729 236.445 -209.082 +v -121.935 222.68 -170.547 +v -77.3161 206.284 32.3854 +v 22.0431 237.22 26.5261 +v 22.0431 237.22 26.5261 +v 39.9729 236.445 -209.082 +v -121.935 222.68 -170.547 +v -391.04 220.314 41.853 +v -298.191 183.448 127.509 +v -213.771 177.887 92.1041 +v -213.771 177.887 92.1041 +v -321.215 158.098 -143.919 +v -391.04 220.314 41.853 +v -321.215 158.098 -143.919 +v -213.771 177.887 92.1041 +v -77.3161 206.284 32.3854 +v -77.3161 206.284 32.3854 +v -121.935 222.68 -170.547 +v -321.215 158.098 -143.919 +v 242.389 284.152 -21.5963 +v 257.24 242.3 50.0724 +v 494.4 199.075 153.3 +v 494.4 199.075 153.3 +v 380.051 331.937 -66.4672 +v 242.389 284.152 -21.5963 +v 22.0431 237.22 26.5261 +v 34.0969 188.524 134.952 +v 257.24 242.3 50.0724 +v 257.24 242.3 50.0724 +v 242.389 284.152 -21.5963 +v 22.0431 237.22 26.5261 +v -77.3161 206.284 32.3854 +v -50.7188 147.901 123.835 +v 34.0969 188.524 134.952 +v 34.0969 188.524 134.952 +v 22.0431 237.22 26.5261 +v -77.3161 206.284 32.3854 +v -298.191 183.448 127.509 +v -314.968 157.494 227.647 +v -257.987 111.014 139.94 +v -257.987 111.014 139.94 +v -213.771 177.887 92.1041 +v -298.191 183.448 127.509 +v -213.771 177.887 92.1041 +v -257.987 111.014 139.94 +v -50.7188 147.901 123.835 +v -50.7188 147.901 123.835 +v -77.3161 206.284 32.3854 +v -213.771 177.887 92.1041 +v 246.315 160.024 147.057 +v 542.48 125.319 151.06 +v 494.4 199.075 153.3 +v 494.4 199.075 153.3 +v 257.24 242.3 50.0724 +v 246.315 160.024 147.057 +v 34.0969 188.524 134.952 +v 22.0084 135.938 181.514 +v 246.315 160.024 147.057 +v 246.315 160.024 147.057 +v 257.24 242.3 50.0724 +v 34.0969 188.524 134.952 +v -50.7188 147.901 123.835 +v -99.0235 40.203 155.844 +v 22.0084 135.938 181.514 +v 22.0084 135.938 181.514 +v 34.0969 188.524 134.952 +v -50.7188 147.901 123.835 +v -314.968 157.494 227.647 +v -284.581 40.203 304.684 +v -260.462 40.203 162.299 +v -260.462 40.203 162.299 +v -257.987 111.014 139.94 +v -314.968 157.494 227.647 +v -257.987 111.014 139.94 +v -260.462 40.203 162.299 +v -99.0235 40.203 155.844 +v -99.0235 40.203 155.844 +v -50.7188 147.901 123.835 +v -257.987 111.014 139.94 +v 558.966 89.8242 -404.09 +v 705.303 219.02 -221.823 +v 676.056 91.0124 -403.457 +v 729.558 324.573 -136.921 +v 789.328 152.689 -20.678 +v 846.835 189.911 -138.578 +v -635.946 99.4678 145.914 +v -615.112 167.209 174.866 +v -537.312 180.662 32.2996 +v -537.312 180.662 32.2996 +v -598.079 97.9116 13.3312 +v -635.946 99.4678 145.914 +v -547.166 40.2031 372.164 +v -550.064 125.358 307.245 +v -615.112 167.209 174.866 +v -615.112 167.209 174.866 +v -635.946 99.4678 145.914 +v -547.166 40.2031 372.164 +v -284.581 40.203 304.684 +v -314.968 157.494 227.647 +v -550.064 125.358 307.245 +v -550.064 125.358 307.245 +v -547.166 40.2031 372.164 +v -284.581 40.203 304.684 +v -537.312 180.662 32.2996 +v -410.355 397.111 -17.6217 +v -281.314 324.041 -48.6181 +v -281.314 324.041 -48.6181 +v -391.04 220.314 41.853 +v -537.312 180.662 32.2996 +v -391.04 220.314 41.853 +v -281.314 324.041 -48.6181 +v -169.529 226.359 141.817 +v -169.529 226.359 141.817 +v -298.191 183.448 127.509 +v -391.04 220.314 41.853 +v -298.191 183.448 127.509 +v -169.529 226.359 141.817 +v -255.603 219.321 227.647 +v -255.603 219.321 227.647 +v -314.968 157.494 227.647 +v -298.191 183.448 127.509 +v -615.112 167.209 174.866 +v -465.797 406.708 178.963 +v -410.355 397.111 -17.6217 +v -410.355 397.111 -17.6217 +v -537.312 180.662 32.2996 +v -615.112 167.209 174.866 +v -550.064 125.358 307.245 +v -440.269 322.717 338.364 +v -465.797 406.708 178.963 +v -465.797 406.708 178.963 +v -615.112 167.209 174.866 +v -550.064 125.358 307.245 +v -314.968 157.494 227.647 +v -255.603 219.321 227.647 +v -440.269 322.717 338.364 +v -440.269 322.717 338.364 +v -550.064 125.358 307.245 +v -314.968 157.494 227.647 +v -410.355 397.111 -17.6217 +v -24.9718 524.939 -3.85764 +v 14.208 415.633 -45.2649 +v 14.208 415.633 -45.2649 +v -281.314 324.041 -48.6181 +v -410.355 397.111 -17.6217 +v -281.314 324.041 -48.6181 +v 14.208 415.633 -45.2649 +v 32.0552 328.291 -11.6047 +v 32.0552 328.291 -11.6047 +v -169.529 226.359 141.817 +v -281.314 324.041 -48.6181 +v -169.529 226.359 141.817 +v 32.0552 328.291 -11.6047 +v -15.0917 323.073 60.2354 +v -15.0917 323.073 60.2354 +v -255.603 219.321 227.647 +v -169.529 226.359 141.817 +v -465.797 406.708 178.963 +v -103.65 527.69 112.409 +v -24.9718 524.939 -3.85764 +v -24.9718 524.939 -3.85764 +v -410.355 397.111 -17.6217 +v -465.797 406.708 178.963 +v -440.269 322.717 338.364 +v -132.463 411.341 170.696 +v -103.65 527.69 112.409 +v -103.65 527.69 112.409 +v -465.797 406.708 178.963 +v -440.269 322.717 338.364 +v -255.603 219.321 227.647 +v -15.0917 323.073 60.2354 +v -132.463 411.341 170.696 +v -132.463 411.341 170.696 +v -440.269 322.717 338.364 +v -255.603 219.321 227.647 +v -24.9718 524.939 -3.85764 +v 65.7942 517.971 48.1499 +v 154.274 503.342 15.1391 +v 154.274 503.342 15.1391 +v 14.208 415.633 -45.2649 +v -24.9718 524.939 -3.85764 +v 14.208 415.633 -45.2649 +v 154.274 503.342 15.1391 +v 160.1 419.483 42.0846 +v 160.1 419.483 42.0846 +v 32.0552 328.291 -11.6047 +v 14.208 415.633 -45.2649 +v 32.0552 328.291 -11.6047 +v 160.1 419.483 42.0846 +v 65.6434 390.704 103.599 +v 65.6434 390.704 103.599 +v -15.0917 323.073 60.2354 +v 32.0552 328.291 -11.6047 +v -103.65 527.69 112.409 +v -52.7381 458.911 120.825 +v 65.7942 517.971 48.1499 +v 65.7942 517.971 48.1499 +v -24.9718 524.939 -3.85764 +v -103.65 527.69 112.409 +v -132.463 411.341 170.696 +v -86.6904 426.227 159.574 +v -52.7381 458.911 120.825 +v -52.7381 458.911 120.825 +v -103.65 527.69 112.409 +v -132.463 411.341 170.696 +v -15.0917 323.073 60.2354 +v 65.6434 390.704 103.599 +v -86.6904 426.227 159.574 +v -86.6904 426.227 159.574 +v -132.463 411.341 170.696 +v -15.0917 323.073 60.2354 +v 65.7942 517.971 48.1499 +v 76.3056 529.56 158.966 +v 143.503 508.238 162.674 +v 143.503 508.238 162.674 +v 154.274 503.342 15.1391 +v 65.7942 517.971 48.1499 +v 154.274 503.342 15.1391 +v 143.503 508.238 162.674 +v 140.897 421.541 179.078 +v 140.897 421.541 179.078 +v 160.1 419.483 42.0846 +v 154.274 503.342 15.1391 +v 160.1 419.483 42.0846 +v 140.897 421.541 179.078 +v 61.6316 389.354 188.674 +v 61.6316 389.354 188.674 +v 65.6434 390.704 103.599 +v 160.1 419.483 42.0846 +v -52.7381 458.911 120.825 +v -56.9872 487.596 172.668 +v 76.3056 529.56 158.966 +v 76.3056 529.56 158.966 +v 65.7942 517.971 48.1499 +v -52.7381 458.911 120.825 +v -86.6904 426.227 159.574 +v -101.405 421.274 187.717 +v -56.9872 487.596 172.668 +v -56.9872 487.596 172.668 +v -52.7381 458.911 120.825 +v -86.6904 426.227 159.574 +v 65.6434 390.704 103.599 +v 61.6316 389.354 188.674 +v -101.405 421.274 187.717 +v -101.405 421.274 187.717 +v -86.6904 426.227 159.574 +v 65.6434 390.704 103.599 +v 76.3056 529.56 158.966 +v -86.1869 457.101 363.046 +v -57.0879 443.509 388.882 +v -57.0879 443.509 388.882 +v 143.503 508.238 162.674 +v 76.3056 529.56 158.966 +v 143.503 508.238 162.674 +v -57.0879 443.509 388.882 +v -61.2254 314.985 389.398 +v -61.2254 314.985 389.398 +v 140.897 421.541 179.078 +v 143.503 508.238 162.674 +v 140.897 421.541 179.078 +v -61.2254 314.985 389.398 +v -121.906 296.789 347.524 +v -121.906 296.789 347.524 +v 61.6316 389.354 188.674 +v 140.897 421.541 179.078 +v -56.9872 487.596 172.668 +v -171.947 433.723 301.741 +v -86.1869 457.101 363.046 +v -86.1869 457.101 363.046 +v 76.3056 529.56 158.966 +v -56.9872 487.596 172.668 +v -101.405 421.274 187.717 +v -194.817 317.703 287.062 +v -171.947 433.723 301.741 +v -171.947 433.723 301.741 +v -56.9872 487.596 172.668 +v -101.405 421.274 187.717 +v 61.6316 389.354 188.674 +v -121.906 296.789 347.524 +v -194.817 317.703 287.062 +v -194.817 317.703 287.062 +v -101.405 421.274 187.717 +v 61.6316 389.354 188.674 +v -86.1869 457.101 363.046 +v -120.57 443.918 397.079 +v -102.276 429.985 417.599 +v -102.276 429.985 417.599 +v -57.0879 443.509 388.882 +v -86.1869 457.101 363.046 +v -57.0879 443.509 388.882 +v -102.276 429.985 417.599 +v -101.46 306.717 419.247 +v -101.46 306.717 419.247 +v -61.2254 314.985 389.398 +v -57.0879 443.509 388.882 +v -61.2254 314.985 389.398 +v -101.46 306.717 419.247 +v -150.824 294.424 384.223 +v -150.824 294.424 384.223 +v -121.906 296.789 347.524 +v -61.2254 314.985 389.398 +v -171.947 433.723 301.741 +v -209.07 429.315 327.758 +v -120.57 443.918 397.079 +v -120.57 443.918 397.079 +v -86.1869 457.101 363.046 +v -171.947 433.723 301.741 +v -194.817 317.703 287.062 +v -220.374 318.156 317.345 +v -209.07 429.315 327.758 +v -209.07 429.315 327.758 +v -171.947 433.723 301.741 +v -194.817 317.703 287.062 +v -121.906 296.789 347.524 +v -150.824 294.424 384.223 +v -220.374 318.156 317.345 +v -220.374 318.156 317.345 +v -194.817 317.703 287.062 +v -121.906 296.789 347.524 +v -120.57 443.918 397.079 +v -279.206 399.004 477.521 +v -216.691 379.768 512.471 +v -216.691 379.768 512.471 +v -102.276 429.985 417.599 +v -120.57 443.918 397.079 +v -102.276 429.985 417.599 +v -216.691 379.768 512.471 +v -220.533 323.142 524.241 +v -220.533 323.142 524.241 +v -101.46 306.717 419.247 +v -102.276 429.985 417.599 +v -101.46 306.717 419.247 +v -220.533 323.142 524.241 +v -257.957 235.824 505.428 +v -257.957 235.824 505.428 +v -150.824 294.424 384.223 +v -101.46 306.717 419.247 +v -209.07 429.315 327.758 +v -372.739 339.847 407.375 +v -279.206 399.004 477.521 +v -279.206 399.004 477.521 +v -120.57 443.918 397.079 +v -209.07 429.315 327.758 +v -220.374 318.156 317.345 +v -394.838 259.653 404.628 +v -372.739 339.847 407.375 +v -372.739 339.847 407.375 +v -209.07 429.315 327.758 +v -220.374 318.156 317.345 +v -150.824 294.424 384.223 +v -257.957 235.824 505.428 +v -394.838 259.653 404.628 +v -394.838 259.653 404.628 +v -220.374 318.156 317.345 +v -150.824 294.424 384.223 +v -279.206 399.004 477.521 +v -317.133 461.908 577.263 +v -236.316 377.472 568.835 +v -236.316 377.472 568.835 +v -216.691 379.768 512.471 +v -279.206 399.004 477.521 +v -216.691 379.768 512.471 +v -236.316 377.472 568.835 +v -240.158 320.846 580.604 +v -240.158 320.846 580.604 +v -220.533 323.142 524.241 +v -216.691 379.768 512.471 +v -220.533 323.142 524.241 +v -240.158 320.846 580.604 +v -231.113 222.436 578.573 +v -231.113 222.436 578.573 +v -257.957 235.824 505.428 +v -220.533 323.142 524.241 +v -372.739 339.847 407.375 +v -521.042 358.693 466.938 +v -317.133 461.908 577.263 +v -317.133 461.908 577.263 +v -279.206 399.004 477.521 +v -372.739 339.847 407.375 +v -394.838 259.653 404.628 +v -543.141 227.102 464.192 +v -521.042 358.693 466.938 +v -521.042 358.693 466.938 +v -372.739 339.847 407.375 +v -394.838 259.653 404.628 +v -257.957 235.824 505.428 +v -231.113 222.436 578.573 +v -543.141 227.102 464.192 +v -543.141 227.102 464.192 +v -394.838 259.653 404.628 +v -257.957 235.824 505.428 +v -317.133 461.908 577.263 +v -180.616 530.627 583.426 +v -143.793 467.96 589.922 +v -143.793 467.96 589.922 +v -236.316 377.472 568.835 +v -317.133 461.908 577.263 +v -236.316 377.472 568.835 +v -143.793 467.96 589.922 +v -123.358 426.144 615.393 +v -123.358 426.144 615.393 +v -240.158 320.846 580.604 +v -236.316 377.472 568.835 +v -240.158 320.846 580.604 +v -123.358 426.144 615.393 +v -97.4353 366.614 662.435 +v -97.4353 366.614 662.435 +v -231.113 222.436 578.573 +v -240.158 320.846 580.604 +v -521.042 358.693 466.938 +v -493.77 358.377 624.724 +v -384.975 518.979 704.341 +v -384.975 518.979 704.341 +v -317.133 461.908 577.263 +v -521.042 358.693 466.938 +v -543.141 227.102 464.192 +v -513.328 278.481 614.68 +v -493.77 358.377 624.724 +v -493.77 358.377 624.724 +v -521.042 358.693 466.938 +v -543.141 227.102 464.192 +v -231.113 222.436 578.573 +v -308.634 250.194 722.901 +v -513.328 278.481 614.68 +v -513.328 278.481 614.68 +v -543.141 227.102 464.192 +v -231.113 222.436 578.573 +v -317.133 461.908 577.263 +v -384.975 518.979 704.341 +v -207.244 534.735 671.558 +v -207.244 534.735 671.558 +v -180.616 530.627 583.426 +v -317.133 461.908 577.263 +v -97.4353 366.614 662.435 +v -117.005 364.061 756.407 +v -308.634 250.194 722.901 +v -308.634 250.194 722.901 +v -231.113 222.436 578.573 +v -97.4353 366.614 662.435 +v -493.77 358.377 624.724 +v -470.518 310.442 648.255 +v -351.878 348.234 729.182 +v -351.878 348.234 729.182 +v -384.975 518.979 704.341 +v -493.77 358.377 624.724 +v -513.328 278.481 614.68 +v -470.518 310.442 648.255 +v -493.77 358.377 624.724 +v -308.634 250.194 722.901 +v -351.878 348.234 729.182 +v -470.518 310.442 648.255 +v -470.518 310.442 648.255 +v -513.328 278.481 614.68 +v -308.634 250.194 722.901 +v -384.975 518.979 704.341 +v -351.878 348.234 729.182 +v -158.686 443.907 717.111 +v -158.686 443.907 717.111 +v -207.244 534.735 671.558 +v -384.975 518.979 704.341 +v -117.005 364.061 756.407 +v -158.686 443.907 717.111 +v -351.878 348.234 729.182 +v -351.878 348.234 729.182 +v -308.634 250.194 722.901 +v -117.005 364.061 756.407 +v -180.616 530.627 583.426 +v -66.3274 584.533 711.51 +v -143.793 467.96 589.922 +v -123.358 426.144 615.393 +v -9.81756 473.005 748.063 +v -97.4353 366.614 662.435 +v -207.244 534.735 671.558 +v -66.3274 584.533 711.51 +v -180.616 530.627 583.426 +v -97.4353 366.614 662.435 +v -9.81756 473.005 748.063 +v -117.005 364.061 756.407 +v -158.686 443.907 717.111 +v -66.3274 584.533 711.51 +v -207.244 534.735 671.558 +v -117.005 364.061 756.407 +v -9.81756 473.005 748.063 +v -158.686 443.907 717.111 +v -143.793 467.96 589.922 +v -158.686 443.907 717.111 +v -123.358 426.144 615.393 +v -123.358 426.144 615.393 +v -158.686 443.907 717.111 +v -9.81756 473.005 748.063 +v -66.3274 584.533 711.51 +v -158.686 443.907 717.111 +v -143.793 467.96 589.922 +v 676.056 91.0124 -403.457 +v 843.785 151.745 -368.243 +v 824.669 40.2029 -424.477 +v 824.669 40.2029 -424.477 +v 679.933 40.2029 -441.705 +v 676.056 91.0124 -403.457 +v 843.785 151.745 -368.243 +v 885.246 144.315 -351.891 +v 878.609 40.2028 -424.149 +v 878.609 40.2028 -424.149 +v 824.669 40.2029 -424.477 +v 843.785 151.745 -368.243 +v 885.246 144.315 -351.891 +v 922.953 120.084 -375.782 +v 909.031 40.2028 -425.978 +v 909.031 40.2028 -425.978 +v 878.609 40.2028 -424.149 +v 885.246 144.315 -351.891 +v 922.953 120.084 -375.782 +v 960.668 78.2619 -383.161 +v 968.589 40.2028 -404.236 +v 968.589 40.2028 -404.236 +v 909.031 40.2028 -425.978 +v 922.953 120.084 -375.782 +v 960.668 78.2619 -383.161 +v 1053.7 98.6425 -346.758 +v 1061.35 40.2028 -356.762 +v 1061.35 40.2028 -356.762 +v 968.589 40.2028 -404.236 +v 960.668 78.2619 -383.161 +v 1053.7 98.6425 -346.758 +v 1117.05 95.0714 -272.841 +v 1119.22 40.2028 -284.353 +v 1119.22 40.2028 -284.353 +v 1061.35 40.2028 -356.762 +v 1053.7 98.6425 -346.758 +v 1117.05 95.0714 -272.841 +v 1130.63 90.2301 -219.113 +v 1152.01 40.2028 -217.427 +v 1152.01 40.2028 -217.427 +v 1119.22 40.2028 -284.353 +v 1117.05 95.0714 -272.841 +v 1130.63 90.2301 -219.113 +v 1120.75 112.796 -128.277 +v 1154.32 40.2028 -99.7808 +v 1154.32 40.2028 -99.7808 +v 1152.01 40.2028 -217.427 +v 1130.63 90.2301 -219.113 +v 1120.75 112.796 -128.277 +v 1077.52 168.519 -75.2012 +v 1150.35 40.2028 -66.9116 +v 1150.35 40.2028 -66.9116 +v 1154.32 40.2028 -99.7808 +v 1120.75 112.796 -128.277 +v 972.648 143.973 25.3677 +v 948.344 40.2028 68.9338 +v 984.155 40.2028 76.1361 +v 972.648 143.973 25.3677 +v 918.453 175.699 -14.2217 +v 912.514 40.2028 48.5509 +v 912.514 40.2028 48.5509 +v 948.344 40.2028 68.9338 +v 972.648 143.973 25.3677 +v 758.412 143.246 43.888 +v 766.139 270.868 -12.7916 +v 682.576 214.241 164.696 +v 682.576 214.241 164.696 +v 542.48 125.319 151.06 +v 758.412 143.246 43.888 +v 789.328 152.689 -20.678 +v 729.558 324.573 -136.921 +v 766.139 270.868 -12.7916 +v 766.139 270.868 -12.7916 +v 758.412 143.246 43.888 +v 789.328 152.689 -20.678 +v 380.051 331.937 -66.4672 +v 415.539 353.513 12.9001 +v 545.802 418.428 -93.8417 +v 380.051 331.937 -66.4672 +v 494.4 199.075 153.3 +v 415.539 353.513 12.9001 +v 542.48 125.319 151.06 +v 682.576 214.241 164.696 +v 494.4 199.075 153.3 +v 705.303 219.02 -221.823 +v 729.558 324.573 -136.921 +v 846.835 189.911 -138.578 +v 596.877 306.826 -202.43 +v 545.802 418.428 -93.8417 +v 690.121 405.282 -50.8344 +v 690.121 405.282 -50.8344 +v 729.558 324.573 -136.921 +v 596.877 306.826 -202.43 +v 729.558 324.573 -136.921 +v 764.322 329.706 -4.0724 +v 766.139 270.868 -12.7916 +v 729.558 324.573 -136.921 +v 690.121 405.282 -50.8344 +v 764.322 329.706 -4.0724 +v 545.802 418.428 -93.8417 +v 518.621 469.651 -11.2156 +v 643.124 465.31 35.5904 +v 643.124 465.31 35.5904 +v 690.121 405.282 -50.8344 +v 545.802 418.428 -93.8417 +v 764.322 329.706 -4.0724 +v 715.597 367.645 118.766 +v 682.576 214.241 164.696 +v 682.576 214.241 164.696 +v 766.139 270.868 -12.7916 +v 764.322 329.706 -4.0724 +v 545.802 418.428 -93.8417 +v 415.539 353.513 12.9001 +v 518.621 469.651 -11.2156 +v 690.121 405.282 -50.8344 +v 643.124 465.31 35.5904 +v 715.597 367.645 118.766 +v 715.597 367.645 118.766 +v 764.322 329.706 -4.0724 +v 690.121 405.282 -50.8344 +v 494.4 199.075 153.3 +v 495.985 266.516 152.255 +v 400.659 372.773 56.5926 +v 400.659 372.773 56.5926 +v 415.539 353.513 12.9001 +v 494.4 199.075 153.3 +v 682.576 214.241 164.696 +v 623.367 257.639 203.09 +v 495.985 266.516 152.255 +v 495.985 266.516 152.255 +v 494.4 199.075 153.3 +v 682.576 214.241 164.696 +v 518.621 469.651 -11.2156 +v 488.245 471.902 48.2971 +v 601.471 480.686 109.919 +v 601.471 480.686 109.919 +v 643.124 465.31 35.5904 +v 518.621 469.651 -11.2156 +v 715.597 367.645 118.766 +v 651.012 395.809 204.493 +v 623.367 257.639 203.09 +v 623.367 257.639 203.09 +v 682.576 214.241 164.696 +v 715.597 367.645 118.766 +v 415.539 353.513 12.9001 +v 400.659 372.773 56.5926 +v 488.245 471.902 48.2971 +v 488.245 471.902 48.2971 +v 518.621 469.651 -11.2156 +v 415.539 353.513 12.9001 +v 643.124 465.31 35.5904 +v 601.471 480.686 109.919 +v 651.012 395.809 204.493 +v 651.012 395.809 204.493 +v 715.597 367.645 118.766 +v 643.124 465.31 35.5904 +v 495.985 266.516 152.255 +v 449.267 308.157 241.792 +v 376.155 448.151 167.649 +v 376.155 448.151 167.649 +v 400.659 372.773 56.5926 +v 495.985 266.516 152.255 +v 623.367 257.639 203.09 +v 595.313 339.781 310.175 +v 449.267 308.157 241.792 +v 449.267 308.157 241.792 +v 495.985 266.516 152.255 +v 623.367 257.639 203.09 +v 488.245 471.902 48.2971 +v 438.002 514.69 183.939 +v 507.031 489.582 228.749 +v 507.031 489.582 228.749 +v 601.471 480.686 109.919 +v 488.245 471.902 48.2971 +v 651.012 395.809 204.493 +v 588.147 455.681 278.435 +v 595.313 339.781 310.175 +v 595.313 339.781 310.175 +v 623.367 257.639 203.09 +v 651.012 395.809 204.493 +v 400.659 372.773 56.5926 +v 376.155 448.151 167.649 +v 438.002 514.69 183.939 +v 438.002 514.69 183.939 +v 488.245 471.902 48.2971 +v 400.659 372.773 56.5926 +v 601.471 480.686 109.919 +v 507.031 489.582 228.749 +v 588.147 455.681 278.435 +v 588.147 455.681 278.435 +v 651.012 395.809 204.493 +v 601.471 480.686 109.919 +v 449.267 308.157 241.792 +v 319.396 336.17 310.744 +v 280.893 488.053 219.652 +v 280.893 488.053 219.652 +v 376.155 448.151 167.649 +v 449.267 308.157 241.792 +v 449.267 308.157 241.792 +v 595.313 339.781 310.175 +v 467.024 370.98 349.519 +v 467.024 370.98 349.519 +v 319.396 336.17 310.744 +v 449.267 308.157 241.792 +v 438.002 514.69 183.939 +v 289.487 530.878 226.311 +v 332.802 560.26 294.654 +v 332.802 560.26 294.654 +v 507.031 489.582 228.749 +v 438.002 514.69 183.939 +v 588.147 455.681 278.435 +v 467.468 531.511 347.786 +v 467.024 370.98 349.519 +v 467.024 370.98 349.519 +v 595.313 339.781 310.175 +v 588.147 455.681 278.435 +v 376.155 448.151 167.649 +v 280.893 488.053 219.652 +v 289.487 530.878 226.311 +v 289.487 530.878 226.311 +v 438.002 514.69 183.939 +v 376.155 448.151 167.649 +v 507.031 489.582 228.749 +v 332.802 560.26 294.654 +v 467.468 531.511 347.786 +v 467.468 531.511 347.786 +v 588.147 455.681 278.435 +v 507.031 489.582 228.749 +v 280.893 488.053 219.652 +v 319.396 336.17 310.744 +v 209.876 488.534 275.697 +v 289.487 530.878 226.311 +v 252.695 546.276 283.647 +v 332.802 560.26 294.654 +v 280.893 488.053 219.652 +v 209.876 488.534 275.697 +v 252.695 546.276 283.647 +v 252.695 546.276 283.647 +v 289.487 530.878 226.311 +v 280.893 488.053 219.652 +v 209.876 488.534 275.697 +v 319.396 336.17 310.744 +v 215.546 483.212 359.667 +v 252.695 546.276 283.647 +v 257.069 532.528 363.088 +v 332.802 560.26 294.654 +v 209.876 488.534 275.697 +v 215.546 483.212 359.667 +v 257.069 532.528 363.088 +v 257.069 532.528 363.088 +v 252.695 546.276 283.647 +v 209.876 488.534 275.697 +v 319.396 336.17 310.744 +v 337.988 332.852 457.825 +v 279.28 386.788 455.978 +v 279.28 386.788 455.978 +v 215.546 483.212 359.667 +v 319.396 336.17 310.744 +v 467.024 370.98 349.519 +v 441.553 365.885 435.883 +v 337.988 332.852 457.825 +v 337.988 332.852 457.825 +v 319.396 336.17 310.744 +v 467.024 370.98 349.519 +v 257.069 532.528 363.088 +v 265.056 461.92 466.161 +v 352.053 483.212 456.275 +v 352.053 483.212 456.275 +v 332.802 560.26 294.654 +v 257.069 532.528 363.088 +v 467.468 531.511 347.786 +v 441.455 448.914 445.446 +v 441.553 365.885 435.883 +v 441.553 365.885 435.883 +v 467.024 370.98 349.519 +v 467.468 531.511 347.786 +v 215.546 483.212 359.667 +v 279.28 386.788 455.978 +v 265.056 461.92 466.161 +v 265.056 461.92 466.161 +v 257.069 532.528 363.088 +v 215.546 483.212 359.667 +v 332.802 560.26 294.654 +v 352.053 483.212 456.275 +v 441.455 448.914 445.446 +v 441.455 448.914 445.446 +v 467.468 531.511 347.786 +v 332.802 560.26 294.654 +v 337.988 332.852 457.825 +v 383.441 310.398 553.769 +v 272.997 377.275 559.478 +v 272.997 377.275 559.478 +v 279.28 386.788 455.978 +v 337.988 332.852 457.825 +v 441.553 365.885 435.883 +v 466.393 331.19 551.819 +v 383.441 310.398 553.769 +v 383.441 310.398 553.769 +v 337.988 332.852 457.825 +v 441.553 365.885 435.883 +v 265.056 461.92 466.161 +v 264.043 467.602 561.999 +v 390.756 478.924 555.537 +v 390.756 478.924 555.537 +v 352.053 483.212 456.275 +v 265.056 461.92 466.161 +v 441.455 448.914 445.446 +v 464.616 423.439 556.584 +v 466.393 331.19 551.819 +v 466.393 331.19 551.819 +v 441.553 365.885 435.883 +v 441.455 448.914 445.446 +v 279.28 386.788 455.978 +v 272.997 377.275 559.478 +v 264.043 467.602 561.999 +v 264.043 467.602 561.999 +v 265.056 461.92 466.161 +v 279.28 386.788 455.978 +v 352.053 483.212 456.275 +v 390.756 478.924 555.537 +v 464.616 423.439 556.584 +v 464.616 423.439 556.584 +v 441.455 448.914 445.446 +v 352.053 483.212 456.275 +v 383.441 310.398 553.769 +v 377.032 310.647 640.682 +v 271.471 377.778 655.645 +v 271.471 377.778 655.645 +v 272.997 377.275 559.478 +v 383.441 310.398 553.769 +v 466.393 331.19 551.819 +v 460.045 334.815 626.719 +v 377.032 310.647 640.682 +v 377.032 310.647 640.682 +v 383.441 310.398 553.769 +v 466.393 331.19 551.819 +v 264.043 467.602 561.999 +v 293.651 458.498 667.416 +v 418.604 481.374 630.348 +v 418.604 481.374 630.348 +v 390.756 478.924 555.537 +v 264.043 467.602 561.999 +v 464.616 423.439 556.584 +v 484.703 406.702 631.39 +v 460.045 334.815 626.719 +v 460.045 334.815 626.719 +v 466.393 331.19 551.819 +v 464.616 423.439 556.584 +v 272.997 377.275 559.478 +v 271.471 377.778 655.645 +v 293.651 458.498 667.416 +v 293.651 458.498 667.416 +v 264.043 467.602 561.999 +v 272.997 377.275 559.478 +v 390.756 478.924 555.537 +v 418.604 481.374 630.348 +v 484.703 406.702 631.39 +v 484.703 406.702 631.39 +v 464.616 423.439 556.584 +v 390.756 478.924 555.537 +v 377.032 310.647 640.682 +v 340.152 283.009 825.111 +v 304.327 381.701 790.592 +v 304.327 381.701 790.592 +v 271.471 377.778 655.645 +v 377.032 310.647 640.682 +v 460.045 334.815 626.719 +v 451.236 316.167 688.596 +v 340.152 283.009 825.111 +v 340.152 283.009 825.111 +v 377.032 310.647 640.682 +v 460.045 334.815 626.719 +v 293.651 458.498 667.416 +v 317.335 458.301 787.405 +v 433.476 496.305 693.155 +v 433.476 496.305 693.155 +v 418.604 481.374 630.348 +v 293.651 458.498 667.416 +v 484.703 406.702 631.39 +v 490.612 394.687 692.953 +v 451.236 316.167 688.596 +v 451.236 316.167 688.596 +v 460.045 334.815 626.719 +v 484.703 406.702 631.39 +v 271.471 377.778 655.645 +v 304.327 381.701 790.592 +v 317.335 458.301 787.405 +v 317.335 458.301 787.405 +v 293.651 458.498 667.416 +v 271.471 377.778 655.645 +v 418.604 481.374 630.348 +v 433.476 496.305 693.155 +v 490.612 394.687 692.953 +v 490.612 394.687 692.953 +v 484.703 406.702 631.39 +v 418.604 481.374 630.348 +v 340.152 283.009 825.111 +v 499.471 257.736 820.408 +v 580.055 378.791 836.628 +v 580.055 378.791 836.628 +v 304.327 381.701 790.592 +v 340.152 283.009 825.111 +v 451.236 316.167 688.596 +v 527.033 273.943 790.548 +v 499.471 257.736 820.408 +v 499.471 257.736 820.408 +v 340.152 283.009 825.111 +v 451.236 316.167 688.596 +v 317.335 458.301 787.405 +v 486.543 519.264 861.734 +v 507.467 493.833 768.062 +v 507.467 493.833 768.062 +v 433.476 496.305 693.155 +v 317.335 458.301 787.405 +v 490.612 394.687 692.953 +v 577.257 377.751 761.531 +v 527.033 273.943 790.548 +v 527.033 273.943 790.548 +v 451.236 316.167 688.596 +v 490.612 394.687 692.953 +v 304.327 381.701 790.592 +v 580.055 378.791 836.628 +v 486.543 519.264 861.734 +v 486.543 519.264 861.734 +v 317.335 458.301 787.405 +v 304.327 381.701 790.592 +v 433.476 496.305 693.155 +v 507.467 493.833 768.062 +v 577.257 377.751 761.531 +v 577.257 377.751 761.531 +v 490.612 394.687 692.953 +v 433.476 496.305 693.155 +v 486.543 519.264 861.734 +v 626.437 533.232 888.782 +v 644.719 516.635 823.452 +v 644.719 516.635 823.452 +v 507.467 493.833 768.062 +v 486.543 519.264 861.734 +v 580.055 378.791 836.628 +v 627.235 439.741 885.962 +v 626.437 533.232 888.782 +v 626.437 533.232 888.782 +v 486.543 519.264 861.734 +v 580.055 378.791 836.628 +v 507.467 493.833 768.062 +v 644.719 516.635 823.452 +v 645.052 439.824 822.685 +v 645.052 439.824 822.685 +v 577.257 377.751 761.531 +v 507.467 493.833 768.062 +v 499.471 257.736 820.408 +v 626.526 261.924 888.681 +v 625.983 345.278 889.066 +v 625.983 345.278 889.066 +v 580.055 378.791 836.628 +v 499.471 257.736 820.408 +v 527.033 273.943 790.548 +v 645.299 278.13 822.477 +v 626.526 261.924 888.681 +v 626.526 261.924 888.681 +v 499.471 257.736 820.408 +v 527.033 273.943 790.548 +v 577.257 377.751 761.531 +v 645.229 332.579 822.897 +v 645.299 278.13 822.477 +v 645.299 278.13 822.477 +v 527.033 273.943 790.548 +v 577.257 377.751 761.531 +v 645.052 439.824 822.685 +v 627.235 439.741 885.962 +v 580.055 378.791 836.628 +v 580.055 378.791 836.628 +v 577.257 377.751 761.531 +v 645.052 439.824 822.685 +v 577.257 377.751 761.531 +v 580.055 378.791 836.628 +v 625.983 345.278 889.066 +v 625.983 345.278 889.066 +v 645.229 332.579 822.897 +v 577.257 377.751 761.531 +v 626.437 533.232 888.782 +v 695.541 460.197 913.328 +v 644.719 516.635 823.452 +v 627.235 439.741 885.962 +v 695.541 460.197 913.328 +v 626.437 533.232 888.782 +v 644.719 516.635 823.452 +v 695.541 460.197 913.328 +v 645.052 439.824 822.685 +v 626.526 261.924 888.681 +v 697.205 261.658 900.122 +v 625.983 345.278 889.066 +v 645.299 278.13 822.477 +v 697.205 261.658 900.122 +v 626.526 261.924 888.681 +v 645.229 332.579 822.897 +v 697.205 261.658 900.122 +v 645.299 278.13 822.477 +v 645.052 439.824 822.685 +v 695.541 460.197 913.328 +v 627.235 439.741 885.962 +v 625.983 345.278 889.066 +v 697.205 261.658 900.122 +v 645.229 332.579 822.897 +v 960.668 78.2619 -383.161 +v 922.953 120.084 -375.782 +v 1039.45 158.244 -303.194 +v 1039.45 158.244 -303.194 +v 1053.7 98.6425 -346.758 +v 960.668 78.2619 -383.161 +v 1053.7 98.6425 -346.758 +v 1039.45 158.244 -303.194 +v 1101.1 130.047 -253.686 +v 1101.1 130.047 -253.686 +v 1117.05 95.0714 -272.841 +v 1053.7 98.6425 -346.758 +v 922.953 120.084 -375.782 +v 885.246 144.315 -351.891 +v 1015.08 192.237 -235.109 +v 1015.08 192.237 -235.109 +v 1039.45 158.244 -303.194 +v 922.953 120.084 -375.782 +v 1039.45 158.244 -303.194 +v 1015.08 192.237 -235.109 +v 1102.84 151.486 -161.378 +v 1102.84 151.486 -161.378 +v 1101.1 130.047 -253.686 +v 1039.45 158.244 -303.194 +v 885.246 144.315 -351.891 +v 843.785 151.745 -368.243 +v 965.997 209.493 -179.582 +v 965.997 209.493 -179.582 +v 1015.08 192.237 -235.109 +v 885.246 144.315 -351.891 +v 1015.08 192.237 -235.109 +v 965.997 209.493 -179.582 +v 1077.52 168.519 -75.2012 +v 1077.52 168.519 -75.2012 +v 1102.84 151.486 -161.378 +v 1015.08 192.237 -235.109 +v 843.785 151.745 -368.243 +v 676.056 91.0124 -403.457 +v 846.835 189.911 -138.578 +v 846.835 189.911 -138.578 +v 965.997 209.493 -179.582 +v 843.785 151.745 -368.243 +v 965.997 209.493 -179.582 +v 846.835 189.911 -138.578 +v 918.453 175.699 -14.2217 +v 965.997 209.493 -179.582 +v 918.453 175.699 -14.2217 +v 987.83 177.274 -34.0016 +v 965.997 209.493 -179.582 +v 987.83 177.274 -34.0016 +v 1077.52 168.519 -75.2012 +v 1101.1 130.047 -253.686 +v 1130.63 90.2301 -219.113 +v 1117.05 95.0714 -272.841 +v 987.83 177.274 -34.0016 +v 1056.88 133.496 2.82201 +v 1077.52 168.519 -75.2012 +v 1102.84 151.486 -161.378 +v 1120.75 112.796 -128.277 +v 1130.63 90.2301 -219.113 +v 1130.63 90.2301 -219.113 +v 1101.1 130.047 -253.686 +v 1102.84 151.486 -161.378 +v 1077.52 168.519 -75.2012 +v 1120.75 112.796 -128.277 +v 1102.84 151.486 -161.378 +v 1150.35 40.2028 -66.9116 +v 1089.27 119.474 -37.2052 +v 1132.43 40.2028 -24.6494 +v 997.81 125.62 37.0952 +v 1031.13 40.2028 65.4153 +v 1100.85 40.2028 23.3858 +v 1100.85 40.2028 23.3858 +v 1056.88 133.496 2.82201 +v 997.81 125.62 37.0952 +v 997.81 125.62 37.0952 +v 972.648 143.973 25.3677 +v 984.155 40.2028 76.1361 +v 1089.27 119.474 -37.2052 +v 1150.35 40.2028 -66.9116 +v 1077.52 168.519 -75.2012 +v 1089.27 119.474 -37.2052 +v 1077.52 168.519 -75.2012 +v 1056.88 133.496 2.82201 +v 972.648 143.973 25.3677 +v 997.81 125.62 37.0952 +v 1056.88 133.496 2.82201 +v 972.648 143.973 25.3677 +v 1056.88 133.496 2.82201 +v 987.83 177.274 -34.0016 +v 972.648 143.973 25.3677 +v 987.83 177.274 -34.0016 +v 918.453 175.699 -14.2217 +v 1089.27 119.474 -37.2052 +v 1122.15 40.2028 2.15071 +v 1132.43 40.2028 -24.6494 +v 1089.27 119.474 -37.2052 +v 1056.88 133.496 2.82201 +v 1100.85 40.2028 23.3858 +v 1100.85 40.2028 23.3858 +v 1122.15 40.2028 2.15071 +v 1089.27 119.474 -37.2052 +v 997.81 125.62 37.0952 +v 984.155 40.2028 76.1361 +v 1031.13 40.2028 65.4153 +v 558.966 89.8242 -404.09 +v 268.451 249.667 -290.85 +v 596.877 306.826 -202.43 +v 558.966 89.8242 -404.09 +v 596.877 306.826 -202.43 +v 705.303 219.02 -221.823 +v 596.877 306.826 -202.43 +v 380.051 331.937 -66.4672 +v 545.802 418.428 -93.8417 +v 380.051 331.937 -66.4672 +v 260.646 303.899 -153.714 +v 242.389 284.152 -21.5963 +v 705.303 219.02 -221.823 +v 596.877 306.826 -202.43 +v 729.558 324.573 -136.921 +v -627.402 40.2031 -27.0373 +v -974.541 40.2032 116.414 +v -598.079 97.9116 13.3312 +v -598.079 97.9116 13.3312 +v -966.748 86.8306 188.349 +v -635.946 99.4678 145.914 +v -973.5 40.2032 204.705 +v -605.1 40.2031 174.866 +v -635.946 99.4678 145.914 +v -635.946 99.4678 145.914 +v -966.748 86.8306 188.349 +v -973.5 40.2032 204.705 +v -598.079 97.9116 13.3312 +v -974.541 40.2032 116.414 +v -966.748 86.8306 188.349 +v -1356.56 67.297 175.492 +v -966.748 86.8306 188.349 +v -974.541 40.2032 116.414 +v -974.541 40.2032 116.414 +v -1360.94 40.2032 134.989 +v -1356.56 67.297 175.492 +v -1360.36 40.2032 184.7 +v -973.5 40.2032 204.705 +v -966.748 86.8306 188.349 +v -966.748 86.8306 188.349 +v -1356.56 67.297 175.492 +v -1360.36 40.2032 184.7 +v -1360.94 40.2032 134.989 +v -1746.98 40.2033 71.4633 +v -1356.56 67.297 175.492 +v -1356.56 67.297 175.492 +v -1746.98 40.2033 71.4633 +v -1360.36 40.2032 184.7 +v 846.835 189.911 -138.578 +v 789.328 152.689 -20.678 +v 918.453 175.699 -14.2217 +v 676.056 91.0124 -403.457 +v 705.303 219.02 -221.823 +v 846.835 189.911 -138.578 +v 558.966 89.8242 -404.09 +v 442.767 40.2029 -586.685 +v 505.639 40.2029 -412.719 +v 676.056 91.0124 -403.457 +v 482.298 74.6915 -625.285 +v 558.966 89.8242 -404.09 +v 676.056 91.0124 -403.457 +v 679.933 40.2029 -441.705 +v 486.264 40.2029 -638.201 +v 486.264 40.2029 -638.201 +v 482.298 74.6915 -625.285 +v 676.056 91.0124 -403.457 +v 482.298 74.6915 -625.285 +v 442.767 40.2029 -586.685 +v 558.966 89.8242 -404.09 +v 442.767 40.2029 -586.685 +v 482.298 74.6915 -625.285 +v 309.065 59.7757 -777.283 +v 309.065 59.7757 -777.283 +v 286.808 40.203 -755.55 +v 442.767 40.2029 -586.685 +v 482.298 74.6915 -625.285 +v 486.264 40.2029 -638.201 +v 311.298 40.203 -784.555 +v 311.298 40.203 -784.555 +v 309.065 59.7757 -777.283 +v 482.298 74.6915 -625.285 +v 309.065 59.7757 -777.283 +v 3.60059 40.203 -892.627 +v 286.808 40.203 -755.55 +v 311.298 40.203 -784.555 +v 3.60059 40.203 -892.627 +v 309.065 59.7757 -777.283 +v -107.623 179.419 -353.534 +v -312.712 40.203 -483.512 +v -233.929 40.203 -337.011 +v 32.5666 159.26 -387.298 +v -261.253 74.6916 -577.012 +v -107.623 179.419 -353.534 +v 32.5666 159.26 -387.298 +v 21.7976 40.203 -426.108 +v -260.883 40.203 -590.518 +v -260.883 40.203 -590.518 +v -261.253 74.6916 -577.012 +v 32.5666 159.26 -387.298 +v -261.253 74.6916 -577.012 +v -312.712 40.203 -483.512 +v -107.623 179.419 -353.534 +v -312.712 40.203 -483.512 +v -261.253 74.6916 -577.012 +v -468.805 59.7759 -677.187 +v -468.805 59.7759 -677.187 +v -484.444 40.2031 -650.296 +v -312.712 40.203 -483.512 +v -261.253 74.6916 -577.012 +v -260.883 40.203 -590.518 +v -468.596 40.2031 -684.792 +v -468.596 40.2031 -684.792 +v -468.805 59.7759 -677.187 +v -261.253 74.6916 -577.012 +v -468.805 59.7759 -677.187 +v -793.984 40.2031 -706.704 +v -484.444 40.2031 -650.296 +v -468.596 40.2031 -684.792 +v -793.984 40.2031 -706.704 +v -468.805 59.7759 -677.187 +f 1 2 3 +f 4 5 6 +f 7 8 9 +f 10 11 12 +f 13 14 15 +f 16 17 18 +f 19 20 21 +f 22 23 24 +f 25 26 27 +f 28 29 30 +f 31 32 33 +f 34 35 36 +f 37 38 39 +f 40 41 42 +f 43 44 45 +f 46 47 48 +f 49 50 51 +f 52 53 54 +f 55 56 57 +f 58 59 60 +f 61 62 63 +f 64 65 66 +f 67 68 69 +f 70 71 72 +f 73 74 75 +f 76 77 78 +f 79 80 81 +f 82 83 84 +f 85 86 87 +f 88 89 90 +f 91 92 93 +f 94 95 96 +f 97 98 99 +f 100 101 102 +f 103 104 105 +f 106 107 108 +f 109 110 111 +f 112 113 114 +f 115 116 117 +f 118 119 120 +f 121 122 123 +f 124 125 126 +f 127 128 129 +f 130 131 132 +f 133 134 135 +f 136 137 138 +f 139 140 141 +f 142 143 144 +f 145 146 147 +f 148 149 150 +f 151 152 153 +f 154 155 156 +f 157 158 159 +f 160 161 162 +f 163 164 165 +f 166 167 168 +f 169 170 171 +f 172 173 174 +f 175 176 177 +f 178 179 180 +f 181 182 183 +f 184 185 186 +f 187 188 189 +f 190 191 192 +f 193 194 195 +f 196 197 198 +f 199 200 201 +f 202 203 204 +f 205 206 207 +f 208 209 210 +f 211 212 213 +f 214 215 216 +f 217 218 219 +f 220 221 222 +f 223 224 225 +f 226 227 228 +f 229 230 231 +f 232 233 234 +f 235 236 237 +f 238 239 240 +f 241 242 243 +f 244 245 246 +f 247 248 249 +f 250 251 252 +f 253 254 255 +f 256 257 258 +f 259 260 261 +f 262 263 264 +f 265 266 267 +f 268 269 270 +f 271 272 273 +f 274 275 276 +f 277 278 279 +f 280 281 282 +f 283 284 285 +f 286 287 288 +f 289 290 291 +f 292 293 294 +f 295 296 297 +f 298 299 300 +f 301 302 303 +f 304 305 306 +f 307 308 309 +f 310 311 312 +f 313 314 315 +f 316 317 318 +f 319 320 321 +f 322 323 324 +f 325 326 327 +f 328 329 330 +f 331 332 333 +f 334 335 336 +f 337 338 339 +f 340 341 342 +f 343 344 345 +f 346 347 348 +f 349 350 351 +f 352 353 354 +f 355 356 357 +f 358 359 360 +f 361 362 363 +f 364 365 366 +f 367 368 369 +f 370 371 372 +f 373 374 375 +f 376 377 378 +f 379 380 381 +f 382 383 384 +f 385 386 387 +f 388 389 390 +f 391 392 393 +f 394 395 396 +f 397 398 399 +f 400 401 402 +f 403 404 405 +f 406 407 408 +f 409 410 411 +f 412 413 414 +f 415 416 417 +f 418 419 420 +f 421 422 423 +f 424 425 426 +f 427 428 429 +f 430 431 432 +f 433 434 435 +f 436 437 438 +f 439 440 441 +f 442 443 444 +f 445 446 447 +f 448 449 450 +f 451 452 453 +f 454 455 456 +f 457 458 459 +f 460 461 462 +f 463 464 465 +f 466 467 468 +f 469 470 471 +f 472 473 474 +f 475 476 477 +f 478 479 480 +f 481 482 483 +f 484 485 486 +f 487 488 489 +f 490 491 492 +f 493 494 495 +f 496 497 498 +f 499 500 501 +f 502 503 504 +f 505 506 507 +f 508 509 510 +f 511 512 513 +f 514 515 516 +f 517 518 519 +f 520 521 522 +f 523 524 525 +f 526 527 528 +f 529 530 531 +f 532 533 534 +f 535 536 537 +f 538 539 540 +f 541 542 543 +f 544 545 546 +f 547 548 549 +f 550 551 552 +f 553 554 555 +f 556 557 558 +f 559 560 561 +f 562 563 564 +f 565 566 567 +f 568 569 570 +f 571 572 573 +f 574 575 576 +f 577 578 579 +f 580 581 582 +f 583 584 585 +f 586 587 588 +f 589 590 591 +f 592 593 594 +f 595 596 597 +f 598 599 600 +f 601 602 603 +f 604 605 606 +f 607 608 609 +f 610 611 612 +f 613 614 615 +f 616 617 618 +f 619 620 621 +f 622 623 624 +f 625 626 627 +f 628 629 630 +f 631 632 633 +f 634 635 636 +f 637 638 639 +f 640 641 642 +f 643 644 645 +f 646 647 648 +f 649 650 651 +f 652 653 654 +f 655 656 657 +f 658 659 660 +f 661 662 663 +f 664 665 666 +f 667 668 669 +f 670 671 672 +f 673 674 675 +f 676 677 678 +f 679 680 681 +f 682 683 684 +f 685 686 687 +f 688 689 690 +f 691 692 693 +f 694 695 696 +f 697 698 699 +f 700 701 702 +f 703 704 705 +f 706 707 708 +f 709 710 711 +f 712 713 714 +f 715 716 717 +f 718 719 720 +f 721 722 723 +f 724 725 726 +f 727 728 729 +f 730 731 732 +f 733 734 735 +f 736 737 738 +f 739 740 741 +f 742 743 744 +f 745 746 747 +f 748 749 750 +f 751 752 753 +f 754 755 756 +f 757 758 759 +f 760 761 762 +f 763 764 765 +f 766 767 768 +f 769 770 771 +f 772 773 774 +f 775 776 777 +f 778 779 780 +f 781 782 783 +f 784 785 786 +f 787 788 789 +f 790 791 792 +f 793 794 795 +f 796 797 798 +f 799 800 801 +f 802 803 804 +f 805 806 807 +f 808 809 810 +f 811 812 813 +f 814 815 816 +f 817 818 819 +f 820 821 822 +f 823 824 825 +f 826 827 828 +f 829 830 831 +f 832 833 834 +f 835 836 837 +f 838 839 840 +f 841 842 843 +f 844 845 846 +f 847 848 849 +f 850 851 852 +f 853 854 855 +f 856 857 858 +f 859 860 861 +f 862 863 864 +f 865 866 867 +f 868 869 870 +f 871 872 873 +f 874 875 876 +f 877 878 879 +f 880 881 882 +f 883 884 885 +f 886 887 888 +f 889 890 891 +f 892 893 894 +f 895 896 897 +f 898 899 900 +f 901 902 903 +f 904 905 906 +f 907 908 909 +f 910 911 912 +f 913 914 915 +f 916 917 918 +f 919 920 921 +f 922 923 924 +f 925 926 927 +f 928 929 930 +f 931 932 933 +f 934 935 936 +f 937 938 939 +f 940 941 942 +f 943 944 945 +f 946 947 948 +f 949 950 951 +f 952 953 954 +f 955 956 957 +f 958 959 960 +f 961 962 963 +f 964 965 966 +f 967 968 969 +f 970 971 972 +f 973 974 975 +f 976 977 978 +f 979 980 981 +f 982 983 984 +f 985 986 987 +f 988 989 990 +f 991 992 993 +f 994 995 996 +f 997 998 999 +f 1000 1001 1002 +f 1003 1004 1005 +f 1006 1007 1008 +f 1009 1010 1011 +f 1012 1013 1014 +f 1015 1016 1017 +f 1018 1019 1020 +f 1021 1022 1023 +f 1024 1025 1026 +f 1027 1028 1029 +f 1030 1031 1032 +f 1033 1034 1035 +f 1036 1037 1038 +f 1039 1040 1041 +f 1042 1043 1044 +f 1045 1046 1047 +f 1048 1049 1050 +f 1051 1052 1053 +f 1054 1055 1056 +f 1057 1058 1059 +f 1060 1061 1062 +f 1063 1064 1065 +f 1066 1067 1068 +f 1069 1070 1071 +f 1072 1073 1074 +f 1075 1076 1077 +f 1078 1079 1080 +f 1081 1082 1083 +f 1084 1085 1086 +f 1087 1088 1089 +f 1090 1091 1092 +f 1093 1094 1095 +f 1096 1097 1098 +f 1099 1100 1101 +f 1102 1103 1104 +f 1105 1106 1107 +f 1108 1109 1110 +f 1111 1112 1113 +f 1114 1115 1116 +f 1117 1118 1119 +f 1120 1121 1122 +f 1123 1124 1125 +f 1126 1127 1128 +f 1129 1130 1131 +f 1132 1133 1134 +f 1135 1136 1137 +f 1138 1139 1140 +f 1141 1142 1143 +f 1144 1145 1146 +f 1147 1148 1149 +f 1150 1151 1152 +f 1153 1154 1155 +f 1156 1157 1158 +f 1159 1160 1161 +f 1162 1163 1164 +f 1165 1166 1167 +f 1168 1169 1170 +f 1171 1172 1173 +f 1174 1175 1176 +f 1177 1178 1179 +f 1180 1181 1182 +f 1183 1184 1185 +f 1186 1187 1188 +f 1189 1190 1191 +f 1192 1193 1194 +f 1195 1196 1197 +f 1198 1199 1200 +f 1201 1202 1203 +f 1204 1205 1206 +f 1207 1208 1209 +f 1210 1211 1212 +f 1213 1214 1215 +f 1216 1217 1218 +f 1219 1220 1221 +f 1222 1223 1224 +f 1225 1226 1227 +f 1228 1229 1230 +f 1231 1232 1233 +f 1234 1235 1236 +f 1237 1238 1239 +f 1240 1241 1242 +f 1243 1244 1245 +f 1246 1247 1248 +f 1249 1250 1251 +f 1252 1253 1254 +f 1255 1256 1257 +f 1258 1259 1260 +f 1261 1262 1263 +f 1264 1265 1266 +f 1267 1268 1269 +f 1270 1271 1272 +f 1273 1274 1275 +f 1276 1277 1278 +f 1279 1280 1281 +f 1282 1283 1284 +f 1285 1286 1287 +f 1288 1289 1290 +f 1291 1292 1293 +f 1294 1295 1296 +f 1297 1298 1299 +f 1300 1301 1302 +f 1303 1304 1305 +f 1306 1307 1308 +f 1309 1310 1311 +f 1312 1313 1314 +f 1315 1316 1317 +f 1318 1319 1320 +f 1321 1322 1323 +f 1324 1325 1326 +f 1327 1328 1329 +f 1330 1331 1332 +f 1333 1334 1335 +f 1336 1337 1338 +f 1339 1340 1341 +f 1342 1343 1344 +f 1345 1346 1347 +f 1348 1349 1350 +f 1351 1352 1353 +f 1354 1355 1356 +f 1357 1358 1359 +f 1360 1361 1362 +f 1363 1364 1365 +f 1366 1367 1368 +f 1369 1370 1371 +f 1372 1373 1374 +f 1375 1376 1377 +f 1378 1379 1380 +f 1381 1382 1383 +f 1384 1385 1386 +f 1387 1388 1389 +f 1390 1391 1392 +f 1393 1394 1395 +f 1396 1397 1398 +f 1399 1400 1401 +f 1402 1403 1404 +f 1405 1406 1407 +f 1408 1409 1410 +f 1411 1412 1413 +f 1414 1415 1416 +f 1417 1418 1419 +f 1420 1421 1422 +f 1423 1424 1425 +f 1426 1427 1428 +f 1429 1430 1431 +f 1432 1433 1434 +f 1435 1436 1437 +f 1438 1439 1440 +f 1441 1442 1443 +f 1444 1445 1446 +f 1447 1448 1449 +f 1450 1451 1452 +f 1453 1454 1455 +f 1456 1457 1458 +f 1459 1460 1461 +f 1462 1463 1464 +f 1465 1466 1467 +f 1468 1469 1470 +f 1471 1472 1473 +f 1474 1475 1476 +f 1477 1478 1479 +f 1480 1481 1482 +f 1483 1484 1485 +f 1486 1487 1488 +f 1489 1490 1491 +f 1492 1493 1494 +f 1495 1496 1497 +f 1498 1499 1500 +f 1501 1502 1503 +f 1504 1505 1506 +f 1507 1508 1509 +f 1510 1511 1512 +f 1513 1514 1515 +f 1516 1517 1518 +f 1519 1520 1521 +f 1522 1523 1524 +f 1525 1526 1527 +f 1528 1529 1530 +f 1531 1532 1533 +f 1534 1535 1536 +f 1537 1538 1539 +f 1540 1541 1542 +f 1543 1544 1545 +f 1546 1547 1548 +f 1549 1550 1551 +f 1552 1553 1554 +f 1555 1556 1557 +f 1558 1559 1560 +f 1561 1562 1563 +f 1564 1565 1566 +f 1567 1568 1569 +f 1570 1571 1572 +f 1573 1574 1575 +f 1576 1577 1578 +f 1579 1580 1581 +f 1582 1583 1584 +f 1585 1586 1587 +f 1588 1589 1590 +f 1591 1592 1593 +f 1594 1595 1596 +f 1597 1598 1599 +f 1600 1601 1602 +f 1603 1604 1605 +f 1606 1607 1608 +f 1609 1610 1611 +f 1612 1613 1614 +f 1615 1616 1617 +f 1618 1619 1620 +f 1621 1622 1623 +f 1624 1625 1626 +f 1627 1628 1629 +f 1630 1631 1632 +f 1633 1634 1635 +f 1636 1637 1638 +f 1639 1640 1641 +f 1642 1643 1644 +f 1645 1646 1647 +f 1648 1649 1650 +f 1651 1652 1653 +f 1654 1655 1656 +f 1657 1658 1659 +f 1660 1661 1662 +f 1663 1664 1665 +f 1666 1667 1668 +f 1669 1670 1671 +f 1672 1673 1674 +f 1675 1676 1677 +f 1678 1679 1680 +f 1681 1682 1683 +f 1684 1685 1686 +f 1687 1688 1689 +f 1690 1691 1692 +f 1693 1694 1695 +f 1696 1697 1698 +f 1699 1700 1701 +f 1702 1703 1704 +f 1705 1706 1707 +f 1708 1709 1710 +f 1711 1712 1713 +f 1714 1715 1716 +f 1717 1718 1719 +f 1720 1721 1722 +f 1723 1724 1725 +f 1726 1727 1728 +f 1729 1730 1731 +f 1732 1733 1734 +f 1735 1736 1737 +f 1738 1739 1740 +f 1741 1742 1743 +f 1744 1745 1746 +f 1747 1748 1749 +f 1750 1751 1752 +f 1753 1754 1755 +f 1756 1757 1758 +f 1759 1760 1761 +f 1762 1763 1764 +f 1765 1766 1767 +f 1768 1769 1770 +f 1771 1772 1773 +f 1774 1775 1776 +f 1777 1778 1779 +f 1780 1781 1782 +f 1783 1784 1785 +f 1786 1787 1788 +f 1789 1790 1791 +f 1792 1793 1794 +f 1795 1796 1797 +f 1798 1799 1800 +f 1801 1802 1803 +f 1804 1805 1806 +f 1807 1808 1809 +f 1810 1811 1812 +f 1813 1814 1815 +f 1816 1817 1818 +f 1819 1820 1821 +f 1822 1823 1824 +f 1825 1826 1827 +f 1828 1829 1830 +f 1831 1832 1833 +f 1834 1835 1836 +f 1837 1838 1839 +f 1840 1841 1842 +f 1843 1844 1845 +f 1846 1847 1848 +f 1849 1850 1851 +f 1852 1853 1854 +f 1855 1856 1857 +f 1858 1859 1860 +f 1861 1862 1863 +f 1864 1865 1866 +f 1867 1868 1869 +f 1870 1871 1872 +f 1873 1874 1875 +f 1876 1877 1878 +f 1879 1880 1881 +f 1882 1883 1884 +f 1885 1886 1887 +f 1888 1889 1890 +f 1891 1892 1893 +f 1894 1895 1896 +f 1897 1898 1899 +f 1900 1901 1902 +f 1903 1904 1905 +f 1906 1907 1908 +f 1909 1910 1911 +f 1912 1913 1914 +f 1915 1916 1917 +f 1918 1919 1920 +f 1921 1922 1923 +f 1924 1925 1926 +f 1927 1928 1929 +f 1930 1931 1932 +f 1933 1934 1935 +f 1936 1937 1938 +f 1939 1940 1941 +f 1942 1943 1944 +f 1945 1946 1947 +f 1948 1949 1950 +f 1951 1952 1953 +f 1954 1955 1956 +f 1957 1958 1959 +f 1960 1961 1962 +f 1963 1964 1965 +f 1966 1967 1968 +f 1969 1970 1971 +f 1972 1973 1974 +f 1975 1976 1977 +f 1978 1979 1980 +f 1981 1982 1983 +f 1984 1985 1986 +f 1987 1988 1989 +f 1990 1991 1992 +f 1993 1994 1995 +f 1996 1997 1998 +f 1999 2000 2001 +f 2002 2003 2004 +f 2005 2006 2007 +f 2008 2009 2010 +f 2011 2012 2013 +f 2014 2015 2016 +f 2017 2018 2019 +f 2020 2021 2022 +f 2023 2024 2025 +f 2026 2027 2028 +f 2029 2030 2031 +f 2032 2033 2034 +f 2035 2036 2037 +f 2038 2039 2040 +f 2041 2042 2043 +f 2044 2045 2046 +f 2047 2048 2049 +f 2050 2051 2052 +f 2053 2054 2055 +f 2056 2057 2058 +f 2059 2060 2061 +f 2062 2063 2064 +f 2065 2066 2067 +f 2068 2069 2070 +f 2071 2072 2073 +f 2074 2075 2076 +f 2077 2078 2079 +f 2080 2081 2082 +f 2083 2084 2085 +f 2086 2087 2088 +f 2089 2090 2091 +f 2092 2093 2094 +f 2095 2096 2097 +f 2098 2099 2100 +f 2101 2102 2103 +f 2104 2105 2106 +f 2107 2108 2109 +f 2110 2111 2112 +f 2113 2114 2115 +f 2116 2117 2118 +f 2119 2120 2121 +f 2122 2123 2124 +f 2125 2126 2127 +f 2128 2129 2130 +f 2131 2132 2133 +f 2134 2135 2136 +f 2137 2138 2139 +f 2140 2141 2142 +f 2143 2144 2145 +f 2146 2147 2148 +f 2149 2150 2151 +f 2152 2153 2154 +f 2155 2156 2157 +f 2158 2159 2160 +f 2161 2162 2163 +f 2164 2165 2166 +f 2167 2168 2169 +f 2170 2171 2172 +f 2173 2174 2175 +f 2176 2177 2178 +f 2179 2180 2181 +f 2182 2183 2184 +f 2185 2186 2187 +f 2188 2189 2190 +f 2191 2192 2193 +f 2194 2195 2196 +f 2197 2198 2199 +f 2200 2201 2202 +f 2203 2204 2205 +f 2206 2207 2208 +f 2209 2210 2211 +f 2212 2213 2214 +f 2215 2216 2217 +f 2218 2219 2220 +f 2221 2222 2223 +f 2224 2225 2226 +f 2227 2228 2229 +f 2230 2231 2232 +f 2233 2234 2235 +f 2236 2237 2238 +f 2239 2240 2241 +f 2242 2243 2244 +f 2245 2246 2247 +f 2248 2249 2250 +f 2251 2252 2253 +f 2254 2255 2256 +f 2257 2258 2259 +f 2260 2261 2262 +f 2263 2264 2265 +f 2266 2267 2268 +f 2269 2270 2271 +f 2272 2273 2274 +f 2275 2276 2277 +f 2278 2279 2280 +f 2281 2282 2283 +f 2284 2285 2286 +f 2287 2288 2289 +f 2290 2291 2292 +f 2293 2294 2295 +f 2296 2297 2298 +f 2299 2300 2301 +f 2302 2303 2304 +f 2305 2306 2307 +f 2308 2309 2310 +f 2311 2312 2313 +f 2314 2315 2316 +f 2317 2318 2319 +f 2320 2321 2322 +f 2323 2324 2325 +f 2326 2327 2328 +f 2329 2330 2331 +f 2332 2333 2334 +f 2335 2336 2337 +f 2338 2339 2340 +f 2341 2342 2343 +f 2344 2345 2346 +f 2347 2348 2349 +f 2350 2351 2352 +f 2353 2354 2355 +f 2356 2357 2358 +f 2359 2360 2361 +f 2362 2363 2364 +f 2365 2366 2367 +f 2368 2369 2370 +f 2371 2372 2373 +f 2374 2375 2376 +f 2377 2378 2379 +f 2380 2381 2382 +f 2383 2384 2385 +f 2386 2387 2388 +f 2389 2390 2391 +f 2392 2393 2394 +f 2395 2396 2397 +f 2398 2399 2400 +f 2401 2402 2403 +f 2404 2405 2406 +f 2407 2408 2409 +f 2410 2411 2412 +f 2413 2414 2415 +f 2416 2417 2418 +f 2419 2420 2421 +f 2422 2423 2424 +f 2425 2426 2427 +f 2428 2429 2430 +f 2431 2432 2433 +f 2434 2435 2436 +f 2437 2438 2439 +f 2440 2441 2442 +f 2443 2444 2445 +f 2446 2447 2448 +f 2449 2450 2451 +f 2452 2453 2454 +f 2455 2456 2457 +f 2458 2459 2460 +f 2461 2462 2463 +f 2464 2465 2466 +f 2467 2468 2469 +f 2470 2471 2472 +f 2473 2474 2475 +f 2476 2477 2478 +f 2479 2480 2481 +f 2482 2483 2484 +f 2485 2486 2487 +f 2488 2489 2490 +f 2491 2492 2493 +f 2494 2495 2496 +f 2497 2498 2499 +f 2500 2501 2502 +f 2503 2504 2505 +f 2506 2507 2508 +f 2509 2510 2511 +f 2512 2513 2514 +f 2515 2516 2517 +f 2518 2519 2520 +f 2521 2522 2523 +f 2524 2525 2526 +f 2527 2528 2529 +f 2530 2531 2532 +f 2533 2534 2535 +f 2536 2537 2538 +f 2539 2540 2541 +f 2542 2543 2544 +f 2545 2546 2547 +f 2548 2549 2550 +f 2551 2552 2553 +f 2554 2555 2556 +f 2557 2558 2559 +f 2560 2561 2562 +f 2563 2564 2565 +f 2566 2567 2568 +f 2569 2570 2571 +f 2572 2573 2574 +f 2575 2576 2577 +f 2578 2579 2580 +f 2581 2582 2583 +f 2584 2585 2586 +f 2587 2588 2589 +f 2590 2591 2592 +f 2593 2594 2595 +f 2596 2597 2598 +f 2599 2600 2601 +f 2602 2603 2604 +f 2605 2606 2607 +f 2608 2609 2610 +f 2611 2612 2613 +f 2614 2615 2616 +f 2617 2618 2619 +f 2620 2621 2622 +f 2623 2624 2625 +f 2626 2627 2628 +f 2629 2630 2631 +f 2632 2633 2634 +f 2635 2636 2637 +f 2638 2639 2640 +f 2641 2642 2643 +f 2644 2645 2646 +f 2647 2648 2649 +f 2650 2651 2652 diff --git a/docdoku-web-front/tests/res/part-upload.obj b/docdoku-web-front/tests/res/part-upload.obj new file mode 100644 index 0000000000..0f3c53cf31 --- /dev/null +++ b/docdoku-web-front/tests/res/part-upload.obj @@ -0,0 +1,3537 @@ +# 2652 vertices, 884 faces +v 260.312 40.203 -440.008 +v 505.639 40.2029 -412.719 +v 558.965 -9.41835 -404.09 +v 558.965 -9.41835 -404.09 +v 252.222 -54.205 -402.864 +v 260.312 40.203 -440.008 +v 21.7976 40.203 -426.108 +v 260.312 40.203 -440.008 +v 252.222 -54.205 -402.864 +v 252.222 -54.205 -402.864 +v 32.5665 -78.854 -387.298 +v 21.7976 40.203 -426.108 +v -107.623 -99.0134 -353.534 +v -359.273 -19.0616 -211.385 +v -378.533 40.2031 -238.981 +v -378.533 40.2031 -238.981 +v -233.929 40.203 -337.011 +v -107.623 -99.0134 -353.534 +v -627.402 40.2031 -27.0373 +v -378.533 40.2031 -238.981 +v -359.273 -19.0616 -211.385 +v -359.273 -19.0616 -211.385 +v -598.079 -17.5054 13.3313 +v -627.402 40.2031 -27.0373 +v -605.1 40.2031 174.866 +v -635.946 -19.0616 145.914 +v -547.166 40.2031 372.164 +v -99.0235 40.203 155.844 +v 22.0084 -55.5322 181.514 +v -25.3967 40.203 194.286 +v 246.48 40.203 170.173 +v -25.3967 40.203 194.286 +v 22.0084 -55.5322 181.514 +v 22.0084 -55.5322 181.514 +v 246.315 -79.6185 147.057 +v 246.48 40.203 170.173 +v 568.185 40.2029 165.976 +v 246.48 40.203 170.173 +v 246.315 -79.6185 147.057 +v 246.315 -79.6185 147.057 +v 542.48 -44.913 151.06 +v 568.185 40.2029 165.976 +v 762.912 40.2029 67.4993 +v 568.185 40.2029 165.976 +v 542.48 -44.913 151.06 +v 542.48 -44.913 151.06 +v 758.412 -62.8404 43.888 +v 762.912 40.2029 67.4993 +v 841.425 40.2029 35.9378 +v 762.912 40.2029 67.4993 +v 758.412 -62.8404 43.888 +v 758.412 -62.8404 43.888 +v 789.328 -72.2831 -20.678 +v 841.425 40.2029 35.9378 +v 912.514 40.2028 48.5509 +v 841.425 40.2029 35.9378 +v 789.328 -72.2831 -20.678 +v 789.328 -72.2831 -20.678 +v 918.453 -95.2934 -14.2217 +v 912.514 40.2028 48.5509 +v 252.222 -54.205 -402.864 +v 558.965 -9.41835 -404.09 +v 268.451 -169.261 -290.85 +v 32.5665 -78.854 -387.298 +v 252.222 -54.205 -402.864 +v 268.451 -169.261 -290.85 +v -598.079 -17.5054 13.3313 +v -359.273 -19.0616 -211.385 +v -321.215 -77.6915 -143.919 +v -321.215 -77.6915 -143.919 +v -537.312 -100.256 32.2996 +v -598.079 -17.5054 13.3313 +v -359.273 -19.0616 -211.385 +v -107.623 -99.0134 -353.534 +v -321.215 -77.6915 -143.919 +v 260.646 -223.493 -153.714 +v 268.451 -169.261 -290.85 +v 596.877 -226.42 -202.43 +v 596.877 -226.42 -202.43 +v 380.051 -251.531 -66.4672 +v 260.646 -223.493 -153.714 +v 39.9728 -156.039 -209.082 +v 32.5665 -78.854 -387.298 +v 268.451 -169.261 -290.85 +v 268.451 -169.261 -290.85 +v 260.646 -223.493 -153.714 +v 39.9728 -156.039 -209.082 +v -107.623 -99.0134 -353.534 +v 32.5665 -78.854 -387.298 +v 39.9728 -156.039 -209.082 +v 39.9728 -156.039 -209.082 +v -121.936 -142.274 -170.547 +v -107.623 -99.0134 -353.534 +v -537.312 -100.256 32.2996 +v -321.215 -77.6915 -143.919 +v -391.04 -139.908 41.853 +v -321.215 -77.6915 -143.919 +v -107.623 -99.0134 -353.534 +v -121.936 -142.274 -170.547 +v 39.9728 -156.039 -209.082 +v 260.646 -223.493 -153.714 +v 242.389 -203.746 -21.5963 +v 242.389 -203.746 -21.5963 +v 22.0431 -156.814 26.5261 +v 39.9728 -156.039 -209.082 +v -121.936 -142.274 -170.547 +v 39.9728 -156.039 -209.082 +v 22.0431 -156.814 26.5261 +v 22.0431 -156.814 26.5261 +v -77.3162 -125.878 32.3855 +v -121.936 -142.274 -170.547 +v -391.04 -139.908 41.853 +v -321.215 -77.6915 -143.919 +v -213.771 -97.4809 92.1041 +v -213.771 -97.4809 92.1041 +v -298.191 -103.042 127.509 +v -391.04 -139.908 41.853 +v -321.215 -77.6915 -143.919 +v -121.936 -142.274 -170.547 +v -77.3162 -125.878 32.3855 +v -77.3162 -125.878 32.3855 +v -213.771 -97.4809 92.1041 +v -321.215 -77.6915 -143.919 +v 242.389 -203.746 -21.5963 +v 380.051 -251.531 -66.4672 +v 494.4 -118.67 153.3 +v 494.4 -118.67 153.3 +v 257.239 -161.894 50.0724 +v 242.389 -203.746 -21.5963 +v 22.0431 -156.814 26.5261 +v 242.389 -203.746 -21.5963 +v 257.239 -161.894 50.0724 +v 257.239 -161.894 50.0724 +v 34.0969 -108.118 134.952 +v 22.0431 -156.814 26.5261 +v -77.3162 -125.878 32.3855 +v 22.0431 -156.814 26.5261 +v 34.0969 -108.118 134.952 +v 34.0969 -108.118 134.952 +v -50.7188 -67.4952 123.835 +v -77.3162 -125.878 32.3855 +v -298.191 -103.042 127.509 +v -213.771 -97.4809 92.1041 +v -257.987 -30.6083 139.94 +v -257.987 -30.6083 139.94 +v -314.968 -77.0878 227.647 +v -298.191 -103.042 127.509 +v -213.771 -97.4809 92.1041 +v -77.3162 -125.878 32.3855 +v -50.7188 -67.4952 123.835 +v -50.7188 -67.4952 123.835 +v -257.987 -30.6083 139.94 +v -213.771 -97.4809 92.1041 +v 246.315 -79.6185 147.057 +v 257.239 -161.894 50.0724 +v 494.4 -118.67 153.3 +v 494.4 -118.67 153.3 +v 542.48 -44.913 151.06 +v 246.315 -79.6185 147.057 +v 34.0969 -108.118 134.952 +v 257.239 -161.894 50.0724 +v 246.315 -79.6185 147.057 +v 246.315 -79.6185 147.057 +v 22.0084 -55.5322 181.514 +v 34.0969 -108.118 134.952 +v -50.7188 -67.4952 123.835 +v 34.0969 -108.118 134.952 +v 22.0084 -55.5322 181.514 +v 22.0084 -55.5322 181.514 +v -99.0235 40.203 155.844 +v -50.7188 -67.4952 123.835 +v -314.968 -77.0878 227.647 +v -257.987 -30.6083 139.94 +v -260.462 40.203 162.299 +v -260.462 40.203 162.299 +v -284.581 40.203 304.684 +v -314.968 -77.0878 227.647 +v -257.987 -30.6083 139.94 +v -50.7188 -67.4952 123.835 +v -99.0235 40.203 155.844 +v -99.0235 40.203 155.844 +v -260.462 40.203 162.299 +v -257.987 -30.6083 139.94 +v 558.965 -9.41835 -404.09 +v 676.056 -10.6067 -403.457 +v 705.303 -138.614 -221.823 +v 729.558 -244.167 -136.921 +v 846.834 -109.506 -138.578 +v 789.328 -72.2831 -20.678 +v -635.946 -19.0616 145.914 +v -598.079 -17.5054 13.3313 +v -537.312 -100.256 32.2996 +v -537.312 -100.256 32.2996 +v -615.112 -86.8028 174.866 +v -635.946 -19.0616 145.914 +v -547.166 40.2031 372.164 +v -635.946 -19.0616 145.914 +v -615.112 -86.8028 174.866 +v -615.112 -86.8028 174.866 +v -550.064 -44.9519 307.245 +v -547.166 40.2031 372.164 +v -284.581 40.203 304.684 +v -547.166 40.2031 372.164 +v -550.064 -44.9519 307.245 +v -550.064 -44.9519 307.245 +v -314.968 -77.0878 227.647 +v -284.581 40.203 304.684 +v -537.312 -100.256 32.2996 +v -391.04 -139.908 41.853 +v -281.314 -243.634 -48.6181 +v -281.314 -243.634 -48.6181 +v -410.356 -316.705 -17.6217 +v -537.312 -100.256 32.2996 +v -391.04 -139.908 41.853 +v -298.191 -103.042 127.509 +v -169.529 -145.953 141.817 +v -169.529 -145.953 141.817 +v -281.314 -243.634 -48.6181 +v -391.04 -139.908 41.853 +v -298.191 -103.042 127.509 +v -314.968 -77.0878 227.647 +v -255.603 -138.915 227.647 +v -255.603 -138.915 227.647 +v -169.529 -145.953 141.817 +v -298.191 -103.042 127.509 +v -615.112 -86.8028 174.866 +v -537.312 -100.256 32.2996 +v -410.356 -316.705 -17.6217 +v -410.356 -316.705 -17.6217 +v -465.797 -326.302 178.963 +v -615.112 -86.8028 174.866 +v -550.064 -44.9519 307.245 +v -615.112 -86.8028 174.866 +v -465.797 -326.302 178.963 +v -465.797 -326.302 178.963 +v -440.269 -242.311 338.364 +v -550.064 -44.9519 307.245 +v -314.968 -77.0878 227.647 +v -550.064 -44.9519 307.245 +v -440.269 -242.311 338.364 +v -440.269 -242.311 338.364 +v -255.603 -138.915 227.647 +v -314.968 -77.0878 227.647 +v -410.356 -316.705 -17.6217 +v -281.314 -243.634 -48.6181 +v 14.2079 -335.227 -45.2649 +v 14.2079 -335.227 -45.2649 +v -24.9719 -444.533 -3.8576 +v -410.356 -316.705 -17.6217 +v -281.314 -243.634 -48.6181 +v -169.529 -145.953 141.817 +v 32.0551 -247.885 -11.6046 +v 32.0551 -247.885 -11.6046 +v 14.2079 -335.227 -45.2649 +v -281.314 -243.634 -48.6181 +v -169.529 -145.953 141.817 +v -255.603 -138.915 227.647 +v -15.0917 -242.667 60.2354 +v -15.0917 -242.667 60.2354 +v 32.0551 -247.885 -11.6046 +v -169.529 -145.953 141.817 +v -465.797 -326.302 178.963 +v -410.356 -316.705 -17.6217 +v -24.9719 -444.533 -3.8576 +v -24.9719 -444.533 -3.8576 +v -103.65 -447.284 112.409 +v -465.797 -326.302 178.963 +v -440.269 -242.311 338.364 +v -465.797 -326.302 178.963 +v -103.65 -447.284 112.409 +v -103.65 -447.284 112.409 +v -132.463 -330.935 170.696 +v -440.269 -242.311 338.364 +v -255.603 -138.915 227.647 +v -440.269 -242.311 338.364 +v -132.463 -330.935 170.696 +v -132.463 -330.935 170.696 +v -15.0917 -242.667 60.2354 +v -255.603 -138.915 227.647 +v -24.9719 -444.533 -3.8576 +v 14.2079 -335.227 -45.2649 +v 154.274 -422.936 15.1391 +v 154.274 -422.936 15.1391 +v 65.7941 -437.565 48.1499 +v -24.9719 -444.533 -3.8576 +v 14.2079 -335.227 -45.2649 +v 32.0551 -247.885 -11.6046 +v 160.1 -339.077 42.0847 +v 160.1 -339.077 42.0847 +v 154.274 -422.936 15.1391 +v 14.2079 -335.227 -45.2649 +v 32.0551 -247.885 -11.6046 +v -15.0917 -242.667 60.2354 +v 65.6433 -310.298 103.599 +v 65.6433 -310.298 103.599 +v 160.1 -339.077 42.0847 +v 32.0551 -247.885 -11.6046 +v -103.65 -447.284 112.409 +v -24.9719 -444.533 -3.8576 +v 65.7941 -437.565 48.1499 +v 65.7941 -437.565 48.1499 +v -52.7382 -378.505 120.825 +v -103.65 -447.284 112.409 +v -132.463 -330.935 170.696 +v -103.65 -447.284 112.409 +v -52.7382 -378.505 120.825 +v -52.7382 -378.505 120.825 +v -86.6906 -345.821 159.574 +v -132.463 -330.935 170.696 +v -15.0917 -242.667 60.2354 +v -132.463 -330.935 170.696 +v -86.6906 -345.821 159.574 +v -86.6906 -345.821 159.574 +v 65.6433 -310.298 103.599 +v -15.0917 -242.667 60.2354 +v 65.7941 -437.565 48.1499 +v 154.274 -422.936 15.1391 +v 143.503 -427.832 162.674 +v 143.503 -427.832 162.674 +v 76.3055 -449.154 158.966 +v 65.7941 -437.565 48.1499 +v 154.274 -422.936 15.1391 +v 160.1 -339.077 42.0847 +v 140.897 -341.135 179.078 +v 140.897 -341.135 179.078 +v 143.503 -427.832 162.674 +v 154.274 -422.936 15.1391 +v 160.1 -339.077 42.0847 +v 65.6433 -310.298 103.599 +v 61.6315 -308.948 188.674 +v 61.6315 -308.948 188.674 +v 140.897 -341.135 179.078 +v 160.1 -339.077 42.0847 +v -52.7382 -378.505 120.825 +v 65.7941 -437.565 48.1499 +v 76.3055 -449.154 158.966 +v 76.3055 -449.154 158.966 +v -56.9874 -407.19 172.668 +v -52.7382 -378.505 120.825 +v -86.6906 -345.821 159.574 +v -52.7382 -378.505 120.825 +v -56.9874 -407.19 172.668 +v -56.9874 -407.19 172.668 +v -101.405 -340.868 187.717 +v -86.6906 -345.821 159.574 +v 65.6433 -310.298 103.599 +v -86.6906 -345.821 159.574 +v -101.405 -340.868 187.717 +v -101.405 -340.868 187.717 +v 61.6315 -308.948 188.674 +v 65.6433 -310.298 103.599 +v 76.3055 -449.154 158.966 +v 143.503 -427.832 162.674 +v -57.088 -363.103 388.882 +v -57.088 -363.103 388.882 +v -86.187 -376.695 363.046 +v 76.3055 -449.154 158.966 +v 143.503 -427.832 162.674 +v 140.897 -341.135 179.078 +v -61.2255 -234.579 389.398 +v -61.2255 -234.579 389.398 +v -57.088 -363.103 388.882 +v 143.503 -427.832 162.674 +v 140.897 -341.135 179.078 +v 61.6315 -308.948 188.674 +v -121.906 -216.383 347.524 +v -121.906 -216.383 347.524 +v -61.2255 -234.579 389.398 +v 140.897 -341.135 179.078 +v -56.9874 -407.19 172.668 +v 76.3055 -449.154 158.966 +v -86.187 -376.695 363.046 +v -86.187 -376.695 363.046 +v -171.947 -353.317 301.741 +v -56.9874 -407.19 172.668 +v -101.405 -340.868 187.717 +v -56.9874 -407.19 172.668 +v -171.947 -353.317 301.741 +v -171.947 -353.317 301.741 +v -194.818 -237.297 287.062 +v -101.405 -340.868 187.717 +v 61.6315 -308.948 188.674 +v -101.405 -340.868 187.717 +v -194.818 -237.297 287.062 +v -194.818 -237.297 287.062 +v -121.906 -216.383 347.524 +v 61.6315 -308.948 188.674 +v -86.187 -376.695 363.046 +v -57.088 -363.103 388.882 +v -102.277 -349.579 417.599 +v -102.277 -349.579 417.599 +v -120.57 -363.511 397.079 +v -86.187 -376.695 363.046 +v -57.088 -363.103 388.882 +v -61.2255 -234.579 389.398 +v -101.46 -226.311 419.247 +v -101.46 -226.311 419.247 +v -102.277 -349.579 417.599 +v -57.088 -363.103 388.882 +v -61.2255 -234.579 389.398 +v -121.906 -216.383 347.524 +v -150.824 -214.018 384.223 +v -150.824 -214.018 384.223 +v -101.46 -226.311 419.247 +v -61.2255 -234.579 389.398 +v -171.947 -353.317 301.741 +v -86.187 -376.695 363.046 +v -120.57 -363.511 397.079 +v -120.57 -363.511 397.079 +v -209.07 -348.909 327.758 +v -171.947 -353.317 301.741 +v -194.818 -237.297 287.062 +v -171.947 -353.317 301.741 +v -209.07 -348.909 327.758 +v -209.07 -348.909 327.758 +v -220.374 -237.75 317.345 +v -194.818 -237.297 287.062 +v -121.906 -216.383 347.524 +v -194.818 -237.297 287.062 +v -220.374 -237.75 317.345 +v -220.374 -237.75 317.345 +v -150.824 -214.018 384.223 +v -121.906 -216.383 347.524 +v -120.57 -363.511 397.079 +v -102.277 -349.579 417.599 +v -216.691 -299.362 512.471 +v -216.691 -299.362 512.471 +v -279.207 -318.598 477.521 +v -120.57 -363.511 397.079 +v -102.277 -349.579 417.599 +v -101.46 -226.311 419.247 +v -220.533 -242.736 524.241 +v -220.533 -242.736 524.241 +v -216.691 -299.362 512.471 +v -102.277 -349.579 417.599 +v -101.46 -226.311 419.247 +v -150.824 -214.018 384.223 +v -257.957 -155.418 505.428 +v -257.957 -155.418 505.428 +v -220.533 -242.736 524.241 +v -101.46 -226.311 419.247 +v -209.07 -348.909 327.758 +v -120.57 -363.511 397.079 +v -279.207 -318.598 477.521 +v -279.207 -318.598 477.521 +v -372.739 -259.441 407.375 +v -209.07 -348.909 327.758 +v -220.374 -237.75 317.345 +v -209.07 -348.909 327.758 +v -372.739 -259.441 407.375 +v -372.739 -259.441 407.375 +v -394.838 -179.247 404.628 +v -220.374 -237.75 317.345 +v -150.824 -214.018 384.223 +v -220.374 -237.75 317.345 +v -394.838 -179.247 404.628 +v -394.838 -179.247 404.628 +v -257.957 -155.418 505.428 +v -150.824 -214.018 384.223 +v -279.207 -318.598 477.521 +v -216.691 -299.362 512.471 +v -236.316 -297.066 568.835 +v -236.316 -297.066 568.835 +v -317.133 -381.502 577.263 +v -279.207 -318.598 477.521 +v -216.691 -299.362 512.471 +v -220.533 -242.736 524.241 +v -240.158 -240.44 580.604 +v -240.158 -240.44 580.604 +v -236.316 -297.066 568.835 +v -216.691 -299.362 512.471 +v -220.533 -242.736 524.241 +v -257.957 -155.418 505.428 +v -231.113 -142.03 578.573 +v -231.113 -142.03 578.573 +v -240.158 -240.44 580.604 +v -220.533 -242.736 524.241 +v -372.739 -259.441 407.375 +v -279.207 -318.598 477.521 +v -317.133 -381.502 577.263 +v -317.133 -381.502 577.263 +v -521.043 -278.287 466.938 +v -372.739 -259.441 407.375 +v -394.838 -179.247 404.628 +v -372.739 -259.441 407.375 +v -521.043 -278.287 466.938 +v -521.043 -278.287 466.938 +v -543.141 -146.696 464.192 +v -394.838 -179.247 404.628 +v -257.957 -155.418 505.428 +v -394.838 -179.247 404.628 +v -543.141 -146.696 464.192 +v -543.141 -146.696 464.192 +v -231.113 -142.03 578.573 +v -257.957 -155.418 505.428 +v -317.133 -381.502 577.263 +v -236.316 -297.066 568.835 +v -143.793 -387.554 589.922 +v -143.793 -387.554 589.922 +v -180.616 -450.221 583.426 +v -317.133 -381.502 577.263 +v -236.316 -297.066 568.835 +v -240.158 -240.44 580.604 +v -123.358 -345.738 615.393 +v -123.358 -345.738 615.393 +v -143.793 -387.554 589.922 +v -236.316 -297.066 568.835 +v -240.158 -240.44 580.604 +v -231.113 -142.03 578.573 +v -97.4354 -286.208 662.435 +v -97.4354 -286.208 662.435 +v -123.358 -345.738 615.393 +v -240.158 -240.44 580.604 +v -521.043 -278.287 466.938 +v -317.133 -381.502 577.263 +v -384.975 -438.572 704.341 +v -384.975 -438.572 704.341 +v -493.77 -277.971 624.724 +v -521.043 -278.287 466.938 +v -543.141 -146.696 464.192 +v -521.043 -278.287 466.938 +v -493.77 -277.971 624.724 +v -493.77 -277.971 624.724 +v -513.328 -198.074 614.68 +v -543.141 -146.696 464.192 +v -231.113 -142.03 578.573 +v -543.141 -146.696 464.192 +v -513.328 -198.074 614.68 +v -513.328 -198.074 614.68 +v -308.634 -169.787 722.901 +v -231.113 -142.03 578.573 +v -317.133 -381.502 577.263 +v -180.616 -450.221 583.426 +v -207.244 -454.329 671.558 +v -207.244 -454.329 671.558 +v -384.975 -438.572 704.341 +v -317.133 -381.502 577.263 +v -97.4354 -286.208 662.435 +v -231.113 -142.03 578.573 +v -308.634 -169.787 722.901 +v -308.634 -169.787 722.901 +v -117.005 -283.655 756.407 +v -97.4354 -286.208 662.435 +v -493.77 -277.971 624.724 +v -384.975 -438.572 704.341 +v -351.878 -267.828 729.182 +v -351.878 -267.828 729.182 +v -470.519 -230.036 648.255 +v -493.77 -277.971 624.724 +v -513.328 -198.074 614.68 +v -493.77 -277.971 624.724 +v -470.519 -230.036 648.255 +v -308.634 -169.787 722.901 +v -513.328 -198.074 614.68 +v -470.519 -230.036 648.255 +v -470.519 -230.036 648.255 +v -351.878 -267.828 729.182 +v -308.634 -169.787 722.901 +v -384.975 -438.572 704.341 +v -207.244 -454.329 671.558 +v -158.686 -363.501 717.111 +v -158.686 -363.501 717.111 +v -351.878 -267.828 729.182 +v -384.975 -438.572 704.341 +v -117.005 -283.655 756.407 +v -308.634 -169.787 722.901 +v -351.878 -267.828 729.182 +v -351.878 -267.828 729.182 +v -158.686 -363.501 717.111 +v -117.005 -283.655 756.407 +v -180.616 -450.221 583.426 +v -143.793 -387.554 589.922 +v -66.3276 -504.127 711.51 +v -123.358 -345.738 615.393 +v -97.4354 -286.208 662.435 +v -9.81772 -392.599 748.063 +v -207.244 -454.329 671.558 +v -180.616 -450.221 583.426 +v -66.3276 -504.127 711.51 +v -97.4354 -286.208 662.435 +v -117.005 -283.655 756.407 +v -9.81772 -392.599 748.063 +v -158.686 -363.501 717.111 +v -207.244 -454.329 671.558 +v -66.3276 -504.127 711.51 +v -117.005 -283.655 756.407 +v -158.686 -363.501 717.111 +v -9.81772 -392.599 748.063 +v -143.793 -387.554 589.922 +v -123.358 -345.738 615.393 +v -158.686 -363.501 717.111 +v -123.358 -345.738 615.393 +v -9.81772 -392.599 748.063 +v -158.686 -363.501 717.111 +v -66.3276 -504.127 711.51 +v -143.793 -387.554 589.922 +v -158.686 -363.501 717.111 +v 676.056 -10.6067 -403.457 +v 679.933 40.2029 -441.705 +v 824.669 40.2029 -424.477 +v 824.669 40.2029 -424.477 +v 843.785 -71.3395 -368.243 +v 676.056 -10.6067 -403.457 +v 843.785 -71.3395 -368.243 +v 824.669 40.2029 -424.477 +v 878.609 40.2028 -424.149 +v 878.609 40.2028 -424.149 +v 885.246 -63.9092 -351.891 +v 843.785 -71.3395 -368.243 +v 885.246 -63.9092 -351.891 +v 878.609 40.2028 -424.149 +v 909.031 40.2028 -425.978 +v 909.031 40.2028 -425.978 +v 922.953 -39.6783 -375.782 +v 885.246 -63.9092 -351.891 +v 922.953 -39.6783 -375.782 +v 909.031 40.2028 -425.978 +v 968.589 40.2028 -404.236 +v 968.589 40.2028 -404.236 +v 960.668 2.14378 -383.161 +v 922.953 -39.6783 -375.782 +v 960.668 2.14378 -383.161 +v 968.589 40.2028 -404.236 +v 1061.35 40.2028 -356.762 +v 1061.35 40.2028 -356.762 +v 1053.7 -18.2369 -346.758 +v 960.668 2.14378 -383.161 +v 1053.7 -18.2369 -346.758 +v 1061.35 40.2028 -356.762 +v 1119.22 40.2028 -284.353 +v 1119.22 40.2028 -284.353 +v 1117.05 -14.6657 -272.841 +v 1053.7 -18.2369 -346.758 +v 1117.05 -14.6657 -272.841 +v 1119.22 40.2028 -284.353 +v 1152.01 40.2028 -217.427 +v 1152.01 40.2028 -217.427 +v 1130.63 -9.82445 -219.113 +v 1117.05 -14.6657 -272.841 +v 1130.63 -9.82445 -219.113 +v 1152.01 40.2028 -217.427 +v 1154.32 40.2028 -99.7808 +v 1154.32 40.2028 -99.7808 +v 1120.75 -32.3903 -128.277 +v 1130.63 -9.82445 -219.113 +v 1120.75 -32.3903 -128.277 +v 1154.32 40.2028 -99.7808 +v 1150.35 40.2028 -66.9116 +v 1150.35 40.2028 -66.9116 +v 1077.52 -88.1132 -75.2012 +v 1120.75 -32.3903 -128.277 +v 972.648 -63.5674 25.3677 +v 984.155 40.2028 76.1361 +v 948.344 40.2028 68.9338 +v 972.648 -63.5674 25.3677 +v 948.344 40.2028 68.9338 +v 912.514 40.2028 48.5509 +v 912.514 40.2028 48.5509 +v 918.453 -95.2934 -14.2217 +v 972.648 -63.5674 25.3677 +v 758.412 -62.8404 43.888 +v 542.48 -44.913 151.06 +v 682.576 -133.836 164.696 +v 682.576 -133.836 164.696 +v 766.139 -190.462 -12.7916 +v 758.412 -62.8404 43.888 +v 789.328 -72.2831 -20.678 +v 758.412 -62.8404 43.888 +v 766.139 -190.462 -12.7916 +v 766.139 -190.462 -12.7916 +v 729.558 -244.167 -136.921 +v 789.328 -72.2831 -20.678 +v 380.051 -251.531 -66.4672 +v 545.802 -338.023 -93.8417 +v 415.539 -273.108 12.9001 +v 380.051 -251.531 -66.4672 +v 415.539 -273.108 12.9001 +v 494.4 -118.67 153.3 +v 542.48 -44.913 151.06 +v 494.4 -118.67 153.3 +v 682.576 -133.836 164.696 +v 705.303 -138.614 -221.823 +v 846.834 -109.506 -138.578 +v 729.558 -244.167 -136.921 +v 596.877 -226.42 -202.43 +v 729.558 -244.167 -136.921 +v 690.121 -324.877 -50.8344 +v 690.121 -324.877 -50.8344 +v 545.802 -338.023 -93.8417 +v 596.877 -226.42 -202.43 +v 729.558 -244.167 -136.921 +v 766.139 -190.462 -12.7916 +v 764.322 -249.3 -4.0724 +v 729.558 -244.167 -136.921 +v 764.322 -249.3 -4.0724 +v 690.121 -324.877 -50.8344 +v 545.802 -338.023 -93.8417 +v 690.121 -324.877 -50.8344 +v 643.124 -384.905 35.5904 +v 643.124 -384.905 35.5904 +v 518.621 -389.245 -11.2156 +v 545.802 -338.023 -93.8417 +v 764.322 -249.3 -4.0724 +v 766.139 -190.462 -12.7916 +v 682.576 -133.836 164.696 +v 682.576 -133.836 164.696 +v 715.597 -287.239 118.766 +v 764.322 -249.3 -4.0724 +v 545.802 -338.023 -93.8417 +v 518.621 -389.245 -11.2156 +v 415.539 -273.108 12.9001 +v 690.121 -324.877 -50.8344 +v 764.322 -249.3 -4.0724 +v 715.597 -287.239 118.766 +v 715.597 -287.239 118.766 +v 643.124 -384.905 35.5904 +v 690.121 -324.877 -50.8344 +v 494.4 -118.67 153.3 +v 415.539 -273.108 12.9001 +v 400.659 -292.368 56.5926 +v 400.659 -292.368 56.5926 +v 495.985 -186.11 152.255 +v 494.4 -118.67 153.3 +v 682.576 -133.836 164.696 +v 494.4 -118.67 153.3 +v 495.985 -186.11 152.255 +v 495.985 -186.11 152.255 +v 623.367 -177.233 203.09 +v 682.576 -133.836 164.696 +v 518.621 -389.245 -11.2156 +v 643.124 -384.905 35.5904 +v 601.471 -400.28 109.919 +v 601.471 -400.28 109.919 +v 488.245 -391.496 48.2971 +v 518.621 -389.245 -11.2156 +v 715.597 -287.239 118.766 +v 682.576 -133.836 164.696 +v 623.367 -177.233 203.09 +v 623.367 -177.233 203.09 +v 651.012 -315.403 204.493 +v 715.597 -287.239 118.766 +v 415.539 -273.108 12.9001 +v 518.621 -389.245 -11.2156 +v 488.245 -391.496 48.2971 +v 488.245 -391.496 48.2971 +v 400.659 -292.368 56.5926 +v 415.539 -273.108 12.9001 +v 643.124 -384.905 35.5904 +v 715.597 -287.239 118.766 +v 651.012 -315.403 204.493 +v 651.012 -315.403 204.493 +v 601.471 -400.28 109.919 +v 643.124 -384.905 35.5904 +v 495.985 -186.11 152.255 +v 400.659 -292.368 56.5926 +v 376.155 -367.745 167.649 +v 376.155 -367.745 167.649 +v 449.267 -227.751 241.792 +v 495.985 -186.11 152.255 +v 623.367 -177.233 203.09 +v 495.985 -186.11 152.255 +v 449.267 -227.751 241.792 +v 449.267 -227.751 241.792 +v 595.313 -259.375 310.175 +v 623.367 -177.233 203.09 +v 488.245 -391.496 48.2971 +v 601.471 -400.28 109.919 +v 507.031 -409.177 228.749 +v 507.031 -409.177 228.749 +v 438.002 -434.285 183.939 +v 488.245 -391.496 48.2971 +v 651.012 -315.403 204.493 +v 623.367 -177.233 203.09 +v 595.313 -259.375 310.175 +v 595.313 -259.375 310.175 +v 588.146 -375.276 278.435 +v 651.012 -315.403 204.493 +v 400.659 -292.368 56.5926 +v 488.245 -391.496 48.2971 +v 438.002 -434.285 183.939 +v 438.002 -434.285 183.939 +v 376.155 -367.745 167.649 +v 400.659 -292.368 56.5926 +v 601.471 -400.28 109.919 +v 651.012 -315.403 204.493 +v 588.146 -375.276 278.435 +v 588.146 -375.276 278.435 +v 507.031 -409.177 228.749 +v 601.471 -400.28 109.919 +v 449.267 -227.751 241.792 +v 376.155 -367.745 167.649 +v 280.893 -407.647 219.652 +v 280.893 -407.647 219.652 +v 319.396 -255.764 310.744 +v 449.267 -227.751 241.792 +v 449.267 -227.751 241.792 +v 319.396 -255.764 310.744 +v 467.024 -290.574 349.519 +v 467.024 -290.574 349.519 +v 595.313 -259.375 310.175 +v 449.267 -227.751 241.792 +v 438.002 -434.285 183.939 +v 507.031 -409.177 228.749 +v 332.802 -479.854 294.654 +v 332.802 -479.854 294.654 +v 289.487 -450.472 226.311 +v 438.002 -434.285 183.939 +v 588.146 -375.276 278.435 +v 595.313 -259.375 310.175 +v 467.024 -290.574 349.519 +v 467.024 -290.574 349.519 +v 467.468 -451.105 347.786 +v 588.146 -375.276 278.435 +v 376.155 -367.745 167.649 +v 438.002 -434.285 183.939 +v 289.487 -450.472 226.311 +v 289.487 -450.472 226.311 +v 280.893 -407.647 219.652 +v 376.155 -367.745 167.649 +v 507.031 -409.177 228.749 +v 588.146 -375.276 278.435 +v 467.468 -451.105 347.786 +v 467.468 -451.105 347.786 +v 332.802 -479.854 294.654 +v 507.031 -409.177 228.749 +v 280.893 -407.647 219.652 +v 209.876 -408.128 275.697 +v 319.396 -255.764 310.744 +v 289.487 -450.472 226.311 +v 332.802 -479.854 294.654 +v 252.694 -465.87 283.647 +v 280.893 -407.647 219.652 +v 289.487 -450.472 226.311 +v 252.694 -465.87 283.647 +v 252.694 -465.87 283.647 +v 209.876 -408.128 275.697 +v 280.893 -407.647 219.652 +v 209.876 -408.128 275.697 +v 215.546 -402.806 359.667 +v 319.396 -255.764 310.744 +v 252.694 -465.87 283.647 +v 332.802 -479.854 294.654 +v 257.069 -452.122 363.088 +v 209.876 -408.128 275.697 +v 252.694 -465.87 283.647 +v 257.069 -452.122 363.088 +v 257.069 -452.122 363.088 +v 215.546 -402.806 359.667 +v 209.876 -408.128 275.697 +v 319.396 -255.764 310.744 +v 215.546 -402.806 359.667 +v 279.28 -306.382 455.978 +v 279.28 -306.382 455.978 +v 337.988 -252.446 457.825 +v 319.396 -255.764 310.744 +v 467.024 -290.574 349.519 +v 319.396 -255.764 310.744 +v 337.988 -252.446 457.825 +v 337.988 -252.446 457.825 +v 441.552 -285.479 435.883 +v 467.024 -290.574 349.519 +v 257.069 -452.122 363.088 +v 332.802 -479.854 294.654 +v 352.053 -402.807 456.275 +v 352.053 -402.807 456.275 +v 265.056 -381.514 466.161 +v 257.069 -452.122 363.088 +v 467.468 -451.105 347.786 +v 467.024 -290.574 349.519 +v 441.552 -285.479 435.883 +v 441.552 -285.479 435.883 +v 441.455 -368.508 445.446 +v 467.468 -451.105 347.786 +v 215.546 -402.806 359.667 +v 257.069 -452.122 363.088 +v 265.056 -381.514 466.161 +v 265.056 -381.514 466.161 +v 279.28 -306.382 455.978 +v 215.546 -402.806 359.667 +v 332.802 -479.854 294.654 +v 467.468 -451.105 347.786 +v 441.455 -368.508 445.446 +v 441.455 -368.508 445.446 +v 352.053 -402.807 456.275 +v 332.802 -479.854 294.654 +v 337.988 -252.446 457.825 +v 279.28 -306.382 455.978 +v 272.997 -296.869 559.478 +v 272.997 -296.869 559.478 +v 383.441 -229.992 553.769 +v 337.988 -252.446 457.825 +v 441.552 -285.479 435.883 +v 337.988 -252.446 457.825 +v 383.441 -229.992 553.769 +v 383.441 -229.992 553.769 +v 466.393 -250.784 551.819 +v 441.552 -285.479 435.883 +v 265.056 -381.514 466.161 +v 352.053 -402.807 456.275 +v 390.756 -398.519 555.537 +v 390.756 -398.519 555.537 +v 264.043 -387.196 561.999 +v 265.056 -381.514 466.161 +v 441.455 -368.508 445.446 +v 441.552 -285.479 435.883 +v 466.393 -250.784 551.819 +v 466.393 -250.784 551.819 +v 464.616 -343.033 556.584 +v 441.455 -368.508 445.446 +v 279.28 -306.382 455.978 +v 265.056 -381.514 466.161 +v 264.043 -387.196 561.999 +v 264.043 -387.196 561.999 +v 272.997 -296.869 559.478 +v 279.28 -306.382 455.978 +v 352.053 -402.807 456.275 +v 441.455 -368.508 445.446 +v 464.616 -343.033 556.584 +v 464.616 -343.033 556.584 +v 390.756 -398.519 555.537 +v 352.053 -402.807 456.275 +v 383.441 -229.992 553.769 +v 272.997 -296.869 559.478 +v 271.471 -297.372 655.645 +v 271.471 -297.372 655.645 +v 377.032 -230.241 640.682 +v 383.441 -229.992 553.769 +v 466.393 -250.784 551.819 +v 383.441 -229.992 553.769 +v 377.032 -230.241 640.682 +v 377.032 -230.241 640.682 +v 460.045 -254.409 626.719 +v 466.393 -250.784 551.819 +v 264.043 -387.196 561.999 +v 390.756 -398.519 555.537 +v 418.604 -400.968 630.348 +v 418.604 -400.968 630.348 +v 293.651 -378.092 667.416 +v 264.043 -387.196 561.999 +v 464.616 -343.033 556.584 +v 466.393 -250.784 551.819 +v 460.045 -254.409 626.719 +v 460.045 -254.409 626.719 +v 484.703 -326.296 631.39 +v 464.616 -343.033 556.584 +v 272.997 -296.869 559.478 +v 264.043 -387.196 561.999 +v 293.651 -378.092 667.416 +v 293.651 -378.092 667.416 +v 271.471 -297.372 655.645 +v 272.997 -296.869 559.478 +v 390.756 -398.519 555.537 +v 464.616 -343.033 556.584 +v 484.703 -326.296 631.39 +v 484.703 -326.296 631.39 +v 418.604 -400.968 630.348 +v 390.756 -398.519 555.537 +v 377.032 -230.241 640.682 +v 271.471 -297.372 655.645 +v 304.327 -301.295 790.592 +v 304.327 -301.295 790.592 +v 340.152 -202.604 825.111 +v 377.032 -230.241 640.682 +v 460.045 -254.409 626.719 +v 377.032 -230.241 640.682 +v 340.152 -202.604 825.111 +v 340.152 -202.604 825.111 +v 451.236 -235.762 688.596 +v 460.045 -254.409 626.719 +v 293.651 -378.092 667.416 +v 418.604 -400.968 630.348 +v 433.476 -415.899 693.155 +v 433.476 -415.899 693.155 +v 317.335 -377.895 787.405 +v 293.651 -378.092 667.416 +v 484.703 -326.296 631.39 +v 460.045 -254.409 626.719 +v 451.236 -235.762 688.596 +v 451.236 -235.762 688.596 +v 490.612 -314.281 692.953 +v 484.703 -326.296 631.39 +v 271.471 -297.372 655.645 +v 293.651 -378.092 667.416 +v 317.335 -377.895 787.405 +v 317.335 -377.895 787.405 +v 304.327 -301.295 790.592 +v 271.471 -297.372 655.645 +v 418.604 -400.968 630.348 +v 484.703 -326.296 631.39 +v 490.612 -314.281 692.953 +v 490.612 -314.281 692.953 +v 433.476 -415.899 693.155 +v 418.604 -400.968 630.348 +v 340.152 -202.604 825.111 +v 304.327 -301.295 790.592 +v 580.055 -298.385 836.628 +v 580.055 -298.385 836.628 +v 499.471 -177.331 820.408 +v 340.152 -202.604 825.111 +v 451.236 -235.762 688.596 +v 340.152 -202.604 825.111 +v 499.471 -177.331 820.408 +v 499.471 -177.331 820.408 +v 527.033 -193.537 790.548 +v 451.236 -235.762 688.596 +v 317.335 -377.895 787.405 +v 433.476 -415.899 693.155 +v 507.467 -413.427 768.062 +v 507.467 -413.427 768.062 +v 486.543 -438.858 861.734 +v 317.335 -377.895 787.405 +v 490.612 -314.281 692.953 +v 451.236 -235.762 688.596 +v 527.033 -193.537 790.548 +v 527.033 -193.537 790.548 +v 577.257 -297.345 761.531 +v 490.612 -314.281 692.953 +v 304.327 -301.295 790.592 +v 317.335 -377.895 787.405 +v 486.543 -438.858 861.734 +v 486.543 -438.858 861.734 +v 580.055 -298.385 836.628 +v 304.327 -301.295 790.592 +v 433.476 -415.899 693.155 +v 490.612 -314.281 692.953 +v 577.257 -297.345 761.531 +v 577.257 -297.345 761.531 +v 507.467 -413.427 768.062 +v 433.476 -415.899 693.155 +v 486.543 -438.858 861.734 +v 507.467 -413.427 768.062 +v 644.719 -436.229 823.452 +v 644.719 -436.229 823.452 +v 626.437 -452.827 888.782 +v 486.543 -438.858 861.734 +v 580.055 -298.385 836.628 +v 486.543 -438.858 861.734 +v 626.437 -452.827 888.782 +v 626.437 -452.827 888.782 +v 627.235 -359.336 885.962 +v 580.055 -298.385 836.628 +v 507.467 -413.427 768.062 +v 577.257 -297.345 761.531 +v 645.052 -359.418 822.685 +v 645.052 -359.418 822.685 +v 644.719 -436.229 823.452 +v 507.467 -413.427 768.062 +v 499.471 -177.331 820.408 +v 580.055 -298.385 836.628 +v 625.983 -264.872 889.066 +v 625.983 -264.872 889.066 +v 626.526 -181.518 888.681 +v 499.471 -177.331 820.408 +v 527.033 -193.537 790.548 +v 499.471 -177.331 820.408 +v 626.526 -181.518 888.681 +v 626.526 -181.518 888.681 +v 645.299 -197.725 822.477 +v 527.033 -193.537 790.548 +v 577.257 -297.345 761.531 +v 527.033 -193.537 790.548 +v 645.299 -197.725 822.477 +v 645.299 -197.725 822.477 +v 645.229 -252.173 822.897 +v 577.257 -297.345 761.531 +v 645.052 -359.418 822.685 +v 577.257 -297.345 761.531 +v 580.055 -298.385 836.628 +v 580.055 -298.385 836.628 +v 627.235 -359.336 885.962 +v 645.052 -359.418 822.685 +v 577.257 -297.345 761.531 +v 645.229 -252.173 822.897 +v 625.983 -264.872 889.066 +v 625.983 -264.872 889.066 +v 580.055 -298.385 836.628 +v 577.257 -297.345 761.531 +v 626.437 -452.827 888.782 +v 644.719 -436.229 823.452 +v 695.541 -379.792 913.328 +v 627.235 -359.336 885.962 +v 626.437 -452.827 888.782 +v 695.541 -379.792 913.328 +v 644.719 -436.229 823.452 +v 645.052 -359.418 822.685 +v 695.541 -379.792 913.328 +v 626.526 -181.518 888.681 +v 625.983 -264.872 889.066 +v 697.205 -181.253 900.122 +v 645.299 -197.725 822.477 +v 626.526 -181.518 888.681 +v 697.205 -181.253 900.122 +v 645.229 -252.173 822.897 +v 645.299 -197.725 822.477 +v 697.205 -181.253 900.122 +v 645.052 -359.418 822.685 +v 627.235 -359.336 885.962 +v 695.541 -379.792 913.328 +v 625.983 -264.872 889.066 +v 645.229 -252.173 822.897 +v 697.205 -181.253 900.122 +v 960.668 2.14378 -383.161 +v 1053.7 -18.2369 -346.758 +v 1039.45 -77.8383 -303.194 +v 1039.45 -77.8383 -303.194 +v 922.953 -39.6783 -375.782 +v 960.668 2.14378 -383.161 +v 1053.7 -18.2369 -346.758 +v 1117.05 -14.6657 -272.841 +v 1101.1 -49.6417 -253.686 +v 1101.1 -49.6417 -253.686 +v 1039.45 -77.8383 -303.194 +v 1053.7 -18.2369 -346.758 +v 922.953 -39.6783 -375.782 +v 1039.45 -77.8383 -303.194 +v 1015.08 -111.831 -235.109 +v 1015.08 -111.831 -235.109 +v 885.246 -63.9092 -351.891 +v 922.953 -39.6783 -375.782 +v 1039.45 -77.8383 -303.194 +v 1101.1 -49.6417 -253.686 +v 1102.84 -71.0802 -161.378 +v 1102.84 -71.0802 -161.378 +v 1015.08 -111.831 -235.109 +v 1039.45 -77.8383 -303.194 +v 885.246 -63.9092 -351.891 +v 1015.08 -111.831 -235.109 +v 965.997 -129.088 -179.582 +v 965.997 -129.088 -179.582 +v 843.785 -71.3395 -368.243 +v 885.246 -63.9092 -351.891 +v 1015.08 -111.831 -235.109 +v 1102.84 -71.0802 -161.378 +v 1077.52 -88.1132 -75.2012 +v 1077.52 -88.1132 -75.2012 +v 965.997 -129.088 -179.582 +v 1015.08 -111.831 -235.109 +v 843.785 -71.3395 -368.243 +v 965.997 -129.088 -179.582 +v 846.834 -109.506 -138.578 +v 846.834 -109.506 -138.578 +v 676.056 -10.6067 -403.457 +v 843.785 -71.3395 -368.243 +v 965.997 -129.088 -179.582 +v 1077.52 -88.1132 -75.2012 +v 987.83 -96.8683 -34.0016 +v 965.997 -129.088 -179.582 +v 987.83 -96.8683 -34.0016 +v 918.453 -95.2934 -14.2217 +v 965.997 -129.088 -179.582 +v 918.453 -95.2934 -14.2217 +v 846.834 -109.506 -138.578 +v 1101.1 -49.6417 -253.686 +v 1117.05 -14.6657 -272.841 +v 1130.63 -9.82445 -219.113 +v 987.83 -96.8683 -34.0016 +v 1077.52 -88.1132 -75.2012 +v 1056.88 -53.0906 2.82201 +v 1102.84 -71.0802 -161.378 +v 1101.1 -49.6417 -253.686 +v 1130.63 -9.82445 -219.113 +v 1130.63 -9.82445 -219.113 +v 1120.75 -32.3903 -128.277 +v 1102.84 -71.0802 -161.378 +v 1077.52 -88.1132 -75.2012 +v 1102.84 -71.0802 -161.378 +v 1120.75 -32.3903 -128.277 +v 1150.35 40.2028 -66.9116 +v 1132.43 40.2028 -24.6494 +v 1089.27 -39.0682 -37.2052 +v 997.81 -45.2141 37.0952 +v 1056.88 -53.0906 2.82201 +v 1100.85 40.2028 23.3858 +v 1100.85 40.2028 23.3858 +v 1031.13 40.2028 65.4153 +v 997.81 -45.2141 37.0952 +v 997.81 -45.2141 37.0952 +v 984.155 40.2028 76.1361 +v 972.648 -63.5674 25.3677 +v 1089.27 -39.0682 -37.2052 +v 1077.52 -88.1132 -75.2012 +v 1150.35 40.2028 -66.9116 +v 1089.27 -39.0682 -37.2052 +v 1056.88 -53.0906 2.82201 +v 1077.52 -88.1132 -75.2012 +v 972.648 -63.5674 25.3677 +v 918.453 -95.2934 -14.2217 +v 987.83 -96.8683 -34.0016 +v 972.648 -63.5674 25.3677 +v 987.83 -96.8683 -34.0016 +v 1056.88 -53.0906 2.82201 +v 972.648 -63.5674 25.3677 +v 1056.88 -53.0906 2.82201 +v 997.81 -45.2141 37.0952 +v 1089.27 -39.0682 -37.2052 +v 1132.43 40.2028 -24.6494 +v 1122.15 40.2028 2.15071 +v 1089.27 -39.0682 -37.2052 +v 1122.15 40.2028 2.15071 +v 1100.85 40.2028 23.3858 +v 1100.85 40.2028 23.3858 +v 1056.88 -53.0906 2.82201 +v 1089.27 -39.0682 -37.2052 +v 997.81 -45.2141 37.0952 +v 1031.13 40.2028 65.4153 +v 984.155 40.2028 76.1361 +v 558.965 -9.41835 -404.09 +v 596.877 -226.42 -202.43 +v 268.451 -169.261 -290.85 +v 558.965 -9.41835 -404.09 +v 705.303 -138.614 -221.823 +v 596.877 -226.42 -202.43 +v 596.877 -226.42 -202.43 +v 545.802 -338.023 -93.8417 +v 380.051 -251.531 -66.4672 +v 380.051 -251.531 -66.4672 +v 242.389 -203.746 -21.5963 +v 260.646 -223.493 -153.714 +v 705.303 -138.614 -221.823 +v 729.558 -244.167 -136.921 +v 596.877 -226.42 -202.43 +v -627.402 40.2031 -27.0373 +v -598.079 -17.5054 13.3313 +v -974.541 40.2032 116.414 +v -598.079 -17.5054 13.3313 +v -635.946 -19.0616 145.914 +v -966.748 -6.42429 188.349 +v -973.5 40.2032 204.705 +v -966.748 -6.42429 188.349 +v -635.946 -19.0616 145.914 +v -635.946 -19.0616 145.914 +v -605.1 40.2031 174.866 +v -973.5 40.2032 204.705 +v -598.079 -17.5054 13.3313 +v -966.748 -6.42429 188.349 +v -974.541 40.2032 116.414 +v -1356.56 13.1094 175.492 +v -1360.94 40.2032 134.989 +v -974.541 40.2032 116.414 +v -974.541 40.2032 116.414 +v -966.748 -6.42429 188.349 +v -1356.56 13.1094 175.492 +v -1360.36 40.2032 184.7 +v -1356.56 13.1094 175.492 +v -966.748 -6.42429 188.349 +v -966.748 -6.42429 188.349 +v -973.5 40.2032 204.705 +v -1360.36 40.2032 184.7 +v -1360.94 40.2032 134.989 +v -1356.56 13.1094 175.492 +v -1746.98 40.2033 71.4633 +v -1356.56 13.1094 175.492 +v -1360.36 40.2032 184.7 +v -1746.98 40.2033 71.4633 +v 846.834 -109.506 -138.578 +v 918.453 -95.2934 -14.2217 +v 789.328 -72.2831 -20.678 +v 676.056 -10.6067 -403.457 +v 846.834 -109.506 -138.578 +v 705.303 -138.614 -221.823 +v 558.965 -9.41835 -404.09 +v 505.639 40.2029 -412.719 +v 442.767 40.2029 -586.685 +v 676.056 -10.6067 -403.457 +v 558.965 -9.41835 -404.09 +v 482.298 5.71433 -625.285 +v 676.056 -10.6067 -403.457 +v 482.298 5.71433 -625.285 +v 486.264 40.2029 -638.201 +v 486.264 40.2029 -638.201 +v 679.933 40.2029 -441.705 +v 676.056 -10.6067 -403.457 +v 482.298 5.71433 -625.285 +v 558.965 -9.41835 -404.09 +v 442.767 40.2029 -586.685 +v 442.767 40.2029 -586.685 +v 286.808 40.203 -755.55 +v 309.065 20.6301 -777.283 +v 309.065 20.6301 -777.283 +v 482.298 5.71433 -625.285 +v 442.767 40.2029 -586.685 +v 482.298 5.71433 -625.285 +v 309.065 20.6301 -777.283 +v 311.298 40.203 -784.555 +v 311.298 40.203 -784.555 +v 486.264 40.2029 -638.201 +v 482.298 5.71433 -625.285 +v 309.065 20.6301 -777.283 +v 286.808 40.203 -755.55 +v 3.60059 40.203 -892.627 +v 311.298 40.203 -784.555 +v 309.065 20.6301 -777.283 +v 3.60059 40.203 -892.627 +v -107.623 -99.0134 -353.534 +v -233.929 40.203 -337.011 +v -312.712 40.203 -483.512 +v 32.5665 -78.854 -387.298 +v -107.623 -99.0134 -353.534 +v -261.253 5.71445 -577.012 +v 32.5665 -78.854 -387.298 +v -261.253 5.71445 -577.012 +v -260.883 40.203 -590.518 +v -260.883 40.203 -590.518 +v 21.7976 40.203 -426.108 +v 32.5665 -78.854 -387.298 +v -261.253 5.71445 -577.012 +v -107.623 -99.0134 -353.534 +v -312.712 40.203 -483.512 +v -312.712 40.203 -483.512 +v -484.444 40.2031 -650.296 +v -468.805 20.6302 -677.187 +v -468.805 20.6302 -677.187 +v -261.253 5.71445 -577.012 +v -312.712 40.203 -483.512 +v -261.253 5.71445 -577.012 +v -468.805 20.6302 -677.187 +v -468.596 40.2031 -684.792 +v -468.596 40.2031 -684.792 +v -260.883 40.203 -590.518 +v -261.253 5.71445 -577.012 +v -468.805 20.6302 -677.187 +v -484.444 40.2031 -650.296 +v -793.984 40.2031 -706.704 +v -468.596 40.2031 -684.792 +v -468.805 20.6302 -677.187 +v -793.984 40.2031 -706.704 +v 260.312 40.203 -440.008 +v 252.222 134.611 -402.864 +v 558.966 89.8242 -404.09 +v 558.966 89.8242 -404.09 +v 505.639 40.2029 -412.719 +v 260.312 40.203 -440.008 +v 21.7976 40.203 -426.108 +v 32.5666 159.26 -387.298 +v 252.222 134.611 -402.864 +v 252.222 134.611 -402.864 +v 260.312 40.203 -440.008 +v 21.7976 40.203 -426.108 +v -107.623 179.419 -353.534 +v -233.929 40.203 -337.011 +v -378.533 40.2031 -238.981 +v -378.533 40.2031 -238.981 +v -359.273 99.4677 -211.385 +v -107.623 179.419 -353.534 +v -627.402 40.2031 -27.0373 +v -598.079 97.9116 13.3312 +v -359.273 99.4677 -211.385 +v -359.273 99.4677 -211.385 +v -378.533 40.2031 -238.981 +v -627.402 40.2031 -27.0373 +v -605.1 40.2031 174.866 +v -547.166 40.2031 372.164 +v -635.946 99.4678 145.914 +v -99.0235 40.203 155.844 +v -25.3967 40.203 194.286 +v 22.0084 135.938 181.514 +v 246.48 40.203 170.173 +v 246.315 160.024 147.057 +v 22.0084 135.938 181.514 +v 22.0084 135.938 181.514 +v -25.3967 40.203 194.286 +v 246.48 40.203 170.173 +v 568.185 40.2029 165.976 +v 542.48 125.319 151.06 +v 246.315 160.024 147.057 +v 246.315 160.024 147.057 +v 246.48 40.203 170.173 +v 568.185 40.2029 165.976 +v 762.912 40.2029 67.4993 +v 758.412 143.246 43.888 +v 542.48 125.319 151.06 +v 542.48 125.319 151.06 +v 568.185 40.2029 165.976 +v 762.912 40.2029 67.4993 +v 841.425 40.2029 35.9378 +v 789.328 152.689 -20.678 +v 758.412 143.246 43.888 +v 758.412 143.246 43.888 +v 762.912 40.2029 67.4993 +v 841.425 40.2029 35.9378 +v 912.514 40.2028 48.5509 +v 918.453 175.699 -14.2217 +v 789.328 152.689 -20.678 +v 789.328 152.689 -20.678 +v 841.425 40.2029 35.9378 +v 912.514 40.2028 48.5509 +v 252.222 134.611 -402.864 +v 268.451 249.667 -290.85 +v 558.966 89.8242 -404.09 +v 32.5666 159.26 -387.298 +v 268.451 249.667 -290.85 +v 252.222 134.611 -402.864 +v -598.079 97.9116 13.3312 +v -537.312 180.662 32.2996 +v -321.215 158.098 -143.919 +v -321.215 158.098 -143.919 +v -359.273 99.4677 -211.385 +v -598.079 97.9116 13.3312 +v -359.273 99.4677 -211.385 +v -321.215 158.098 -143.919 +v -107.623 179.419 -353.534 +v 260.646 303.899 -153.714 +v 380.051 331.937 -66.4672 +v 596.877 306.826 -202.43 +v 596.877 306.826 -202.43 +v 268.451 249.667 -290.85 +v 260.646 303.899 -153.714 +v 39.9729 236.445 -209.082 +v 260.646 303.899 -153.714 +v 268.451 249.667 -290.85 +v 268.451 249.667 -290.85 +v 32.5666 159.26 -387.298 +v 39.9729 236.445 -209.082 +v -107.623 179.419 -353.534 +v -121.935 222.68 -170.547 +v 39.9729 236.445 -209.082 +v 39.9729 236.445 -209.082 +v 32.5666 159.26 -387.298 +v -107.623 179.419 -353.534 +v -537.312 180.662 32.2996 +v -391.04 220.314 41.853 +v -321.215 158.098 -143.919 +v -321.215 158.098 -143.919 +v -121.935 222.68 -170.547 +v -107.623 179.419 -353.534 +v 39.9729 236.445 -209.082 +v 22.0431 237.22 26.5261 +v 242.389 284.152 -21.5963 +v 242.389 284.152 -21.5963 +v 260.646 303.899 -153.714 +v 39.9729 236.445 -209.082 +v -121.935 222.68 -170.547 +v -77.3161 206.284 32.3854 +v 22.0431 237.22 26.5261 +v 22.0431 237.22 26.5261 +v 39.9729 236.445 -209.082 +v -121.935 222.68 -170.547 +v -391.04 220.314 41.853 +v -298.191 183.448 127.509 +v -213.771 177.887 92.1041 +v -213.771 177.887 92.1041 +v -321.215 158.098 -143.919 +v -391.04 220.314 41.853 +v -321.215 158.098 -143.919 +v -213.771 177.887 92.1041 +v -77.3161 206.284 32.3854 +v -77.3161 206.284 32.3854 +v -121.935 222.68 -170.547 +v -321.215 158.098 -143.919 +v 242.389 284.152 -21.5963 +v 257.24 242.3 50.0724 +v 494.4 199.075 153.3 +v 494.4 199.075 153.3 +v 380.051 331.937 -66.4672 +v 242.389 284.152 -21.5963 +v 22.0431 237.22 26.5261 +v 34.0969 188.524 134.952 +v 257.24 242.3 50.0724 +v 257.24 242.3 50.0724 +v 242.389 284.152 -21.5963 +v 22.0431 237.22 26.5261 +v -77.3161 206.284 32.3854 +v -50.7188 147.901 123.835 +v 34.0969 188.524 134.952 +v 34.0969 188.524 134.952 +v 22.0431 237.22 26.5261 +v -77.3161 206.284 32.3854 +v -298.191 183.448 127.509 +v -314.968 157.494 227.647 +v -257.987 111.014 139.94 +v -257.987 111.014 139.94 +v -213.771 177.887 92.1041 +v -298.191 183.448 127.509 +v -213.771 177.887 92.1041 +v -257.987 111.014 139.94 +v -50.7188 147.901 123.835 +v -50.7188 147.901 123.835 +v -77.3161 206.284 32.3854 +v -213.771 177.887 92.1041 +v 246.315 160.024 147.057 +v 542.48 125.319 151.06 +v 494.4 199.075 153.3 +v 494.4 199.075 153.3 +v 257.24 242.3 50.0724 +v 246.315 160.024 147.057 +v 34.0969 188.524 134.952 +v 22.0084 135.938 181.514 +v 246.315 160.024 147.057 +v 246.315 160.024 147.057 +v 257.24 242.3 50.0724 +v 34.0969 188.524 134.952 +v -50.7188 147.901 123.835 +v -99.0235 40.203 155.844 +v 22.0084 135.938 181.514 +v 22.0084 135.938 181.514 +v 34.0969 188.524 134.952 +v -50.7188 147.901 123.835 +v -314.968 157.494 227.647 +v -284.581 40.203 304.684 +v -260.462 40.203 162.299 +v -260.462 40.203 162.299 +v -257.987 111.014 139.94 +v -314.968 157.494 227.647 +v -257.987 111.014 139.94 +v -260.462 40.203 162.299 +v -99.0235 40.203 155.844 +v -99.0235 40.203 155.844 +v -50.7188 147.901 123.835 +v -257.987 111.014 139.94 +v 558.966 89.8242 -404.09 +v 705.303 219.02 -221.823 +v 676.056 91.0124 -403.457 +v 729.558 324.573 -136.921 +v 789.328 152.689 -20.678 +v 846.835 189.911 -138.578 +v -635.946 99.4678 145.914 +v -615.112 167.209 174.866 +v -537.312 180.662 32.2996 +v -537.312 180.662 32.2996 +v -598.079 97.9116 13.3312 +v -635.946 99.4678 145.914 +v -547.166 40.2031 372.164 +v -550.064 125.358 307.245 +v -615.112 167.209 174.866 +v -615.112 167.209 174.866 +v -635.946 99.4678 145.914 +v -547.166 40.2031 372.164 +v -284.581 40.203 304.684 +v -314.968 157.494 227.647 +v -550.064 125.358 307.245 +v -550.064 125.358 307.245 +v -547.166 40.2031 372.164 +v -284.581 40.203 304.684 +v -537.312 180.662 32.2996 +v -410.355 397.111 -17.6217 +v -281.314 324.041 -48.6181 +v -281.314 324.041 -48.6181 +v -391.04 220.314 41.853 +v -537.312 180.662 32.2996 +v -391.04 220.314 41.853 +v -281.314 324.041 -48.6181 +v -169.529 226.359 141.817 +v -169.529 226.359 141.817 +v -298.191 183.448 127.509 +v -391.04 220.314 41.853 +v -298.191 183.448 127.509 +v -169.529 226.359 141.817 +v -255.603 219.321 227.647 +v -255.603 219.321 227.647 +v -314.968 157.494 227.647 +v -298.191 183.448 127.509 +v -615.112 167.209 174.866 +v -465.797 406.708 178.963 +v -410.355 397.111 -17.6217 +v -410.355 397.111 -17.6217 +v -537.312 180.662 32.2996 +v -615.112 167.209 174.866 +v -550.064 125.358 307.245 +v -440.269 322.717 338.364 +v -465.797 406.708 178.963 +v -465.797 406.708 178.963 +v -615.112 167.209 174.866 +v -550.064 125.358 307.245 +v -314.968 157.494 227.647 +v -255.603 219.321 227.647 +v -440.269 322.717 338.364 +v -440.269 322.717 338.364 +v -550.064 125.358 307.245 +v -314.968 157.494 227.647 +v -410.355 397.111 -17.6217 +v -24.9718 524.939 -3.85764 +v 14.208 415.633 -45.2649 +v 14.208 415.633 -45.2649 +v -281.314 324.041 -48.6181 +v -410.355 397.111 -17.6217 +v -281.314 324.041 -48.6181 +v 14.208 415.633 -45.2649 +v 32.0552 328.291 -11.6047 +v 32.0552 328.291 -11.6047 +v -169.529 226.359 141.817 +v -281.314 324.041 -48.6181 +v -169.529 226.359 141.817 +v 32.0552 328.291 -11.6047 +v -15.0917 323.073 60.2354 +v -15.0917 323.073 60.2354 +v -255.603 219.321 227.647 +v -169.529 226.359 141.817 +v -465.797 406.708 178.963 +v -103.65 527.69 112.409 +v -24.9718 524.939 -3.85764 +v -24.9718 524.939 -3.85764 +v -410.355 397.111 -17.6217 +v -465.797 406.708 178.963 +v -440.269 322.717 338.364 +v -132.463 411.341 170.696 +v -103.65 527.69 112.409 +v -103.65 527.69 112.409 +v -465.797 406.708 178.963 +v -440.269 322.717 338.364 +v -255.603 219.321 227.647 +v -15.0917 323.073 60.2354 +v -132.463 411.341 170.696 +v -132.463 411.341 170.696 +v -440.269 322.717 338.364 +v -255.603 219.321 227.647 +v -24.9718 524.939 -3.85764 +v 65.7942 517.971 48.1499 +v 154.274 503.342 15.1391 +v 154.274 503.342 15.1391 +v 14.208 415.633 -45.2649 +v -24.9718 524.939 -3.85764 +v 14.208 415.633 -45.2649 +v 154.274 503.342 15.1391 +v 160.1 419.483 42.0846 +v 160.1 419.483 42.0846 +v 32.0552 328.291 -11.6047 +v 14.208 415.633 -45.2649 +v 32.0552 328.291 -11.6047 +v 160.1 419.483 42.0846 +v 65.6434 390.704 103.599 +v 65.6434 390.704 103.599 +v -15.0917 323.073 60.2354 +v 32.0552 328.291 -11.6047 +v -103.65 527.69 112.409 +v -52.7381 458.911 120.825 +v 65.7942 517.971 48.1499 +v 65.7942 517.971 48.1499 +v -24.9718 524.939 -3.85764 +v -103.65 527.69 112.409 +v -132.463 411.341 170.696 +v -86.6904 426.227 159.574 +v -52.7381 458.911 120.825 +v -52.7381 458.911 120.825 +v -103.65 527.69 112.409 +v -132.463 411.341 170.696 +v -15.0917 323.073 60.2354 +v 65.6434 390.704 103.599 +v -86.6904 426.227 159.574 +v -86.6904 426.227 159.574 +v -132.463 411.341 170.696 +v -15.0917 323.073 60.2354 +v 65.7942 517.971 48.1499 +v 76.3056 529.56 158.966 +v 143.503 508.238 162.674 +v 143.503 508.238 162.674 +v 154.274 503.342 15.1391 +v 65.7942 517.971 48.1499 +v 154.274 503.342 15.1391 +v 143.503 508.238 162.674 +v 140.897 421.541 179.078 +v 140.897 421.541 179.078 +v 160.1 419.483 42.0846 +v 154.274 503.342 15.1391 +v 160.1 419.483 42.0846 +v 140.897 421.541 179.078 +v 61.6316 389.354 188.674 +v 61.6316 389.354 188.674 +v 65.6434 390.704 103.599 +v 160.1 419.483 42.0846 +v -52.7381 458.911 120.825 +v -56.9872 487.596 172.668 +v 76.3056 529.56 158.966 +v 76.3056 529.56 158.966 +v 65.7942 517.971 48.1499 +v -52.7381 458.911 120.825 +v -86.6904 426.227 159.574 +v -101.405 421.274 187.717 +v -56.9872 487.596 172.668 +v -56.9872 487.596 172.668 +v -52.7381 458.911 120.825 +v -86.6904 426.227 159.574 +v 65.6434 390.704 103.599 +v 61.6316 389.354 188.674 +v -101.405 421.274 187.717 +v -101.405 421.274 187.717 +v -86.6904 426.227 159.574 +v 65.6434 390.704 103.599 +v 76.3056 529.56 158.966 +v -86.1869 457.101 363.046 +v -57.0879 443.509 388.882 +v -57.0879 443.509 388.882 +v 143.503 508.238 162.674 +v 76.3056 529.56 158.966 +v 143.503 508.238 162.674 +v -57.0879 443.509 388.882 +v -61.2254 314.985 389.398 +v -61.2254 314.985 389.398 +v 140.897 421.541 179.078 +v 143.503 508.238 162.674 +v 140.897 421.541 179.078 +v -61.2254 314.985 389.398 +v -121.906 296.789 347.524 +v -121.906 296.789 347.524 +v 61.6316 389.354 188.674 +v 140.897 421.541 179.078 +v -56.9872 487.596 172.668 +v -171.947 433.723 301.741 +v -86.1869 457.101 363.046 +v -86.1869 457.101 363.046 +v 76.3056 529.56 158.966 +v -56.9872 487.596 172.668 +v -101.405 421.274 187.717 +v -194.817 317.703 287.062 +v -171.947 433.723 301.741 +v -171.947 433.723 301.741 +v -56.9872 487.596 172.668 +v -101.405 421.274 187.717 +v 61.6316 389.354 188.674 +v -121.906 296.789 347.524 +v -194.817 317.703 287.062 +v -194.817 317.703 287.062 +v -101.405 421.274 187.717 +v 61.6316 389.354 188.674 +v -86.1869 457.101 363.046 +v -120.57 443.918 397.079 +v -102.276 429.985 417.599 +v -102.276 429.985 417.599 +v -57.0879 443.509 388.882 +v -86.1869 457.101 363.046 +v -57.0879 443.509 388.882 +v -102.276 429.985 417.599 +v -101.46 306.717 419.247 +v -101.46 306.717 419.247 +v -61.2254 314.985 389.398 +v -57.0879 443.509 388.882 +v -61.2254 314.985 389.398 +v -101.46 306.717 419.247 +v -150.824 294.424 384.223 +v -150.824 294.424 384.223 +v -121.906 296.789 347.524 +v -61.2254 314.985 389.398 +v -171.947 433.723 301.741 +v -209.07 429.315 327.758 +v -120.57 443.918 397.079 +v -120.57 443.918 397.079 +v -86.1869 457.101 363.046 +v -171.947 433.723 301.741 +v -194.817 317.703 287.062 +v -220.374 318.156 317.345 +v -209.07 429.315 327.758 +v -209.07 429.315 327.758 +v -171.947 433.723 301.741 +v -194.817 317.703 287.062 +v -121.906 296.789 347.524 +v -150.824 294.424 384.223 +v -220.374 318.156 317.345 +v -220.374 318.156 317.345 +v -194.817 317.703 287.062 +v -121.906 296.789 347.524 +v -120.57 443.918 397.079 +v -279.206 399.004 477.521 +v -216.691 379.768 512.471 +v -216.691 379.768 512.471 +v -102.276 429.985 417.599 +v -120.57 443.918 397.079 +v -102.276 429.985 417.599 +v -216.691 379.768 512.471 +v -220.533 323.142 524.241 +v -220.533 323.142 524.241 +v -101.46 306.717 419.247 +v -102.276 429.985 417.599 +v -101.46 306.717 419.247 +v -220.533 323.142 524.241 +v -257.957 235.824 505.428 +v -257.957 235.824 505.428 +v -150.824 294.424 384.223 +v -101.46 306.717 419.247 +v -209.07 429.315 327.758 +v -372.739 339.847 407.375 +v -279.206 399.004 477.521 +v -279.206 399.004 477.521 +v -120.57 443.918 397.079 +v -209.07 429.315 327.758 +v -220.374 318.156 317.345 +v -394.838 259.653 404.628 +v -372.739 339.847 407.375 +v -372.739 339.847 407.375 +v -209.07 429.315 327.758 +v -220.374 318.156 317.345 +v -150.824 294.424 384.223 +v -257.957 235.824 505.428 +v -394.838 259.653 404.628 +v -394.838 259.653 404.628 +v -220.374 318.156 317.345 +v -150.824 294.424 384.223 +v -279.206 399.004 477.521 +v -317.133 461.908 577.263 +v -236.316 377.472 568.835 +v -236.316 377.472 568.835 +v -216.691 379.768 512.471 +v -279.206 399.004 477.521 +v -216.691 379.768 512.471 +v -236.316 377.472 568.835 +v -240.158 320.846 580.604 +v -240.158 320.846 580.604 +v -220.533 323.142 524.241 +v -216.691 379.768 512.471 +v -220.533 323.142 524.241 +v -240.158 320.846 580.604 +v -231.113 222.436 578.573 +v -231.113 222.436 578.573 +v -257.957 235.824 505.428 +v -220.533 323.142 524.241 +v -372.739 339.847 407.375 +v -521.042 358.693 466.938 +v -317.133 461.908 577.263 +v -317.133 461.908 577.263 +v -279.206 399.004 477.521 +v -372.739 339.847 407.375 +v -394.838 259.653 404.628 +v -543.141 227.102 464.192 +v -521.042 358.693 466.938 +v -521.042 358.693 466.938 +v -372.739 339.847 407.375 +v -394.838 259.653 404.628 +v -257.957 235.824 505.428 +v -231.113 222.436 578.573 +v -543.141 227.102 464.192 +v -543.141 227.102 464.192 +v -394.838 259.653 404.628 +v -257.957 235.824 505.428 +v -317.133 461.908 577.263 +v -180.616 530.627 583.426 +v -143.793 467.96 589.922 +v -143.793 467.96 589.922 +v -236.316 377.472 568.835 +v -317.133 461.908 577.263 +v -236.316 377.472 568.835 +v -143.793 467.96 589.922 +v -123.358 426.144 615.393 +v -123.358 426.144 615.393 +v -240.158 320.846 580.604 +v -236.316 377.472 568.835 +v -240.158 320.846 580.604 +v -123.358 426.144 615.393 +v -97.4353 366.614 662.435 +v -97.4353 366.614 662.435 +v -231.113 222.436 578.573 +v -240.158 320.846 580.604 +v -521.042 358.693 466.938 +v -493.77 358.377 624.724 +v -384.975 518.979 704.341 +v -384.975 518.979 704.341 +v -317.133 461.908 577.263 +v -521.042 358.693 466.938 +v -543.141 227.102 464.192 +v -513.328 278.481 614.68 +v -493.77 358.377 624.724 +v -493.77 358.377 624.724 +v -521.042 358.693 466.938 +v -543.141 227.102 464.192 +v -231.113 222.436 578.573 +v -308.634 250.194 722.901 +v -513.328 278.481 614.68 +v -513.328 278.481 614.68 +v -543.141 227.102 464.192 +v -231.113 222.436 578.573 +v -317.133 461.908 577.263 +v -384.975 518.979 704.341 +v -207.244 534.735 671.558 +v -207.244 534.735 671.558 +v -180.616 530.627 583.426 +v -317.133 461.908 577.263 +v -97.4353 366.614 662.435 +v -117.005 364.061 756.407 +v -308.634 250.194 722.901 +v -308.634 250.194 722.901 +v -231.113 222.436 578.573 +v -97.4353 366.614 662.435 +v -493.77 358.377 624.724 +v -470.518 310.442 648.255 +v -351.878 348.234 729.182 +v -351.878 348.234 729.182 +v -384.975 518.979 704.341 +v -493.77 358.377 624.724 +v -513.328 278.481 614.68 +v -470.518 310.442 648.255 +v -493.77 358.377 624.724 +v -308.634 250.194 722.901 +v -351.878 348.234 729.182 +v -470.518 310.442 648.255 +v -470.518 310.442 648.255 +v -513.328 278.481 614.68 +v -308.634 250.194 722.901 +v -384.975 518.979 704.341 +v -351.878 348.234 729.182 +v -158.686 443.907 717.111 +v -158.686 443.907 717.111 +v -207.244 534.735 671.558 +v -384.975 518.979 704.341 +v -117.005 364.061 756.407 +v -158.686 443.907 717.111 +v -351.878 348.234 729.182 +v -351.878 348.234 729.182 +v -308.634 250.194 722.901 +v -117.005 364.061 756.407 +v -180.616 530.627 583.426 +v -66.3274 584.533 711.51 +v -143.793 467.96 589.922 +v -123.358 426.144 615.393 +v -9.81756 473.005 748.063 +v -97.4353 366.614 662.435 +v -207.244 534.735 671.558 +v -66.3274 584.533 711.51 +v -180.616 530.627 583.426 +v -97.4353 366.614 662.435 +v -9.81756 473.005 748.063 +v -117.005 364.061 756.407 +v -158.686 443.907 717.111 +v -66.3274 584.533 711.51 +v -207.244 534.735 671.558 +v -117.005 364.061 756.407 +v -9.81756 473.005 748.063 +v -158.686 443.907 717.111 +v -143.793 467.96 589.922 +v -158.686 443.907 717.111 +v -123.358 426.144 615.393 +v -123.358 426.144 615.393 +v -158.686 443.907 717.111 +v -9.81756 473.005 748.063 +v -66.3274 584.533 711.51 +v -158.686 443.907 717.111 +v -143.793 467.96 589.922 +v 676.056 91.0124 -403.457 +v 843.785 151.745 -368.243 +v 824.669 40.2029 -424.477 +v 824.669 40.2029 -424.477 +v 679.933 40.2029 -441.705 +v 676.056 91.0124 -403.457 +v 843.785 151.745 -368.243 +v 885.246 144.315 -351.891 +v 878.609 40.2028 -424.149 +v 878.609 40.2028 -424.149 +v 824.669 40.2029 -424.477 +v 843.785 151.745 -368.243 +v 885.246 144.315 -351.891 +v 922.953 120.084 -375.782 +v 909.031 40.2028 -425.978 +v 909.031 40.2028 -425.978 +v 878.609 40.2028 -424.149 +v 885.246 144.315 -351.891 +v 922.953 120.084 -375.782 +v 960.668 78.2619 -383.161 +v 968.589 40.2028 -404.236 +v 968.589 40.2028 -404.236 +v 909.031 40.2028 -425.978 +v 922.953 120.084 -375.782 +v 960.668 78.2619 -383.161 +v 1053.7 98.6425 -346.758 +v 1061.35 40.2028 -356.762 +v 1061.35 40.2028 -356.762 +v 968.589 40.2028 -404.236 +v 960.668 78.2619 -383.161 +v 1053.7 98.6425 -346.758 +v 1117.05 95.0714 -272.841 +v 1119.22 40.2028 -284.353 +v 1119.22 40.2028 -284.353 +v 1061.35 40.2028 -356.762 +v 1053.7 98.6425 -346.758 +v 1117.05 95.0714 -272.841 +v 1130.63 90.2301 -219.113 +v 1152.01 40.2028 -217.427 +v 1152.01 40.2028 -217.427 +v 1119.22 40.2028 -284.353 +v 1117.05 95.0714 -272.841 +v 1130.63 90.2301 -219.113 +v 1120.75 112.796 -128.277 +v 1154.32 40.2028 -99.7808 +v 1154.32 40.2028 -99.7808 +v 1152.01 40.2028 -217.427 +v 1130.63 90.2301 -219.113 +v 1120.75 112.796 -128.277 +v 1077.52 168.519 -75.2012 +v 1150.35 40.2028 -66.9116 +v 1150.35 40.2028 -66.9116 +v 1154.32 40.2028 -99.7808 +v 1120.75 112.796 -128.277 +v 972.648 143.973 25.3677 +v 948.344 40.2028 68.9338 +v 984.155 40.2028 76.1361 +v 972.648 143.973 25.3677 +v 918.453 175.699 -14.2217 +v 912.514 40.2028 48.5509 +v 912.514 40.2028 48.5509 +v 948.344 40.2028 68.9338 +v 972.648 143.973 25.3677 +v 758.412 143.246 43.888 +v 766.139 270.868 -12.7916 +v 682.576 214.241 164.696 +v 682.576 214.241 164.696 +v 542.48 125.319 151.06 +v 758.412 143.246 43.888 +v 789.328 152.689 -20.678 +v 729.558 324.573 -136.921 +v 766.139 270.868 -12.7916 +v 766.139 270.868 -12.7916 +v 758.412 143.246 43.888 +v 789.328 152.689 -20.678 +v 380.051 331.937 -66.4672 +v 415.539 353.513 12.9001 +v 545.802 418.428 -93.8417 +v 380.051 331.937 -66.4672 +v 494.4 199.075 153.3 +v 415.539 353.513 12.9001 +v 542.48 125.319 151.06 +v 682.576 214.241 164.696 +v 494.4 199.075 153.3 +v 705.303 219.02 -221.823 +v 729.558 324.573 -136.921 +v 846.835 189.911 -138.578 +v 596.877 306.826 -202.43 +v 545.802 418.428 -93.8417 +v 690.121 405.282 -50.8344 +v 690.121 405.282 -50.8344 +v 729.558 324.573 -136.921 +v 596.877 306.826 -202.43 +v 729.558 324.573 -136.921 +v 764.322 329.706 -4.0724 +v 766.139 270.868 -12.7916 +v 729.558 324.573 -136.921 +v 690.121 405.282 -50.8344 +v 764.322 329.706 -4.0724 +v 545.802 418.428 -93.8417 +v 518.621 469.651 -11.2156 +v 643.124 465.31 35.5904 +v 643.124 465.31 35.5904 +v 690.121 405.282 -50.8344 +v 545.802 418.428 -93.8417 +v 764.322 329.706 -4.0724 +v 715.597 367.645 118.766 +v 682.576 214.241 164.696 +v 682.576 214.241 164.696 +v 766.139 270.868 -12.7916 +v 764.322 329.706 -4.0724 +v 545.802 418.428 -93.8417 +v 415.539 353.513 12.9001 +v 518.621 469.651 -11.2156 +v 690.121 405.282 -50.8344 +v 643.124 465.31 35.5904 +v 715.597 367.645 118.766 +v 715.597 367.645 118.766 +v 764.322 329.706 -4.0724 +v 690.121 405.282 -50.8344 +v 494.4 199.075 153.3 +v 495.985 266.516 152.255 +v 400.659 372.773 56.5926 +v 400.659 372.773 56.5926 +v 415.539 353.513 12.9001 +v 494.4 199.075 153.3 +v 682.576 214.241 164.696 +v 623.367 257.639 203.09 +v 495.985 266.516 152.255 +v 495.985 266.516 152.255 +v 494.4 199.075 153.3 +v 682.576 214.241 164.696 +v 518.621 469.651 -11.2156 +v 488.245 471.902 48.2971 +v 601.471 480.686 109.919 +v 601.471 480.686 109.919 +v 643.124 465.31 35.5904 +v 518.621 469.651 -11.2156 +v 715.597 367.645 118.766 +v 651.012 395.809 204.493 +v 623.367 257.639 203.09 +v 623.367 257.639 203.09 +v 682.576 214.241 164.696 +v 715.597 367.645 118.766 +v 415.539 353.513 12.9001 +v 400.659 372.773 56.5926 +v 488.245 471.902 48.2971 +v 488.245 471.902 48.2971 +v 518.621 469.651 -11.2156 +v 415.539 353.513 12.9001 +v 643.124 465.31 35.5904 +v 601.471 480.686 109.919 +v 651.012 395.809 204.493 +v 651.012 395.809 204.493 +v 715.597 367.645 118.766 +v 643.124 465.31 35.5904 +v 495.985 266.516 152.255 +v 449.267 308.157 241.792 +v 376.155 448.151 167.649 +v 376.155 448.151 167.649 +v 400.659 372.773 56.5926 +v 495.985 266.516 152.255 +v 623.367 257.639 203.09 +v 595.313 339.781 310.175 +v 449.267 308.157 241.792 +v 449.267 308.157 241.792 +v 495.985 266.516 152.255 +v 623.367 257.639 203.09 +v 488.245 471.902 48.2971 +v 438.002 514.69 183.939 +v 507.031 489.582 228.749 +v 507.031 489.582 228.749 +v 601.471 480.686 109.919 +v 488.245 471.902 48.2971 +v 651.012 395.809 204.493 +v 588.147 455.681 278.435 +v 595.313 339.781 310.175 +v 595.313 339.781 310.175 +v 623.367 257.639 203.09 +v 651.012 395.809 204.493 +v 400.659 372.773 56.5926 +v 376.155 448.151 167.649 +v 438.002 514.69 183.939 +v 438.002 514.69 183.939 +v 488.245 471.902 48.2971 +v 400.659 372.773 56.5926 +v 601.471 480.686 109.919 +v 507.031 489.582 228.749 +v 588.147 455.681 278.435 +v 588.147 455.681 278.435 +v 651.012 395.809 204.493 +v 601.471 480.686 109.919 +v 449.267 308.157 241.792 +v 319.396 336.17 310.744 +v 280.893 488.053 219.652 +v 280.893 488.053 219.652 +v 376.155 448.151 167.649 +v 449.267 308.157 241.792 +v 449.267 308.157 241.792 +v 595.313 339.781 310.175 +v 467.024 370.98 349.519 +v 467.024 370.98 349.519 +v 319.396 336.17 310.744 +v 449.267 308.157 241.792 +v 438.002 514.69 183.939 +v 289.487 530.878 226.311 +v 332.802 560.26 294.654 +v 332.802 560.26 294.654 +v 507.031 489.582 228.749 +v 438.002 514.69 183.939 +v 588.147 455.681 278.435 +v 467.468 531.511 347.786 +v 467.024 370.98 349.519 +v 467.024 370.98 349.519 +v 595.313 339.781 310.175 +v 588.147 455.681 278.435 +v 376.155 448.151 167.649 +v 280.893 488.053 219.652 +v 289.487 530.878 226.311 +v 289.487 530.878 226.311 +v 438.002 514.69 183.939 +v 376.155 448.151 167.649 +v 507.031 489.582 228.749 +v 332.802 560.26 294.654 +v 467.468 531.511 347.786 +v 467.468 531.511 347.786 +v 588.147 455.681 278.435 +v 507.031 489.582 228.749 +v 280.893 488.053 219.652 +v 319.396 336.17 310.744 +v 209.876 488.534 275.697 +v 289.487 530.878 226.311 +v 252.695 546.276 283.647 +v 332.802 560.26 294.654 +v 280.893 488.053 219.652 +v 209.876 488.534 275.697 +v 252.695 546.276 283.647 +v 252.695 546.276 283.647 +v 289.487 530.878 226.311 +v 280.893 488.053 219.652 +v 209.876 488.534 275.697 +v 319.396 336.17 310.744 +v 215.546 483.212 359.667 +v 252.695 546.276 283.647 +v 257.069 532.528 363.088 +v 332.802 560.26 294.654 +v 209.876 488.534 275.697 +v 215.546 483.212 359.667 +v 257.069 532.528 363.088 +v 257.069 532.528 363.088 +v 252.695 546.276 283.647 +v 209.876 488.534 275.697 +v 319.396 336.17 310.744 +v 337.988 332.852 457.825 +v 279.28 386.788 455.978 +v 279.28 386.788 455.978 +v 215.546 483.212 359.667 +v 319.396 336.17 310.744 +v 467.024 370.98 349.519 +v 441.553 365.885 435.883 +v 337.988 332.852 457.825 +v 337.988 332.852 457.825 +v 319.396 336.17 310.744 +v 467.024 370.98 349.519 +v 257.069 532.528 363.088 +v 265.056 461.92 466.161 +v 352.053 483.212 456.275 +v 352.053 483.212 456.275 +v 332.802 560.26 294.654 +v 257.069 532.528 363.088 +v 467.468 531.511 347.786 +v 441.455 448.914 445.446 +v 441.553 365.885 435.883 +v 441.553 365.885 435.883 +v 467.024 370.98 349.519 +v 467.468 531.511 347.786 +v 215.546 483.212 359.667 +v 279.28 386.788 455.978 +v 265.056 461.92 466.161 +v 265.056 461.92 466.161 +v 257.069 532.528 363.088 +v 215.546 483.212 359.667 +v 332.802 560.26 294.654 +v 352.053 483.212 456.275 +v 441.455 448.914 445.446 +v 441.455 448.914 445.446 +v 467.468 531.511 347.786 +v 332.802 560.26 294.654 +v 337.988 332.852 457.825 +v 383.441 310.398 553.769 +v 272.997 377.275 559.478 +v 272.997 377.275 559.478 +v 279.28 386.788 455.978 +v 337.988 332.852 457.825 +v 441.553 365.885 435.883 +v 466.393 331.19 551.819 +v 383.441 310.398 553.769 +v 383.441 310.398 553.769 +v 337.988 332.852 457.825 +v 441.553 365.885 435.883 +v 265.056 461.92 466.161 +v 264.043 467.602 561.999 +v 390.756 478.924 555.537 +v 390.756 478.924 555.537 +v 352.053 483.212 456.275 +v 265.056 461.92 466.161 +v 441.455 448.914 445.446 +v 464.616 423.439 556.584 +v 466.393 331.19 551.819 +v 466.393 331.19 551.819 +v 441.553 365.885 435.883 +v 441.455 448.914 445.446 +v 279.28 386.788 455.978 +v 272.997 377.275 559.478 +v 264.043 467.602 561.999 +v 264.043 467.602 561.999 +v 265.056 461.92 466.161 +v 279.28 386.788 455.978 +v 352.053 483.212 456.275 +v 390.756 478.924 555.537 +v 464.616 423.439 556.584 +v 464.616 423.439 556.584 +v 441.455 448.914 445.446 +v 352.053 483.212 456.275 +v 383.441 310.398 553.769 +v 377.032 310.647 640.682 +v 271.471 377.778 655.645 +v 271.471 377.778 655.645 +v 272.997 377.275 559.478 +v 383.441 310.398 553.769 +v 466.393 331.19 551.819 +v 460.045 334.815 626.719 +v 377.032 310.647 640.682 +v 377.032 310.647 640.682 +v 383.441 310.398 553.769 +v 466.393 331.19 551.819 +v 264.043 467.602 561.999 +v 293.651 458.498 667.416 +v 418.604 481.374 630.348 +v 418.604 481.374 630.348 +v 390.756 478.924 555.537 +v 264.043 467.602 561.999 +v 464.616 423.439 556.584 +v 484.703 406.702 631.39 +v 460.045 334.815 626.719 +v 460.045 334.815 626.719 +v 466.393 331.19 551.819 +v 464.616 423.439 556.584 +v 272.997 377.275 559.478 +v 271.471 377.778 655.645 +v 293.651 458.498 667.416 +v 293.651 458.498 667.416 +v 264.043 467.602 561.999 +v 272.997 377.275 559.478 +v 390.756 478.924 555.537 +v 418.604 481.374 630.348 +v 484.703 406.702 631.39 +v 484.703 406.702 631.39 +v 464.616 423.439 556.584 +v 390.756 478.924 555.537 +v 377.032 310.647 640.682 +v 340.152 283.009 825.111 +v 304.327 381.701 790.592 +v 304.327 381.701 790.592 +v 271.471 377.778 655.645 +v 377.032 310.647 640.682 +v 460.045 334.815 626.719 +v 451.236 316.167 688.596 +v 340.152 283.009 825.111 +v 340.152 283.009 825.111 +v 377.032 310.647 640.682 +v 460.045 334.815 626.719 +v 293.651 458.498 667.416 +v 317.335 458.301 787.405 +v 433.476 496.305 693.155 +v 433.476 496.305 693.155 +v 418.604 481.374 630.348 +v 293.651 458.498 667.416 +v 484.703 406.702 631.39 +v 490.612 394.687 692.953 +v 451.236 316.167 688.596 +v 451.236 316.167 688.596 +v 460.045 334.815 626.719 +v 484.703 406.702 631.39 +v 271.471 377.778 655.645 +v 304.327 381.701 790.592 +v 317.335 458.301 787.405 +v 317.335 458.301 787.405 +v 293.651 458.498 667.416 +v 271.471 377.778 655.645 +v 418.604 481.374 630.348 +v 433.476 496.305 693.155 +v 490.612 394.687 692.953 +v 490.612 394.687 692.953 +v 484.703 406.702 631.39 +v 418.604 481.374 630.348 +v 340.152 283.009 825.111 +v 499.471 257.736 820.408 +v 580.055 378.791 836.628 +v 580.055 378.791 836.628 +v 304.327 381.701 790.592 +v 340.152 283.009 825.111 +v 451.236 316.167 688.596 +v 527.033 273.943 790.548 +v 499.471 257.736 820.408 +v 499.471 257.736 820.408 +v 340.152 283.009 825.111 +v 451.236 316.167 688.596 +v 317.335 458.301 787.405 +v 486.543 519.264 861.734 +v 507.467 493.833 768.062 +v 507.467 493.833 768.062 +v 433.476 496.305 693.155 +v 317.335 458.301 787.405 +v 490.612 394.687 692.953 +v 577.257 377.751 761.531 +v 527.033 273.943 790.548 +v 527.033 273.943 790.548 +v 451.236 316.167 688.596 +v 490.612 394.687 692.953 +v 304.327 381.701 790.592 +v 580.055 378.791 836.628 +v 486.543 519.264 861.734 +v 486.543 519.264 861.734 +v 317.335 458.301 787.405 +v 304.327 381.701 790.592 +v 433.476 496.305 693.155 +v 507.467 493.833 768.062 +v 577.257 377.751 761.531 +v 577.257 377.751 761.531 +v 490.612 394.687 692.953 +v 433.476 496.305 693.155 +v 486.543 519.264 861.734 +v 626.437 533.232 888.782 +v 644.719 516.635 823.452 +v 644.719 516.635 823.452 +v 507.467 493.833 768.062 +v 486.543 519.264 861.734 +v 580.055 378.791 836.628 +v 627.235 439.741 885.962 +v 626.437 533.232 888.782 +v 626.437 533.232 888.782 +v 486.543 519.264 861.734 +v 580.055 378.791 836.628 +v 507.467 493.833 768.062 +v 644.719 516.635 823.452 +v 645.052 439.824 822.685 +v 645.052 439.824 822.685 +v 577.257 377.751 761.531 +v 507.467 493.833 768.062 +v 499.471 257.736 820.408 +v 626.526 261.924 888.681 +v 625.983 345.278 889.066 +v 625.983 345.278 889.066 +v 580.055 378.791 836.628 +v 499.471 257.736 820.408 +v 527.033 273.943 790.548 +v 645.299 278.13 822.477 +v 626.526 261.924 888.681 +v 626.526 261.924 888.681 +v 499.471 257.736 820.408 +v 527.033 273.943 790.548 +v 577.257 377.751 761.531 +v 645.229 332.579 822.897 +v 645.299 278.13 822.477 +v 645.299 278.13 822.477 +v 527.033 273.943 790.548 +v 577.257 377.751 761.531 +v 645.052 439.824 822.685 +v 627.235 439.741 885.962 +v 580.055 378.791 836.628 +v 580.055 378.791 836.628 +v 577.257 377.751 761.531 +v 645.052 439.824 822.685 +v 577.257 377.751 761.531 +v 580.055 378.791 836.628 +v 625.983 345.278 889.066 +v 625.983 345.278 889.066 +v 645.229 332.579 822.897 +v 577.257 377.751 761.531 +v 626.437 533.232 888.782 +v 695.541 460.197 913.328 +v 644.719 516.635 823.452 +v 627.235 439.741 885.962 +v 695.541 460.197 913.328 +v 626.437 533.232 888.782 +v 644.719 516.635 823.452 +v 695.541 460.197 913.328 +v 645.052 439.824 822.685 +v 626.526 261.924 888.681 +v 697.205 261.658 900.122 +v 625.983 345.278 889.066 +v 645.299 278.13 822.477 +v 697.205 261.658 900.122 +v 626.526 261.924 888.681 +v 645.229 332.579 822.897 +v 697.205 261.658 900.122 +v 645.299 278.13 822.477 +v 645.052 439.824 822.685 +v 695.541 460.197 913.328 +v 627.235 439.741 885.962 +v 625.983 345.278 889.066 +v 697.205 261.658 900.122 +v 645.229 332.579 822.897 +v 960.668 78.2619 -383.161 +v 922.953 120.084 -375.782 +v 1039.45 158.244 -303.194 +v 1039.45 158.244 -303.194 +v 1053.7 98.6425 -346.758 +v 960.668 78.2619 -383.161 +v 1053.7 98.6425 -346.758 +v 1039.45 158.244 -303.194 +v 1101.1 130.047 -253.686 +v 1101.1 130.047 -253.686 +v 1117.05 95.0714 -272.841 +v 1053.7 98.6425 -346.758 +v 922.953 120.084 -375.782 +v 885.246 144.315 -351.891 +v 1015.08 192.237 -235.109 +v 1015.08 192.237 -235.109 +v 1039.45 158.244 -303.194 +v 922.953 120.084 -375.782 +v 1039.45 158.244 -303.194 +v 1015.08 192.237 -235.109 +v 1102.84 151.486 -161.378 +v 1102.84 151.486 -161.378 +v 1101.1 130.047 -253.686 +v 1039.45 158.244 -303.194 +v 885.246 144.315 -351.891 +v 843.785 151.745 -368.243 +v 965.997 209.493 -179.582 +v 965.997 209.493 -179.582 +v 1015.08 192.237 -235.109 +v 885.246 144.315 -351.891 +v 1015.08 192.237 -235.109 +v 965.997 209.493 -179.582 +v 1077.52 168.519 -75.2012 +v 1077.52 168.519 -75.2012 +v 1102.84 151.486 -161.378 +v 1015.08 192.237 -235.109 +v 843.785 151.745 -368.243 +v 676.056 91.0124 -403.457 +v 846.835 189.911 -138.578 +v 846.835 189.911 -138.578 +v 965.997 209.493 -179.582 +v 843.785 151.745 -368.243 +v 965.997 209.493 -179.582 +v 846.835 189.911 -138.578 +v 918.453 175.699 -14.2217 +v 965.997 209.493 -179.582 +v 918.453 175.699 -14.2217 +v 987.83 177.274 -34.0016 +v 965.997 209.493 -179.582 +v 987.83 177.274 -34.0016 +v 1077.52 168.519 -75.2012 +v 1101.1 130.047 -253.686 +v 1130.63 90.2301 -219.113 +v 1117.05 95.0714 -272.841 +v 987.83 177.274 -34.0016 +v 1056.88 133.496 2.82201 +v 1077.52 168.519 -75.2012 +v 1102.84 151.486 -161.378 +v 1120.75 112.796 -128.277 +v 1130.63 90.2301 -219.113 +v 1130.63 90.2301 -219.113 +v 1101.1 130.047 -253.686 +v 1102.84 151.486 -161.378 +v 1077.52 168.519 -75.2012 +v 1120.75 112.796 -128.277 +v 1102.84 151.486 -161.378 +v 1150.35 40.2028 -66.9116 +v 1089.27 119.474 -37.2052 +v 1132.43 40.2028 -24.6494 +v 997.81 125.62 37.0952 +v 1031.13 40.2028 65.4153 +v 1100.85 40.2028 23.3858 +v 1100.85 40.2028 23.3858 +v 1056.88 133.496 2.82201 +v 997.81 125.62 37.0952 +v 997.81 125.62 37.0952 +v 972.648 143.973 25.3677 +v 984.155 40.2028 76.1361 +v 1089.27 119.474 -37.2052 +v 1150.35 40.2028 -66.9116 +v 1077.52 168.519 -75.2012 +v 1089.27 119.474 -37.2052 +v 1077.52 168.519 -75.2012 +v 1056.88 133.496 2.82201 +v 972.648 143.973 25.3677 +v 997.81 125.62 37.0952 +v 1056.88 133.496 2.82201 +v 972.648 143.973 25.3677 +v 1056.88 133.496 2.82201 +v 987.83 177.274 -34.0016 +v 972.648 143.973 25.3677 +v 987.83 177.274 -34.0016 +v 918.453 175.699 -14.2217 +v 1089.27 119.474 -37.2052 +v 1122.15 40.2028 2.15071 +v 1132.43 40.2028 -24.6494 +v 1089.27 119.474 -37.2052 +v 1056.88 133.496 2.82201 +v 1100.85 40.2028 23.3858 +v 1100.85 40.2028 23.3858 +v 1122.15 40.2028 2.15071 +v 1089.27 119.474 -37.2052 +v 997.81 125.62 37.0952 +v 984.155 40.2028 76.1361 +v 1031.13 40.2028 65.4153 +v 558.966 89.8242 -404.09 +v 268.451 249.667 -290.85 +v 596.877 306.826 -202.43 +v 558.966 89.8242 -404.09 +v 596.877 306.826 -202.43 +v 705.303 219.02 -221.823 +v 596.877 306.826 -202.43 +v 380.051 331.937 -66.4672 +v 545.802 418.428 -93.8417 +v 380.051 331.937 -66.4672 +v 260.646 303.899 -153.714 +v 242.389 284.152 -21.5963 +v 705.303 219.02 -221.823 +v 596.877 306.826 -202.43 +v 729.558 324.573 -136.921 +v -627.402 40.2031 -27.0373 +v -974.541 40.2032 116.414 +v -598.079 97.9116 13.3312 +v -598.079 97.9116 13.3312 +v -966.748 86.8306 188.349 +v -635.946 99.4678 145.914 +v -973.5 40.2032 204.705 +v -605.1 40.2031 174.866 +v -635.946 99.4678 145.914 +v -635.946 99.4678 145.914 +v -966.748 86.8306 188.349 +v -973.5 40.2032 204.705 +v -598.079 97.9116 13.3312 +v -974.541 40.2032 116.414 +v -966.748 86.8306 188.349 +v -1356.56 67.297 175.492 +v -966.748 86.8306 188.349 +v -974.541 40.2032 116.414 +v -974.541 40.2032 116.414 +v -1360.94 40.2032 134.989 +v -1356.56 67.297 175.492 +v -1360.36 40.2032 184.7 +v -973.5 40.2032 204.705 +v -966.748 86.8306 188.349 +v -966.748 86.8306 188.349 +v -1356.56 67.297 175.492 +v -1360.36 40.2032 184.7 +v -1360.94 40.2032 134.989 +v -1746.98 40.2033 71.4633 +v -1356.56 67.297 175.492 +v -1356.56 67.297 175.492 +v -1746.98 40.2033 71.4633 +v -1360.36 40.2032 184.7 +v 846.835 189.911 -138.578 +v 789.328 152.689 -20.678 +v 918.453 175.699 -14.2217 +v 676.056 91.0124 -403.457 +v 705.303 219.02 -221.823 +v 846.835 189.911 -138.578 +v 558.966 89.8242 -404.09 +v 442.767 40.2029 -586.685 +v 505.639 40.2029 -412.719 +v 676.056 91.0124 -403.457 +v 482.298 74.6915 -625.285 +v 558.966 89.8242 -404.09 +v 676.056 91.0124 -403.457 +v 679.933 40.2029 -441.705 +v 486.264 40.2029 -638.201 +v 486.264 40.2029 -638.201 +v 482.298 74.6915 -625.285 +v 676.056 91.0124 -403.457 +v 482.298 74.6915 -625.285 +v 442.767 40.2029 -586.685 +v 558.966 89.8242 -404.09 +v 442.767 40.2029 -586.685 +v 482.298 74.6915 -625.285 +v 309.065 59.7757 -777.283 +v 309.065 59.7757 -777.283 +v 286.808 40.203 -755.55 +v 442.767 40.2029 -586.685 +v 482.298 74.6915 -625.285 +v 486.264 40.2029 -638.201 +v 311.298 40.203 -784.555 +v 311.298 40.203 -784.555 +v 309.065 59.7757 -777.283 +v 482.298 74.6915 -625.285 +v 309.065 59.7757 -777.283 +v 3.60059 40.203 -892.627 +v 286.808 40.203 -755.55 +v 311.298 40.203 -784.555 +v 3.60059 40.203 -892.627 +v 309.065 59.7757 -777.283 +v -107.623 179.419 -353.534 +v -312.712 40.203 -483.512 +v -233.929 40.203 -337.011 +v 32.5666 159.26 -387.298 +v -261.253 74.6916 -577.012 +v -107.623 179.419 -353.534 +v 32.5666 159.26 -387.298 +v 21.7976 40.203 -426.108 +v -260.883 40.203 -590.518 +v -260.883 40.203 -590.518 +v -261.253 74.6916 -577.012 +v 32.5666 159.26 -387.298 +v -261.253 74.6916 -577.012 +v -312.712 40.203 -483.512 +v -107.623 179.419 -353.534 +v -312.712 40.203 -483.512 +v -261.253 74.6916 -577.012 +v -468.805 59.7759 -677.187 +v -468.805 59.7759 -677.187 +v -484.444 40.2031 -650.296 +v -312.712 40.203 -483.512 +v -261.253 74.6916 -577.012 +v -260.883 40.203 -590.518 +v -468.596 40.2031 -684.792 +v -468.596 40.2031 -684.792 +v -468.805 59.7759 -677.187 +v -261.253 74.6916 -577.012 +v -468.805 59.7759 -677.187 +v -793.984 40.2031 -706.704 +v -484.444 40.2031 -650.296 +v -468.596 40.2031 -684.792 +v -793.984 40.2031 -706.704 +v -468.805 59.7759 -677.187 +f 1 2 3 +f 4 5 6 +f 7 8 9 +f 10 11 12 +f 13 14 15 +f 16 17 18 +f 19 20 21 +f 22 23 24 +f 25 26 27 +f 28 29 30 +f 31 32 33 +f 34 35 36 +f 37 38 39 +f 40 41 42 +f 43 44 45 +f 46 47 48 +f 49 50 51 +f 52 53 54 +f 55 56 57 +f 58 59 60 +f 61 62 63 +f 64 65 66 +f 67 68 69 +f 70 71 72 +f 73 74 75 +f 76 77 78 +f 79 80 81 +f 82 83 84 +f 85 86 87 +f 88 89 90 +f 91 92 93 +f 94 95 96 +f 97 98 99 +f 100 101 102 +f 103 104 105 +f 106 107 108 +f 109 110 111 +f 112 113 114 +f 115 116 117 +f 118 119 120 +f 121 122 123 +f 124 125 126 +f 127 128 129 +f 130 131 132 +f 133 134 135 +f 136 137 138 +f 139 140 141 +f 142 143 144 +f 145 146 147 +f 148 149 150 +f 151 152 153 +f 154 155 156 +f 157 158 159 +f 160 161 162 +f 163 164 165 +f 166 167 168 +f 169 170 171 +f 172 173 174 +f 175 176 177 +f 178 179 180 +f 181 182 183 +f 184 185 186 +f 187 188 189 +f 190 191 192 +f 193 194 195 +f 196 197 198 +f 199 200 201 +f 202 203 204 +f 205 206 207 +f 208 209 210 +f 211 212 213 +f 214 215 216 +f 217 218 219 +f 220 221 222 +f 223 224 225 +f 226 227 228 +f 229 230 231 +f 232 233 234 +f 235 236 237 +f 238 239 240 +f 241 242 243 +f 244 245 246 +f 247 248 249 +f 250 251 252 +f 253 254 255 +f 256 257 258 +f 259 260 261 +f 262 263 264 +f 265 266 267 +f 268 269 270 +f 271 272 273 +f 274 275 276 +f 277 278 279 +f 280 281 282 +f 283 284 285 +f 286 287 288 +f 289 290 291 +f 292 293 294 +f 295 296 297 +f 298 299 300 +f 301 302 303 +f 304 305 306 +f 307 308 309 +f 310 311 312 +f 313 314 315 +f 316 317 318 +f 319 320 321 +f 322 323 324 +f 325 326 327 +f 328 329 330 +f 331 332 333 +f 334 335 336 +f 337 338 339 +f 340 341 342 +f 343 344 345 +f 346 347 348 +f 349 350 351 +f 352 353 354 +f 355 356 357 +f 358 359 360 +f 361 362 363 +f 364 365 366 +f 367 368 369 +f 370 371 372 +f 373 374 375 +f 376 377 378 +f 379 380 381 +f 382 383 384 +f 385 386 387 +f 388 389 390 +f 391 392 393 +f 394 395 396 +f 397 398 399 +f 400 401 402 +f 403 404 405 +f 406 407 408 +f 409 410 411 +f 412 413 414 +f 415 416 417 +f 418 419 420 +f 421 422 423 +f 424 425 426 +f 427 428 429 +f 430 431 432 +f 433 434 435 +f 436 437 438 +f 439 440 441 +f 442 443 444 +f 445 446 447 +f 448 449 450 +f 451 452 453 +f 454 455 456 +f 457 458 459 +f 460 461 462 +f 463 464 465 +f 466 467 468 +f 469 470 471 +f 472 473 474 +f 475 476 477 +f 478 479 480 +f 481 482 483 +f 484 485 486 +f 487 488 489 +f 490 491 492 +f 493 494 495 +f 496 497 498 +f 499 500 501 +f 502 503 504 +f 505 506 507 +f 508 509 510 +f 511 512 513 +f 514 515 516 +f 517 518 519 +f 520 521 522 +f 523 524 525 +f 526 527 528 +f 529 530 531 +f 532 533 534 +f 535 536 537 +f 538 539 540 +f 541 542 543 +f 544 545 546 +f 547 548 549 +f 550 551 552 +f 553 554 555 +f 556 557 558 +f 559 560 561 +f 562 563 564 +f 565 566 567 +f 568 569 570 +f 571 572 573 +f 574 575 576 +f 577 578 579 +f 580 581 582 +f 583 584 585 +f 586 587 588 +f 589 590 591 +f 592 593 594 +f 595 596 597 +f 598 599 600 +f 601 602 603 +f 604 605 606 +f 607 608 609 +f 610 611 612 +f 613 614 615 +f 616 617 618 +f 619 620 621 +f 622 623 624 +f 625 626 627 +f 628 629 630 +f 631 632 633 +f 634 635 636 +f 637 638 639 +f 640 641 642 +f 643 644 645 +f 646 647 648 +f 649 650 651 +f 652 653 654 +f 655 656 657 +f 658 659 660 +f 661 662 663 +f 664 665 666 +f 667 668 669 +f 670 671 672 +f 673 674 675 +f 676 677 678 +f 679 680 681 +f 682 683 684 +f 685 686 687 +f 688 689 690 +f 691 692 693 +f 694 695 696 +f 697 698 699 +f 700 701 702 +f 703 704 705 +f 706 707 708 +f 709 710 711 +f 712 713 714 +f 715 716 717 +f 718 719 720 +f 721 722 723 +f 724 725 726 +f 727 728 729 +f 730 731 732 +f 733 734 735 +f 736 737 738 +f 739 740 741 +f 742 743 744 +f 745 746 747 +f 748 749 750 +f 751 752 753 +f 754 755 756 +f 757 758 759 +f 760 761 762 +f 763 764 765 +f 766 767 768 +f 769 770 771 +f 772 773 774 +f 775 776 777 +f 778 779 780 +f 781 782 783 +f 784 785 786 +f 787 788 789 +f 790 791 792 +f 793 794 795 +f 796 797 798 +f 799 800 801 +f 802 803 804 +f 805 806 807 +f 808 809 810 +f 811 812 813 +f 814 815 816 +f 817 818 819 +f 820 821 822 +f 823 824 825 +f 826 827 828 +f 829 830 831 +f 832 833 834 +f 835 836 837 +f 838 839 840 +f 841 842 843 +f 844 845 846 +f 847 848 849 +f 850 851 852 +f 853 854 855 +f 856 857 858 +f 859 860 861 +f 862 863 864 +f 865 866 867 +f 868 869 870 +f 871 872 873 +f 874 875 876 +f 877 878 879 +f 880 881 882 +f 883 884 885 +f 886 887 888 +f 889 890 891 +f 892 893 894 +f 895 896 897 +f 898 899 900 +f 901 902 903 +f 904 905 906 +f 907 908 909 +f 910 911 912 +f 913 914 915 +f 916 917 918 +f 919 920 921 +f 922 923 924 +f 925 926 927 +f 928 929 930 +f 931 932 933 +f 934 935 936 +f 937 938 939 +f 940 941 942 +f 943 944 945 +f 946 947 948 +f 949 950 951 +f 952 953 954 +f 955 956 957 +f 958 959 960 +f 961 962 963 +f 964 965 966 +f 967 968 969 +f 970 971 972 +f 973 974 975 +f 976 977 978 +f 979 980 981 +f 982 983 984 +f 985 986 987 +f 988 989 990 +f 991 992 993 +f 994 995 996 +f 997 998 999 +f 1000 1001 1002 +f 1003 1004 1005 +f 1006 1007 1008 +f 1009 1010 1011 +f 1012 1013 1014 +f 1015 1016 1017 +f 1018 1019 1020 +f 1021 1022 1023 +f 1024 1025 1026 +f 1027 1028 1029 +f 1030 1031 1032 +f 1033 1034 1035 +f 1036 1037 1038 +f 1039 1040 1041 +f 1042 1043 1044 +f 1045 1046 1047 +f 1048 1049 1050 +f 1051 1052 1053 +f 1054 1055 1056 +f 1057 1058 1059 +f 1060 1061 1062 +f 1063 1064 1065 +f 1066 1067 1068 +f 1069 1070 1071 +f 1072 1073 1074 +f 1075 1076 1077 +f 1078 1079 1080 +f 1081 1082 1083 +f 1084 1085 1086 +f 1087 1088 1089 +f 1090 1091 1092 +f 1093 1094 1095 +f 1096 1097 1098 +f 1099 1100 1101 +f 1102 1103 1104 +f 1105 1106 1107 +f 1108 1109 1110 +f 1111 1112 1113 +f 1114 1115 1116 +f 1117 1118 1119 +f 1120 1121 1122 +f 1123 1124 1125 +f 1126 1127 1128 +f 1129 1130 1131 +f 1132 1133 1134 +f 1135 1136 1137 +f 1138 1139 1140 +f 1141 1142 1143 +f 1144 1145 1146 +f 1147 1148 1149 +f 1150 1151 1152 +f 1153 1154 1155 +f 1156 1157 1158 +f 1159 1160 1161 +f 1162 1163 1164 +f 1165 1166 1167 +f 1168 1169 1170 +f 1171 1172 1173 +f 1174 1175 1176 +f 1177 1178 1179 +f 1180 1181 1182 +f 1183 1184 1185 +f 1186 1187 1188 +f 1189 1190 1191 +f 1192 1193 1194 +f 1195 1196 1197 +f 1198 1199 1200 +f 1201 1202 1203 +f 1204 1205 1206 +f 1207 1208 1209 +f 1210 1211 1212 +f 1213 1214 1215 +f 1216 1217 1218 +f 1219 1220 1221 +f 1222 1223 1224 +f 1225 1226 1227 +f 1228 1229 1230 +f 1231 1232 1233 +f 1234 1235 1236 +f 1237 1238 1239 +f 1240 1241 1242 +f 1243 1244 1245 +f 1246 1247 1248 +f 1249 1250 1251 +f 1252 1253 1254 +f 1255 1256 1257 +f 1258 1259 1260 +f 1261 1262 1263 +f 1264 1265 1266 +f 1267 1268 1269 +f 1270 1271 1272 +f 1273 1274 1275 +f 1276 1277 1278 +f 1279 1280 1281 +f 1282 1283 1284 +f 1285 1286 1287 +f 1288 1289 1290 +f 1291 1292 1293 +f 1294 1295 1296 +f 1297 1298 1299 +f 1300 1301 1302 +f 1303 1304 1305 +f 1306 1307 1308 +f 1309 1310 1311 +f 1312 1313 1314 +f 1315 1316 1317 +f 1318 1319 1320 +f 1321 1322 1323 +f 1324 1325 1326 +f 1327 1328 1329 +f 1330 1331 1332 +f 1333 1334 1335 +f 1336 1337 1338 +f 1339 1340 1341 +f 1342 1343 1344 +f 1345 1346 1347 +f 1348 1349 1350 +f 1351 1352 1353 +f 1354 1355 1356 +f 1357 1358 1359 +f 1360 1361 1362 +f 1363 1364 1365 +f 1366 1367 1368 +f 1369 1370 1371 +f 1372 1373 1374 +f 1375 1376 1377 +f 1378 1379 1380 +f 1381 1382 1383 +f 1384 1385 1386 +f 1387 1388 1389 +f 1390 1391 1392 +f 1393 1394 1395 +f 1396 1397 1398 +f 1399 1400 1401 +f 1402 1403 1404 +f 1405 1406 1407 +f 1408 1409 1410 +f 1411 1412 1413 +f 1414 1415 1416 +f 1417 1418 1419 +f 1420 1421 1422 +f 1423 1424 1425 +f 1426 1427 1428 +f 1429 1430 1431 +f 1432 1433 1434 +f 1435 1436 1437 +f 1438 1439 1440 +f 1441 1442 1443 +f 1444 1445 1446 +f 1447 1448 1449 +f 1450 1451 1452 +f 1453 1454 1455 +f 1456 1457 1458 +f 1459 1460 1461 +f 1462 1463 1464 +f 1465 1466 1467 +f 1468 1469 1470 +f 1471 1472 1473 +f 1474 1475 1476 +f 1477 1478 1479 +f 1480 1481 1482 +f 1483 1484 1485 +f 1486 1487 1488 +f 1489 1490 1491 +f 1492 1493 1494 +f 1495 1496 1497 +f 1498 1499 1500 +f 1501 1502 1503 +f 1504 1505 1506 +f 1507 1508 1509 +f 1510 1511 1512 +f 1513 1514 1515 +f 1516 1517 1518 +f 1519 1520 1521 +f 1522 1523 1524 +f 1525 1526 1527 +f 1528 1529 1530 +f 1531 1532 1533 +f 1534 1535 1536 +f 1537 1538 1539 +f 1540 1541 1542 +f 1543 1544 1545 +f 1546 1547 1548 +f 1549 1550 1551 +f 1552 1553 1554 +f 1555 1556 1557 +f 1558 1559 1560 +f 1561 1562 1563 +f 1564 1565 1566 +f 1567 1568 1569 +f 1570 1571 1572 +f 1573 1574 1575 +f 1576 1577 1578 +f 1579 1580 1581 +f 1582 1583 1584 +f 1585 1586 1587 +f 1588 1589 1590 +f 1591 1592 1593 +f 1594 1595 1596 +f 1597 1598 1599 +f 1600 1601 1602 +f 1603 1604 1605 +f 1606 1607 1608 +f 1609 1610 1611 +f 1612 1613 1614 +f 1615 1616 1617 +f 1618 1619 1620 +f 1621 1622 1623 +f 1624 1625 1626 +f 1627 1628 1629 +f 1630 1631 1632 +f 1633 1634 1635 +f 1636 1637 1638 +f 1639 1640 1641 +f 1642 1643 1644 +f 1645 1646 1647 +f 1648 1649 1650 +f 1651 1652 1653 +f 1654 1655 1656 +f 1657 1658 1659 +f 1660 1661 1662 +f 1663 1664 1665 +f 1666 1667 1668 +f 1669 1670 1671 +f 1672 1673 1674 +f 1675 1676 1677 +f 1678 1679 1680 +f 1681 1682 1683 +f 1684 1685 1686 +f 1687 1688 1689 +f 1690 1691 1692 +f 1693 1694 1695 +f 1696 1697 1698 +f 1699 1700 1701 +f 1702 1703 1704 +f 1705 1706 1707 +f 1708 1709 1710 +f 1711 1712 1713 +f 1714 1715 1716 +f 1717 1718 1719 +f 1720 1721 1722 +f 1723 1724 1725 +f 1726 1727 1728 +f 1729 1730 1731 +f 1732 1733 1734 +f 1735 1736 1737 +f 1738 1739 1740 +f 1741 1742 1743 +f 1744 1745 1746 +f 1747 1748 1749 +f 1750 1751 1752 +f 1753 1754 1755 +f 1756 1757 1758 +f 1759 1760 1761 +f 1762 1763 1764 +f 1765 1766 1767 +f 1768 1769 1770 +f 1771 1772 1773 +f 1774 1775 1776 +f 1777 1778 1779 +f 1780 1781 1782 +f 1783 1784 1785 +f 1786 1787 1788 +f 1789 1790 1791 +f 1792 1793 1794 +f 1795 1796 1797 +f 1798 1799 1800 +f 1801 1802 1803 +f 1804 1805 1806 +f 1807 1808 1809 +f 1810 1811 1812 +f 1813 1814 1815 +f 1816 1817 1818 +f 1819 1820 1821 +f 1822 1823 1824 +f 1825 1826 1827 +f 1828 1829 1830 +f 1831 1832 1833 +f 1834 1835 1836 +f 1837 1838 1839 +f 1840 1841 1842 +f 1843 1844 1845 +f 1846 1847 1848 +f 1849 1850 1851 +f 1852 1853 1854 +f 1855 1856 1857 +f 1858 1859 1860 +f 1861 1862 1863 +f 1864 1865 1866 +f 1867 1868 1869 +f 1870 1871 1872 +f 1873 1874 1875 +f 1876 1877 1878 +f 1879 1880 1881 +f 1882 1883 1884 +f 1885 1886 1887 +f 1888 1889 1890 +f 1891 1892 1893 +f 1894 1895 1896 +f 1897 1898 1899 +f 1900 1901 1902 +f 1903 1904 1905 +f 1906 1907 1908 +f 1909 1910 1911 +f 1912 1913 1914 +f 1915 1916 1917 +f 1918 1919 1920 +f 1921 1922 1923 +f 1924 1925 1926 +f 1927 1928 1929 +f 1930 1931 1932 +f 1933 1934 1935 +f 1936 1937 1938 +f 1939 1940 1941 +f 1942 1943 1944 +f 1945 1946 1947 +f 1948 1949 1950 +f 1951 1952 1953 +f 1954 1955 1956 +f 1957 1958 1959 +f 1960 1961 1962 +f 1963 1964 1965 +f 1966 1967 1968 +f 1969 1970 1971 +f 1972 1973 1974 +f 1975 1976 1977 +f 1978 1979 1980 +f 1981 1982 1983 +f 1984 1985 1986 +f 1987 1988 1989 +f 1990 1991 1992 +f 1993 1994 1995 +f 1996 1997 1998 +f 1999 2000 2001 +f 2002 2003 2004 +f 2005 2006 2007 +f 2008 2009 2010 +f 2011 2012 2013 +f 2014 2015 2016 +f 2017 2018 2019 +f 2020 2021 2022 +f 2023 2024 2025 +f 2026 2027 2028 +f 2029 2030 2031 +f 2032 2033 2034 +f 2035 2036 2037 +f 2038 2039 2040 +f 2041 2042 2043 +f 2044 2045 2046 +f 2047 2048 2049 +f 2050 2051 2052 +f 2053 2054 2055 +f 2056 2057 2058 +f 2059 2060 2061 +f 2062 2063 2064 +f 2065 2066 2067 +f 2068 2069 2070 +f 2071 2072 2073 +f 2074 2075 2076 +f 2077 2078 2079 +f 2080 2081 2082 +f 2083 2084 2085 +f 2086 2087 2088 +f 2089 2090 2091 +f 2092 2093 2094 +f 2095 2096 2097 +f 2098 2099 2100 +f 2101 2102 2103 +f 2104 2105 2106 +f 2107 2108 2109 +f 2110 2111 2112 +f 2113 2114 2115 +f 2116 2117 2118 +f 2119 2120 2121 +f 2122 2123 2124 +f 2125 2126 2127 +f 2128 2129 2130 +f 2131 2132 2133 +f 2134 2135 2136 +f 2137 2138 2139 +f 2140 2141 2142 +f 2143 2144 2145 +f 2146 2147 2148 +f 2149 2150 2151 +f 2152 2153 2154 +f 2155 2156 2157 +f 2158 2159 2160 +f 2161 2162 2163 +f 2164 2165 2166 +f 2167 2168 2169 +f 2170 2171 2172 +f 2173 2174 2175 +f 2176 2177 2178 +f 2179 2180 2181 +f 2182 2183 2184 +f 2185 2186 2187 +f 2188 2189 2190 +f 2191 2192 2193 +f 2194 2195 2196 +f 2197 2198 2199 +f 2200 2201 2202 +f 2203 2204 2205 +f 2206 2207 2208 +f 2209 2210 2211 +f 2212 2213 2214 +f 2215 2216 2217 +f 2218 2219 2220 +f 2221 2222 2223 +f 2224 2225 2226 +f 2227 2228 2229 +f 2230 2231 2232 +f 2233 2234 2235 +f 2236 2237 2238 +f 2239 2240 2241 +f 2242 2243 2244 +f 2245 2246 2247 +f 2248 2249 2250 +f 2251 2252 2253 +f 2254 2255 2256 +f 2257 2258 2259 +f 2260 2261 2262 +f 2263 2264 2265 +f 2266 2267 2268 +f 2269 2270 2271 +f 2272 2273 2274 +f 2275 2276 2277 +f 2278 2279 2280 +f 2281 2282 2283 +f 2284 2285 2286 +f 2287 2288 2289 +f 2290 2291 2292 +f 2293 2294 2295 +f 2296 2297 2298 +f 2299 2300 2301 +f 2302 2303 2304 +f 2305 2306 2307 +f 2308 2309 2310 +f 2311 2312 2313 +f 2314 2315 2316 +f 2317 2318 2319 +f 2320 2321 2322 +f 2323 2324 2325 +f 2326 2327 2328 +f 2329 2330 2331 +f 2332 2333 2334 +f 2335 2336 2337 +f 2338 2339 2340 +f 2341 2342 2343 +f 2344 2345 2346 +f 2347 2348 2349 +f 2350 2351 2352 +f 2353 2354 2355 +f 2356 2357 2358 +f 2359 2360 2361 +f 2362 2363 2364 +f 2365 2366 2367 +f 2368 2369 2370 +f 2371 2372 2373 +f 2374 2375 2376 +f 2377 2378 2379 +f 2380 2381 2382 +f 2383 2384 2385 +f 2386 2387 2388 +f 2389 2390 2391 +f 2392 2393 2394 +f 2395 2396 2397 +f 2398 2399 2400 +f 2401 2402 2403 +f 2404 2405 2406 +f 2407 2408 2409 +f 2410 2411 2412 +f 2413 2414 2415 +f 2416 2417 2418 +f 2419 2420 2421 +f 2422 2423 2424 +f 2425 2426 2427 +f 2428 2429 2430 +f 2431 2432 2433 +f 2434 2435 2436 +f 2437 2438 2439 +f 2440 2441 2442 +f 2443 2444 2445 +f 2446 2447 2448 +f 2449 2450 2451 +f 2452 2453 2454 +f 2455 2456 2457 +f 2458 2459 2460 +f 2461 2462 2463 +f 2464 2465 2466 +f 2467 2468 2469 +f 2470 2471 2472 +f 2473 2474 2475 +f 2476 2477 2478 +f 2479 2480 2481 +f 2482 2483 2484 +f 2485 2486 2487 +f 2488 2489 2490 +f 2491 2492 2493 +f 2494 2495 2496 +f 2497 2498 2499 +f 2500 2501 2502 +f 2503 2504 2505 +f 2506 2507 2508 +f 2509 2510 2511 +f 2512 2513 2514 +f 2515 2516 2517 +f 2518 2519 2520 +f 2521 2522 2523 +f 2524 2525 2526 +f 2527 2528 2529 +f 2530 2531 2532 +f 2533 2534 2535 +f 2536 2537 2538 +f 2539 2540 2541 +f 2542 2543 2544 +f 2545 2546 2547 +f 2548 2549 2550 +f 2551 2552 2553 +f 2554 2555 2556 +f 2557 2558 2559 +f 2560 2561 2562 +f 2563 2564 2565 +f 2566 2567 2568 +f 2569 2570 2571 +f 2572 2573 2574 +f 2575 2576 2577 +f 2578 2579 2580 +f 2581 2582 2583 +f 2584 2585 2586 +f 2587 2588 2589 +f 2590 2591 2592 +f 2593 2594 2595 +f 2596 2597 2598 +f 2599 2600 2601 +f 2602 2603 2604 +f 2605 2606 2607 +f 2608 2609 2610 +f 2611 2612 2613 +f 2614 2615 2616 +f 2617 2618 2619 +f 2620 2621 2622 +f 2623 2624 2625 +f 2626 2627 2628 +f 2629 2630 2631 +f 2632 2633 2634 +f 2635 2636 2637 +f 2638 2639 2640 +f 2641 2642 2643 +f 2644 2645 2646 +f 2647 2648 2649 +f 2650 2651 2652 diff --git a/docdoku-web-front/tests/run.js b/docdoku-web-front/tests/run.js new file mode 100644 index 0000000000..b4cfc2b9ec --- /dev/null +++ b/docdoku-web-front/tests/run.js @@ -0,0 +1,69 @@ +/*global require,__dirname*/ +/* + * Node script wrapper to execute casper executable + * execution : node run.js + * configuration : config.js - override any arguments from command line + * node run.js --domain=foo.com --port=8383 ... + * */ + +'use strict'; + +var util = require('util'); +var fs = require('fs'); +var exec = require('child_process').exec; +var _ = require('underscore'); +var config = require('./config'); +var xml2js = require('xml2js'); +var del = require('del'); +var argv = require('yargs').argv; + +del.sync(['screenshot/**']); + +var conf = _.extend(config, argv); + +var casperCommand = 'casperjs test' + + ' --ignore-ssl-errors=true '+ + ' --protocol=' + conf.protocol + + ' --domain=' + conf.domain + + ' --port=' + conf.port + + ' --login=' + conf.login + + ' --pass=' + conf.pass + + ' --workspace=' + conf.workspace + + ' --contextPath=' + conf.contextPath + + ' --requestTimeOut=' + conf.requestTimeOut + + ' --globalTimeout=' + conf.globalTimeout + + (conf.debug ? ' --debug=true' : '') + + (conf.debugRequests ? ' --debugRequests=true' : '') + + (conf.waitOnRequest ? ' --waitOnRequest=true' : '') + + (conf.debugResponses ? ' --debugResponses=true' : '') + + ' --pre=' + conf.pre.join(',') + + ' --post=' + conf.post.join(',') + + ' --includes=' + conf.includes.join(',') + + ' --xunit=' + conf.xunit + + (conf.failFast ? ' --fail-fast' : '') + + (conf.verbose ? ' --verbose' : '') + + ' --log-level='+conf.logLevel + + ' ' + conf.paths.join(' '); + +util.print('Running DocdokuPLM tests. Command : \n ' + casperCommand + '\n\n'); + +var child = exec(casperCommand, {maxBuffer: 5 * 1024 * 1024}, function (error) { + util.print(error||''); + if(conf.soundOnTestsEnd){ + var parser = new xml2js.Parser(); + fs.readFile(__dirname + '/results.xml', function(err, data) { + parser.parseString(data, function (err, result) { + var suites = result.testsuites.testsuite; + var lastSuite = suites[suites.length-1]; + if(lastSuite.$.failures !== '0'){ + exec('cvlc --play-and-exit fail.wav'); + }else { + exec('cvlc --play-and-exit success.wav'); + } + }); + }); + } +}); + +child.stdout.on('data', util.print); +child.stderr.on('data', util.print); diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000..53ee88b7c7 --- /dev/null +++ b/pom.xml @@ -0,0 +1,118 @@ + + + + UTF-8 + + 4.0.0 + com.docdoku + docdoku-plm + 2.5-SNAPSHOT + pom + docdoku-plm DocDokuPLM + DocDokuPLM is a high end Open Source PLM solution. + http://www.docdokuplm.com + + + GNU Affero General Public License, version 3 + http://www.gnu.org/licenses/agpl-3.0.txt + repo + + + + DocDoku + http://www.docdoku.com + + + scm:git:git://github.com/docdoku/docdoku-plm.git + scm:git:git@github.com:docdoku/docdoku-plm.git + https://github.com/docdoku/docdoku-plm + + + docdoku-common + docdoku-server + docdoku-api + docdoku-api-java + docdoku-api-js + docdoku-cli + docdoku-dplm + docdoku-web-front + + + + + org.apache.maven.plugins + maven-release-plugin + 2.4.2 + + true + + + + + + + env-prod + + true + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.16 + + true + + + + + + + env-dev + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.16 + + true + + + + + + + env-ci + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.16 + + false + + + + + + + env-test + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.16 + + false + + + + + + + \ No newline at end of file diff --git a/scripts/autodeploy.sh b/scripts/autodeploy.sh deleted file mode 100755 index 69661a62e7..0000000000 --- a/scripts/autodeploy.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -set -e - -mvn clean install -f docdoku-plm-server/pom.xml -cp docdoku-plm-server/docdoku-plm-server-ear/target/docdoku-plm-server-ear.ear docdoku-plm-docker/autodeploy/ diff --git a/scripts/build.sh b/scripts/build.sh deleted file mode 100755 index 17050d34a1..0000000000 --- a/scripts/build.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -git submodule foreach "./build.sh" - diff --git a/scripts/change-protocol.sh b/scripts/change-protocol.sh deleted file mode 100755 index 7ac53fa6ad..0000000000 --- a/scripts/change-protocol.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -if [ $# -ne 1 ] || [ "$1" != "SSH" ] && [ "$1" != "HTTPS" ] - then - echo "Usage : $0 " - echo " protocol must be HTTPS or SSH" - exit 1 -fi - -HTTPS_BASE=https://github.com/docdoku -SSH_BASE=git@github.com:docdoku - -if [ $1 = 'SSH' ]; -then - BASE=${SSH_BASE} -else - BASE=${HTTPS_BASE} -fi - -git submodule foreach ' - git remote remove origin - git remote add origin '$BASE'/${name}.git - git remote -v -' - diff --git a/scripts/checkout.sh b/scripts/checkout.sh deleted file mode 100755 index 2836bcfbb0..0000000000 --- a/scripts/checkout.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -if [ $# -eq 0 ] - then - echo "Usage : $0 " - exit 1 -fi - -BRANCH=$1 - -git submodule foreach "git checkout $BRANCH" diff --git a/scripts/hard-restore.sh b/scripts/hard-restore.sh deleted file mode 100755 index 99153afdec..0000000000 --- a/scripts/hard-restore.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -git submodule foreach " - git fetch origin -p&& \ - git reset --hard && \ - git checkout master && \ - git reset --hard origin/master && \ - git checkout dev && \ - git reset --hard origin/dev -" - diff --git a/scripts/pull.sh b/scripts/pull.sh deleted file mode 100755 index 416299f28d..0000000000 --- a/scripts/pull.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -git submodule foreach "git pull" diff --git a/scripts/release.sh b/scripts/release.sh deleted file mode 100755 index 416c9f5ad3..0000000000 --- a/scripts/release.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh - -if [ $# -lt 2 ] - then - echo "Usage : $0 " - exit 1 -fi - -RELEASE=$1 -SNAPSHOT=$2 - -git submodule foreach " - -if test -f pom.xml; then - git checkout dev - mvn versions:set -DnewVersion=$RELEASE - git commit -am \"Prepare $RELEASE release\" - git tag $RELEASE - mvn versions:set -DnewVersion=$SNAPSHOT - git commit -am \"Prepare $SNAPSHOT snapshot\" - git checkout master - git merge $RELEASE -fi - -if test -f package.json; then - git checkout dev - npm version --no-git-tag-version $RELEASE - git commit -am \"Prepare $RELEASE release\" - git tag $RELEASE - npm version --no-git-tag-version $SNAPSHOT - git commit -am \"Prepare $SNAPSHOT snapshot\" - git checkout master - git merge $RELEASE -fi - -"
                                    + {{ chatSession.remoteUser }} {{ i18n.VIDEO_INVITE_TEXT }} + {{ i18n.VIDEO_INVITE_ACCEPT + }} + {{ i18n.VIDEO_INVITE_REJECT }}